From bfa57860f6756b814e09919174cbc98b1c96f017 Mon Sep 17 00:00:00 2001 From: blowery Date: Sat, 27 Oct 2007 18:10:21 +0000 Subject: [PATCH] initial import --- Contrib/jporter/HttpCompressionModule.cs | 170 + Example/Default.aspx | 12 + Example/DefaultController.cs | 32 + Example/Example.csproj | 153 + Example/ExceptionThrowingHandler.cs | 22 + Example/ExistingImage.ashx | 22 + Example/Image.ashx | 36 + Example/NoCompress.aspx | 9 + Example/blowery.gif | Bin 0 -> 5954 bytes Example/web.config | 52 + Fetch/App.ico | Bin 0 -> 1078 bytes Fetch/AssemblyInfo.cs | 58 + Fetch/EntryPoint.cs | 20 + Fetch/Fetch.csproj | 136 + Fetch/MainForm.cs | 305 ++ Fetch/MainForm.resx | 256 ++ HttpCompress.sln | 46 + HttpCompress/AssemblyInfo.cs | 115 + HttpCompress/CompressingFilter.cs | 67 + HttpCompress/DeflateFilter.cs | 66 + HttpCompress/Enums.cs | 42 + HttpCompress/GZipFilter.cs | 66 + HttpCompress/HttpCompress.csproj | 146 + HttpCompress/HttpModule.cs | 237 ++ HttpCompress/HttpOutputFilter.cs | 119 + HttpCompress/SectionHandler.cs | 27 + HttpCompress/Settings.cs | 150 + Tests/AssemblyInfo.cs | 121 + Tests/SettingsTests.cs | 146 + Tests/Tests.csproj | 143 + Tests/Utility.cs | 13 + Tests/XmlTestDocuments.resx | 114 + Tests/XmlTestDocuments.xsd | 28 + Tests/app.config | 1 + .../ICSharpCode.SharpZipLib.dll | Bin 0 -> 114688 bytes .../ICSharpCode.SharpZipLib.xml | 3466 +++++++++++++++++ license.txt | 20 + readme.txt | 100 + 38 files changed, 6516 insertions(+) create mode 100644 Contrib/jporter/HttpCompressionModule.cs create mode 100644 Example/Default.aspx create mode 100644 Example/DefaultController.cs create mode 100644 Example/Example.csproj create mode 100644 Example/ExceptionThrowingHandler.cs create mode 100644 Example/ExistingImage.ashx create mode 100644 Example/Image.ashx create mode 100644 Example/NoCompress.aspx create mode 100644 Example/blowery.gif create mode 100644 Example/web.config create mode 100644 Fetch/App.ico create mode 100644 Fetch/AssemblyInfo.cs create mode 100644 Fetch/EntryPoint.cs create mode 100644 Fetch/Fetch.csproj create mode 100644 Fetch/MainForm.cs create mode 100644 Fetch/MainForm.resx create mode 100644 HttpCompress.sln create mode 100644 HttpCompress/AssemblyInfo.cs create mode 100644 HttpCompress/CompressingFilter.cs create mode 100644 HttpCompress/DeflateFilter.cs create mode 100644 HttpCompress/Enums.cs create mode 100644 HttpCompress/GZipFilter.cs create mode 100644 HttpCompress/HttpCompress.csproj create mode 100644 HttpCompress/HttpModule.cs create mode 100644 HttpCompress/HttpOutputFilter.cs create mode 100644 HttpCompress/SectionHandler.cs create mode 100644 HttpCompress/Settings.cs create mode 100644 Tests/AssemblyInfo.cs create mode 100644 Tests/SettingsTests.cs create mode 100644 Tests/Tests.csproj create mode 100644 Tests/Utility.cs create mode 100644 Tests/XmlTestDocuments.resx create mode 100644 Tests/XmlTestDocuments.xsd create mode 100644 Tests/app.config create mode 100644 Vendor/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.dll create mode 100644 Vendor/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.xml create mode 100644 license.txt create mode 100644 readme.txt diff --git a/Contrib/jporter/HttpCompressionModule.cs b/Contrib/jporter/HttpCompressionModule.cs new file mode 100644 index 0000000..26e6f80 --- /dev/null +++ b/Contrib/jporter/HttpCompressionModule.cs @@ -0,0 +1,170 @@ +using System; +using System.IO; +using System.Web; + +using System.Collections; +using System.Collections.Specialized; + +namespace blowery.Web.HttpModules { + /// + /// An HttpModule that hooks onto the Response.Filter property of the + /// current request and tries to compress the output, based on what + /// the browser supports + /// + /// + ///

This HttpModule uses classes that inherit from . + /// We already support gzip and deflate (aka zlib), if you'd like to add + /// support for compress (which uses LZW, which is licensed), add in another + /// class that inherits from HttpFilter to do the work.

+ /// + ///

This module checks the Accept-Encoding HTTP header to determine if the + /// client actually supports any notion of compression. Currently, we support + /// the deflate (zlib) and gzip compression schemes. I chose not to implement + /// compress, because it's uses lzw, which generally requires a license from + /// Unisys. For more information about the common compression types supported, + /// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 for details.

+ ///
+ /// + /// + public sealed class HttpCompressionModule : IHttpModule { + + /// + /// Init the handler and fulfill + /// + /// + /// This implementation hooks the BeginRequest event on the . + /// This should be fine. + /// + /// The this handler is working for. + void IHttpModule.Init(HttpApplication context) { + context.BeginRequest += new EventHandler(this.CompressContent); + } + + /// + /// Implementation of + /// + /// + /// Currently empty. Nothing to really do, as I have no member variables. + /// + void IHttpModule.Dispose() { } + + /// + /// EventHandler that gets ahold of the current request context and attempts to compress the output. + /// + /// The that is firing this event. + /// Arguments to the event + void CompressContent(object sender, EventArgs e) { + + HttpApplication app = (HttpApplication)sender; + + // get the accepted types of compression + // i'm getting them all because the string[] that GetValues was returning + // contained on member with , delimited items. rather silly, so i just go for the whole thing now + // also, the get call is case-insensitive, i just use the pretty casing cause it looks nice + string[] acceptedTypes = app.Request.Headers.GetValues("Accept-Encoding"); + + // this will happen if the header wasn't found. just bail out because the client doesn't want compression + if(acceptedTypes == null) { + return; + } + + // try and find a viable filter for this request + HttpCompressingFilter filter = GetFilterForScheme(acceptedTypes, app.Response.Filter); + + // the filter will be null if no filter was found. if no filter, just bail. + if(filter == null) { + app.Context.Trace.Write("HttpCompressionModule", "Cannot find filter to support any of the client's desired compression schemes"); + return; + } + + // if we get here, we found a viable filter. + // set the filter and change the Content-Encoding header to match so the client can decode the response + app.Response.Filter = filter; + app.Response.AppendHeader("Content-Encoding", filter.NameOfContentEncoding); + + } + + + /// + /// Get ahold of a for the given encoding scheme. + /// If no encoding scheme can be found, it returns null. + /// + HttpCompressingFilter GetFilterForScheme(string[] schemes, Stream currentFilterStream) { + + bool foundDeflate = false; + bool foundGZip = false; + bool foundStar = false; + + float deflateQuality = 0; + float gZipQuality = 0; + float starQuality = 0; + + bool isAcceptableDeflate; + bool isAcceptableGZip; + bool isAcceptableStar; + + for (int i = 0; i 0); + isAcceptableDeflate = (foundDeflate && (deflateQuality > 0)) || (!foundDeflate && isAcceptableStar); + isAcceptableGZip = (foundGZip && (gZipQuality > 0)) || (!foundGZip && isAcceptableStar); + + if (isAcceptableDeflate && !foundDeflate) + deflateQuality = starQuality; + + if (isAcceptableGZip && !foundGZip) + gZipQuality = starQuality; + + HttpCompressionModuleSettings settings = HttpCompressionModuleSettings.GetSettings(); + + if (isAcceptableDeflate && (!isAcceptableGZip || (deflateQuality > gZipQuality))) + return new DeflateFilter(currentFilterStream, settings.CompressionLevel); + if (isAcceptableGZip && (!isAcceptableDeflate || (deflateQuality < gZipQuality))) + return new GZipFilter(currentFilterStream); + + // if they support the preferred algorithm, use it + if(isAcceptableDeflate && settings.PreferredAlgorithm == CompressionTypes.Deflate) + return new DeflateFilter(currentFilterStream, settings.CompressionLevel); + if(isAcceptableGZip && settings.PreferredAlgorithm == CompressionTypes.GZip) + return new GZipFilter(currentFilterStream); + + // return null. we couldn't find a filter. + return null; + } + + float GetQuality(string acceptEncodingValue) { + int qParam = acceptEncodingValue.IndexOf("q="); + + if (qParam >= 0) { + return float.Parse(acceptEncodingValue.Substring(qParam+2, acceptEncodingValue.Length - (qParam+2))); + } else + return 1; + } + } +} diff --git a/Example/Default.aspx b/Example/Default.aspx new file mode 100644 index 0000000..bf162f9 --- /dev/null +++ b/Example/Default.aspx @@ -0,0 +1,12 @@ +<%@ Page Inherits="Example.DefaultController"%> + + + + a test of compression + + + Hi there pokey!
+ + + diff --git a/Example/DefaultController.cs b/Example/DefaultController.cs new file mode 100644 index 0000000..ef7471a --- /dev/null +++ b/Example/DefaultController.cs @@ -0,0 +1,32 @@ +using System; +using System.Web.UI; +using System.Web.UI.WebControls; + +namespace Example { + /// + /// This class acts as a controller for the default.aspx page. + /// It handles Page events and maps events from + /// the classes it contains to event handlers. + /// + public class DefaultController : Page { + + /// + /// A label on the form + /// + protected Label MyLabel; + + /// + /// Override of OnLoad that adds some processing. + /// + /// + protected override void OnLoad(EventArgs e) { + try { + MyLabel.Text = "Right Now: " + DateTime.Now.ToString(); + }finally { + base.OnLoad(e); // be sure to call base to fire the event + } + } + + + } +} diff --git a/Example/Example.csproj b/Example/Example.csproj new file mode 100644 index 0000000..99d0ea6 --- /dev/null +++ b/Example/Example.csproj @@ -0,0 +1,153 @@ + + + Local + 8.0.50727 + 2.0 + {621AAC3C-38EC-4F70-80D3-68DE4829AA33} + Debug + AnyCPU + + + + + Example + + + JScript + Grid + IE50 + false + Library + Example + OnBuildSuccess + + + + + + + + + bin\ + false + 285212672 + false + + + + + + + true + 4096 + false + + + false + false + false + false + 1 + full + prompt + + + bin\ + false + 285212672 + false + + + + + + + false + 4096 + false + + + false + false + false + false + 1 + none + prompt + + + bin\ + false + 285212672 + false + + + + + + + false + 4096 + false + + + false + false + false + false + 1 + none + prompt + + + + ICSharpCode.SharpZipLib + ..\Vendor\ICSharpCode.SharpZipLib\ICSharpCode.SharpZipLib.dll + + + System + + + System.Data + + + System.Drawing + + + System.Web + + + System.Windows.Forms + + + System.XML + + + HttpCompress + {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + + Form + + + ASPXCodeBehind + + + Code + + + + + + + + + + + + + \ No newline at end of file diff --git a/Example/ExceptionThrowingHandler.cs b/Example/ExceptionThrowingHandler.cs new file mode 100644 index 0000000..5f12e30 --- /dev/null +++ b/Example/ExceptionThrowingHandler.cs @@ -0,0 +1,22 @@ +using System; +using System.Web; + +namespace Example +{ + /// + /// An HttpHandler that just throws exceptions. + /// + public class ExceptionThrowingHandler : IHttpHandler + { + public void ProcessRequest(HttpContext context) { + //context.Response.Write("foo"); + throw new Exception("My custom exception."); + } + + public bool IsReusable { + get { + return true; + } + } + } +} diff --git a/Example/ExistingImage.ashx b/Example/ExistingImage.ashx new file mode 100644 index 0000000..8e325b2 --- /dev/null +++ b/Example/ExistingImage.ashx @@ -0,0 +1,22 @@ +<%@ WebHandler Language="C#" Class="ExistingImage" %> + +using System.Web; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +public class ExistingImage : IHttpHandler +{ + + public void ProcessRequest (HttpContext context) + { + context.Response.ContentType = "image/gif"; + context.Response.WriteFile("blowery.gif"); + } + + + public bool IsReusable + { + get { return true; } + } +} \ No newline at end of file diff --git a/Example/Image.ashx b/Example/Image.ashx new file mode 100644 index 0000000..ca166fa --- /dev/null +++ b/Example/Image.ashx @@ -0,0 +1,36 @@ +<%@ WebHandler Language="C#" Class="ImageMaker" %> + +using System.Web; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +public class ImageMaker : IHttpHandler +{ + public void ProcessRequest (HttpContext context) + { + using(Bitmap myFoo = new Bitmap(200,200,PixelFormat.Format24bppRgb)) { + using(Graphics g = Graphics.FromImage(myFoo)) { + + g.FillRectangle(Brushes.Gray,0,0,200,200); + g.FillPie(Brushes.Yellow,100,100,100,100,0,90); + + } + context.Response.ContentType = "image/png"; + + // have to do this crap because saving + // png directly to HttpResponse.OutputStream + // is broken in the 1.0 bits (at least up to sp2) + // should just be + // myFoo.Save(context.Response.OutputStream, ImageFormat.Png); + MemoryStream ms = new MemoryStream(); + myFoo.Save(ms, ImageFormat.Png); + context.Response.OutputStream.Write(ms.ToArray(), 0, (int)ms.Length); + } + } + + public bool IsReusable + { + get { return true; } + } +} \ No newline at end of file diff --git a/Example/NoCompress.aspx b/Example/NoCompress.aspx new file mode 100644 index 0000000..f20e48c --- /dev/null +++ b/Example/NoCompress.aspx @@ -0,0 +1,9 @@ +<%@ Page %> + + + A non-compressed page! + + +

This page was not compressed! Hopefully.

+ + \ No newline at end of file diff --git a/Example/blowery.gif b/Example/blowery.gif new file mode 100644 index 0000000000000000000000000000000000000000..e3e89a019e1fb0ce703856dcb290c54468326138 GIT binary patch literal 5954 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 F{{g!5>E8eV literal 0 HcmV?d00001 diff --git a/Example/web.config b/Example/web.config new file mode 100644 index 0000000..729d761 --- /dev/null +++ b/Example/web.config @@ -0,0 +1,52 @@ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fetch/App.ico b/Fetch/App.ico new file mode 100644 index 0000000000000000000000000000000000000000..3a5525fd794f7a7c5c8e6187f470ea3af38cd2b6 GIT binary patch literal 1078 zcmeHHJr05}7=1t!Hp3A*8IHkVf+j?-!eHY14Gtcw1Eb*_9>Bq^zETJ@GKj{_2j4$w zo9}xCh!8{T3=X##Skq>ikMjsvB|y%crWBM2iW(4pI}c%z6%lW!=~4v77#3{z!dmB1 z__&l)-{KUYR+|8|;wB^R|9ET$J@(@=#rd^=)qs85?vAy(PSF5CyNkus435LVkZ$rj zNw|JG-P7^hF<(;#o*Vk}5R#e|^13tBbQkeF?djULtvqyxd3<{9 literal 0 HcmV?d00001 diff --git a/Fetch/AssemblyInfo.cs b/Fetch/AssemblyInfo.cs new file mode 100644 index 0000000..895bd7f --- /dev/null +++ b/Fetch/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("6.0")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/Fetch/EntryPoint.cs b/Fetch/EntryPoint.cs new file mode 100644 index 0000000..f30f562 --- /dev/null +++ b/Fetch/EntryPoint.cs @@ -0,0 +1,20 @@ +using System; +using System.IO; +using System.Net; + +using System.Windows.Forms; + +namespace Fetch { + class EntryPoint { + + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main(string[] args) { + Application.Run(new MainForm()); + } + + + } +} diff --git a/Fetch/Fetch.csproj b/Fetch/Fetch.csproj new file mode 100644 index 0000000..966da43 --- /dev/null +++ b/Fetch/Fetch.csproj @@ -0,0 +1,136 @@ + + + Local + 8.0.50727 + 2.0 + {3EFC307A-7AA7-4566-8517-BD80263C2830} + Debug + AnyCPU + App.ico + + + Fetch + + + JScript + Grid + IE50 + false + WinExe + Fetch + OnBuildSuccess + Fetch.EntryPoint + + + + + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + + System + + + System.Data + + + System.Drawing + + + System.Windows.Forms + + + System.XML + + + + + + Code + + + Code + + + Form + + + MainForm.cs + + + + + + + + + + \ No newline at end of file diff --git a/Fetch/MainForm.cs b/Fetch/MainForm.cs new file mode 100644 index 0000000..da92e1f --- /dev/null +++ b/Fetch/MainForm.cs @@ -0,0 +1,305 @@ +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Windows.Forms; + +namespace Fetch +{ + /// + /// Summary description for MainForm. + /// + public class MainForm : System.Windows.Forms.Form + { + private System.Windows.Forms.StatusBar statusBar; + private System.Windows.Forms.Label urlLabel; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.RadioButton gzipOption; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.TextBox urlToHit; + private System.Windows.Forms.Button evaluateButton; + private System.Windows.Forms.RadioButton deflateOption; + private System.Windows.Forms.ListBox resultsList; + private System.Windows.Forms.Label userAgentLabel; + private System.Windows.Forms.TextBox userAgentToUse; + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + public MainForm() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + // + // TODO: Add any constructor code after InitializeComponent call + // + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose( bool disposing ) + { + if( disposing ) + { + if(components != null) + { + components.Dispose(); + } + } + base.Dispose( disposing ); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.statusBar = new System.Windows.Forms.StatusBar(); + this.urlToHit = new System.Windows.Forms.TextBox(); + this.urlLabel = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.userAgentToUse = new System.Windows.Forms.TextBox(); + this.userAgentLabel = new System.Windows.Forms.Label(); + this.gzipOption = new System.Windows.Forms.RadioButton(); + this.deflateOption = new System.Windows.Forms.RadioButton(); + this.label1 = new System.Windows.Forms.Label(); + this.evaluateButton = new System.Windows.Forms.Button(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.resultsList = new System.Windows.Forms.ListBox(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.SuspendLayout(); + // + // statusBar + // + this.statusBar.Location = new System.Drawing.Point(0, 247); + this.statusBar.Name = "statusBar"; + this.statusBar.Size = new System.Drawing.Size(492, 22); + this.statusBar.TabIndex = 0; + this.statusBar.Text = "OK"; + // + // urlToHit + // + this.urlToHit.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.urlToHit.Location = new System.Drawing.Point(8, 32); + this.urlToHit.Name = "urlToHit"; + this.urlToHit.Size = new System.Drawing.Size(376, 21); + this.urlToHit.TabIndex = 1; + this.urlToHit.Text = "http://"; + // + // urlLabel + // + this.urlLabel.Location = new System.Drawing.Point(8, 16); + this.urlLabel.Name = "urlLabel"; + this.urlLabel.Size = new System.Drawing.Size(120, 16); + this.urlLabel.TabIndex = 2; + this.urlLabel.Text = "URL to Check:"; + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.userAgentToUse); + this.groupBox1.Controls.Add(this.userAgentLabel); + this.groupBox1.Controls.Add(this.gzipOption); + this.groupBox1.Controls.Add(this.deflateOption); + this.groupBox1.Controls.Add(this.label1); + this.groupBox1.Controls.Add(this.urlLabel); + this.groupBox1.Controls.Add(this.urlToHit); + this.groupBox1.Controls.Add(this.evaluateButton); + this.groupBox1.Location = new System.Drawing.Point(8, 8); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(476, 136); + this.groupBox1.TabIndex = 3; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Connection Options"; + // + // userAgentToUse + // + this.userAgentToUse.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.userAgentToUse.Location = new System.Drawing.Point(224, 64); + this.userAgentToUse.Name = "userAgentToUse"; + this.userAgentToUse.Size = new System.Drawing.Size(160, 21); + this.userAgentToUse.TabIndex = 6; + this.userAgentToUse.Text = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT)"; + // + // userAgentLabel + // + this.userAgentLabel.Location = new System.Drawing.Point(152, 64); + this.userAgentLabel.Name = "userAgentLabel"; + this.userAgentLabel.Size = new System.Drawing.Size(64, 16); + this.userAgentLabel.TabIndex = 7; + this.userAgentLabel.Text = "User Agent"; + // + // gzipOption + // + this.gzipOption.Checked = true; + this.gzipOption.Location = new System.Drawing.Point(24, 104); + this.gzipOption.Name = "gzipOption"; + this.gzipOption.TabIndex = 5; + this.gzipOption.TabStop = true; + this.gzipOption.Text = "GZip"; + // + // deflateOption + // + this.deflateOption.Location = new System.Drawing.Point(24, 80); + this.deflateOption.Name = "deflateOption"; + this.deflateOption.TabIndex = 4; + this.deflateOption.Tag = "deflate"; + this.deflateOption.Text = "Deflate"; + // + // label1 + // + this.label1.Location = new System.Drawing.Point(8, 64); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(144, 23); + this.label1.TabIndex = 3; + this.label1.Text = "Compression Algorithm:"; + // + // evaluateButton + // + this.evaluateButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.evaluateButton.Location = new System.Drawing.Point(392, 32); + this.evaluateButton.Name = "evaluateButton"; + this.evaluateButton.TabIndex = 4; + this.evaluateButton.Text = "Fetch!"; + this.evaluateButton.Click += new System.EventHandler(this.evaluateButton_Click); + // + // groupBox2 + // + this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox2.Controls.Add(this.resultsList); + this.groupBox2.Location = new System.Drawing.Point(8, 160); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(476, 84); + this.groupBox2.TabIndex = 4; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Results"; + // + // resultsList + // + this.resultsList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.resultsList.Location = new System.Drawing.Point(8, 16); + this.resultsList.Name = "resultsList"; + this.resultsList.Size = new System.Drawing.Size(456, 56); + this.resultsList.TabIndex = 0; + // + // MainForm + // + this.AcceptButton = this.evaluateButton; + this.AutoScaleBaseSize = new System.Drawing.Size(5, 14); + this.ClientSize = new System.Drawing.Size(492, 269); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.statusBar); + this.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.MinimumSize = new System.Drawing.Size(500, 300); + this.Name = "MainForm"; + this.Text = "Compression Checker"; + this.groupBox1.ResumeLayout(false); + this.groupBox2.ResumeLayout(false); + this.ResumeLayout(false); + + } + #endregion + + private void evaluateButton_Click(object sender, System.EventArgs e) { + Uri url; + try { + url = new Uri(urlToHit.Text); + } catch(UriFormatException) { + statusBar.Text = "Invalid URL!"; + return; + } + statusBar.Text = "Fetching uncompressed..."; + + RunInfo[] results = new RunInfo[2]; + + results[0] = FetchUncompressedUrl(url); + statusBar.Text = "Fetching compressed..."; + + if(deflateOption.Checked) + results[1] = FetchCompressedUrl(url, "deflate"); + else + results[1] = FetchCompressedUrl(url, "gzip"); + + resultsList.DataSource = results; + + statusBar.Text = "Done!"; + + } + + RunInfo FetchUncompressedUrl(Uri url) { + RunInfo ri = new RunInfo(); + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.UserAgent = userAgentToUse.Text; + DateTime start = DateTime.Now; + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + ri.TimeToFirstByte = DateTime.Now - start; + ri.BytesReceived = DumpStream(response.GetResponseStream(), Stream.Null); + ri.TimeToLastByte = DateTime.Now - start; + return ri; + } + + RunInfo FetchCompressedUrl(Uri url, string algo) { + RunInfo ri = new RunInfo(algo); + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.Headers["Accept-Encoding"] = algo; + request.UserAgent = userAgentToUse.Text; + DateTime start = DateTime.Now; + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + ri.TimeToFirstByte = DateTime.Now - start; + ri.BytesReceived = DumpStream(response.GetResponseStream(), Stream.Null); + ri.TimeToLastByte = DateTime.Now - start; + return ri; + } + + static long DumpStream(Stream s, Stream output) { + + byte[] buffer = new byte[1024]; + long count = 0; + int read = 0; + while((read = s.Read(buffer, 0, 1024)) > 0) { + count += read; + output.Write(buffer, 0, read); + } + return count; + } + + class RunInfo { + public long BytesReceived = -1; + public TimeSpan TimeToFirstByte = TimeSpan.Zero; + public TimeSpan TimeToLastByte = TimeSpan.Zero; + public TimeSpan TransitTime { get { return TimeToLastByte - TimeToFirstByte; } } + public string Algorithm = "none"; + + public RunInfo() { + } + + public RunInfo(string algo) { + Algorithm = algo; + } + + public override string ToString() { + return string.Format("{0}; {1} bytes; TTFB={2}ms; TTLB={3}ms; Transit={4}ms", Algorithm, BytesReceived, TimeToFirstByte.TotalMilliseconds, TimeToLastByte.TotalMilliseconds, TransitTime.TotalMilliseconds); + } + + } + } +} diff --git a/Fetch/MainForm.resx b/Fetch/MainForm.resx new file mode 100644 index 0000000..fb2df8b --- /dev/null +++ b/Fetch/MainForm.resx @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + Private + + + Private + + + Private + + + False + + + Private + + + False + + + Private + + + Private + + + Private + + + 8, 8 + + + True + + + False + + + True + + + Private + + + Private + + + False + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + Private + + + 8, 8 + + + True + + + False + + + True + + + Private + + + Private + + + False + + + Private + + + False + + + (Default) + + + False + + + False + + + 8, 8 + + + True + + + 80 + + + MainForm + + + True + + + Private + + \ No newline at end of file diff --git a/HttpCompress.sln b/HttpCompress.sln new file mode 100644 index 0000000..592ac3f --- /dev/null +++ b/HttpCompress.sln @@ -0,0 +1,46 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "example\Example.csproj", "{621AAC3C-38EC-4F70-80D3-68DE4829AA33}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fetch", "Fetch\Fetch.csproj", "{3EFC307A-7AA7-4566-8517-BD80263C2830}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{758C0F47-99BF-43B2-8C91-C94CAEC72DAC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpCompress", "HttpCompress\HttpCompress.csproj", "{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Production|Any CPU = Production|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Debug|Any CPU.Build.0 = Debug|Any CPU + {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Production|Any CPU.ActiveCfg = Production|Any CPU + {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Production|Any CPU.Build.0 = Production|Any CPU + {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Release|Any CPU.ActiveCfg = Release|Any CPU + {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Release|Any CPU.Build.0 = Release|Any CPU + {3EFC307A-7AA7-4566-8517-BD80263C2830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3EFC307A-7AA7-4566-8517-BD80263C2830}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3EFC307A-7AA7-4566-8517-BD80263C2830}.Production|Any CPU.ActiveCfg = Production|Any CPU + {3EFC307A-7AA7-4566-8517-BD80263C2830}.Production|Any CPU.Build.0 = Production|Any CPU + {3EFC307A-7AA7-4566-8517-BD80263C2830}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3EFC307A-7AA7-4566-8517-BD80263C2830}.Release|Any CPU.Build.0 = Release|Any CPU + {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Production|Any CPU.ActiveCfg = Production|Any CPU + {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Production|Any CPU.Build.0 = Production|Any CPU + {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Release|Any CPU.Build.0 = Release|Any CPU + {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Production|Any CPU.ActiveCfg = Production|Any CPU + {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Production|Any CPU.Build.0 = Production|Any CPU + {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/HttpCompress/AssemblyInfo.cs b/HttpCompress/AssemblyInfo.cs new file mode 100644 index 0000000..c8a04fa --- /dev/null +++ b/HttpCompress/AssemblyInfo.cs @@ -0,0 +1,115 @@ +// A Better AssemblyInfo.cs based on better practices. +// +// This little guy sets up all the assembly level attributes +// on the assembly that you're compiling +// +// This version heavily influenced (read copied) from Jeffery Richter's +// Applied Microsoft.NET Framework Programming (pg. 93) + +using System.Reflection; + +#region Company Info + +// Set the CompanyName, LegalCopyright, and LegalTrademark fields +// You shouldn't have to mess with these +[assembly: AssemblyCompany("blowery.org")] +[assembly: AssemblyCopyright("Copyright (c) 2003-Present Ben Lowery")] + +#endregion + +#region Product Info + +// Set the ProductName and ProductVersion fields +// AssemblyProduct should be a descriptive name for the product that includes this assembly +// If this is a generic library that's going to be used by a whole bunch of stuff, +// just make the name the same as the Assembly's name. This isn't really used by +// anything except Windows Explorer's properties tab, so it's not a big deal if you mess it up +// See pg. 60 of Richter for more info. +[assembly: AssemblyProduct("HttpCompress")] +#endregion + +#region Version and Identity Info + +// AssemblyInformationalVersion should be the version of the product that is including this +// assembly. Again, if this assembly isn't being included in a "product", just make this +// the same as the FileVersion and be done with it. See pg 60 of Richter for more info. +[assembly: AssemblyInformationalVersion("6.0")] + +// Set the FileVersion, AssemblyVersion, FileDescription, and Comments fields +// AssemblyFileVersion is stored in the Win32 version resource. It's ignored by the CLR, +// we typically use it to store the build and revision bits every time a build +// is performed. Unfortunately, the c# compiler doesn't do this automatically, +// so we'll have to work out another way to do it. +[assembly: AssemblyFileVersion("6.0")] + +// AssemblyVersion is the version used by the CLR as part of the strong name for +// an assembly. You don't really want to mess with this unless you're +// making changes that add / remove / change functionality within the library. +// For bugfixes, don't mess with this. +// +// Do this: +// [assembly: AssemblyVersion("1.0.0.0")] +// +// Don't do this: +// [assembly: AssemblyVersion("1.0.*.*")] +// as it breaks all the other assemblies that reference this one every time +// you build the project. +[assembly: AssemblyVersion("6.0")] + +// Title is just for inspection utilities and isn't really used for much +// Generally just set this to the name of the file containing the +// manifest, sans extension. I.E. for BensLibrary.dll, name it BensLibrary +[assembly: AssemblyTitle("HttpCompress")] + +// Description is just for inspection utilities and isn't really that important. +// It's generally a good idea to write a decent description so that you +// don't end up looking like a tool when your stuff shows up in an inspector. +[assembly: AssemblyDescription("An HttpModule that compresses the output stream!")] + +#endregion + +#region Culture Info + +// Set the assembly's culture affinity. Generally don't want to set this +// unless you're building an resource only assembly. Assemblies that contain +// code should always be culture neutral +[assembly: AssemblyCulture("")] + +#endregion + +#region Assembly Signing Info + +#if !StronglyNamedAssembly + +// Weakly named assemblies are never signed +[assembly: AssemblyDelaySign(false)] + +#else + + // Strongly named assemblies are usually delay signed while building and + // completely signed using sn.exe's -R or -Rc switch + + #if !SignedUsingACryptoServiceProvider + + // Give the name of the file that contains the public/private key pair. + // If delay signing, only the public key is used + [assembly: AssemblyKeyFile(THE_KEY_FILE_RELATIVE_TO_THIS_FILE)] + + // Note: If AssemblyKeyFile and AssemblyKeyName are both specified, + // here's what happens... + // 1) If the container exists, the key file is ignored + // 2) If the container doesn't exist, the keys from the key + // file are copied in the container and the assembly is signed + + #else + + // Give the name of the cryptographic service provider (CSP) container + // that contains the public/private key pair + // If delay signing, only the public key is used + [assembly: AssemblyKeyName("blowery.org")] + + #endif + +#endif + +#endregion diff --git a/HttpCompress/CompressingFilter.cs b/HttpCompress/CompressingFilter.cs new file mode 100644 index 0000000..a8f5869 --- /dev/null +++ b/HttpCompress/CompressingFilter.cs @@ -0,0 +1,67 @@ +using System; +using System.IO; +using System.Web; + +namespace blowery.Web.HttpCompress { + /// + /// Base for any HttpFilter that performing compression + /// + /// + /// When implementing this class, you need to implement a + /// along with a . The latter corresponds to a + /// content coding (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5) + /// that your implementation will support. + /// + public abstract class CompressingFilter : HttpOutputFilter { + + private bool hasWrittenHeaders = false; + + /// + /// Protected constructor that sets up the underlying stream we're compressing into + /// + /// The stream we're wrapping up + /// The level of compression to use when compressing the content + protected CompressingFilter(Stream baseStream, CompressionLevels compressionLevel) : base(baseStream) { + _compressionLevel = compressionLevel; + } + + /// + /// The name of the content-encoding that's being implemented + /// + /// + /// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5 for more + /// details on content codings. + /// + public abstract string ContentEncoding { get; } + + private CompressionLevels _compressionLevel; + + /// + /// Allow inheriting classes to get access the the level of compression that should be used + /// + protected CompressionLevels CompressionLevel { + get { return _compressionLevel; } + } + + /// + /// Keeps track of whether or not we're written the compression headers + /// + protected bool HasWrittenHeaders { + get { return hasWrittenHeaders; } + } + + /// + /// Writes out the compression-related headers. Subclasses should call this once before writing to the output stream. + /// + protected void WriteHeaders() { + // this is dangerous. if Response.End is called before the filter is used, directly or indirectly, + // the content will not pass through the filter. However, this header will still be appended. + // Look for handling cases in PreRequestSendHeaders and Pre + HttpContext.Current.Response.AppendHeader("Content-Encoding", this.ContentEncoding); + HttpContext.Current.Response.AppendHeader("X-Compressed-By", "HttpCompress"); + hasWrittenHeaders = true; + } + + + } +} diff --git a/HttpCompress/DeflateFilter.cs b/HttpCompress/DeflateFilter.cs new file mode 100644 index 0000000..dd5c7f4 --- /dev/null +++ b/HttpCompress/DeflateFilter.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; + +using System.IO.Compression; + +namespace blowery.Web.HttpCompress { + /// + /// Summary description for DeflateFilter. + /// + public class DeflateFilter : CompressingFilter { + + /// + /// compression stream member + /// has to be a member as we can only have one instance of the + /// actual filter class + /// + private DeflateStream m_stream = null; + + /// + /// Basic constructor that uses the Normal compression level + /// + /// The stream to wrap up with the deflate algorithm + public DeflateFilter(Stream baseStream) : this(baseStream, CompressionLevels.Normal) { } + + /// + /// Full constructor that allows you to set the wrapped stream and the level of compression + /// + /// The stream to wrap up with the deflate algorithm + /// The level of compression to use + public DeflateFilter(Stream baseStream, CompressionLevels compressionLevel) : base(baseStream, compressionLevel) { + m_stream = new DeflateStream(baseStream, CompressionMode.Compress); + } + + /// + /// Write out bytes to the underlying stream after compressing them using deflate + /// + /// The array of bytes to write + /// The offset into the supplied buffer to start + /// The number of bytes to write + public override void Write(byte[] buffer, int offset, int count) { + if(!HasWrittenHeaders) WriteHeaders(); + m_stream.Write(buffer, offset, count); + } + + /// + /// Return the Http name for this encoding. Here, deflate. + /// + public override string ContentEncoding { + get { return "deflate"; } + } + + /// + /// Closes this Filter and calls the base class implementation. + /// + public override void Close() { + m_stream.Close(); + } + + /// + /// Flushes that the filter out to underlying storage + /// + public override void Flush() { + m_stream.Flush(); + } + } +} diff --git a/HttpCompress/Enums.cs b/HttpCompress/Enums.cs new file mode 100644 index 0000000..ee63e43 --- /dev/null +++ b/HttpCompress/Enums.cs @@ -0,0 +1,42 @@ +namespace blowery.Web.HttpCompress +{ + /// + /// The available compression algorithms to use with the HttpCompressionModule + /// + public enum Algorithms { + /// Use the Deflate algorithm + Deflate, + /// Use the GZip algorithm + GZip, + /// Use the default algorithm (picked by client) + Default=-1 + } + + /// + /// The level of compression to use with deflate + /// + public enum CompressionLevels { + /// Use the default compression level + Default = -1, + /// The highest level of compression. Also the slowest. + Highest = 9, + /// A higher level of compression. + Higher = 8, + /// A high level of compression. + High = 7, + /// More compression. + More = 6, + /// Normal compression. + Normal = 5, + /// Less than normal compression. + Less = 4, + /// A low level of compression. + Low = 3, + /// A lower level of compression. + Lower = 2, + /// The lowest level of compression that still performs compression. + Lowest = 1, + /// No compression. Use this is you are quite silly. + None = 0 + } +} diff --git a/HttpCompress/GZipFilter.cs b/HttpCompress/GZipFilter.cs new file mode 100644 index 0000000..64ea4c1 --- /dev/null +++ b/HttpCompress/GZipFilter.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; + +using System.Text; +using System.Diagnostics; + +using System.IO.Compression; + +namespace blowery.Web.HttpCompress { + /// + /// This is a little filter to support HTTP compression using GZip + /// + public class GZipFilter : CompressingFilter { + + /// + /// compression stream member + /// has to be a member as we can only have one instance of the + /// actual filter class + /// + private GZipStream m_stream = null; + + /// + /// Primary constructor. Need to pass in a stream to wrap up with gzip. + /// + /// The stream to wrap in gzip. Must have CanWrite. + public GZipFilter(Stream baseStream) : base(baseStream, CompressionLevels.Normal) { + m_stream = new GZipStream(baseStream, CompressionMode.Compress); + } + + /// + /// Write content to the stream and have it compressed using gzip. + /// + /// The bytes to write + /// The offset into the buffer to start reading bytes + /// The number of bytes to write + public override void Write(byte[] buffer, int offset, int count) { + if(!HasWrittenHeaders) WriteHeaders(); + m_stream.Write(buffer, offset, count); + } + + /// + /// The Http name of this encoding. Here, gzip. + /// + public override string ContentEncoding { + get { return "gzip"; } + } + + /// + /// Closes this Filter and calls the base class implementation. + /// + public override void Close() { + m_stream.Close(); // this will close the gzip stream along with the underlying stream + // no need for call to base.Close() here. + } + + /// + /// Flushes the stream out to underlying storage + /// + public override void Flush() { + m_stream.Flush(); + } + + + + } +} diff --git a/HttpCompress/HttpCompress.csproj b/HttpCompress/HttpCompress.csproj new file mode 100644 index 0000000..77cb9fc --- /dev/null +++ b/HttpCompress/HttpCompress.csproj @@ -0,0 +1,146 @@ + + + Local + 8.0.50727 + 2.0 + {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5} + Debug + AnyCPU + + + + + blowery.Web.HttpCompress + + + JScript + Grid + IE50 + false + Library + blowery.Web.HttpCompress + OnBuildSuccess + + + + + + + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + blowery.Web.HttpCompress.xml + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + blowery.Web.HttpCompress.xml + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + bin\Production\ + false + 285212672 + false + + + TRACE,StronglyNamedAssembly,SignedUsingACryptoServiceProvider + blowery.Web.HttpCompress.xml + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + + System + + + System.Data + + + System.Web + + + System.XML + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + \ No newline at end of file diff --git a/HttpCompress/HttpModule.cs b/HttpCompress/HttpModule.cs new file mode 100644 index 0000000..ecb92c4 --- /dev/null +++ b/HttpCompress/HttpModule.cs @@ -0,0 +1,237 @@ +using System; +using System.IO; +using System.Web; + +using System.Collections; +using System.Collections.Specialized; + +namespace blowery.Web.HttpCompress { + /// + /// An HttpModule that hooks onto the Response.Filter property of the + /// current request and tries to compress the output, based on what + /// the browser supports + /// + /// + ///

This HttpModule uses classes that inherit from . + /// We already support gzip and deflate (aka zlib), if you'd like to add + /// support for compress (which uses LZW, which is licensed), add in another + /// class that inherits from HttpFilter to do the work.

+ /// + ///

This module checks the Accept-Encoding HTTP header to determine if the + /// client actually supports any notion of compression. Currently, we support + /// the deflate (zlib) and gzip compression schemes. I chose to not implement + /// compress because it uses lzw which requires a license from + /// Unisys. For more information about the common compression types supported, + /// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 for details.

+ ///
+ /// + /// + public sealed class HttpModule : IHttpModule { + + const string INSTALLED_KEY = "httpcompress.attemptedinstall"; + static readonly object INSTALLED_TAG = new object(); + + /// + /// Init the handler and fulfill + /// + /// + /// This implementation hooks the ReleaseRequestState and PreSendRequestHeaders events to + /// figure out as late as possible if we should install the filter. Previous versions did + /// not do this as well. + /// + /// The this handler is working for. + void IHttpModule.Init(HttpApplication context) { + context.ReleaseRequestState += new EventHandler(this.CompressContent); + context.PreSendRequestHeaders += new EventHandler(this.CompressContent); + } + + /// + /// Implementation of + /// + /// + /// Currently empty. Nothing to really do, as I have no member variables. + /// + void IHttpModule.Dispose() { } + + /// + /// EventHandler that gets ahold of the current request context and attempts to compress the output. + /// + /// The that is firing this event. + /// Arguments to the event + void CompressContent(object sender, EventArgs e) { + + HttpApplication app = (HttpApplication)sender; + + // only do this if we havn't already attempted an install. This prevents PreSendRequestHeaders from + // trying to add this item way to late. We only want the first run through to do anything. + // also, we use the context to store whether or not we've attempted an add, as it's thread-safe and + // scoped to the request. An instance of this module can service multiple requests at the same time, + // so we cannot use a member variable. + if(!app.Context.Items.Contains(INSTALLED_KEY)) { + + // log the install attempt in the HttpContext + // must do this first as several IF statements + // below skip full processing of this method + app.Context.Items.Add(INSTALLED_KEY, INSTALLED_TAG); + + // get the config settings + Settings settings = Settings.GetSettings(); + + if(settings.CompressionLevel == CompressionLevels.None){ + // skip if the CompressionLevel is set to 'None' + return; + } + + string realPath = app.Request.Path.Remove(0, app.Request.ApplicationPath.Length+1); + if(settings.IsExcludedPath(realPath)){ + // skip if the file path excludes compression + return; + } + + if(settings.IsExcludedMimeType(app.Response.ContentType)){ + // skip if the MimeType excludes compression + return; + } + + // fix to handle caching appropriately + // see http://www.pocketsoap.com/weblog/2003/07/1330.html + // Note, this header is added only when the request + // has the possibility of being compressed... + // i.e. it is not added when the request is excluded from + // compression by CompressionLevel, Path, or MimeType + app.Response.Cache.VaryByHeaders["Accept-Encoding"] = true; + + // grab an array of algorithm;q=x, algorith;q=x style values + string acceptedTypes = app.Request.Headers["Accept-Encoding"]; + // if we couldn't find the header, bail out + if(acceptedTypes == null){ + return; + } + + // the actual types could be , delimited. split 'em out. + string[] types = acceptedTypes.Split(','); + + CompressingFilter filter = GetFilterForScheme(types, app.Response.Filter, settings); + + if(filter == null){ + // if we didn't find a filter, bail out + return; + } + + // if we get here, we found a viable filter. + // set the filter and change the Content-Encoding header to match so the client can decode the response + app.Response.Filter = filter; + + } + } + + /// + /// Get ahold of a for the given encoding scheme. + /// If no encoding scheme can be found, it returns null. + /// + /// + /// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 for details + /// on how clients are supposed to construct the Accept-Encoding header. This + /// implementation follows those rules, though we allow the server to override + /// the preference given to different supported algorithms. I'm doing this as + /// I would rather give the server control over the algorithm decision than + /// the client. If the clients send up * as an accepted encoding with highest + /// quality, we use the preferred algorithm as specified in the config file. + /// + public static CompressingFilter GetFilterForScheme(string[] schemes, Stream output, Settings prefs) { + bool foundDeflate = false; + bool foundGZip = false; + bool foundStar = false; + + float deflateQuality = 0f; + float gZipQuality = 0f; + float starQuality = 0f; + + bool isAcceptableDeflate; + bool isAcceptableGZip; + bool isAcceptableStar; + + for (int i = 0; i 0); + isAcceptableDeflate = (foundDeflate && (deflateQuality > 0)) || (!foundDeflate && isAcceptableStar); + isAcceptableGZip = (foundGZip && (gZipQuality > 0)) || (!foundGZip && isAcceptableStar); + + if (isAcceptableDeflate && !foundDeflate) + deflateQuality = starQuality; + + if (isAcceptableGZip && !foundGZip) + gZipQuality = starQuality; + + + // do they support any of our compression methods? + if(!(isAcceptableDeflate || isAcceptableGZip || isAcceptableStar)) { + return null; + } + + + // if deflate is better according to client + if (isAcceptableDeflate && (!isAcceptableGZip || (deflateQuality > gZipQuality))) + return new DeflateFilter(output, prefs.CompressionLevel); + + // if gzip is better according to client + if (isAcceptableGZip && (!isAcceptableDeflate || (deflateQuality < gZipQuality))) + return new GZipFilter(output); + + // if we're here, the client either didn't have a preference or they don't support compression + if(isAcceptableDeflate && (prefs.PreferredAlgorithm == Algorithms.Deflate || prefs.PreferredAlgorithm == Algorithms.Default)) + return new DeflateFilter(output, prefs.CompressionLevel); + if(isAcceptableGZip && prefs.PreferredAlgorithm == Algorithms.GZip) + return new GZipFilter(output); + + if(isAcceptableDeflate || isAcceptableStar) + return new DeflateFilter(output, prefs.CompressionLevel); + if(isAcceptableGZip) + return new GZipFilter(output); + + // return null. we couldn't find a filter. + return null; + } + + static float GetQuality(string acceptEncodingValue) { + int qParam = acceptEncodingValue.IndexOf("q="); + + if (qParam >= 0) { + float val = 0.0f; + try { + val = float.Parse(acceptEncodingValue.Substring(qParam+2, acceptEncodingValue.Length - (qParam+2))); + } catch(FormatException) { + + } + return val; + } else + return 1; + } + } +} diff --git a/HttpCompress/HttpOutputFilter.cs b/HttpCompress/HttpOutputFilter.cs new file mode 100644 index 0000000..1fbf15d --- /dev/null +++ b/HttpCompress/HttpOutputFilter.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; + +namespace blowery.Web.HttpCompress { + /// + /// The base of anything you want to latch onto the Filter property of a + /// object. + /// + /// + ///

These are generally used with but you could really use them in + /// other HttpModules. This is a general, write-only stream that writes to some underlying stream. When implementing + /// a real class, you have to override void Write(byte[], int offset, int count). Your work will be performed there. + ///
+ public abstract class HttpOutputFilter : Stream { + + private Stream _sink; + + /// + /// Subclasses need to call this on contruction to setup the underlying stream + /// + /// The stream we're wrapping up in a filter + protected HttpOutputFilter(Stream baseStream) { + _sink = baseStream; + } + + /// + /// Allow subclasses access to the underlying stream + /// + protected Stream BaseStream { + get{ return _sink; } + } + + /// + /// False. These are write-only streams + /// + public override bool CanRead { + get { return false; } + } + + /// + /// False. These are write-only streams + /// + public override bool CanSeek { + get { return false; } + } + + /// + /// True. You can write to the stream. May change if you call Close or Dispose + /// + public override bool CanWrite { + get { return _sink.CanWrite; } + } + + /// + /// Not supported. Throws an exception saying so. + /// + /// Thrown. Always. + public override long Length { + get { throw new NotSupportedException(); } + } + + /// + /// Not supported. Throws an exception saying so. + /// + /// Thrown. Always. + public override long Position { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + /// + /// Not supported. Throws an exception saying so. + /// + /// Thrown. Always. + public override long Seek(long offset, System.IO.SeekOrigin direction) { + throw new NotSupportedException(); + } + + /// + /// Not supported. Throws an exception saying so. + /// + /// Thrown. Always. + public override void SetLength(long length) { + throw new NotSupportedException(); + } + + /// + /// Closes this Filter and the underlying stream. + /// + /// + /// If you override, call up to this method in your implementation. + /// + public override void Close() { + _sink.Close(); + } + + /// + /// Fluses this Filter and the underlying stream. + /// + /// + /// If you override, call up to this method in your implementation. + /// + public override void Flush() { + _sink.Flush(); + } + + /// + /// Not supported. + /// + /// The buffer to write into. + /// The offset on the buffer to write into + /// The number of bytes to write. Must be less than buffer.Length + /// An int telling you how many bytes were written + public override int Read(byte[] buffer, int offset, int count) { + throw new NotSupportedException(); + } + + } +} diff --git a/HttpCompress/SectionHandler.cs b/HttpCompress/SectionHandler.cs new file mode 100644 index 0000000..3d211bc --- /dev/null +++ b/HttpCompress/SectionHandler.cs @@ -0,0 +1,27 @@ +using System; +using System.Xml; +using System.Xml.Serialization; +using System.Configuration; + +namespace blowery.Web.HttpCompress +{ + /// + /// This class acts as a factory for the configuration settings. + /// + public sealed class SectionHandler : IConfigurationSectionHandler + { + /// + /// Create a new config section handler. This is of type + /// + object IConfigurationSectionHandler.Create(object parent, object configContext, XmlNode configSection) { + Settings settings; + if(parent == null) + settings = Settings.Default; + else + settings = (Settings)parent; + settings.AddSettings(configSection); + return settings; + } + } + +} diff --git a/HttpCompress/Settings.cs b/HttpCompress/Settings.cs new file mode 100644 index 0000000..d03db67 --- /dev/null +++ b/HttpCompress/Settings.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Xml; + +namespace blowery.Web.HttpCompress { + /// + /// This class encapsulates the settings for an HttpCompressionModule + /// + public sealed class Settings { + + private Algorithms _preferredAlgorithm; + private CompressionLevels _compressionLevel; + private StringCollection _excludedTypes; + private StringCollection _excludedPaths; + + /// + /// Create an HttpCompressionModuleSettings from an XmlNode + /// + /// The XmlNode to configure from + public Settings(XmlNode node) : this() { + AddSettings(node); + } + + + private Settings() { + _preferredAlgorithm = Algorithms.Default; + _compressionLevel = CompressionLevels.Default; + _excludedTypes = new StringCollection(); + _excludedPaths = new StringCollection(); + } + + /// + /// Suck in some more changes from an XmlNode. Handy for config file parenting. + /// + /// The node to read from + public void AddSettings(XmlNode node) { + if(node == null) + return; + + XmlAttribute preferredAlgorithm = node.Attributes["preferredAlgorithm"]; + if(preferredAlgorithm != null) { + try { + _preferredAlgorithm = (Algorithms)Enum.Parse(typeof(Algorithms), preferredAlgorithm.Value, true); + }catch(ArgumentException) { } + } + + XmlAttribute compressionLevel = node.Attributes["compressionLevel"]; + if(compressionLevel != null) { + try { + _compressionLevel = (CompressionLevels)Enum.Parse(typeof(CompressionLevels), compressionLevel.Value, true); + }catch(ArgumentException) { } + } + + ParseExcludedTypes(node.SelectSingleNode("excludedMimeTypes")); + ParseExcludedPaths(node.SelectSingleNode("excludedPaths")); + + } + + + /// + /// Get the current settings from the xml config file + /// + public static Settings GetSettings() { + Settings settings = (Settings)System.Configuration.ConfigurationSettings.GetConfig("blowery.web/httpCompress"); + if(settings == null) + return Settings.Default; + else + return settings; + } + + /// + /// The default settings. Deflate + normal. + /// + public static Settings Default { + get { return new Settings(); } + } + + /// + /// The preferred algorithm to use for compression + /// + public Algorithms PreferredAlgorithm { + get { return _preferredAlgorithm; } + } + + + /// + /// The preferred compression level + /// + public CompressionLevels CompressionLevel { + get { return _compressionLevel; } + } + + + /// + /// Checks a given mime type to determine if it has been excluded from compression + /// + /// The MimeType to check. Can include wildcards like image/* or */xml. + /// true if the mime type passed in is excluded from compression, false otherwise + public bool IsExcludedMimeType(string mimetype) { + return _excludedTypes.Contains(mimetype.ToLower()); + } + + /// + /// Looks for a given path in the list of paths excluded from compression + /// + /// the relative url to check + /// true if excluded, false if not + public bool IsExcludedPath(string relUrl) { + return _excludedPaths.Contains(relUrl.ToLower()); + } + + private void ParseExcludedTypes(XmlNode node) { + if(node == null) return; + + for(int i = 0; i < node.ChildNodes.Count; ++i) { + switch(node.ChildNodes[i].LocalName) { + case "add": + if(node.ChildNodes[i].Attributes["type"] != null) + _excludedTypes.Add(node.ChildNodes[i].Attributes["type"].Value.ToLower()); + break; + case "delete": + if(node.ChildNodes[i].Attributes["type"] != null) + _excludedTypes.Remove(node.ChildNodes[i].Attributes["type"].Value.ToLower()); + break; + } + } + } + + private void ParseExcludedPaths(XmlNode node) { + if(node == null) return; + + for(int i = 0; i < node.ChildNodes.Count; ++i) { + switch(node.ChildNodes[i].LocalName) { + case "add": + if(node.ChildNodes[i].Attributes["path"] != null) + _excludedPaths.Add(node.ChildNodes[i].Attributes["path"].Value.ToLower()); + break; + case "delete": + if(node.ChildNodes[i].Attributes["path"] != null) + _excludedPaths.Remove(node.ChildNodes[i].Attributes["path"].Value.ToLower()); + break; + } + } + } + + } + +} + diff --git a/Tests/AssemblyInfo.cs b/Tests/AssemblyInfo.cs new file mode 100644 index 0000000..059c964 --- /dev/null +++ b/Tests/AssemblyInfo.cs @@ -0,0 +1,121 @@ +// A Better AssemblyInfo.cs based on better practices. +// +// This little guy sets up all the assembly level attributes +// on the assembly that you're compiling +// +// This version heavily influenced (read copied) from Jeffery Richter's +// Applied Microsoft.NET Framework Programming (pg. 93) + +using System.Reflection; + +#region Company Info + +// Set the CompanyName, LegalCopyright, and LegalTrademark fields +// You shouldn't have to mess with these +[assembly: AssemblyCompany("blowery.org")] +[assembly: AssemblyCopyright("Copyright (c) 2003 Ben Lowery")] + +#endregion + +#region Product Info + +// Set the ProductName and ProductVersion fields +// AssemblyProduct should be a descriptive name for the product that includes this assembly +// If this is a generic library that's going to be used by a whole bunch of stuff, +// just make the name the same as the Assembly's name. This isn't really used by +// anything except Windows Explorer's properties tab, so it's not a big deal if you mess it up +// See pg. 60 of Richter for more info. +[assembly: AssemblyProduct("HttpCompressionModule Tests")] +#endregion + +#region Version and Identity Info + +// AssemblyInformationalVersion should be the version of the product that is including this +// assembly. Again, if this assembly isn't being included in a "product", just make this +// the same as the FileVersion and be done with it. See pg 60 of Richter for more info. +[assembly: AssemblyInformationalVersion("6.0")] + +// Set the FileVersion, AssemblyVersion, FileDescription, and Comments fields +// AssemblyFileVersion is stored in the Win32 version resource. It's ignored by the CLR, +// we typically use it to store the build and revision bits every time a build +// is performed. Unfortunately, the c# compiler doesn't do this automatically, +// so we'll have to work out another way to do it. +[assembly: AssemblyFileVersion("6.0")] + +// AssemblyVersion is the version used by the CLR as part of the strong name for +// an assembly. You don't really want to mess with this unless you're +// making changes that add / remove / change functionality within the library. +// For bugfixes, don't mess with this. +// +// Do this: +// [assembly: AssemblyVersion("1.0.0.0")] +// +// Don't do this: +// [assembly: AssemblyVersion("1.0.*.*")] +// as it breaks all the other assemblies that reference this one every time +// you build the project. +[assembly: AssemblyVersion("6.0")] + +// Title is just for inspection utilities and isn't really used for much +// Generally just set this to the name of the file containing the +// manifest, sans extension. I.E. for BensLibrary.dll, name it BensLibrary +[assembly: AssemblyTitle("Tests")] + +// Description is just for inspection utilities and isn't really that important. +// It's generally a good idea to write a decent description so that you +// don't end up looking like a tool when your stuff shows up in an inspector. +[assembly: AssemblyDescription("Tests for the HttpCompress")] + +#endregion + +#region Culture Info + +// Set the assembly's culture affinity. Generally don't want to set this +// unless you're building an resource only assembly. Assemblies that contain +// code should always be culture neutral +[assembly: AssemblyCulture("")] + +#endregion + +#region Assembly Signing Info + +#if !StronglyNamedAssembly + +// Weakly named assemblies are never signed +[assembly: AssemblyDelaySign(false)] + +#else + + // Strongly named assemblies are usually delay signed while building and + // completely signed using sn.exe's -R or -Rc switch + +#if !SignedUsingACryptoServiceProvider + + // Give the name of the file that contains the public/private key pair. + // If delay signing, only the public key is used + [assembly: AssemblyKeyFile(THE_KEY_FILE_RELATIVE_TO_THIS_FILE)] + + // Note: If AssemblyKeyFile and AssemblyKeyName are both specified, + // here's what happens... + // 1) If the container exists, the key file is ignored + // 2) If the container doesn't exist, the keys from the key + // file are copied in the container and the assembly is signed + +#else + + // Give the name of the cryptographic service provider (CSP) container + // that contains the public/private key pair + // If delay signing, only the public key is used + [assembly: AssemblyKeyName("blowery.org")] + +#endif + +#endif + +#endregion + +#region AssemblyWideConstants +class Constants { + public const string Namespace = "blowery.Web.HttpCompress.Tests"; +} +#endregion diff --git a/Tests/SettingsTests.cs b/Tests/SettingsTests.cs new file mode 100644 index 0000000..d82e136 --- /dev/null +++ b/Tests/SettingsTests.cs @@ -0,0 +1,146 @@ +using System; +using System.Diagnostics; +using System.Resources; +using NUnit.Framework; + +using blowery.Web.HttpCompress; + +namespace blowery.Web.HttpCompress.Tests { + /// + /// Tests for the class + /// + [TestFixture] + public class SettingsTests { + + ResourceManager rman; + + [SetUp] public void Setup() { + rman = Utility.GetResourceManager("XmlTestDocuments"); + } + + [Test] public void DefaultTest() { + Assertion.AssertEquals(CompressionLevels.Default, Settings.Default.CompressionLevel); + Assertion.AssertEquals(Algorithms.Default, Settings.Default.PreferredAlgorithm); + } + + [Test] public void EmptyNodeTest() { + Settings setting = new Settings(MakeNode(rman.GetString("EmptyNode"))); + Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel); + Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm); + } + + [Test] public void DeflateTest() { + Settings setting = new Settings(MakeNode(rman.GetString("Deflate"))); + Assertion.AssertEquals(Algorithms.Deflate, setting.PreferredAlgorithm); + Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel); + } + + [Test] public void GZipTest() { + Settings setting = new Settings(MakeNode(rman.GetString("GZip"))); + Assertion.AssertEquals(Algorithms.GZip, setting.PreferredAlgorithm); + Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel); + } + + [Test] public void HighLevelTest() { + Settings setting = new Settings(MakeNode(rman.GetString("LevelHigh"))); + Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm); + Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel); + } + + [Test] public void NormalLevelTest() { + Settings setting = new Settings(MakeNode(rman.GetString("LevelNormal"))); + Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm); + Assertion.AssertEquals(CompressionLevels.Normal, setting.CompressionLevel); + } + + [Test] public void LowLevelTest() { + Settings setting = new Settings(MakeNode(rman.GetString("LevelLow"))); + Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm); + Assertion.AssertEquals(CompressionLevels.Low, setting.CompressionLevel); + } + + [Test] public void BadLevelTest() { + Settings setting = new Settings(MakeNode(rman.GetString("BadLevel"))); + Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel); + } + + [Test] public void BadAlgorithmTest() { + Settings setting = new Settings(MakeNode(rman.GetString("BadAlgorithm"))); + Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm); + } + + [Test] + public void AddSettingsTests() { + Settings setting = new Settings(MakeNode(rman.GetString("DeflateAndHigh"))); + Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel); + Assertion.AssertEquals(Algorithms.Deflate, setting.PreferredAlgorithm); + + // test adding a null node + setting.AddSettings(null); + Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel); + Assertion.AssertEquals(Algorithms.Deflate, setting.PreferredAlgorithm); + + // test overriding algorithm + setting.AddSettings(MakeNode(rman.GetString("GZip"))); + Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel); + Assertion.AssertEquals(Algorithms.GZip, setting.PreferredAlgorithm); + + // test overriding compression level + setting.AddSettings(MakeNode(rman.GetString("LevelLow"))); + Assertion.AssertEquals(CompressionLevels.Low, setting.CompressionLevel); + Assertion.AssertEquals(Algorithms.GZip, setting.PreferredAlgorithm); + + } + + [Test] public void UserAddedExcludedType() { + Settings setting = new Settings(MakeNode(rman.GetString("ExcludeBenFoo"))); + Assertion.Assert(setting.IsExcludedMimeType("ben/foo")); + } + + [Test] public void UserAddedMultipleExcludedTypes() { + Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultipleTypes"))); + Assertion.Assert(setting.IsExcludedMimeType("ben/foo")); + Assertion.Assert(setting.IsExcludedMimeType("image/jpeg")); + } + + [Test] public void UserAddedAndRemovedType() { + Settings setting = new Settings(MakeNode(rman.GetString("AddAndRemoveSameType"))); + Assertion.Assert(!setting.IsExcludedMimeType("user/silly")); + } + + [Test] public void UserRemovedExcludedType() { + Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultipleTypes"))); + Assertion.Assert(setting.IsExcludedMimeType("ben/foo")); + Assertion.Assert(setting.IsExcludedMimeType("image/jpeg")); + + setting.AddSettings(MakeNode(rman.GetString("RemoveImageJpegExclusion"))); + Assertion.Assert(setting.IsExcludedMimeType("ben/foo")); + Assertion.Assert(!setting.IsExcludedMimeType("image/jpeg")); + } + + [Test] public void ChildAddsMimeExclude(){ + Settings parent = Settings.Default; + parent.AddSettings(MakeNode(rman.GetString("ExcludeBenFoo"))); + Assertion.Assert(parent.IsExcludedMimeType("ben/foo")); + } + + [Test] public void ExcludeMultiplePaths() { + Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultiplePaths"))); + Assertion.Assert(setting.IsExcludedPath("foo.aspx")); + Assertion.Assert(setting.IsExcludedPath("foo/bar.aspx")); + } + + [Test] public void RemoveExistingPathExclude() { + Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultiplePaths"))); + setting.AddSettings(MakeNode(rman.GetString("RemoveFooAspxExclude"))); + Assertion.Assert(!setting.IsExcludedPath("foo.aspx")); + Assertion.Assert(setting.IsExcludedPath("foo/bar.aspx")); + } + + private System.Xml.XmlNode MakeNode(string xml) { + System.Xml.XmlDocument dom = new System.Xml.XmlDocument(); + dom.LoadXml(xml); + return dom.DocumentElement; + } + } +} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj new file mode 100644 index 0000000..0e999e6 --- /dev/null +++ b/Tests/Tests.csproj @@ -0,0 +1,143 @@ + + + Local + 8.0.50727 + 2.0 + {758C0F47-99BF-43B2-8C91-C94CAEC72DAC} + Debug + AnyCPU + + + + + blowery.Web.HttpCompress.Tests + + + JScript + Grid + IE50 + false + Library + blowery.Web.HttpCompress.Tests + OnBuildSuccess + + + + + + + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + + nunit.framework + lib\nunit.framework.dll + + + System + + + System.Data + + + System.XML + + + HttpCompress + {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + + XmlTestDocuments.xsd + + + Code + + + Code + + + Code + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Utility.cs b/Tests/Utility.cs new file mode 100644 index 0000000..047f498 --- /dev/null +++ b/Tests/Utility.cs @@ -0,0 +1,13 @@ +using System; +using System.Resources; + +namespace blowery.Web.HttpCompress.Tests { + /// Some utility functions for the tests + sealed class Utility { + private Utility() { } + + public static System.Resources.ResourceManager GetResourceManager(string nameOfResouce) { + return new ResourceManager(Constants.Namespace + "." + nameOfResouce, System.Reflection.Assembly.GetExecutingAssembly()); + } + } +} diff --git a/Tests/XmlTestDocuments.resx b/Tests/XmlTestDocuments.resx new file mode 100644 index 0000000..b978665 --- /dev/null +++ b/Tests/XmlTestDocuments.resx @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3.0.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <httpCompressionModule/> + + + <httpCompressionModule preferredAlgorithm="deflate" compressionLevel="high"/> + + + <httpCompressionModule preferredAlgorithm="deflate"/> + + + <httpCompressionModule preferredAlgorithm="gzip"/> + + + <httpCompressionModule compressionLevel="high"/> + + + <httpCompressionModule compressionLevel="normal"/> + + + <httpCompressionModule compressionLevel="low"/> + + + <httpCompressionModule compressionLevel="xxx"/> + + + <httpCompressionModule preferredAlgorithm="xxx"/> + + + <httpCompressionModule> + <excludedMimeTypes> + <add type="ben/foo"/> + </excludedMimeTypes> +</httpCompressionModule> + + + <httpCompressionModule> + <excludedMimeTypes> + <delete type="image/jpeg"/> + </excludedMimeTypes> +</httpCompressionModule> + + + <httpCompressionModule> + <excludedMimeTypes> + <add type="ben/foo"/> + <add type="image/jpeg"/> + </excludedMimeTypes> +</httpCompressionModule> + + + <httpCompressionModule> + <excludedMimeTypes> + <add type="user/silly"/> + <delete type="user/silly"/> + </excludedMimeTypes> +</httpCompressionModule> + + + <httpCompressionModule> + <excludedPaths> + <add path="foo.aspx"/> + <add path="foo/bar.aspx"/> + </excludedPaths> +</httpCompressionModule> + + + <httpCompressionModule> + <excludedPaths> + <delete path="foo.aspx"/> + </excludedPaths> +</httpCompressionModule> + + \ No newline at end of file diff --git a/Tests/XmlTestDocuments.xsd b/Tests/XmlTestDocuments.xsd new file mode 100644 index 0000000..65ab7f0 --- /dev/null +++ b/Tests/XmlTestDocuments.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/app.config b/Tests/app.config new file mode 100644 index 0000000..9392975 --- /dev/null +++ b/Tests/app.config @@ -0,0 +1 @@ + diff --git a/Vendor/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.dll b/Vendor/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.dll new file mode 100644 index 0000000000000000000000000000000000000000..b0cadba7664dd0adbac45530b73bcc0a622f516a GIT binary patch literal 114688 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 + + + ICSharpCode.SharpZipLib + + + + + Does all the compress and decompress pre-operation stuff. + Sets up the streams and file header characters. + Uses multiply overloaded methods to call for the compress/decompress. + + + + + Base class for both the compress and decompress classes. + Holds common arrays, and static data. + + + + + An input stream that decompresses from the BZip2 format (without the file + header chars) to be read as any other stream. + + + + + Flushes the baseInputStream + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + Closes the input stream + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + An output stream that compresses into the BZip2 format (without the file + header chars) into another stream. + TODO: Update to BZip2 1.0.1, 1.0.2 + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + Computes Adler32 checksum for a stream of data. An Adler32 + checksum is not as reliable as a CRC32 checksum, but a lot faster to + compute. + + The specification for Adler32 may be found in RFC 1950. + ZLIB Compressed Data Format Specification version 3.3) + + + From that document: + + "ADLER32 (Adler-32 checksum) + This contains a checksum value of the uncompressed data + (excluding any dictionary data) computed according to Adler-32 + algorithm. This algorithm is a 32-bit extension and improvement + of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 + standard. + + Adler-32 is composed of two sums accumulated per byte: s1 is + the sum of all bytes, s2 is the sum of all s1 values. Both sums + are done modulo 65521. s1 is initialized to 1, s2 to zero. The + Adler-32 checksum is stored as s2*65536 + s1 in most- + significant-byte first (network) order." + + "8.2. The Adler-32 algorithm + + The Adler-32 algorithm is much faster than the CRC32 algorithm yet + still provides an extremely low probability of undetected errors. + + The modulo on unsigned long accumulators can be delayed for 5552 + bytes, so the modulo operation time is negligible. If the bytes + are a, b, c, the second sum is 3a + 2b + c + 3, and so is position + and order sensitive, unlike the first sum, which is just a + checksum. That 65521 is prime is important to avoid a possible + large class of two-byte errors that leave the check unchanged. + (The Fletcher checksum uses 255, which is not prime and which also + makes the Fletcher check insensitive to single byte changes 0 - + 255.) + + The sum s1 is initialized to 1 instead of zero to make the length + of the sequence part of s2, so that the length does not have to be + checked separately. (Any sequence of zeroes has a Fletcher + checksum of zero.)" + + + + + + + Interface to compute a data checksum used by checked input/output streams. + A data checksum can be updated by one byte or with a byte array. After each + update the value of the current checksum can be returned by calling + getValue. The complete checksum object can also be reset + so it can be used again with new data. + + + + + Resets the data checksum as if no update was ever called. + + + + + Adds one byte to the data checksum. + + + the data value to add. The high byte of the int is ignored. + + + + + Updates the data checksum with the bytes taken from the array. + + + buffer an array of bytes + + + + + Adds the byte array to the data checksum. + + + the buffer which contains the data + + + the offset in the buffer where the data starts + + + the length of the data + + + + + Returns the data checksum computed so far. + + + + + largest prime smaller than 65536 + + + + + Creates a new instance of the Adler32 class. + The checksum starts off with a value of 1. + + + + + Resets the Adler32 checksum to the initial value. + + + + + Updates the checksum with the byte b. + + + the data value to add. The high byte of the int is ignored. + + + + + Updates the checksum with the bytes taken from the array. + + + buffer an array of bytes + + + + + Updates the checksum with the bytes taken from the array. + + + an array of bytes + + + the start of the data used for this update + + + the number of bytes to use for this update + + + + + Returns the Adler32 data checksum computed so far. + + + + + Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The table is simply the CRC of all possible eight bit values. This is all + the information needed to generate CRC's on data a byte at a time for all + combinations of CRC register values and incoming bytes. + + + + + The crc data checksum so far. + + + + + Resets the CRC32 data checksum as if no update was ever called. + + + + + Updates the checksum with the int bval. + + + the byte is taken as the lower 8 bits of bval + + + + + Updates the checksum with the bytes taken from the array. + + + buffer an array of bytes + + + + + Adds the byte array to the data checksum. + + + the buffer which contains the data + + + the offset in the buffer where the data starts + + + the length of the data + + + + + Returns the CRC32 data checksum computed so far. + + + + + This class contains constants used for gzip. + + + + + Magic number found at start of GZIP header + + + + + This filter stream is used to decompress a "GZIP" format stream. + The "GZIP" format is described baseInputStream RFC 1952. + + author of the original java version : John Leuner + + This sample shows how to unzip a gzipped file + + using System; + using System.IO; + + using NZlib.GZip; + + class MainClass + { + public static void Main(string[] args) + { + Stream s = new GZipInputStream(File.OpenRead(args[0])); + FileStream fs = File.Create(Path.GetFileNameWithoutExtension(args[0])); + int size = 2048; + byte[] writeData = new byte[2048]; + while (true) { + size = s.Read(writeData, 0, size); + if (size > 0) { + fs.Write(writeData, 0, size); + } else { + break; + } + } + s.Close(); + } + } + + + + + + This filter stream is used to decompress data compressed baseInputStream the "deflate" + format. The "deflate" format is described baseInputStream RFC 1951. + + This stream may form the basis for other decompression filters, such + as the GzipInputStream. + + author of the original java version : John Leuner + + + + + Decompressor for this filter + + + + + Byte array used as a buffer + + + + + Size of buffer + + + + + base stream the inflater depends on. + + + + + Flushes the baseInputStream + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + Create an InflaterInputStream with the default decompresseor + and a default buffer size. + + + the InputStream to read bytes from + + + + + Create an InflaterInputStream with the specified decompresseor + and a default buffer size. + + + the InputStream to read bytes from + + + the decompressor used to decompress data read from baseInputStream + + + + + Create an InflaterInputStream with the specified decompresseor + and a specified buffer size. + + + the InputStream to read bytes from + + + the decompressor used to decompress data read from baseInputStream + + + size of the buffer to use + + + + + Closes the input stream + + + + + Fills the buffer with more data to decompress. + + + + + Reads one byte of decompressed data. + + The byte is baseInputStream the lower 8 bits of the int. + + + + + Decompresses data into the byte array + + + the array to read and decompress data into + + + the offset indicating where the data should be placed + + + the number of bytes to decompress + + + + + Skip specified number of bytes of uncompressed data + + + number of bytes to skip + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + Returns 0 once the end of the stream (EOF) has been reached. + Otherwise returns 1. + + + + + CRC-32 value for uncompressed data + + + + + Indicates end of stream + + + + + Creates a GzipInputStream with the default buffer size + + + The stream to read compressed data from (baseInputStream GZIP format) + + + + + Creates a GZIPInputStream with the specified buffer size + + + The stream to read compressed data from (baseInputStream GZIP format) + + + Size of the buffer to use + + + + + Reads uncompressed data into an array of bytes + + + the buffer to read uncompressed data into + + + the offset indicating where the data should be placed + + + the number of uncompressed bytes to be read + + + + + This filter stream is used to compress a stream into a "GZIP" stream. + The "GZIP" format is described in RFC 1952. + + author of the original java version : John Leuner + + This sample shows how to gzip a file + + using System; + using System.IO; + + using NZlib.GZip; + + class MainClass + { + public static void Main(string[] args) + { + Stream s = new GZipOutputStream(File.Create(args[0] + ".gz")); + FileStream fs = File.OpenRead(args[0]); + byte[] writeData = new byte[fs.Length]; + fs.Read(writeData, 0, (int)fs.Length); + s.Write(writeData, 0, writeData.Length); + s.Close(); + } + } + + + + + + This is a special FilterOutputStream deflating the bytes that are + written through it. It uses the Deflater for deflating. + + authors of the original java version : Tom Tromey, Jochen Hoenicke + + + + + This buffer is used temporarily to retrieve the bytes from the + deflater and write them to the underlying output stream. + + + + + The deflater which is used to deflate the stream. + + + + + base stream the deflater depends on. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + Deflates everything in the def's input buffers. This will call + def.deflate() until all bytes from the input buffers + are processed. + + + + + Creates a new DeflaterOutputStream with a default Deflater and default buffer size. + + + the output stream where deflated output should be written. + + + + + Creates a new DeflaterOutputStream with the given Deflater and + default buffer size. + + + the output stream where deflated output should be written. + + + the underlying deflater. + + + + + Creates a new DeflaterOutputStream with the given Deflater and + buffer size. + + + the output stream where deflated output should be written. + + + the underlying deflater. + + + the buffer size. + + + if bufsize isn't positive. + + + + + Flushes the stream by calling flush() on the deflater and then + on the underlying stream. This ensures that all bytes are + flushed. + + + + + Finishes the stream by calling finish() on the deflater. + + + + + Calls finish () and closes the stream. + + + + + Writes a single byte to the compressed output stream. + + + the byte value. + + + + + Writes a len bytes from an array to the compressed stream. + + + the byte array. + + + the offset into the byte array where to start. + + + the number of bytes to write. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + CRC-32 value for uncompressed data + + + + + Creates a GzipOutputStream with the default buffer size + + + The stream to read data (to be compressed) from + + + + + Creates a GZIPOutputStream with the specified buffer size + + + The stream to read data (to be compressed) from + + + Size of the buffer to use + + + + + Writes remaining compressed output data to the output stream + and closes it. + + + + + This exception is used to indicate that there is a problem + with a TAR archive header. + + + + + The TarArchive class implements the concept of a + tar archive. A tar archive is a series of entries, each of + which represents a file system object. Each entry in + the archive consists of a header record. Directory entries + consist only of the header record, and are followed by entries + for the directory's contents. File entries consist of a + header record followed by the number of records needed to + contain the file's contents. All entries are written on + record boundaries. Records are 512 bytes long. + + TarArchives are instantiated in either read or write mode, + based upon whether they are instantiated with an InputStream + or an OutputStream. Once instantiated TarArchives read/write + mode can not be changed. + + There is currently no support for random access to tar archives. + However, it seems that subclassing TarArchive, and using the + TarBuffer.getCurrentRecordNum() and TarBuffer.getCurrentBlockNum() + methods, this would be rather trvial. + + + + + The InputStream based constructors create a TarArchive for the + purposes of e'x'tracting or lis't'ing a tar archive. Thus, use + these constructors when you wish to extract files from or list + the contents of an existing tar archive. + + + + + The OutputStream based constructors create a TarArchive for the + purposes of 'c'reating a tar archive. Thus, use these constructors + when you wish to create a new tar archive and write files into it. + + + + + Common constructor initialization code. + + + + Set the debugging flag. + + @param debugF The new debug setting. + + + + Set the flag that determines whether existing files are + kept, or overwritten during extraction. + + + If true, do not overwrite existing files. + + + + + Set the ascii file translation flag. If ascii file translatio + is true, then the MIME file type will be consulted to determine + if the file is of type 'text/*'. If the MIME type is not found, + then the TransFileTyper is consulted if it is not null. If + either of these two checks indicates the file is an ascii text + file, it will be translated. The translation converts the local + operating system's concept of line ends into the UNIX line end, + '\n', which is the defacto standard for a TAR archive. This makes + text files compatible with UNIX, and since most tar implementations + text files compatible with UNIX, and since most tar implementations + + + If true, translate ascii text files. + + + + + Set user and group information that will be used to fill in the + tar archive's entry headers. Since Java currently provides no means + of determining a user name, user id, group name, or group id for + a given File, TarArchive allows the programmer to specify values + to be used in their place. + + + The user Id to use in the headers. + + + The user name to use in the headers. + + + The group id to use in the headers. + + + The group name to use in the headers. + + + + + Close the archive. This simply calls the underlying + tar stream's close() method. + + + + + Perform the "list" command and list the contents of the archive. + + NOTE That this method uses the progress display to actually list + the conents. If the progress display is not set, nothing will be + listed! + + + + + Perform the "extract" command and extract the contents of the archive. + + + The destination directory into which to extract. + + + + + Extract an entry from the archive. This method assumes that the + tarIn stream has been properly set with a call to getNextEntry(). + + + The destination directory into which to extract. + + + The TarEntry returned by tarIn.getNextEntry(). + + + + + Write an entry to the archive. This method will call the putNextEntry + and then write the contents of the entry, and finally call closeEntry()() + for entries that are files. For directories, it will call putNextEntry(), + and then, if the recurse flag is true, process each entry that is a + child of the directory. + + + The TarEntry representing the entry to write to the archive. + + + If true, process the children of directory entries. + + + + + Get/Set the verbosity setting. + + + + + Get the user id being used for archive entry headers. + + + The current user id. + + + + + Get the user name being used for archive entry headers. + + + The current user name. + + + + + Get the group id being used for archive entry headers. + + + The current group id. + + + + + Get the group name being used for archive entry headers. + + + The current group name. + + + + + Get the archive's record size. Because of its history, tar + supports the concept of buffered IO consisting of BLOCKS of + RECORDS. This allowed tar to match the IO characteristics of + the physical device being used. Of course, in the Java world, + this makes no sense, WITH ONE EXCEPTION - archives are expected + to be propertly "blocked". Thus, all of the horrible TarBuffer + support boils down to simply getting the "boundaries" correct. + + + The record size this archive is using. + + + + + The TarBuffer class implements the tar archive concept + of a buffered input stream. This concept goes back to the + days of blocked tape drives and special io devices. In the + C# universe, the only real function that this class + performs is to ensure that files have the correct "block" + size, or other tars will complain. +

+ You should never have a need to access this class directly. + TarBuffers are created by Tar IO Streams. +

+
+
+ + + Initialization common to all constructors. + + + + + Get the TAR Buffer's block size. Blocks consist of multiple records. + + + + + Get the TAR Buffer's record size. + + + + + Set the debugging flag for the buffer. + + + + + Determine if an archive record indicate End of Archive. End of + archive is indicated by a record that consists entirely of null bytes. + + + The record data to check. + + + + + Skip over a record on the input stream. + + + + + Read a record from the input stream and return the data. + + + The record data. + + + + + false if End-Of-File, else true + + + + + Get the current block number, zero based. + + + The current zero based block number. + + + + + Get the current record number, within the current block, zero based. + Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum. + + + The current zero based record number. + + + + + Write an archive record to the archive. + + + The record data to write to the archive. + + + + + + Write an archive record to the archive, where the record may be + inside of a larger array buffer. The buffer must be "offset plus + record size" long. + + + The buffer containing the record data to write. + + + The offset of the record data within buf. + + + + + Write a TarBuffer block to the archive. + + + + + Flush the current data block if it has any data in it. + + + + + Close the TarBuffer. If this is an output buffer, also flush the + current block before closing. + + + + + This class represents an entry in a Tar archive. It consists + of the entry's header, as well as the entry's File. Entries + can be instantiated in one of three ways, depending on how + they are to be used. +

+ TarEntries that are created from the header bytes read from + an archive are instantiated with the TarEntry( byte[] ) + constructor. These entries will be used when extracting from + or listing the contents of an archive. These entries have their + header filled in using the header bytes. They also set the File + to null, since they reference an archive entry not a file.

+

+ TarEntries that are created from Files that are to be written + into an archive are instantiated with the TarEntry( File ) + constructor. These entries have their header filled in using + the File's information. They also keep a reference to the File + for convenience when writing entries.

+

+ Finally, TarEntries can be constructed from nothing but a name. + This allows the programmer to construct the entry by hand, for + instance when only an InputStream is available for writing to + the archive, and the header information is constructed from + other information. In this case the header fields are set to + defaults and the File is set to null.

+ +

+ The C structure for a Tar Entry's header is: +

+            struct header {
+            	char	name[NAMSIZ];
+            	char	mode[8];
+            	char	uid[8];
+            	char	gid[8];
+            	char	size[12];
+            	char	mtime[12];
+            	char	chksum[8];
+            	char	linkflag;
+            	char	linkname[NAMSIZ];
+            	char	magic[8];
+            	char	uname[TUNMLEN];
+            	char	gname[TGNMLEN];
+            	char	devmajor[8];
+            	char	devminor[8];
+            	} header;
+            
+

+ +
+
+ + + If this entry represents a File, this references it. + + + + + This is the entry's header information. + + + + + Only Create Entries with the static CreateXYZ methods or a headerBuffer. + + + + + Construct an entry from an archive's header bytes. File is set + to null. + + + The header bytes from a tar archive entry. + + + + + Construct an entry with only a name. This allows the programmer + to construct the entry's header "by hand". File is set to null. + + + + + Construct an entry for a file. File is set to file, and the + header is constructed from information from the file. + + + The file that the entry represents. + + + + + Initialization code common to all constructors. + + + + + Determine if the two entries are equal. Equality is determined + by the header names being equal. + + + True if the entries are equal. + + + + + Must be overridden when you override Equals. + + + + + Determine if the given entry is a descendant of this entry. + Descendancy is determined by the name of the descendant + starting with this entry's name. + + + Entry to be checked as a descendent of this. + + + True if entry is a descendant of this. + + + + + Convenience method to set this entry's group and user ids. + + + This entry's new user id. + + + This entry's new group id. + + + + + Convenience method to set this entry's group and user names. + + + This entry's new user name. + + + This entry's new group name. + + + + + Convenience method that will modify an entry's name directly + in place in an entry header buffer byte array. + + + The buffer containing the entry header to modify. + + + The new name to place into the header buffer. + + + + + Fill in a TarHeader with information from a File. + + + The TarHeader to fill in. + + + The file from which to get the header information. + + + + + If this entry represents a file, and the file is a directory, return + an array of TarEntries for this entry's children. + + + An array of TarEntry's for this entry's children. + + + + + Compute the checksum of a tar entry header. + + + The tar entry's header buffer. + + + The computed checksum. + + + + + Write an entry's header information to a header buffer. + + + The tar entry header buffer to fill in. + + + + + Parse an entry's TarHeader information from a header buffer. + + + Parse an entry's TarHeader information from a header buffer. + + + The tar entry header buffer to get information from. + + + + + Fill in a TarHeader given only the entry's name. + + + The TarHeader to fill in. + + + The tar entry name. + + + + + Get this entry's header. + + + This entry's TarHeader. + + + + + Get/Set this entry's name. + + + + + Get/set this entry's user id. + + + + + Get/set this entry's group id. + + + + + Get/set this entry's user name. + + + + + Get/set this entry's group name. + + + + Convert time to DateTimes + Get/Set this entry's modification time. + + @param time This entry's new modification time. + + + + Get this entry's file. + + + This entry's file. + + + + + Get/set this entry's file size. + + + + + Return whether or not this entry represents a directory. + + + True if this entry is a directory. + + + + + This class encapsulates the Tar Entry Header used in Tar Archives. + The class also holds a number of tar constants, used mostly in headers. + + + + + The length of the name field in a header buffer. + + + + + The length of the mode field in a header buffer. + + + + + The length of the user id field in a header buffer. + + + + + The length of the group id field in a header buffer. + + + + + The length of the checksum field in a header buffer. + + + + + The length of the size field in a header buffer. + + + + + The length of the magic field in a header buffer. + + + + + The length of the modification time field in a header buffer. + + + + + The length of the user name field in a header buffer. + + + + + The length of the group name field in a header buffer. + + + + + The length of the devices field in a header buffer. + + + + + LF_ constants represent the "link flag" of an entry, or more commonly, + the "entry type". This is the "old way" of indicating a normal file. + + + + + Normal file type. + + + + + Link file type. + + + + + Symbolic link file type. + + + + + Character device file type. + + + + + Block device file type. + + + + + Directory file type. + + + + + FIFO (pipe) file type. + + + + + Contiguous file type. + + + + + The magic tag representing a POSIX tar archive. + + + + + The magic tag representing a GNU tar archive. + + + + + The entry's name. + + + + + The entry's permission mode. + + + + + The entry's user id. + + + + + The entry's group id. + + + + + The entry's size. + + + + + The entry's modification time. + + + + + The entry's checksum. + + + + + The entry's link flag. + + + + + The entry's link name. + + + + + The entry's magic tag. + + + + + The entry's user name. + + + + + The entry's group name. + + + + + The entry's major device number. + + + + + The entry's minor device number. + + + + + TarHeaders can be cloned. + + + + + Get the name of this entry. + + + The entry's name. + + + + + Parse an octal string from a header buffer. This is used for the + file permission mode value. + + + The header buffer from which to parse. + + + The offset into the buffer from which to parse. + + + The number of header bytes to parse. + + + The long value of the octal string. + + + + + Parse an entry name from a header buffer. + + + The header buffer from which to parse. + + + The offset into the buffer from which to parse. + + + The number of header bytes to parse. + + + The header's entry name. + + + + + Determine the number of bytes in an entry name. + + + + + The header buffer from which to parse. + + + The offset into the buffer from which to parse. + + + The number of header bytes to parse. + + + The number of bytes in a header's entry name. + + + + + Parse an octal integer from a header buffer. + + + + + The header buffer from which to parse. + + + The offset into the buffer from which to parse. + + + The number of header bytes to parse. + + + The integer value of the octal bytes. + + + + + Parse an octal long integer from a header buffer. + + + + + The header buffer from which to parse. + + + The offset into the buffer from which to parse. + + + The number of header bytes to parse. + + + The long value of the octal bytes. + + + + + Parse the checksum octal integer from a header buffer. + + + + + The header buffer from which to parse. + + + The offset into the buffer from which to parse. + + + The number of header bytes to parse. + + + The integer value of the entry's checksum. + + + + + The TarInputStream reads a UNIX tar archive as an InputStream. + methods are provided to position at each successive entry in + the archive, and the read each entry as a normal input stream + using read(). + + + + + Flushes the baseInputStream + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + Closes this stream. Calls the TarBuffer's close() method. + The underlying stream is closed by the TarBuffer. + + + + + Get the record size being used by this stream's TarBuffer. + + + TarBuffer record size. + + + + + Skip bytes in the input buffer. This skips bytes in the + current entry's data, not the entire archive, and will + stop at the end of the current entry's data if the number + to skip extends beyond that point. + + + The number of bytes to skip. + + + + + Since we do not support marking just yet, we do nothing. + + + The limit to mark. + + + + + Since we do not support marking just yet, we do nothing. + + + + + Get the next entry in this tar archive. This will skip + over any remaining data in the current entry, if there + is one, and place the input stream at the header of the + next entry, and read the header and instantiate a new + TarEntry from the header bytes and return that entry. + If there are no more entries in the archive, null will + be returned to indicate that the end of the archive has + been reached. + + + The next TarEntry in the archive, or null. + + + + + Reads a byte from the current tar archive entry. + This method simply calls read(byte[], int, int). + + + + + Reads bytes from the current tar archive entry. + + This method is aware of the boundaries of the current + entry in the archive and will deal with them as if they + entry in the archive and will deal with them as if they + + + The buffer into which to place bytes read. + + + The offset at which to place bytes read. + + + The number of bytes to read. + + + The number of bytes read, or -1 at EOF. + + + + + Copies the contents of the current tar archive entry directly into + an output stream. + + + The OutputStream into which to write the entry's data. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + Get the available data that can be read from the current + entry in the archive. This does not indicate how much data + is left in the entire archive, only in the current entry. + This value is determined from the entry's size header field + and the amount of data already read from the current entry. + + + The number of available bytes for the current entry. + + + + + Since we do not support marking just yet, we return false. + + + + + This interface is provided, with the method setEntryFactory(), to allow + the programmer to have their own TarEntry subclass instantiated for the + entries return from getNextEntry(). + + + + + The TarOutputStream writes a UNIX tar archive as an OutputStream. + Methods are provided to put entries, and then write their contents + by writing to this stream using write(). + + public + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + Sets the debugging flag. + + + True to turn on debugging. + + + + + Ends the TAR archive without closing the underlying OutputStream. + The result is that the EOF record of nulls is written. + + + + + Ends the TAR archive and closes the underlying OutputStream. + This means that finish() is called followed by calling the + TarBuffer's close(). + + + + + Get the record size being used by this stream's TarBuffer. + + + The TarBuffer record size. + + + + + Put an entry on the output stream. This writes the entry's + header record and positions the output stream for writing + the contents of the entry. Once this method is called, the + stream is ready for calls to write() to write the entry's + contents. Once the contents are written, closeEntry() + MUST be called to ensure that all buffered data + is completely written to the output stream. + + + The TarEntry to be written to the archive. + + + + + Close an entry. This method MUST be called for all file + entries that contain data. The reason is that we must + buffer data written to the stream in order to satisfy + the buffer's record based writes. Thus, there may be + data fragments still being assembled that must be written + to the output stream before this entry is closed and the + next entry written. + + + + + Writes a byte to the current tar archive entry. + This method simply calls Write(byte[], int, int). + + + The byte written. + + + + + Writes bytes to the current tar archive entry. This method + is aware of the current entry and will throw an exception if + you attempt to write bytes past the length specified for the + current entry. The method is also (painfully) aware of the + record buffering required by TarBuffer, and manages buffers + that are not a multiple of recordsize in length, including + assembling records from small buffers. + + + The buffer to write to the archive. + + + The offset in the buffer from which to get bytes. + + + The number of bytes to write. + + + + + Write an EOF (end of archive) record to the tar archive. + An EOF record consists of a record of all zeros. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + I needed to implement the abstract member. + + + + + Contains the output from the Inflation process. + We need to have a window so that we can refer backwards into the output stream + to repeat stuff. + + author of the original java version : John Leuner + + + + + This class allows us to retrieve a specified amount of bits from + the input buffer, as well as copy big byte blocks. + + It uses an int buffer to store up to 31 bits for direct + manipulation. This guarantees that we can get at least 16 bits, + but we only need at most 15, so this is all safe. + + There are some optimizations in this class, for example, you must + never peek more then 8 bits more than needed, and you must first + peek bits before you may drop them. This is not a general purpose + class but optimized for the behaviour of the Inflater. + + authors of the original java version : John Leuner, Jochen Hoenicke + + + + + Get the next n bits but don't increase input pointer. n must be + less or equal 16 and if you if this call succeeds, you must drop + at least n-8 bits in the next call. + + + the value of the bits, or -1 if not enough bits available. */ + + + + + Drops the next n bits from the input. You should have called peekBits + with a bigger or equal n before, to make sure that enough bits are in + the bit buffer. + + + + + Gets the next n bits and increases input pointer. This is equivalent + to peekBits followed by dropBits, except for correct error handling. + + + the value of the bits, or -1 if not enough bits available. + + + + + Skips to the next byte boundary. + + + + + Copies length bytes from input buffer to output buffer starting + at output[offset]. You have to make sure, that the buffer is + byte aligned. If not enough bytes are available, copies fewer + bytes. + + + the buffer. + + + the offset in the buffer. + + + the length to copy, 0 is allowed. + + + the number of bytes copied, 0 if no byte is available. + + + + + Gets the number of bits available in the bit buffer. This must be + only called when a previous peekBits() returned -1. + + + the number of bits available. + + + + + Gets the number of bytes available. + + + the number of bytes available. + + + + + This is the Deflater class. The deflater class compresses input + with the deflate algorithm described in RFC 1951. It has several + compression levels and three different strategies described below. + + This class is not thread safe. This is inherent in the API, due + to the split of deflate and setInput. + + author of the original java version : Jochen Hoenicke + + + + + The best and slowest compression level. This tries to find very + long and distant string repetitions. + + + + + The worst but fastest compression level. + + + + + The default compression level. + + + + + This level won't compress at all but output uncompressed blocks. + + + + + The compression method. This is the only method supported so far. + There is no need to use this constant at all. + + + + + Compression level. + + + + + should we include a header. + + + + + The current state. + + + + + The total bytes of output written. + + + + + The pending output. + + + + + The deflater engine. + + + + + Creates a new deflater with default compression level. + + + + + Creates a new deflater with given compression level. + + + the compression level, a value between NO_COMPRESSION + and BEST_COMPRESSION, or DEFAULT_COMPRESSION. + + if lvl is out of range. + + + + Creates a new deflater with given compression level. + + + the compression level, a value between NO_COMPRESSION + and BEST_COMPRESSION. + + + true, if we should suppress the deflate header at the + beginning and the adler checksum at the end of the output. This is + useful for the GZIP format. + + if lvl is out of range. + + + + Resets the deflater. The deflater acts afterwards as if it was + just created with the same compression level and strategy as it + had before. + + + + + Flushes the current input block. Further calls to deflate() will + produce enough output to inflate everything in the current input + block. This is not part of Sun's JDK so I have made it package + private. It is used by DeflaterOutputStream to implement + flush(). + + + + + Finishes the deflater with the current input block. It is an error + to give more input after this method was called. This method must + be called to force all bytes to be flushed. + + + + + Sets the data which should be compressed next. This should be only + called when needsInput indicates that more input is needed. + If you call setInput when needsInput() returns false, the + previous input that is still pending will be thrown away. + The given byte array should not be changed, before needsInput() returns + true again. + This call is equivalent to setInput(input, 0, input.length). + + + the buffer containing the input data. + + + if the buffer was finished() or ended(). + + + + + Sets the data which should be compressed next. This should be + only called when needsInput indicates that more input is needed. + The given byte array should not be changed, before needsInput() returns + true again. + + + the buffer containing the input data. + + + the start of the data. + + + the length of the data. + + + if the buffer was finished() or ended() or if previous input is still pending. + + + + + Sets the compression level. There is no guarantee of the exact + position of the change, but if you call this when needsInput is + true the change of compression level will occur somewhere near + before the end of the so far given input. + + + the new compression level. + + + + + Sets the compression strategy. Strategy is one of + DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact + position where the strategy is changed, the same as for + setLevel() applies. + + + the new compression strategy. + + + + + Deflates the current input block to the given array. It returns + the number of bytes compressed, or 0 if either + needsInput() or finished() returns true or length is zero. + + + the buffer where to write the compressed data. + + + + + Deflates the current input block to the given array. It returns + the number of bytes compressed, or 0 if either + needsInput() or finished() returns true or length is zero. + + + the buffer where to write the compressed data. + + + the offset into the output array. + + + the maximum number of bytes that may be written. + + + if end() was called. + + + if offset and/or length don't match the array length. + + + + + Sets the dictionary which should be used in the deflate process. + This call is equivalent to setDictionary(dict, 0, dict.Length). + + + the dictionary. + + + if setInput () or deflate () were already called or another dictionary was already set. + + + + + Sets the dictionary which should be used in the deflate process. + The dictionary should be a byte array containing strings that are + likely to occur in the data which should be compressed. The + dictionary is not stored in the compressed output, only a + checksum. To decompress the output you need to supply the same + dictionary again. + + + the dictionary. + + + an offset into the dictionary. + + + the length of the dictionary. + + + if setInput () or deflate () were already called or another dictionary was already set. + + + + + Gets the current adler checksum of the data that was processed so far. + + + + + Gets the number of input bytes processed so far. + + + + + Gets the number of output bytes so far. + + + + + Returns true if the stream was finished and no more output bytes + are available. + + + + + Returns true, if the input buffer is empty. + You should then call setInput(). + NOTE: This method can also return true when the stream + was finished. + + + + + This class contains constants used for the deflater. + + + + + The current compression function. + + + + + The input data for compression. + + + + + The total bytes of input read. + + + + + The offset into inputBuf, where input data starts. + + + + + The end offset of the input data. + + + + + The adler checksum + + + + + This is the DeflaterHuffman class. + + This class is not thread safe. This is inherent in the API, due + to the split of deflate and setInput. + + author of the original java version : Jochen Hoenicke + + + + + Reverse the bits of a 16 bit value. + + + + + This class stores the pending output of the Deflater. + + author of the original java version : Jochen Hoenicke + + + + + This class is general purpose class for writing data to a buffer. + + It allows you to write bits as well as bytes + Based on DeflaterPending.java + + author of the original java version : Jochen Hoenicke + + + + + Flushes the pending buffer into the given output array. If the + output array is to small, only a partial flush is done. + + + the output array; + + + the offset into output array; + + + length the maximum number of bytes to store; + + + IndexOutOfBoundsException if offset or length are invalid. + + + + + Inflater is used to decompress data that has been compressed according + to the "deflate" standard described in rfc1950. + + The usage is as following. First you have to set some input with + setInput(), then inflate() it. If inflate doesn't + inflate any bytes there may be three reasons: +
    +
  • needsInput() returns true because the input buffer is empty. + You have to provide more input with setInput(). + NOTE: needsInput() also returns true when, the stream is finished. +
  • +
  • needsDictionary() returns true, you have to provide a preset + dictionary with setDictionary().
  • +
  • finished() returns true, the inflater has finished.
  • +
+ Once the first output byte is produced, a dictionary will not be + needed at a later stage. + + author of the original java version : John Leuner, Jochen Hoenicke +
+
+ + + This are the state in which the inflater can be. + + + + + Copy lengths for literal codes 257..285 + + + + + Extra bits for literal codes 257..285 + + + + + Copy offsets for distance codes 0..29 + + + + + Extra bits for distance codes + + + + + This variable contains the current state. + + + + + The adler checksum of the dictionary or of the decompressed + stream, as it is written in the header resp. footer of the + compressed stream. + Only valid if mode is DECODE_DICT or DECODE_CHKSUM. + + + + + The number of bits needed to complete the current state. This + is valid, if mode is DECODE_DICT, DECODE_CHKSUM, + DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS. + + + + + True, if the last block flag was set in the last block of the + inflated stream. This means that the stream ends after the + current block. + + + + + The total number of inflated bytes. + + + + + The total number of bytes set with setInput(). This is not the + value returned by getTotalIn(), since this also includes the + unprocessed input. + + + + + This variable stores the nowrap flag that was given to the constructor. + True means, that the inflated stream doesn't contain a header nor the + checksum in the footer. + + + + + Creates a new inflater. + + + + + Creates a new inflater. + + + true if no header and checksum field appears in the + stream. This is used for GZIPed input. For compatibility with + Sun JDK you should provide one byte of input more than needed in + this case. + + + + + Resets the inflater so that a new stream can be decompressed. All + pending input and output will be discarded. + + + + + Decodes the deflate header. + + + false if more input is needed. + + + if header is invalid. + + + + + Decodes the dictionary checksum after the deflate header. + + + false if more input is needed. + + + + + Decodes the huffman encoded symbols in the input stream. + + + false if more input is needed, true if output window is + full or the current block ends. + + + if deflated stream is invalid. + + + + + Decodes the adler checksum after the deflate stream. + + + false if more input is needed. + + + DataFormatException, if checksum doesn't match. + + + + + Decodes the deflated stream. + + + false if more input is needed, or if finished. + + + DataFormatException, if deflated stream is invalid. + + + + + Sets the preset dictionary. This should only be called, if + needsDictionary() returns true and it should set the same + dictionary, that was used for deflating. The getAdler() + function returns the checksum of the dictionary needed. + + + the dictionary. + + + if no dictionary is needed. + + + if the dictionary checksum is wrong. + + + + + Sets the preset dictionary. This should only be called, if + needsDictionary() returns true and it should set the same + dictionary, that was used for deflating. The getAdler() + function returns the checksum of the dictionary needed. + + + the dictionary. + + + the offset into buffer where the dictionary starts. + + + the length of the dictionary. + + + if no dictionary is needed. + + + if the dictionary checksum is wrong. + + + if the off and/or len are wrong. + + + + + Sets the input. This should only be called, if needsInput() + returns true. + + + the input. + + + if no input is needed. + + + + + Sets the input. This should only be called, if needsInput() + returns true. + + + the input. + + + the offset into buffer where the input starts. + + + the length of the input. + + + if no input is needed. + + + if the off and/or len are wrong. + + + + + Inflates the compressed stream to the output buffer. If this + returns 0, you should check, whether needsDictionary(), + needsInput() or finished() returns true, to determine why no + further output is produced. + + + the output buffer. + + + the number of bytes written to the buffer, 0 if no further + output can be produced. + + + if buf has length 0. + + + if deflated stream is invalid. + + + + + Inflates the compressed stream to the output buffer. If this + returns 0, you should check, whether needsDictionary(), + needsInput() or finished() returns true, to determine why no + further output is produced. + + + the output buffer. + + + the offset into buffer where the output should start. + + + the maximum length of the output. + + + the number of bytes written to the buffer, 0 if no further output can be produced. + + + if len is <= 0. + + + if the off and/or len are wrong. + + + if deflated stream is invalid. + + + + + Returns true, if the input buffer is empty. + You should then call setInput(). + NOTE: This method also returns true when the stream is finished. + + + + + Returns true, if a preset dictionary is needed to inflate the input. + + + + + Returns true, if the inflater has finished. This means, that no + input is needed and no output can be produced. + + + + + Gets the adler checksum. This is either the checksum of all + uncompressed bytes returned by inflate(), or if needsDictionary() + returns true (and thus no output was yet produced) this is the + adler checksum of the expected dictionary. + + + the adler checksum. + + + + + Gets the total number of output bytes returned by inflate(). + + + the total number of output bytes. + + + + + Gets the total number of processed compressed input bytes. + + + the total number of bytes of processed input bytes. + + + + + Gets the number of unprocessed input. Useful, if the end of the + stream is reached and you want to further process the bytes after + the deflate stream. + + + the number of bytes of the input which were not processed. + + + + + Constructs a Huffman tree from the array of code lengths. + + + the array of code lengths + + + + + Reads the next symbol from input. The symbol is encoded using the + huffman tree. + + + input the input source. + + + the next symbol, or -1 if not enough input is available. + + + + + This class contains constants used for zip. + + + + + This class represents a member of a zip archive. ZipFile and + ZipInputStream will give you instances of this class as information + about the members in an archive. On the other hand ZipOutputStream + needs an instance of this class to create a new member. + + author of the original java version : Jochen Hoenicke + + + + + Creates a zip entry with the given name. + + + the name. May include directory components separated by '/'. + + + + + Creates a copy of the given zip entry. + + + the entry to copy. + + + + + Creates a copy of this zip entry. + + + + + Gets the string representation of this ZipEntry. This is just + the name as returned by getName(). + + + + + Gets/Sets the time of last modification of the entry. + + + + + Returns the entry name. The path components in the entry are + always separated by slashes ('/'). + + + + + Gets/Sets the size of the uncompressed data. + + + if size is not in 0..0xffffffffL + + + the size or -1 if unknown. + + + + + Gets/Sets the size of the compressed data. + + + if csize is not in 0..0xffffffffL + + + the size or -1 if unknown. + + + + + Gets/Sets the crc of the uncompressed data. + + + if crc is not in 0..0xffffffffL + + + the crc or -1 if unknown. + + + + + Gets/Sets the compression method. Only DEFLATED and STORED are supported. + + + if method is not supported. + + + the compression method or -1 if unknown. + + + + + + + Gets/Sets the extra data. + + + if extra is longer than 0xffff bytes. + + + the extra data or null if not set. + + + + + Gets/Sets the entry comment. + + + if comment is longer than 0xffff. + + + the comment or null if not set. + + + + + Gets true, if the entry is a directory. This is solely + determined by the name, a trailing slash '/' marks a directory. + + + + + True, if the entry is encrypted. + + + + + This class represents a Zip archive. You can ask for the contained + entries, or get an input stream for a file entry. The entry is + automatically decompressed. + + This class is thread safe: You can open input streams for arbitrary + entries in different threads. + + author of the original java version : Jochen Hoenicke + + + using System; + using System.Text; + using System.Collections; + using System.IO; + + using NZlib.Zip; + + class MainClass + { + static public void Main(string[] args) + { + ZipFile zFile = new ZipFile(args[0]); + //Console.WriteLine("Listing of : " + zFile.Name); + //Console.WriteLine(""); + //Console.WriteLine("Raw Size Size Date Time Name"); + //Console.WriteLine("-------- -------- -------- ------ ---------"); + foreach (ZipEntry e in zFile) { + DateTime d = e.DateTime; + //Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, + d.ToString("dd-MM-yy"), d.ToString("t"), + e.Name); + } + } + } + + + + + Opens a Zip file with the given name for reading. + + + IOException if a i/o error occured. + + + if the file doesn't contain a valid zip archive. + + + + + Opens a Zip file reading the given FileStream + + + IOException if a i/o error occured. + + + if the file doesn't contain a valid zip archive. + + + + + Opens a Zip file reading the given Stream + + + IOException if a i/o error occured. + + + if the file doesn't contain a valid zip archive. + + + + + Read an unsigned short in little endian byte order. + + + if a i/o error occured. + + + if the file ends prematurely + + + + + Read an int in little endian byte order. + + + if a i/o error occured. + + + if the file ends prematurely + + + + + Read the central directory of a zip file and fill the entries + array. This is called exactly once by the constructors. + + + if a i/o error occured. + + + if the central directory is malformed + + + + + Closes the ZipFile. This also closes all input streams given by + this class. After this is called, no further method should be + called. + + + if a i/o error occured. + + + + + Returns an IEnumerator of all Zip entries in this Zip file. + + + + + Searches for a zip entry in this archive with the given name. + + + the name. May contain directory components separated by slashes ('/'). + + + the zip entry, or null if no entry with that name exists. + + + + + Checks, if the local header of the entry at index i matches the + central directory, and returns the offset to the data. + + + the start offset of the (compressed) data. + + + if a i/o error occured. + + + if the local header doesn't match the central directory header + + + + + Creates an input stream reading the given zip entry as + uncompressed data. Normally zip entry should be an entry + returned by GetEntry(). + + + the input stream. + + + if a i/o error occured. + + + if the Zip archive is malformed. + + + + + The comment for the whole zip file. + + + + + Returns the name of this zip file. + + + + + Returns the number of entries in this zip file. + + + + + This is a FilterInputStream that reads the files baseInputStream an zip archive + one after another. It has a special method to get the zip entry of + the next file. The zip entry contains information about the file name + size, compressed size, CRC, etc. + It includes support for STORED and DEFLATED entries. + + author of the original java version : Jochen Hoenicke + + This sample shows how to read a zip file + + using System; + using System.Text; + using System.IO; + + using NZlib.Zip; + + class MainClass + { + public static void Main(string[] args) + { + ZipInputStream s = new ZipInputStream(File.OpenRead(args[0])); + + ZipEntry theEntry; + while ((theEntry = s.GetNextEntry()) != null) { + int size = 2048; + byte[] data = new byte[2048]; + + Console.Write("Show contents (y/n) ?"); + if (Console.ReadLine() == "y") { + while (true) { + size = s.Read(data, 0, data.Length); + if (size > 0) { + Console.Write(new ASCIIEncoding().GetString(data, 0, size)); + } else { + break; + } + } + } + } + s.Close(); + } + } + + + + + + Creates a new Zip input stream, reading a zip archive. + + + + + Read an unsigned short baseInputStream little endian byte order. + + + + + Read an int baseInputStream little endian byte order. + + + + + Read an int baseInputStream little endian byte order. + + + + + Open the next entry from the zip archive, and return its description. + If the previous entry wasn't closed, this method will close it. + + + + + Closes the current zip entry and moves to the next one. + + + + + Reads a byte from the current zip entry. + + + the byte or -1 on EOF. + + + IOException if a i/o error occured. + + + ZipException if the deflated stream is corrupted. + + + + + Reads a block of bytes from the current zip entry. + + + the number of bytes read (may be smaller, even before EOF), or -1 on EOF. + + + IOException if a i/o error occured. + ZipException if the deflated stream is corrupted. + + + + + Closes the zip file. + + + if a i/o error occured. + + + + + This is a FilterOutputStream that writes the files into a zip + archive one after another. It has a special method to start a new + zip entry. The zip entries contains information about the file name + size, compressed size, CRC, etc. + + It includes support for STORED and DEFLATED entries. + This class is not thread safe. + + author of the original java version : Jochen Hoenicke + + This sample shows how to create a zip file + + using System; + using System.IO; + + using NZlib.Zip; + + class MainClass + { + public static void Main(string[] args) + { + string[] filenames = Directory.GetFiles(args[0]); + + ZipOutputStream s = new ZipOutputStream(File.Create(args[1])); + + s.SetLevel(5); // 0 - store only to 9 - means best compression + + foreach (string file in filenames) { + FileStream fs = File.OpenRead(file); + + byte[] buffer = new byte[fs.Length]; + fs.Read(buffer, 0, buffer.Length); + + ZipEntry entry = new ZipEntry(file); + + s.PutNextEntry(entry); + + s.Write(buffer, 0, buffer.Length); + + } + + s.Finish(); + s.Close(); + } + } + + + + + + Our Zip version is hard coded to 1.0 resp. 2.0 + + + + + Compression method. This method doesn't compress at all. + + + + + Compression method. This method uses the Deflater. + + + + + Creates a new Zip output stream, writing a zip archive. + + + the output stream to which the zip archive is written. + + + + + Set the zip file comment. + + + the comment. + + + if UTF8 encoding of comment is longer than 0xffff bytes. + + + + + Sets default compression method. If the Zip entry specifies + another method its method takes precedence. + + + the method. + + + if method is not supported. + + + + + Sets default compression level. The new level will be activated + immediately. + + + if level is not supported. + + + + + + Write an unsigned short in little endian byte order. + + + + + Write an int in little endian byte order. + + + + + Write an int in little endian byte order. + + + + + Starts a new Zip entry. It automatically closes the previous + entry if present. If the compression method is stored, the entry + must have a valid size and crc, otherwise all elements (except + name) are optional, but must be correct if present. If the time + is not set in the entry, the current time is used. + + + the entry. + + + if an I/O error occured. + + + if stream was finished + + + + + Closes the current entry. + + + if an I/O error occured. + + + if no entry is active. + + + + + Writes the given buffer to the current entry. + + + if an I/O error occured. + + + if no entry is active. + + + + + Finishes the stream. This will write the central directory at the + end of the zip file and flush the stream. + + + if an I/O error occured. + + + + + Is thrown during the creation or input of a zip file. + + + + + Initializes a new instance of the ZipException class with default properties. + + + + + Initializes a new instance of the ZipException class with a specified error message. + + +
+
diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..589bfae --- /dev/null +++ b/license.txt @@ -0,0 +1,20 @@ +Copyright (C) 2002-Present Ben Lowery (compress@blowery.org) + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Ben Lowery + compress@blowery.org diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..fd350ee --- /dev/null +++ b/readme.txt @@ -0,0 +1,100 @@ +== Version History == +6.0, V2 ----- + Changes: + - Compiled against v2 of the .NET framework, using the framework supplied GZip + and Deflate streams. This rev requires .NET2 and does not use #ziplib. + +6.0 --------- + Changes: + - I now delay the insertion of the compression header until the first + write is attempted on the compressing stream. This allows exceptions + to be written properly, though they will be written uncompressed. + - The same fix should allow Server.Transfer and friends to work without + modification, though the contents of pages written using Server.Transfer + will not be compressed. + - Thanks to Milan Negovan for helping me track down this issue and thanks + to the shower for helping me come up with a general solution. + +5.0.0.0 ----- + Changes: + - All these came from outside sources. No real work from me. + - Added support for the VaryByHeader, thanks to Simon Fell and Ian Anderson + - Reworked the filter picker (all thanks to Ian Anderson) + - No filter is added if the compression level is set to None + - Fixed a bug where the INSTALLED_TAG was not being set into the + Context properly if compression was determined unnecessary. + +4.0.0.0 ----- + Changes: + - upped to version 4. From now on, every release will be a major + version release. + - now have support for path-based and ContentType-based exlusions + - now using a slightly custom edition of #zlib 0.5 to fix a couple bugs + - better method for sinking asp.net pipeline events + - modified all namespaces to blowery.Web.HttpCompress + - added custom response header (X-Compressed-By) + - the assembly is now blowery.Web.HttpCompress + - the config section handler is now blowery.Web.HttpCompress.SectionHandler + - the config section is now httpCompress, in keeping with the new naming + - you will have to update your configuration files for this release! + - fixed a bug where i was improperly detecting the supported compression + types sent by the browser. I would only read in the first one. + + + +1.1 --------- + + MAJOR CHANGES THAT WILL AFFECT YOU + - the assembly is now named HttpCompressionModule.dll + - the config section handler is now HttpCompressionModuleSectionHandler + - YOU WILL NEED TO UPDATE YOUR CONFIG FILES + FOR THIS VERSION TO WORK (see samples for direction) + + Other stuff + - moved to SharpZipLib (formerly NZipLib) 0.31 which + contains a bunch of bug fixes. This means I just + inherited a bunch of bug fixes. yay! + - updated the code to use the new ICSharpCode namespace + - reworked the way the configuration works + - no more generic http modules. i'm only writing this + one, so this made the code simpler + - removed the Unspecified flag on the Enums. Not needed. + now, it defaults to Deflate + Normal + - decided to not support config parenting, as it doesn't + really make sense for this + - pulled out some trace stuff from the DEBUG version that + didn't need to be there + - actually shipping compiled versions, both DEBUG and RELEASE + - added examples. + + +1.0 --------- + - initial introduction + +=== Introduction == +Hey there, + +Thanks for downloading my compressing filter for ASP.NET! As +you can see, the full source is provided so you can +understand how it works and modify it if you want. + +If you don't have visual studio, no fear. The whole project +lives in one directory, so csc *.cs should work, you just need +to add a reference to the supplied SharpZipLib.dll. + +For instructions on how to slip the HttpCompressionModule in, +see the provided example. It shows what entries have +to be added to the web.config to set things up. + +So, to get things going, here's what you have to do: +1) compile the project into a library (or just use the + version in /lib) +2) move the .dll that comes from compilation to the /bin directory of + your asp.net web app +3) add the entries to the web.config of your asp.net app + +That's it. That should get you going. + + + +--b \ No newline at end of file