123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- // 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
- /// <summary>
- /// Indicates whether the stream can be read.
- /// </summary>
- /// <remarks>
- /// The return value depends on whether the captive stream supports reading.
- /// </remarks>
- public override bool CanRead
- {
- get
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("GZipStream");
- }
- return BaseStream._stream.CanRead;
- }
- }
- /// <summary>
- /// Indicates whether the stream supports Seek operations.
- /// </summary>
- /// <remarks>
- /// Always returns false.
- /// </remarks>
- public override bool CanSeek => false;
- /// <summary>
- /// Indicates whether the stream can be written.
- /// </summary>
- /// <remarks>
- /// The return value depends on whether the captive stream supports writing.
- /// </remarks>
- public override bool CanWrite
- {
- get
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("GZipStream");
- }
- return BaseStream._stream.CanWrite;
- }
- }
- /// <summary>
- /// Reading this property always throws a <see cref="NotImplementedException"/>.
- /// </summary>
- public override long Length => throw new NotSupportedException();
- /// <summary>
- /// The position of the stream pointer.
- /// </summary>
- ///
- /// <remarks>
- /// Setting this property always throws a <see
- /// cref="NotImplementedException"/>. 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.
- /// </remarks>
- 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();
- }
- /// <summary>
- /// Dispose the stream.
- /// </summary>
- /// <remarks>
- /// This may or may not result in a <c>Close()</c> call on the captive stream.
- /// </remarks>
- protected override void Dispose(bool disposing)
- {
- try
- {
- if (!_disposed)
- {
- if (disposing && (BaseStream != null))
- {
- BaseStream.Dispose();
- Crc32 = BaseStream.Crc32;
- }
- _disposed = true;
- }
- }
- finally
- {
- base.Dispose(disposing);
- }
- }
- /// <summary>
- /// Flush the stream.
- /// </summary>
- public override void Flush()
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("GZipStream");
- }
- BaseStream.Flush();
- }
- /// <summary>
- /// Read and decompress data from the source stream.
- /// </summary>
- ///
- /// <remarks>
- /// With a <c>GZipStream</c>, decompression is done through reading.
- /// </remarks>
- ///
- /// <example>
- /// <code>
- /// 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);
- /// }
- /// }
- /// }
- /// }
- /// </code>
- /// </example>
- /// <param name="buffer">The buffer into which the decompressed data should be placed.</param>
- /// <param name="offset">the offset within that data array to put the first byte read.</param>
- /// <param name="count">the number of bytes to read.</param>
- /// <returns>the number of bytes actually read</returns>
- 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;
- }
- /// <summary>
- /// Calling this method always throws a <see cref="NotImplementedException"/>.
- /// </summary>
- /// <param name="offset">irrelevant; it will always throw!</param>
- /// <param name="origin">irrelevant; it will always throw!</param>
- /// <returns>irrelevant!</returns>
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotSupportedException();
- }
- /// <summary>
- /// Calling this method always throws a <see cref="NotImplementedException"/>.
- /// </summary>
- /// <param name="value">irrelevant; this method will always throw!</param>
- public override void SetLength(long value)
- {
- throw new NotSupportedException();
- }
- /// <summary>
- /// Write data to the stream.
- /// </summary>
- ///
- /// <remarks>
- /// <para>
- /// If you wish to use the <c>GZipStream</c> to compress data while writing,
- /// you can create a <c>GZipStream</c> with <c>CompressionMode.Compress</c>, and a
- /// writable output stream. Then call <c>Write()</c> on that <c>GZipStream</c>,
- /// providing uncompressed data as input. The data sent to the output stream
- /// will be the compressed form of the data written.
- /// </para>
- ///
- /// <para>
- /// A <c>GZipStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but not
- /// both. Writing implies compression. Reading implies decompression.
- /// </para>
- ///
- /// </remarks>
- /// <param name="buffer">The buffer holding data to write to the stream.</param>
- /// <param name="offset">the offset within that data array to find the first byte to write.</param>
- /// <param name="count">the number of bytes to write.</param>
- 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
- }
- }
- }
|