// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.IO; using System.IO.Compression; namespace SixLabors.Fonts.IO { internal sealed class ZlibInflateStream : Stream { private long position; /// /// The raw stream containing the uncompressed image data. /// private readonly Stream rawStream; /// /// 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 read crc data. /// private byte[]? crcRead; /// /// The stream responsible for decompressing the input stream. /// private DeflateStream? deflateStream; /// /// Initializes a new instance of the class. /// /// The stream. /// /// Thrown if the compression method is incorrect. /// public ZlibInflateStream(Stream stream) { // The DICT dictionary identifier identifying the used dictionary. // The preset dictionary. bool fdict; 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 IOException($"Bad compression method for ZLIB header: cmf={cmf}"); } // CINFO is the base-2 logarithm of the LZ77 window size, minus eight. // int cinfo = ((cmf & (0xf0)) >> 8); fdict = (flag & 32) != 0; if (fdict) { // The DICT dictionary identifier identifying the used dictionary. byte[] dictId = new byte[4]; for (int i = 0; i < 4; i++) { // We consume but don't use 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 => throw new NotSupportedException(); /// public override long Position { get => this.position; set => throw new NotSupportedException(); } /// public override void Flush() => this.deflateStream?.Flush(); /// public override int Read(byte[] buffer, int offset, int count) { ObjectDisposedException.ThrowIf(this.deflateStream is null, this.GetType()); // We don't check CRC on reading int read = this.deflateStream.Read(buffer, offset, count); if (read < 1 && this.crcRead is 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 don't really check/use this this.crcRead[i] = (byte)this.rawStream.ReadByte(); } } this.position += read; return read; } /// public override long Seek(long offset, SeekOrigin origin) { if (origin == SeekOrigin.Begin) { origin = SeekOrigin.Current; offset -= this.position; } if (origin == SeekOrigin.Current && offset >= 0) { // consume bytes for (int i = 0; i < offset; i++) { this.ReadByte(); } return this.position; } throw new NotSupportedException("can only seek forwards"); } /// public override void SetLength(long value) => throw new NotSupportedException(); /// public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); /// 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 is 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; } } }