using System; using System.IO; using System.Security.Cryptography; namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams { /// /// An input buffer customised for use by /// /// /// The buffer supports decryption of incoming data. /// public class InflaterInputBuffer { #region Constructors /// /// Initialise a new instance of with a default buffer size /// /// The stream to buffer. public InflaterInputBuffer(Stream stream) : this(stream, 4096) { } /// /// Initialise a new instance of /// /// The stream to buffer. /// The size to use for the buffer /// A minimum buffer size of 1KB is permitted. Lower sizes are treated as 1KB. public InflaterInputBuffer(Stream stream, int bufferSize) { inputStream = stream; if (bufferSize < 1024) { bufferSize = 1024; } rawData = new byte[bufferSize]; clearText = rawData; } #endregion /// /// Get the length of bytes bytes in the /// public int RawLength { get { return rawLength; } } /// /// Get the contents of the raw data buffer. /// /// This may contain encrypted data. public byte[] RawData { get { return rawData; } } /// /// Get the number of useable bytes in /// public int ClearTextLength { get { return clearTextLength; } } /// /// Get the contents of the clear text buffer. /// public byte[] ClearText { get { return clearText; } } /// /// Get/set the number of bytes available /// public int Available { get { return available; } set { available = value; } } /// /// Call passing the current clear text buffer contents. /// /// The inflater to set input for. public void SetInflaterInput(Inflater inflater) { if (available > 0) { inflater.SetInput(clearText, clearTextLength - available, available); available = 0; } } /// /// Fill the buffer from the underlying input stream. /// public void Fill() { rawLength = 0; int toRead = rawData.Length; while (toRead > 0) { int count = inputStream.Read(rawData, rawLength, toRead); if (count <= 0) { break; } rawLength += count; toRead -= count; } if (cryptoTransform != null) { clearTextLength = cryptoTransform.TransformBlock(rawData, 0, rawLength, clearText, 0); } else { clearTextLength = rawLength; } available = clearTextLength; } /// /// Read a buffer directly from the input stream /// /// The buffer to fill /// Returns the number of bytes read. public int ReadRawBuffer(byte[] buffer) { return ReadRawBuffer(buffer, 0, buffer.Length); } /// /// Read a buffer directly from the input stream /// /// The buffer to read into /// The offset to start reading data into. /// The number of bytes to read. /// Returns the number of bytes read. public int ReadRawBuffer(byte[] outBuffer, int offset, int length) { if (length < 0) { throw new ArgumentOutOfRangeException("nameof(length)"); } int currentOffset = offset; int currentLength = length; while (currentLength > 0) { if (available <= 0) { Fill(); if (available <= 0) { return 0; } } int toCopy = Math.Min(currentLength, available); System.Array.Copy(rawData, rawLength - (int)available, outBuffer, currentOffset, toCopy); currentOffset += toCopy; currentLength -= toCopy; available -= toCopy; } return length; } /// /// Read clear text data from the input stream. /// /// The buffer to add data to. /// The offset to start adding data at. /// The number of bytes to read. /// Returns the number of bytes actually read. public int ReadClearTextBuffer(byte[] outBuffer, int offset, int length) { if (length < 0) { throw new ArgumentOutOfRangeException("nameof(length)"); } int currentOffset = offset; int currentLength = length; while (currentLength > 0) { if (available <= 0) { Fill(); if (available <= 0) { return 0; } } int toCopy = Math.Min(currentLength, available); Array.Copy(clearText, clearTextLength - (int)available, outBuffer, currentOffset, toCopy); currentOffset += toCopy; currentLength -= toCopy; available -= toCopy; } return length; } /// /// Read a from the input stream. /// /// Returns the byte read. public int ReadLeByte() { if (available <= 0) { Fill(); if (available <= 0) { throw new ZipException("EOF in header"); } } byte result = rawData[rawLength - available]; available -= 1; return result; } /// /// Read an in little endian byte order. /// /// The short value read case to an int. public int ReadLeShort() { return ReadLeByte() | (ReadLeByte() << 8); } /// /// Read an in little endian byte order. /// /// The int value read. public int ReadLeInt() { return ReadLeShort() | (ReadLeShort() << 16); } /// /// Read a in little endian byte order. /// /// The long value read. public long ReadLeLong() { return (uint)ReadLeInt() | ((long)ReadLeInt() << 32); } /// /// Get/set the to apply to any data. /// /// Set this value to null to have no transform applied. public ICryptoTransform CryptoTransform { set { cryptoTransform = value; if (cryptoTransform != null) { if (rawData == clearText) { if (internalClearText == null) { internalClearText = new byte[rawData.Length]; } clearText = internalClearText; } clearTextLength = rawLength; if (available > 0) { cryptoTransform.TransformBlock(rawData, rawLength - available, available, clearText, rawLength - available); } } else { clearText = rawData; clearTextLength = rawLength; } } } #region Instance Fields int rawLength; byte[] rawData; int clearTextLength; byte[] clearText; byte[] internalClearText; int available; ICryptoTransform cryptoTransform; Stream inputStream; #endregion } /// /// This filter stream is used to decompress data compressed using the "deflate" /// format. The "deflate" format is described in RFC 1951. /// /// This stream may form the basis for other decompression filters, such /// as the GZipInputStream. /// /// Author of the original java version : John Leuner. /// public class InflaterInputStream : Stream { #region Constructors /// /// Create an InflaterInputStream with the default decompressor /// and a default buffer size of 4KB. /// /// /// The InputStream to read bytes from /// public InflaterInputStream(Stream baseInputStream) : this(baseInputStream, new Inflater(), 4096) { } /// /// Create an InflaterInputStream with the specified decompressor /// and a default buffer size of 4KB. /// /// /// The source of input data /// /// /// The decompressor used to decompress data read from baseInputStream /// public InflaterInputStream(Stream baseInputStream, Inflater inf) : this(baseInputStream, inf, 4096) { } /// /// Create an InflaterInputStream with the specified decompressor /// and the specified buffer size. /// /// /// The InputStream to read bytes from /// /// /// The decompressor to use /// /// /// Size of the buffer to use /// public InflaterInputStream(Stream baseInputStream, Inflater inflater, int bufferSize) { if (baseInputStream == null) { throw new ArgumentNullException("nameof(baseInputStream)"); } if (inflater == null) { throw new ArgumentNullException("nameof(inflater)"); } if (bufferSize <= 0) { throw new ArgumentOutOfRangeException("nameof(bufferSize)"); } this.baseInputStream = baseInputStream; this.inf = inflater; inputBuffer = new InflaterInputBuffer(baseInputStream, bufferSize); } #endregion /// /// 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; } } /// /// Skip specified number of bytes of uncompressed data /// /// /// Number of bytes to skip /// /// /// The number of bytes skipped, zero if the end of /// stream has been reached /// /// /// The number of bytes to skip is less than or equal to zero. /// public long Skip(long count) { if (count <= 0) { throw new ArgumentOutOfRangeException("nameof(count)"); } // v0.80 Skip by seeking if underlying stream supports it... if (baseInputStream.CanSeek) { baseInputStream.Seek(count, SeekOrigin.Current); return count; } else { int length = 2048; if (count < length) { length = (int)count; } byte[] tmp = new byte[length]; int readCount = 1; long toSkip = count; while ((toSkip > 0) && (readCount > 0)) { if (toSkip < length) { length = (int)toSkip; } readCount = baseInputStream.Read(tmp, 0, length); toSkip -= readCount; } return count - toSkip; } } /// /// Clear any cryptographic state. /// protected void StopDecrypting() { inputBuffer.CryptoTransform = null; } /// /// Returns 0 once the end of the stream (EOF) has been reached. /// Otherwise returns 1. /// public virtual int Available { get { return inf.IsFinished ? 0 : 1; } } /// /// Fills the buffer with more data to decompress. /// /// /// Stream ends early /// protected void Fill() { // Protect against redundant calls if (inputBuffer.Available <= 0) { inputBuffer.Fill(); if (inputBuffer.Available <= 0) { throw new SharpZipBaseException("Unexpected EOF"); } } inputBuffer.SetInflaterInput(inf); } #region Stream Overrides /// /// Gets a value indicating whether the current stream supports reading /// public override bool CanRead { get { return baseInputStream.CanRead; } } /// /// Gets a value of false indicating seeking is not supported for this stream. /// public override bool CanSeek { get { return false; } } /// /// Gets a value of false indicating that this stream is not writeable. /// public override bool CanWrite { get { return false; } } /// /// A value representing the length of the stream in bytes. /// public override long Length { get { //return inputBuffer.RawLength; throw new NotSupportedException("InflaterInputStream Length is not supported"); } } /// /// The current position within the stream. /// Throws a NotSupportedException when attempting to set the position /// /// Attempting to set the position public override long Position { get { return baseInputStream.Position; } set { throw new NotSupportedException("InflaterInputStream Position not supported"); } } /// /// Flushes the baseInputStream /// public override void Flush() { baseInputStream.Flush(); } /// /// Sets the position within the current stream /// Always throws a NotSupportedException /// /// The relative offset to seek to. /// The defining where to seek from. /// The new position in the stream. /// Any access public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("Seek not supported"); } /// /// Set the length of the current stream /// Always throws a NotSupportedException /// /// The new length value for the stream. /// Any access public override void SetLength(long value) { throw new NotSupportedException("InflaterInputStream SetLength not supported"); } /// /// Writes a sequence of bytes to stream and advances the current position /// This method always throws a NotSupportedException /// /// Thew buffer containing data to write. /// The offset of the first byte to write. /// The number of bytes to write. /// Any access public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException("InflaterInputStream Write not supported"); } /// /// Writes one byte to the current stream and advances the current position /// Always throws a NotSupportedException /// /// The byte to write. /// Any access public override void WriteByte(byte value) { throw new NotSupportedException("InflaterInputStream WriteByte not supported"); } /// /// Closes the input stream. When /// is true the underlying stream is also closed. /// protected override void Dispose(bool disposing) { if (!isClosed) { isClosed = true; if (IsStreamOwner) { baseInputStream.Dispose(); } } } /// /// Reads decompressed data into the provided buffer 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 /// /// The number of bytes read. Zero signals the end of stream /// /// Inflater needs a dictionary /// public override int Read(byte[] buffer, int offset, int count) { if (inf.IsNeedingDictionary) { throw new SharpZipBaseException("Need a dictionary"); } int remainingBytes = count; while (true) { int bytesRead = inf.Inflate(buffer, offset, remainingBytes); offset += bytesRead; remainingBytes -= bytesRead; if (remainingBytes == 0 || inf.IsFinished) { break; } if (inf.IsNeedingInput) { Fill(); } else if (bytesRead == 0) { throw new ZipException("Dont know what to do"); } } return count - remainingBytes; } #endregion #region Instance Fields /// /// Decompressor for this stream /// protected Inflater inf; /// /// Input buffer for this stream. /// protected InflaterInputBuffer inputBuffer; /// /// Base stream the inflater reads from. /// private Stream baseInputStream; /// /// The compressed size /// protected long csize; /// /// Flag indicating wether this instance has been closed or not. /// bool isClosed; #endregion } }