// 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; using SharpCompress.Converters; using System.Text; namespace SharpCompress.Compressors.Deflate { public class GZipStream : Stream { internal static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public DateTime? LastModified { get; set; } private string _comment; private string _fileName; internal ZlibBaseStream BaseStream; private bool _disposed; private bool _firstReadDone; private int _headerByteCount; private readonly Encoding _encoding; public GZipStream(Stream stream, CompressionMode mode) : this(stream, mode, CompressionLevel.Default, Encoding.UTF8) { } public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level) : this(stream, mode, level, Encoding.UTF8) { } public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level, Encoding encoding) { BaseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, encoding); _encoding = encoding; } #region Zlib properties public virtual FlushType FlushMode { get => (BaseStream._flushMode); set { if (_disposed) { throw new ObjectDisposedException("GZipStream"); } BaseStream._flushMode = value; } } public int BufferSize { get => 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 => BaseStream._z.TotalBytesIn; internal virtual long TotalOut => 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 => 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 => throw new NotSupportedException(); /// /// 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 NotSupportedException(); } /// /// Dispose the stream. /// /// /// This may or may not result in a Close() call on the captive stream. /// 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 NotSupportedException(); } /// /// Calling this method always throws a . /// /// irrelevant; this method will always throw! public override void SetLength(long value) { throw new NotSupportedException(); } /// /// 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 Stream methods public String Comment { get => _comment; set { if (_disposed) { throw new ObjectDisposedException("GZipStream"); } _comment = value; } } public string FileName { get => _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"); } var index = _fileName.IndexOf("\\"); if (index != -1) { // trim any leading path int length = _fileName.Length; int num = length; while (--num >= 0) { char c = _fileName[num]; if (c == '\\') { _fileName = _fileName.Substring(num + 1, length - num - 1); } } } } } public int Crc32 { get; private set; } private int EmitHeader() { byte[] commentBytes = (Comment == null) ? null : _encoding.GetBytes(Comment); byte[] filenameBytes = (FileName == null) ? null : _encoding.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 - UNIX_EPOCH; var timet = (Int32)delta.TotalSeconds; DataConverter.LittleEndian.PutBytes(header, i, timet); 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 } } }