namespace ImageProcessor.Formats { using System; using System.IO; using System.IO.Compression; internal class ZlibInputStream : Stream { /// /// A value indicating whether this instance of the given entity has been disposed. /// /// if this instance has been disposed; otherwise, . /// /// If the entity is disposed, it must not be disposed a second /// time. The isDisposed field is set the first time the entity /// is disposed. If the isDisposed field is true, then the Dispose() /// method will not dispose again. This help not to prolong the entity's /// life in the Garbage Collector. /// private bool isDisposed; /// /// The raw stream containing the uncompressed image data. /// private readonly Stream rawStream; /// /// The preset dictionary. /// Merely informational, not used. /// private bool fdict; /// /// The DICT dictionary identifier identifying the used dictionary. /// Merely informational, not used. /// private byte[] dictId; /// /// CINFO is the base-2 logarithm of the LZ77 window size, minus eight. /// Merely informational, not used. /// private int cinfo; /// /// The read crc data. /// private byte[] crcread; // The stream responsible for decompressing the input stream. private DeflateStream deflateStream; public ZlibInputStream(Stream stream) { this.rawStream = stream; // Read the zlib header : http://tools.ietf.org/html/rfc1950 // CMF(Compression Method and flags) // This byte is divided into a 4 - bit compression method and a // 4-bit information field depending on the compression method. // bits 0 to 3 CM Compression method // bits 4 to 7 CINFO Compression info // // 0 1 // +---+---+ // |CMF|FLG| // +---+---+ int cmf = this.rawStream.ReadByte(); int flag = this.rawStream.ReadByte(); if (cmf == -1 || flag == -1) { return; } if ((cmf & 0x0f) != 8) { throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}"); } this.cinfo = ((cmf & (0xf0)) >> 8); this.fdict = (flag & 32) != 0; if (this.fdict) { this.dictId = new byte[4]; for (int i = 0; i < 4; i++) { // We consume but don't use this. this.dictId[i] = (byte)this.rawStream.ReadByte(); } } // Initialize the deflate Stream. this.deflateStream = new DeflateStream(this.rawStream, CompressionMode.Decompress, true); } /// public override bool CanRead => true; /// public override bool CanSeek => false; /// public override bool CanWrite => false; /// public override long Length { get { throw new NotImplementedException(); } } /// public override long Position { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } /// public override void Flush() { this.deflateStream?.Flush(); } /// public override int Read(byte[] buffer, int offset, int count) { // We dont't check CRC on reading int read = this.deflateStream.Read(buffer, offset, count); if (read < 1 && this.crcread == null) { // The deflater has ended. We try to read the next 4 bytes from raw stream (crc) this.crcread = new byte[4]; for (int i = 0; i < 4; i++) { // we dont really check/use this this.crcread[i] = (byte)rawStream.ReadByte(); } } return read; } /// public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } /// public override void SetLength(long value) { throw new NotImplementedException(); } /// public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } /// protected override void Dispose(bool disposing) { if (this.isDisposed) { return; } if (disposing) { // dispose managed resources if (this.deflateStream != null) { this.deflateStream.Dispose(); this.deflateStream = null; if (this.crcread == null) { // Consume the trailing 4 bytes this.crcread = new byte[4]; for (int i = 0; i < 4; i++) { this.crcread[i] = (byte)this.rawStream.ReadByte(); } } } } base.Dispose(disposing); // Call the appropriate methods to clean up // unmanaged resources here. // Note disposing is done. this.isDisposed = true; } } }