// GZipStream.cs // ------------------------------------------------------------------ // // Copyright (c) 2009 Dino Chiesa and Microsoft Corporation. // All rights reserved. // // This code module is part of DotNetZip, a zipfile class library. // // ------------------------------------------------------------------ // // This code is licensed under the Microsoft Public License. // See the file License.txt for the license details. // More info on: http://dotnetzip.codeplex.com // // ------------------------------------------------------------------ // // last saved (in emacs): // Time-stamp: <2010-January-09 12:04:28> // // ------------------------------------------------------------------ // // This module defines the GZipStream class, which can be used as a replacement for // the System.IO.Compression.GZipStream class in the .NET BCL. NB: The design is not // completely OO clean: there is some intelligence in the ZlibBaseStream that reads the // GZip header. // // ------------------------------------------------------------------ using System; using System.IO; using SharpCompress.Common; namespace SharpCompress.Compressor.Deflate { public class GZipStream : Stream { internal static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); /* #if SILVERLIGHT || PORTABLE || MONO internal static readonly Encoding Iso8859Dash1 = Encoding.UTF8; #else internal static readonly Encoding Iso8859Dash1 = Encoding.GetEncoding("iso-8859-1"); #endif */ public DateTime? LastModified { get; set; } private string comment; private int crc32; private string fileName; internal ZlibBaseStream BaseStream; private bool disposed; private bool firstReadDone; private int headerByteCount; public GZipStream(Stream stream, CompressionMode mode) : this(stream, mode, CompressionLevel.Default, false) { } public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level) : this(stream, mode, level, false) { } public GZipStream(Stream stream, CompressionMode mode, bool leaveOpen) : this(stream, mode, CompressionLevel.Default, leaveOpen) { } public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen) { BaseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, leaveOpen); } #region Zlib properties public virtual FlushType FlushMode { get { return (BaseStream._flushMode); } set { if (disposed) throw new ObjectDisposedException("GZipStream"); BaseStream._flushMode = value; } } public int BufferSize { get { return BaseStream._bufferSize; } set { if (disposed) throw new ObjectDisposedException("GZipStream"); if (BaseStream._workingBuffer != null) throw new ZlibException("The working buffer is already set."); if (value < ZlibConstants.WorkingBufferSizeMin) throw new ZlibException( String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, ZlibConstants.WorkingBufferSizeMin)); BaseStream._bufferSize = value; } } internal virtual long TotalIn { get { return BaseStream._z.TotalBytesIn; } } internal virtual long TotalOut { get { return BaseStream._z.TotalBytesOut; } } #endregion #region Stream methods /// /// Indicates whether the stream can be read. /// /// /// The return value depends on whether the captive stream supports reading. /// public override bool CanRead { get { if (disposed) throw new ObjectDisposedException("GZipStream"); return BaseStream._stream.CanRead; } } /// /// Indicates whether the stream supports Seek operations. /// /// /// Always returns false. /// public override bool CanSeek { get { return false; } } /// /// Indicates whether the stream can be written. /// /// /// The return value depends on whether the captive stream supports writing. /// public override bool CanWrite { get { if (disposed) throw new ObjectDisposedException("GZipStream"); return BaseStream._stream.CanWrite; } } /// /// Reading this property always throws a . /// public override long Length { get { throw new NotImplementedException(); } } /// /// The position of the stream pointer. /// /// /// /// Setting this property always throws a . Reading will return the total bytes /// written out, if used in writing, or the total bytes read in, if used in /// reading. The count may refer to compressed bytes or uncompressed bytes, /// depending on how you've used the stream. /// public override long Position { get { if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Writer) return BaseStream._z.TotalBytesOut + headerByteCount; if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Reader) return BaseStream._z.TotalBytesIn + BaseStream._gzipHeaderByteCount; return 0; } set { throw new NotImplementedException(); } } /// /// Dispose the stream. /// /// /// This may or may not result in a Close() call on the captive stream. /// See the doc on constructors that take a leaveOpen parameter for more information. /// protected override void Dispose(bool disposing) { try { if (!disposed) { if (disposing && (BaseStream != null)) { BaseStream.Dispose(); crc32 = BaseStream.Crc32; } disposed = true; } } finally { base.Dispose(disposing); } } /// /// Flush the stream. /// public override void Flush() { if (disposed) throw new ObjectDisposedException("GZipStream"); BaseStream.Flush(); } /// /// Read and decompress data from the source stream. /// /// /// /// With a GZipStream, decompression is done through reading. /// /// /// /// /// byte[] working = new byte[WORKING_BUFFER_SIZE]; /// using (System.IO.Stream input = System.IO.File.OpenRead(_CompressedFile)) /// { /// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true)) /// { /// using (var output = System.IO.File.Create(_DecompressedFile)) /// { /// int n; /// while ((n= decompressor.Read(working, 0, working.Length)) !=0) /// { /// output.Write(working, 0, n); /// } /// } /// } /// } /// /// /// The buffer into which the decompressed data should be placed. /// the offset within that data array to put the first byte read. /// the number of bytes to read. /// the number of bytes actually read public override int Read(byte[] buffer, int offset, int count) { if (disposed) throw new ObjectDisposedException("GZipStream"); int n = BaseStream.Read(buffer, offset, count); // Console.WriteLine("GZipStream::Read(buffer, off({0}), c({1}) = {2}", offset, count, n); // Console.WriteLine( Util.FormatByteArray(buffer, offset, n) ); if (!firstReadDone) { firstReadDone = true; FileName = BaseStream._GzipFileName; Comment = BaseStream._GzipComment; } return n; } /// /// Calling this method always throws a . /// /// irrelevant; it will always throw! /// irrelevant; it will always throw! /// irrelevant! public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } /// /// Calling this method always throws a . /// /// irrelevant; this method will always throw! public override void SetLength(long value) { throw new NotImplementedException(); } /// /// Write data to the stream. /// /// /// /// /// If you wish to use the GZipStream to compress data while writing, /// you can create a GZipStream with CompressionMode.Compress, and a /// writable output stream. Then call Write() on that GZipStream, /// providing uncompressed data as input. The data sent to the output stream /// will be the compressed form of the data written. /// /// /// /// A GZipStream can be used for Read() or Write(), but not /// both. Writing implies compression. Reading implies decompression. /// /// /// /// The buffer holding data to write to the stream. /// the offset within that data array to find the first byte to write. /// the number of bytes to write. public override void Write(byte[] buffer, int offset, int count) { if (disposed) throw new ObjectDisposedException("GZipStream"); if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Undefined) { //Console.WriteLine("GZipStream: First write"); if (BaseStream._wantCompress) { // first write in compression, therefore, emit the GZIP header headerByteCount = EmitHeader(); } else { throw new InvalidOperationException(); } } BaseStream.Write(buffer, offset, count); } #endregion public String Comment { get { return comment; } set { if (disposed) throw new ObjectDisposedException("GZipStream"); comment = value; } } public String FileName { get { return fileName; } set { if (disposed) throw new ObjectDisposedException("GZipStream"); fileName = value; if (fileName == null) return; if (fileName.IndexOf("/") != -1) { fileName = fileName.Replace("/", "\\"); } if (fileName.EndsWith("\\")) throw new InvalidOperationException("Illegal filename"); #if !PORTABLE if (fileName.IndexOf("\\") != -1) { // trim any leading path fileName = Path.GetFileName(fileName); } #endif } } public int Crc32 { get { return crc32; } } private int EmitHeader() { byte[] commentBytes = (Comment == null) ? null : ArchiveEncoding.Default.GetBytes(Comment); byte[] filenameBytes = (FileName == null) ? null : ArchiveEncoding.Default.GetBytes(FileName); int cbLength = (Comment == null) ? 0 : commentBytes.Length + 1; int fnLength = (FileName == null) ? 0 : filenameBytes.Length + 1; int bufferLength = 10 + cbLength + fnLength; var header = new byte[bufferLength]; int i = 0; // ID header[i++] = 0x1F; header[i++] = 0x8B; // compression method header[i++] = 8; byte flag = 0; if (Comment != null) flag ^= 0x10; if (FileName != null) flag ^= 0x8; // flag header[i++] = flag; // mtime if (!LastModified.HasValue) LastModified = DateTime.Now; TimeSpan delta = LastModified.Value - UnixEpoch; var timet = (Int32)delta.TotalSeconds; Array.Copy(BitConverter.GetBytes(timet), 0, header, i, 4); i += 4; // xflg header[i++] = 0; // this field is totally useless // OS header[i++] = 0xFF; // 0xFF == unspecified // extra field length - only if FEXTRA is set, which it is not. //header[i++]= 0; //header[i++]= 0; // filename if (fnLength != 0) { Array.Copy(filenameBytes, 0, header, i, fnLength - 1); i += fnLength - 1; header[i++] = 0; // terminate } // comment if (cbLength != 0) { Array.Copy(commentBytes, 0, header, i, cbLength - 1); i += cbLength - 1; header[i++] = 0; // terminate } BaseStream._stream.Write(header, 0, header.Length); return header.Length; // bytes written } } }