using System; using System.IO; using System.Security.Cryptography; using ICSharpCode.SharpZipLib.Encryption; namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams { /// /// A special stream deflating or compressing the bytes that are /// written to it. It uses a Deflater to perform actual deflating.
/// Authors of the original java version : Tom Tromey, Jochen Hoenicke ///
public class DeflaterOutputStream : Stream { #region Constructors /// /// Creates a new DeflaterOutputStream with a default Deflater and default buffer size. /// /// /// the output stream where deflated output should be written. /// public DeflaterOutputStream(Stream baseOutputStream) : this(baseOutputStream, new Deflater(), 512) { } /// /// Creates a new DeflaterOutputStream with the given Deflater and /// default buffer size. /// /// /// the output stream where deflated output should be written. /// /// /// the underlying deflater. /// public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater) : this(baseOutputStream, deflater, 512) { } /// /// Creates a new DeflaterOutputStream with the given Deflater and /// buffer size. /// /// /// The output stream where deflated output is written. /// /// /// The underlying deflater to use /// /// /// The buffer size in bytes to use when deflating (minimum value 512) /// /// /// bufsize is less than or equal to zero. /// /// /// baseOutputStream does not support writing /// /// /// deflater instance is null /// public DeflaterOutputStream(Stream baseOutputStream, Deflater deflater, int bufferSize) { if (baseOutputStream == null) { throw new ArgumentNullException("nameof(baseOutputStream)"); } if (baseOutputStream.CanWrite == false) { throw new ArgumentException("Must support writing", "nameof(baseOutputStream)"); } if (deflater == null) { throw new ArgumentNullException("nameof(deflater)"); } if (bufferSize < 512) { throw new ArgumentOutOfRangeException("nameof(bufferSize)"); } baseOutputStream_ = baseOutputStream; buffer_ = new byte[bufferSize]; deflater_ = deflater; } #endregion #region Public API /// /// Finishes the stream by calling finish() on the deflater. /// /// /// Not all input is deflated /// public virtual void Finish() { deflater_.Finish(); while (!deflater_.IsFinished) { int len = deflater_.Deflate(buffer_, 0, buffer_.Length); if (len <= 0) { break; } if (cryptoTransform_ != null) { EncryptBlock(buffer_, 0, len); } baseOutputStream_.Write(buffer_, 0, len); } if (!deflater_.IsFinished) { throw new SharpZipBaseException("Can't deflate all input?"); } baseOutputStream_.Flush(); if (cryptoTransform_ != null) { if (cryptoTransform_ is ZipAESTransform) { AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); } cryptoTransform_.Dispose(); cryptoTransform_ = null; } } /// /// Gets or sets a flag indicating ownership of underlying stream. /// When the flag is true will close the underlying stream also. /// /// The default value is true. private bool isStreamOwner = true; public bool IsStreamOwner { get { return isStreamOwner;} set { isStreamOwner = value; } } /// /// Allows client to determine if an entry can be patched after its added /// public bool CanPatchEntries { get { return baseOutputStream_.CanSeek; } } #endregion #region Encryption string password; ICryptoTransform cryptoTransform_; /// /// Returns the 10 byte AUTH CODE to be appended immediately following the AES data stream. /// protected byte[] AESAuthCode; /// /// Get/set the password used for encryption. /// /// When set to null or if the password is empty no encryption is performed public string Password { get { return password; } set { if ((value != null) && (value.Length == 0)) { password = null; } else { password = value; } } } /// /// Encrypt a block of data /// /// /// Data to encrypt. NOTE the original contents of the buffer are lost /// /// /// Offset of first byte in buffer to encrypt /// /// /// Number of bytes in buffer to encrypt /// protected void EncryptBlock(byte[] buffer, int offset, int length) { cryptoTransform_.TransformBlock(buffer, 0, length, buffer, 0); } /// /// Initializes encryption keys based on given . /// /// The password. protected void InitializePassword(string password) { var pkManaged = new PkzipClassicManaged(); byte[] key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(password)); cryptoTransform_ = pkManaged.CreateEncryptor(key, null); } /// /// Initializes encryption keys based on given password. /// protected void InitializeAESPassword(ZipEntry entry, string rawPassword, out byte[] salt, out byte[] pwdVerifier) { salt = new byte[entry.AESSaltLen]; // Salt needs to be cryptographically random, and unique per file if (_aesRnd == null) _aesRnd = RandomNumberGenerator.Create(); _aesRnd.GetBytes(salt); int blockSize = entry.AESKeySize / 8; // bits to bytes cryptoTransform_ = new ZipAESTransform(rawPassword, salt, blockSize, true); pwdVerifier = ((ZipAESTransform)cryptoTransform_).PwdVerifier; } #endregion #region Deflation Support /// /// Deflates everything in the input buffers. This will call /// def.deflate() until all bytes from the input buffers /// are processed. /// protected void Deflate() { while (!deflater_.IsNeedingInput) { int deflateCount = deflater_.Deflate(buffer_, 0, buffer_.Length); if (deflateCount <= 0) { break; } if (cryptoTransform_ != null) { EncryptBlock(buffer_, 0, deflateCount); } baseOutputStream_.Write(buffer_, 0, deflateCount); } if (!deflater_.IsNeedingInput) { throw new SharpZipBaseException("DeflaterOutputStream can't deflate all input?"); } } #endregion #region Stream Overrides /// /// Gets value indicating stream can be read from /// public override bool CanRead { get { return false; } } /// /// Gets a value indicating if seeking is supported for this stream /// This property always returns false /// public override bool CanSeek { get { return false; } } /// /// Get value indicating if this stream supports writing /// public override bool CanWrite { get { return baseOutputStream_.CanWrite; } } /// /// Get current length of stream /// public override long Length { get { return baseOutputStream_.Length; } } /// /// Gets the current position within the stream. /// /// Any attempt to set position public override long Position { get { return baseOutputStream_.Position; } set { throw new NotSupportedException("Position property not supported"); } } /// /// Sets the current position of this stream to the given value. Not supported by this class! /// /// The offset relative to the to seek. /// The to seek from. /// The new position in the stream. /// Any access public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("DeflaterOutputStream Seek not supported"); } /// /// Sets the length of this stream to the given value. Not supported by this class! /// /// The new stream length. /// Any access public override void SetLength(long value) { throw new NotSupportedException("DeflaterOutputStream SetLength not supported"); } /// /// Read a byte from stream advancing position by one /// /// The byte read cast to an int. THe value is -1 if at the end of the stream. /// Any access public override int ReadByte() { throw new NotSupportedException("DeflaterOutputStream ReadByte not supported"); } /// /// Read a block of bytes from stream /// /// The buffer to store read data in. /// The offset to start storing at. /// The maximum number of bytes to read. /// The actual number of bytes read. Zero if end of stream is detected. /// Any access public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException("DeflaterOutputStream Read not supported"); } /// /// Flushes the stream by calling Flush on the deflater and then /// on the underlying stream. This ensures that all bytes are flushed. /// public override void Flush() { deflater_.Flush(); Deflate(); baseOutputStream_.Flush(); } /// /// Calls and closes the underlying /// stream when is true. /// protected override void Dispose(bool disposing) { if (!isClosed_) { isClosed_ = true; try { Finish(); if (cryptoTransform_ != null) { GetAuthCodeIfAES(); cryptoTransform_.Dispose(); cryptoTransform_ = null; } } finally { if (IsStreamOwner) { baseOutputStream_.Dispose(); } } } } private void GetAuthCodeIfAES() { if (cryptoTransform_ is ZipAESTransform) { AESAuthCode = ((ZipAESTransform)cryptoTransform_).GetAuthCode(); } } /// /// Writes a single byte to the compressed output stream. /// /// /// The byte value. /// public override void WriteByte(byte value) { byte[] b = new byte[1]; b[0] = value; Write(b, 0, 1); } /// /// Writes 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. /// public override void Write(byte[] buffer, int offset, int count) { deflater_.SetInput(buffer, offset, count); Deflate(); } #endregion #region Instance Fields /// /// This buffer is used temporarily to retrieve the bytes from the /// deflater and write them to the underlying output stream. /// byte[] buffer_; /// /// The deflater which is used to deflate the stream. /// protected Deflater deflater_; /// /// Base stream the deflater depends on. /// protected Stream baseOutputStream_; bool isClosed_; #endregion #region Static Fields // Static to help ensure that multiple files within a zip will get different random salt private static RandomNumberGenerator _aesRnd = RandomNumberGenerator.Create(); #endregion } }