// // CompressedStream.cs // // Author: Jeffrey Stedfast // // Copyright (c) 2013-2020 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Org.BouncyCastle.Utilities.Zlib; namespace MailKit { /// /// A compressed stream. /// class CompressedStream : Stream { readonly ZStream zIn, zOut; bool eos, disposed; public CompressedStream (Stream innerStream) { InnerStream = innerStream; zOut = new ZStream (); zOut.deflateInit (5, true); zOut.next_out = new byte[4096]; zIn = new ZStream (); zIn.inflateInit (true); zIn.next_in = new byte[4096]; } /// /// Gets the inner stream. /// /// The inner stream. public Stream InnerStream { get; private set; } /// /// Gets whether the stream supports reading. /// /// true if the stream supports reading; otherwise, false. public override bool CanRead { get { return InnerStream.CanRead; } } /// /// Gets whether the stream supports writing. /// /// true if the stream supports writing; otherwise, false. public override bool CanWrite { get { return InnerStream.CanWrite; } } /// /// Gets whether the stream supports seeking. /// /// true if the stream supports seeking; otherwise, false. public override bool CanSeek { get { return false; } } /// /// Gets whether the stream supports I/O timeouts. /// /// true if the stream supports I/O timeouts; otherwise, false. public override bool CanTimeout { get { return InnerStream.CanTimeout; } } /// /// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to read before timing out. /// /// A value, in miliseconds, that determines how long the stream will attempt to read before timing out. /// The read timeout. public override int ReadTimeout { get { return InnerStream.ReadTimeout; } set { InnerStream.ReadTimeout = value; } } /// /// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to write before timing out. /// /// A value, in miliseconds, that determines how long the stream will attempt to write before timing out. /// The write timeout. public override int WriteTimeout { get { return InnerStream.WriteTimeout; } set { InnerStream.WriteTimeout = value; } } /// /// Gets or sets the position within the current stream. /// /// The current position within the stream. /// The position of the stream. /// /// The stream does not support seeking. /// public override long Position { get { throw new NotSupportedException (); } set { throw new NotSupportedException (); } } /// /// Gets the length in bytes of the stream. /// /// A long value representing the length of the stream in bytes. /// The length of the stream. /// /// The stream does not support seeking. /// public override long Length { get { throw new NotSupportedException (); } } static void ValidateArguments (byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException (nameof (buffer)); if (offset < 0 || offset > buffer.Length) throw new ArgumentOutOfRangeException (nameof (offset)); if (count < 0 || count > (buffer.Length - offset)) throw new ArgumentOutOfRangeException (nameof (count)); } void CheckDisposed () { if (disposed) throw new ObjectDisposedException (nameof (CompressedStream)); } async Task ReadAsync (byte[] buffer, int offset, int count, bool doAsync, CancellationToken cancellationToken) { CheckDisposed (); ValidateArguments (buffer, offset, count); if (count == 0) return 0; zIn.next_out = buffer; zIn.next_out_index = offset; zIn.avail_out = count; do { if (zIn.avail_in == 0 && !eos) { cancellationToken.ThrowIfCancellationRequested (); if (doAsync) zIn.avail_in = await InnerStream.ReadAsync (zIn.next_in, 0, zIn.next_in.Length, cancellationToken).ConfigureAwait (false); else zIn.avail_in = InnerStream.Read (zIn.next_in, 0, zIn.next_in.Length); eos = zIn.avail_in == 0; zIn.next_in_index = 0; } int retval = zIn.inflate (JZlib.Z_FULL_FLUSH); if (retval == JZlib.Z_STREAM_END) break; if (eos && retval == JZlib.Z_BUF_ERROR) return 0; if (retval != JZlib.Z_OK) throw new IOException ("Error inflating: " + zIn.msg); } while (zIn.avail_out == count); return count - zIn.avail_out; } /// /// Reads a sequence of bytes from the stream and advances the position /// within the stream by the number of bytes read. /// /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many /// bytes are not currently available, or zero (0) if the end of the stream has been reached. /// The buffer. /// The buffer offset. /// The number of bytes to read. /// /// is null. /// /// /// is less than zero or greater than the length of . /// -or- /// The is not large enough to contain bytes strting /// at the specified . /// /// /// The stream has been disposed. /// /// /// An I/O error occurred. /// public override int Read (byte[] buffer, int offset, int count) { return ReadAsync (buffer, offset, count, false, CancellationToken.None).GetAwaiter ().GetResult (); } /// /// Reads a sequence of bytes from the stream and advances the position /// within the stream by the number of bytes read. /// /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many /// bytes are not currently available, or zero (0) if the end of the stream has been reached. /// The buffer. /// The buffer offset. /// The number of bytes to read. /// The cancellation token. /// /// is null. /// /// /// is less than zero or greater than the length of . /// -or- /// The is not large enough to contain bytes strting /// at the specified . /// /// /// The stream has been disposed. /// /// /// An I/O error occurred. /// public override Task ReadAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken) { return ReadAsync (buffer, offset, count, true, cancellationToken); } async Task WriteAsync (byte[] buffer, int offset, int count, bool doAsync, CancellationToken cancellationToken) { CheckDisposed (); ValidateArguments (buffer, offset, count); if (count == 0) return; zOut.next_in = buffer; zOut.next_in_index = offset; zOut.avail_in = count; do { cancellationToken.ThrowIfCancellationRequested (); zOut.avail_out = zOut.next_out.Length; zOut.next_out_index = 0; if (zOut.deflate (JZlib.Z_FULL_FLUSH) != JZlib.Z_OK) throw new IOException ("Error deflating: " + zOut.msg); if (doAsync) await InnerStream.WriteAsync (zOut.next_out, 0, zOut.next_out.Length - zOut.avail_out, cancellationToken).ConfigureAwait (false); else InnerStream.Write (zOut.next_out, 0, zOut.next_out.Length - zOut.avail_out); } while (zOut.avail_in > 0 || zOut.avail_out == 0); } /// /// Writes a sequence of bytes to the stream and advances the current /// position within this stream by the number of bytes written. /// /// The buffer to write. /// The offset of the first byte to write. /// The number of bytes to write. /// /// is null. /// /// /// is less than zero or greater than the length of . /// -or- /// The is not large enough to contain bytes strting /// at the specified . /// /// /// The stream has been disposed. /// /// /// The stream does not support writing. /// /// /// An I/O error occurred. /// public override void Write (byte[] buffer, int offset, int count) { WriteAsync (buffer, offset, count, false, CancellationToken.None).GetAwaiter ().GetResult (); } /// /// Writes a sequence of bytes to the stream and advances the current /// position within this stream by the number of bytes written. /// /// A task that represents the asynchronous write operation. /// The buffer to write. /// The offset of the first byte to write. /// The number of bytes to write. /// The cancellation token. /// /// is null. /// /// /// is less than zero or greater than the length of . /// -or- /// The is not large enough to contain bytes strting /// at the specified . /// /// /// The stream has been disposed. /// /// /// The stream does not support writing. /// /// /// An I/O error occurred. /// public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken) { return WriteAsync (buffer, offset, count, true, cancellationToken); } /// /// Clears all output buffers for this stream and causes any buffered data to be written /// to the underlying device. /// /// /// The stream has been disposed. /// /// /// The stream does not support writing. /// /// /// An I/O error occurred. /// public override void Flush () { CheckDisposed (); InnerStream.Flush (); } /// /// Clears all output buffers for this stream and causes any buffered data to be written /// to the underlying device. /// /// A task that represents the asynchronous flush operation. /// /// The stream has been disposed. /// /// /// The stream does not support writing. /// /// /// An I/O error occurred. /// public override Task FlushAsync (CancellationToken cancellationToken) { CheckDisposed (); return InnerStream.FlushAsync (cancellationToken); } /// /// Sets the position within the current stream. /// /// The new position within the stream. /// The offset into the stream relative to the . /// The origin to seek from. /// /// The stream does not support seeking. /// public override long Seek (long offset, SeekOrigin origin) { throw new NotSupportedException (); } /// /// Sets the length of the stream. /// /// The desired length of the stream in bytes. /// /// The stream does not support setting the length. /// public override void SetLength (long value) { throw new NotSupportedException (); } /// /// Releases the unmanaged resources used by the and /// optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; /// false to release only the unmanaged resources. protected override void Dispose (bool disposing) { if (disposing && !disposed) { InnerStream.Dispose (); disposed = true; zOut.free (); zIn.free (); } base.Dispose (disposing); } } }