|
///----------- ----------- ----------- ----------- ----------- ----------- -----------
/// <copyright file="DeflateStream.cs" company="Microsoft">
/// Copyright (c) Microsoft Corporation. All rights reserved.
/// </copyright>
///
///----------- ----------- ----------- ----------- ----------- ----------- -----------
///
using System.Diagnostics;
using System.Threading;
using System.Security.Permissions;
using System.Diagnostics.Contracts;
namespace System.IO.Compression {
public class DeflateStream : Stream {
internal const int DefaultBufferSize = 8192;
private const int WindowSizeUpperBound = 47;
internal delegate void AsyncWriteDelegate(byte[] array, int offset, int count, bool isAsync);
//private const String OrigStackTrace_ExceptionDataKey = "ORIGINAL_STACK_TRACE";
private Stream _stream;
private CompressionMode _mode;
private bool _leaveOpen;
private IInflater inflater;
private IDeflater deflater;
private byte[] buffer;
private int asyncOperations;
private readonly AsyncCallback m_CallBack;
private readonly AsyncWriteDelegate m_AsyncWriterDelegate;
private IFileFormatWriter formatWriter;
private bool wroteHeader;
private bool wroteBytes;
private enum WorkerType : byte { Managed, ZLib, Unknown };
private static volatile WorkerType deflaterType = WorkerType.Unknown;
private static volatile WorkerType inflaterType = WorkerType.Unknown;
public DeflateStream(Stream stream, CompressionMode mode)
: this(stream, mode, false) {
}
// Since a reader is being taken, CompressionMode.Decompress is implied
internal DeflateStream(Stream stream, bool leaveOpen, IFileFormatReader reader)
{
Debug.Assert(reader != null, "The IFileFormatReader passed to the internal DeflateStream constructor must be non-null");
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanRead)
throw new ArgumentException(SR.GetString(SR.NotReadableStream), "stream");
inflater = CreateInflater(reader);
m_CallBack = new AsyncCallback(ReadCallback);
_stream = stream;
_mode = CompressionMode.Decompress;
_leaveOpen = leaveOpen;
buffer = new byte[DefaultBufferSize];
}
public DeflateStream(Stream stream, CompressionMode mode, bool leaveOpen) {
if(stream == null )
throw new ArgumentNullException("stream");
if (CompressionMode.Compress != mode && CompressionMode.Decompress != mode)
throw new ArgumentException(SR.GetString(SR.ArgumentOutOfRange_Enum), "mode");
_stream = stream;
_mode = mode;
_leaveOpen = leaveOpen;
switch (_mode) {
case CompressionMode.Decompress:
if (!_stream.CanRead) {
throw new ArgumentException(SR.GetString(SR.NotReadableStream), "stream");
}
inflater = CreateInflater();
m_CallBack = new AsyncCallback(ReadCallback);
break;
case CompressionMode.Compress:
if (!_stream.CanWrite) {
throw new ArgumentException(SR.GetString(SR.NotWriteableStream), "stream");
}
deflater = CreateDeflater(null);
m_AsyncWriterDelegate = new AsyncWriteDelegate(this.InternalWrite);
m_CallBack = new AsyncCallback(WriteCallback);
break;
} // switch (_mode)
buffer = new byte[DefaultBufferSize];
}
// Implies mode = Compress
public DeflateStream(Stream stream, CompressionLevel compressionLevel)
: this(stream, compressionLevel, false) {
}
// Implies mode = Compress
public DeflateStream(Stream stream, CompressionLevel compressionLevel, bool leaveOpen) {
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanWrite)
throw new ArgumentException(SR.GetString(SR.NotWriteableStream), "stream");
// Checking of compressionLevel is passed down to the IDeflater implementation as it
// is a pluggable component that completely encapsulates the meaning of compressionLevel.
Contract.EndContractBlock();
_stream = stream;
_mode = CompressionMode.Compress;
_leaveOpen = leaveOpen;
deflater = CreateDeflater(compressionLevel);
m_AsyncWriterDelegate = new AsyncWriteDelegate(this.InternalWrite);
m_CallBack = new AsyncCallback(WriteCallback);
buffer = new byte[DefaultBufferSize];
}
private static IDeflater CreateDeflater(CompressionLevel? compressionLevel) {
switch (GetDeflaterType()) {
case WorkerType.Managed:
return new DeflaterManaged();
case WorkerType.ZLib:
if (compressionLevel.HasValue)
return new DeflaterZLib(compressionLevel.Value);
else
return new DeflaterZLib();
default:
// We do not expect this to ever be thrown.
// But this is better practice than returning null.
throw new SystemException("Program entered an unexpected state.");
}
}
private static IInflater CreateInflater(IFileFormatReader reader = null) {
switch (GetInflaterType()) {
case WorkerType.Managed:
return new Inflater(reader);
case WorkerType.ZLib:
// Rather than reading raw data and using a FormatReader to interpret
// headers/footers manually, we instead set the zlib stream to parse
// that information for us.
if (reader == null) {
return new InflaterZlib(ZLibNative.Deflate_DefaultWindowBits);
}
else {
// Since we don't necessarily know what WindowSize the stream was compressed with,
// we use the upper bound of (32..47) as the WindowSize for decompression. This enables
// detection for Zlib and GZip headers
return new InflaterZlib(WindowSizeUpperBound);
}
default:
// We do not expect this to ever be thrown.
// But this is better practice than returning null.
throw new SystemException("Program entered an unexpected state.");
}
}
#if !SILVERLIGHT
[System.Security.SecuritySafeCritical]
#endif
private static WorkerType GetDeflaterType() {
// Let's not worry about race conditions:
// Yes, we risk initialising the singleton multiple times.
// However, initialising the singleton multiple times has no bad consequences, and is fairly cheap.
if (WorkerType.Unknown != deflaterType)
return deflaterType;
#if !SILVERLIGHT // Do this on Desktop. CLRConfig doesn't exist on CoreSys nor Silverlight.
// CLRConfig is internal in mscorlib and is a friend
if (System.CLRConfig.CheckLegacyManagedDeflateStream())
return (deflaterType = WorkerType.Managed);
#endif
#if !SILVERLIGHT || FEATURE_NETCORE // Only skip this for Silverlight, which doesn't ship ZLib.
if (!CompatibilitySwitches.IsNetFx45LegacyManagedDeflateStream)
return (deflaterType = WorkerType.ZLib);
#endif
return (deflaterType = WorkerType.Managed);
}
#if !SILVERLIGHT
[System.Security.SecuritySafeCritical]
#endif
private static WorkerType GetInflaterType() {
// Let's not worry about race conditions:
// Yes, we risk initialising the singleton multiple times.
// However, initialising the singleton multiple times has no bad consequences, and is fairly cheap.
if (WorkerType.Unknown != inflaterType)
return inflaterType;
#if !SILVERLIGHT || FEATURE_NETCORE // Only skip this for Silverlight, which doesn't ship ZLib.
if (!LocalAppContextSwitches.DoNotUseNativeZipLibraryForDecompression)
return (inflaterType = WorkerType.ZLib);
#endif
return (inflaterType = WorkerType.Managed);
}
internal void SetFileFormatWriter(IFileFormatWriter writer) {
if (writer != null) {
formatWriter = writer;
}
}
public Stream BaseStream {
get {
return _stream;
}
}
public override bool CanRead {
get {
if( _stream == null) {
return false;
}
return (_mode == CompressionMode.Decompress && _stream.CanRead);
}
}
public override bool CanWrite {
get {
if( _stream == null) {
return false;
}
return (_mode == CompressionMode.Compress && _stream.CanWrite);
}
}
public override bool CanSeek {
get {
return false;
}
}
public override long Length {
get {
throw new NotSupportedException(SR.GetString(SR.NotSupported));
}
}
public override long Position {
get {
throw new NotSupportedException(SR.GetString(SR.NotSupported));
}
set {
throw new NotSupportedException(SR.GetString(SR.NotSupported));
}
}
public override void Flush() {
EnsureNotDisposed();
return;
}
public override long Seek(long offset, SeekOrigin origin) {
throw new NotSupportedException(SR.GetString(SR.NotSupported));
}
public override void SetLength(long value) {
throw new NotSupportedException(SR.GetString(SR.NotSupported));
}
public override int Read(byte[] array, int offset, int count) {
EnsureDecompressionMode();
ValidateParameters(array, offset, count);
EnsureNotDisposed();
int bytesRead;
int currentOffset = offset;
int remainingCount = count;
while(true) {
bytesRead = inflater.Inflate(array, currentOffset, remainingCount);
currentOffset += bytesRead;
remainingCount -= bytesRead;
if( remainingCount == 0) {
break;
}
if (inflater.Finished()) {
break;
}
Debug.Assert(inflater.NeedsInput(), "We can only run into this case if we are short of input");
int bytes = _stream.Read(buffer, 0, buffer.Length);
if( bytes == 0) {
break; //Do we want to throw an exception here?
}
inflater.SetInput(buffer, 0 , bytes);
}
return count - remainingCount;
}
private void ValidateParameters(byte[] array, int offset, int count) {
if (array==null)
throw new ArgumentNullException("array");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset");
if (count < 0)
throw new ArgumentOutOfRangeException("count");
if (array.Length - offset < count)
throw new ArgumentException(SR.GetString(SR.InvalidArgumentOffsetCount));
}
private void EnsureNotDisposed() {
if (_stream == null)
throw new ObjectDisposedException(null, SR.GetString(SR.ObjectDisposed_StreamClosed));
}
private void EnsureDecompressionMode() {
if( _mode != CompressionMode.Decompress)
throw new InvalidOperationException(SR.GetString(SR.CannotReadFromDeflateStream));
}
private void EnsureCompressionMode() {
if( _mode != CompressionMode.Compress)
throw new InvalidOperationException(SR.GetString(SR.CannotWriteToDeflateStream));
}
#if !FEATURE_NETCORE
[HostProtection(ExternalThreading=true)]
#endif
public override IAsyncResult BeginRead(byte[] array, int offset, int count, AsyncCallback asyncCallback, object asyncState) {
EnsureDecompressionMode();
// We use this checking order for compat to earlier versions:
if (asyncOperations != 0)
throw new InvalidOperationException(SR.GetString(SR.InvalidBeginCall));
ValidateParameters(array, offset, count);
EnsureNotDisposed();
Interlocked.Increment(ref asyncOperations);
try {
DeflateStreamAsyncResult userResult = new DeflateStreamAsyncResult(
this, asyncState, asyncCallback, array, offset, count);
userResult.isWrite = false;
// Try to read decompressed data in output buffer
int bytesRead = inflater.Inflate(array, offset, count);
if( bytesRead != 0) {
// If decompression output buffer is not empty, return immediately.
// 'true' means we complete synchronously.
userResult.InvokeCallback(true, (object) bytesRead);
return userResult;
}
if (inflater.Finished() ) {
// end of compression stream
userResult.InvokeCallback(true, (object) 0);
return userResult;
}
// If there is no data on the output buffer and we are not at
// the end of the stream, we need to get more data from the base stream
_stream.BeginRead(buffer, 0, buffer.Length, m_CallBack, userResult);
userResult.m_CompletedSynchronously &= userResult.IsCompleted;
return userResult;
} catch {
Interlocked.Decrement( ref asyncOperations);
throw;
}
}
// callback function for asynchrous reading on base stream
private void ReadCallback(IAsyncResult baseStreamResult) {
DeflateStreamAsyncResult outerResult = (DeflateStreamAsyncResult) baseStreamResult.AsyncState;
outerResult.m_CompletedSynchronously &= baseStreamResult.CompletedSynchronously;
int bytesRead = 0;
try {
EnsureNotDisposed();
bytesRead = _stream.EndRead(baseStreamResult);
if (bytesRead <= 0 ) {
// This indicates the base stream has received EOF
outerResult.InvokeCallback((object) 0);
return;
}
// Feed the data from base stream into decompression engine
inflater.SetInput(buffer, 0 , bytesRead);
bytesRead = inflater.Inflate(outerResult.buffer, outerResult.offset, outerResult.count);
if (bytesRead == 0 && !inflater.Finished()) {
// We could have read in head information and didn't get any data.
// Read from the base stream again.
// Need to solve recusion.
_stream.BeginRead(buffer, 0, buffer.Length, m_CallBack, outerResult);
} else {
outerResult.InvokeCallback((object) bytesRead);
}
} catch (Exception exc) {
// Defer throwing this until EndRead where we will likely have user code on the stack.
outerResult.InvokeCallback(exc);
return;
}
}
public override int EndRead(IAsyncResult asyncResult) {
EnsureDecompressionMode();
CheckEndXxxxLegalStateAndParams(asyncResult);
// We checked that this will work in CheckEndXxxxLegalStateAndParams:
DeflateStreamAsyncResult deflateStrmAsyncResult = (DeflateStreamAsyncResult) asyncResult;
AwaitAsyncResultCompletion(deflateStrmAsyncResult);
Exception previousException = deflateStrmAsyncResult.Result as Exception;
if (previousException != null) {
// Rethrowing will delete the stack trace. Let's help future debuggers:
//previousException.Data.Add(OrigStackTrace_ExceptionDataKey, previousException.StackTrace);
throw previousException;
}
return (int) deflateStrmAsyncResult.Result;
}
public override void Write(byte[] array, int offset, int count) {
EnsureCompressionMode();
ValidateParameters(array, offset, count);
EnsureNotDisposed();
InternalWrite(array, offset, count, false);
}
// isAsync always seems to be false. why do we have it?
internal void InternalWrite(byte[] array, int offset, int count, bool isAsync) {
DoMaintenance(array, offset, count);
// Write compressed the bytes we already passed to the deflater:
WriteDeflaterOutput(isAsync);
// Pass new bytes through deflater and write them too:
deflater.SetInput(array, offset, count);
WriteDeflaterOutput(isAsync);
}
private void WriteDeflaterOutput(bool isAsync) {
while (!deflater.NeedsInput()) {
int compressedBytes = deflater.GetDeflateOutput(buffer);
if (compressedBytes > 0)
DoWrite(buffer, 0, compressedBytes, isAsync);
}
}
private void DoWrite(byte[] array, int offset, int count, bool isAsync) {
Debug.Assert(array != null);
Debug.Assert(count != 0);
if (isAsync) {
IAsyncResult result = _stream.BeginWrite(array, offset, count, null, null);
_stream.EndWrite(result);
} else {
_stream.Write(array, offset, count);
}
}
// Perform deflate-mode maintenance required due to custom header and footer writers
// (e.g. set by GZipStream):
private void DoMaintenance(byte[] array, int offset, int count) {
// If no bytes written, do nothing:
if (count <= 0)
return;
// Note that stream contains more than zero data bytes:
wroteBytes = true;
// If no header/footer formatter present, nothing else to do:
if (formatWriter == null)
return;
// If formatter has not yet written a header, do it now:
if (!wroteHeader) {
byte[] b = formatWriter.GetHeader();
_stream.Write(b, 0, b.Length);
wroteHeader = true;
}
// Inform formatter of the data bytes written:
formatWriter.UpdateWithBytesRead(array, offset, count);
}
// This is called by Dispose:
private void PurgeBuffers(bool disposing) {
if (!disposing)
return;
if (_stream == null)
return;
Flush();
if (_mode != CompressionMode.Compress)
return;
// Some deflaters (e.g. ZLib write more than zero bytes for zero bytes inpuits.
// This round-trips and we should be ok with this, but our legacy managed deflater
// always wrote zero output for zero input and upstack code (e.g. GZipStream)
// took dependencies on it. Thus, make sure to only "flush" when we actually had
// some input:
if (wroteBytes) {
// Compress any bytes left:
WriteDeflaterOutput(false);
// Pull out any bytes left inside deflater:
bool finished;
do {
int compressedBytes;
finished = deflater.Finish(buffer, out compressedBytes);
if (compressedBytes > 0)
DoWrite(buffer, 0, compressedBytes, false);
} while (!finished);
}
// Write format footer:
if (formatWriter != null && wroteHeader) {
byte[] b = formatWriter.GetFooter();
_stream.Write(b, 0, b.Length);
}
}
protected override void Dispose(bool disposing) {
try {
PurgeBuffers(disposing);
} finally {
// Close the underlying stream even if PurgeBuffers threw.
// Stream.Close() may throw here (may or may not be due to the same error).
// In this case, we still need to clean up internal resources, hence the inner finally blocks.
try {
if(disposing && !_leaveOpen && _stream != null)
_stream.Close();
} finally {
_stream = null;
try {
if (deflater != null)
deflater.Dispose();
if (inflater != null)
inflater.Dispose();
} finally {
inflater = null;
deflater = null;
base.Dispose(disposing);
}
} // finally
} // finally
} // Dispose
#if !FEATURE_NETCORE
[HostProtection(ExternalThreading=true)]
#endif
public override IAsyncResult BeginWrite(byte[] array, int offset, int count, AsyncCallback asyncCallback, object asyncState) {
EnsureCompressionMode();
// We use this checking order for compat to earlier versions:
if (asyncOperations != 0 )
throw new InvalidOperationException(SR.GetString(SR.InvalidBeginCall));
ValidateParameters(array, offset, count);
EnsureNotDisposed();
Interlocked.Increment(ref asyncOperations);
try {
DeflateStreamAsyncResult userResult = new DeflateStreamAsyncResult(
this, asyncState, asyncCallback, array, offset, count);
userResult.isWrite = true;
m_AsyncWriterDelegate.BeginInvoke(array, offset, count, true, m_CallBack, userResult);
userResult.m_CompletedSynchronously &= userResult.IsCompleted;
return userResult;
} catch {
Interlocked.Decrement(ref asyncOperations);
throw;
}
}
// Callback function for asynchrous reading on base stream
private void WriteCallback(IAsyncResult asyncResult) {
DeflateStreamAsyncResult outerResult = (DeflateStreamAsyncResult) asyncResult.AsyncState;
outerResult.m_CompletedSynchronously &= asyncResult.CompletedSynchronously;
try {
m_AsyncWriterDelegate.EndInvoke(asyncResult);
} catch (Exception exc) {
// Defer throwing this until EndWrite where there is user code on the stack:
outerResult.InvokeCallback(exc);
return;
}
outerResult.InvokeCallback(null);
}
public override void EndWrite(IAsyncResult asyncResult) {
EnsureCompressionMode();
CheckEndXxxxLegalStateAndParams(asyncResult);
// We checked that this will work in CheckEndXxxxLegalStateAndParams:
DeflateStreamAsyncResult deflateStrmAsyncResult = (DeflateStreamAsyncResult) asyncResult;
AwaitAsyncResultCompletion(deflateStrmAsyncResult);
Exception previousException = deflateStrmAsyncResult.Result as Exception;
if (previousException != null) {
// Rethrowing will delete the stack trace. Let's help future debuggers:
//previousException.Data.Add(OrigStackTrace_ExceptionDataKey, previousException.StackTrace);
throw previousException;
}
}
private void CheckEndXxxxLegalStateAndParams(IAsyncResult asyncResult) {
if (asyncOperations != 1)
throw new InvalidOperationException(SR.GetString(SR.InvalidEndCall));
if (asyncResult == null)
throw new ArgumentNullException("asyncResult");
EnsureNotDisposed();
DeflateStreamAsyncResult myResult = asyncResult as DeflateStreamAsyncResult;
// This should really be an ArgumentException, but we keep this for compat to previous versions:
if (myResult == null)
throw new ArgumentNullException("asyncResult");
}
private void AwaitAsyncResultCompletion(DeflateStreamAsyncResult asyncResult) {
try {
if (!asyncResult.IsCompleted)
asyncResult.AsyncWaitHandle.WaitOne();
} finally {
Interlocked.Decrement(ref asyncOperations);
asyncResult.Close(); // this will just close the wait handle
}
}
} // public class DeflateStream
} // namespace System.IO.Compression
|