1 ///----------- ----------- ----------- ----------- ----------- ----------- -----------
2 /// <copyright file="DeflateStream.cs" company="Microsoft">
3 /// Copyright (c) Microsoft Corporation. All rights reserved.
6 ///----------- ----------- ----------- ----------- ----------- ----------- -----------
9 using System.Diagnostics;
10 using System.Threading;
11 using System.Security.Permissions;
12 using System.Diagnostics.Contracts;
14 namespace System.IO.Compression {
16 public class DeflateStream : Stream {
18 internal const int DefaultBufferSize = 8192;
20 internal delegate void AsyncWriteDelegate(byte[] array, int offset, int count, bool isAsync);
22 //private const String OrigStackTrace_ExceptionDataKey = "ORIGINAL_STACK_TRACE";
24 private Stream _stream;
25 private CompressionMode _mode;
26 private bool _leaveOpen;
27 private Inflater inflater;
28 private IDeflater deflater;
29 private byte[] buffer;
31 private int asyncOperations;
32 private readonly AsyncCallback m_CallBack;
33 private readonly AsyncWriteDelegate m_AsyncWriterDelegate;
35 private IFileFormatWriter formatWriter;
36 private bool wroteHeader;
37 private bool wroteBytes;
39 private enum WorkerType : byte { Managed, ZLib, Unknown };
40 private static volatile WorkerType deflaterType = WorkerType.Unknown;
43 public DeflateStream(Stream stream, CompressionMode mode)
44 : this(stream, mode, false) {
47 public DeflateStream(Stream stream, CompressionMode mode, bool leaveOpen) {
50 throw new ArgumentNullException("stream");
52 if (CompressionMode.Compress != mode && CompressionMode.Decompress != mode)
53 throw new ArgumentException(SR.GetString(SR.ArgumentOutOfRange_Enum), "mode");
57 _leaveOpen = leaveOpen;
61 case CompressionMode.Decompress:
63 if (!_stream.CanRead) {
64 throw new ArgumentException(SR.GetString(SR.NotReadableStream), "stream");
67 inflater = new Inflater();
69 m_CallBack = new AsyncCallback(ReadCallback);
72 case CompressionMode.Compress:
74 if (!_stream.CanWrite) {
75 throw new ArgumentException(SR.GetString(SR.NotWriteableStream), "stream");
78 deflater = CreateDeflater(null);
80 m_AsyncWriterDelegate = new AsyncWriteDelegate(this.InternalWrite);
81 m_CallBack = new AsyncCallback(WriteCallback);
87 buffer = new byte[DefaultBufferSize];
90 // Implies mode = Compress
91 public DeflateStream(Stream stream, CompressionLevel compressionLevel)
93 : this(stream, compressionLevel, false) {
96 // Implies mode = Compress
97 public DeflateStream(Stream stream, CompressionLevel compressionLevel, bool leaveOpen) {
100 throw new ArgumentNullException("stream");
102 if (!stream.CanWrite)
103 throw new ArgumentException(SR.GetString(SR.NotWriteableStream), "stream");
105 // Checking of compressionLevel is passed down to the IDeflater implementation as it
106 // is a pluggable component that completely encapsulates the meaning of compressionLevel.
108 Contract.EndContractBlock();
111 _mode = CompressionMode.Compress;
112 _leaveOpen = leaveOpen;
114 deflater = CreateDeflater(compressionLevel);
116 m_AsyncWriterDelegate = new AsyncWriteDelegate(this.InternalWrite);
117 m_CallBack = new AsyncCallback(WriteCallback);
119 buffer = new byte[DefaultBufferSize];
122 private static IDeflater CreateDeflater(CompressionLevel? compressionLevel) {
124 switch (GetDeflaterType()) {
126 case WorkerType.Managed:
127 return new DeflaterManaged();
129 case WorkerType.ZLib:
130 if (compressionLevel.HasValue)
131 return new DeflaterZLib(compressionLevel.Value);
133 return new DeflaterZLib();
136 // We do not expect this to ever be thrown.
137 // But this is better practice than returning null.
138 throw new SystemException("Program entered an unexpected state.");
143 [System.Security.SecuritySafeCritical]
145 private static WorkerType GetDeflaterType() {
147 // Let's not worry about race conditions:
148 // Yes, we risk initialising the singleton multiple times.
149 // However, initialising the singleton multiple times has no bad consequences, and is fairly cheap.
151 if (WorkerType.Unknown != deflaterType)
154 #if !SILVERLIGHT // Do this on Desktop. CLRConfig doesn't exist on CoreSys nor Silverlight.
156 // CLRConfig is internal in mscorlib and is a friend
157 if (System.CLRConfig.CheckLegacyManagedDeflateStream())
158 return (deflaterType = WorkerType.Managed);
162 #if !SILVERLIGHT || FEATURE_NETCORE // Only skip this for Silverlight, which doesn't ship ZLib.
164 if (!CompatibilitySwitches.IsNetFx45LegacyManagedDeflateStream)
165 return (deflaterType = WorkerType.ZLib);
169 return (deflaterType = WorkerType.Managed);
172 internal void SetFileFormatReader(IFileFormatReader reader) {
173 if (reader != null) {
174 inflater.SetFileFormatReader(reader);
178 internal void SetFileFormatWriter(IFileFormatWriter writer) {
179 if (writer != null) {
180 formatWriter = writer;
184 public Stream BaseStream {
190 public override bool CanRead {
192 if( _stream == null) {
196 return (_mode == CompressionMode.Decompress && _stream.CanRead);
200 public override bool CanWrite {
202 if( _stream == null) {
206 return (_mode == CompressionMode.Compress && _stream.CanWrite);
210 public override bool CanSeek {
216 public override long Length {
218 throw new NotSupportedException(SR.GetString(SR.NotSupported));
222 public override long Position {
224 throw new NotSupportedException(SR.GetString(SR.NotSupported));
228 throw new NotSupportedException(SR.GetString(SR.NotSupported));
232 public override void Flush() {
237 public override long Seek(long offset, SeekOrigin origin) {
238 throw new NotSupportedException(SR.GetString(SR.NotSupported));
241 public override void SetLength(long value) {
242 throw new NotSupportedException(SR.GetString(SR.NotSupported));
245 public override int Read(byte[] array, int offset, int count) {
247 EnsureDecompressionMode();
248 ValidateParameters(array, offset, count);
252 int currentOffset = offset;
253 int remainingCount = count;
256 bytesRead = inflater.Inflate(array, currentOffset, remainingCount);
257 currentOffset += bytesRead;
258 remainingCount -= bytesRead;
260 if( remainingCount == 0) {
264 if (inflater.Finished() ) {
265 // if we finished decompressing, we can't have anything left in the outputwindow.
266 Debug.Assert(inflater.AvailableOutput == 0, "We should have copied all stuff out!");
270 Debug.Assert(inflater.NeedsInput(), "We can only run into this case if we are short of input");
272 int bytes = _stream.Read(buffer, 0, buffer.Length);
274 break; //Do we want to throw an exception here?
277 inflater.SetInput(buffer, 0 , bytes);
280 return count - remainingCount;
283 private void ValidateParameters(byte[] array, int offset, int count) {
286 throw new ArgumentNullException("array");
289 throw new ArgumentOutOfRangeException("offset");
292 throw new ArgumentOutOfRangeException("count");
294 if (array.Length - offset < count)
295 throw new ArgumentException(SR.GetString(SR.InvalidArgumentOffsetCount));
298 private void EnsureNotDisposed() {
301 throw new ObjectDisposedException(null, SR.GetString(SR.ObjectDisposed_StreamClosed));
304 private void EnsureDecompressionMode() {
306 if( _mode != CompressionMode.Decompress)
307 throw new InvalidOperationException(SR.GetString(SR.CannotReadFromDeflateStream));
310 private void EnsureCompressionMode() {
312 if( _mode != CompressionMode.Compress)
313 throw new InvalidOperationException(SR.GetString(SR.CannotWriteToDeflateStream));
317 [HostProtection(ExternalThreading=true)]
319 public override IAsyncResult BeginRead(byte[] array, int offset, int count, AsyncCallback asyncCallback, object asyncState) {
321 EnsureDecompressionMode();
323 // We use this checking order for compat to earlier versions:
324 if (asyncOperations != 0)
325 throw new InvalidOperationException(SR.GetString(SR.InvalidBeginCall));
327 ValidateParameters(array, offset, count);
330 Interlocked.Increment(ref asyncOperations);
334 DeflateStreamAsyncResult userResult = new DeflateStreamAsyncResult(
335 this, asyncState, asyncCallback, array, offset, count);
336 userResult.isWrite = false;
338 // Try to read decompressed data in output buffer
339 int bytesRead = inflater.Inflate(array, offset, count);
340 if( bytesRead != 0) {
341 // If decompression output buffer is not empty, return immediately.
342 // 'true' means we complete synchronously.
343 userResult.InvokeCallback(true, (object) bytesRead);
347 if (inflater.Finished() ) {
348 // end of compression stream
349 userResult.InvokeCallback(true, (object) 0);
353 // If there is no data on the output buffer and we are not at
354 // the end of the stream, we need to get more data from the base stream
355 _stream.BeginRead(buffer, 0, buffer.Length, m_CallBack, userResult);
356 userResult.m_CompletedSynchronously &= userResult.IsCompleted;
361 Interlocked.Decrement( ref asyncOperations);
366 // callback function for asynchrous reading on base stream
367 private void ReadCallback(IAsyncResult baseStreamResult) {
369 DeflateStreamAsyncResult outerResult = (DeflateStreamAsyncResult) baseStreamResult.AsyncState;
370 outerResult.m_CompletedSynchronously &= baseStreamResult.CompletedSynchronously;
377 bytesRead = _stream.EndRead(baseStreamResult);
379 if (bytesRead <= 0 ) {
380 // This indicates the base stream has received EOF
381 outerResult.InvokeCallback((object) 0);
385 // Feed the data from base stream into decompression engine
386 inflater.SetInput(buffer, 0 , bytesRead);
387 bytesRead = inflater.Inflate(outerResult.buffer, outerResult.offset, outerResult.count);
389 if (bytesRead == 0 && !inflater.Finished()) {
391 // We could have read in head information and didn't get any data.
392 // Read from the base stream again.
393 // Need to solve recusion.
394 _stream.BeginRead(buffer, 0, buffer.Length, m_CallBack, outerResult);
397 outerResult.InvokeCallback((object) bytesRead);
399 } catch (Exception exc) {
400 // Defer throwing this until EndRead where we will likely have user code on the stack.
401 outerResult.InvokeCallback(exc);
406 public override int EndRead(IAsyncResult asyncResult) {
408 EnsureDecompressionMode();
409 CheckEndXxxxLegalStateAndParams(asyncResult);
411 // We checked that this will work in CheckEndXxxxLegalStateAndParams:
412 DeflateStreamAsyncResult deflateStrmAsyncResult = (DeflateStreamAsyncResult) asyncResult;
414 AwaitAsyncResultCompletion(deflateStrmAsyncResult);
416 Exception previousException = deflateStrmAsyncResult.Result as Exception;
417 if (previousException != null) {
418 // Rethrowing will delete the stack trace. Let's help future debuggers:
419 //previousException.Data.Add(OrigStackTrace_ExceptionDataKey, previousException.StackTrace);
420 throw previousException;
423 return (int) deflateStrmAsyncResult.Result;
426 public override void Write(byte[] array, int offset, int count) {
427 EnsureCompressionMode();
428 ValidateParameters(array, offset, count);
430 InternalWrite(array, offset, count, false);
433 // isAsync always seems to be false. why do we have it?
434 internal void InternalWrite(byte[] array, int offset, int count, bool isAsync) {
436 DoMaintenance(array, offset, count);
438 // Write compressed the bytes we already passed to the deflater:
440 WriteDeflaterOutput(isAsync);
442 // Pass new bytes through deflater and write them too:
444 deflater.SetInput(array, offset, count);
445 WriteDeflaterOutput(isAsync);
449 private void WriteDeflaterOutput(bool isAsync) {
451 while (!deflater.NeedsInput()) {
453 int compressedBytes = deflater.GetDeflateOutput(buffer);
454 if (compressedBytes > 0)
455 DoWrite(buffer, 0, compressedBytes, isAsync);
459 private void DoWrite(byte[] array, int offset, int count, bool isAsync) {
460 Debug.Assert(array != null);
461 Debug.Assert(count != 0);
464 IAsyncResult result = _stream.BeginWrite(array, offset, count, null, null);
465 _stream.EndWrite(result);
468 _stream.Write(array, offset, count);
472 // Perform deflate-mode maintenance required due to custom header and footer writers
473 // (e.g. set by GZipStream):
474 private void DoMaintenance(byte[] array, int offset, int count) {
476 // If no bytes written, do nothing:
480 // Note that stream contains more than zero data bytes:
483 // If no header/footer formatter present, nothing else to do:
484 if (formatWriter == null)
487 // If formatter has not yet written a header, do it now:
489 byte[] b = formatWriter.GetHeader();
490 _stream.Write(b, 0, b.Length);
494 // Inform formatter of the data bytes written:
495 formatWriter.UpdateWithBytesRead(array, offset, count);
498 // This is called by Dispose:
499 private void PurgeBuffers(bool disposing) {
509 if (_mode != CompressionMode.Compress)
512 // Some deflaters (e.g. ZLib write more than zero bytes for zero bytes inpuits.
513 // This round-trips and we should be ok with this, but our legacy managed deflater
514 // always wrote zero output for zero input and upstack code (e.g. GZipStream)
515 // took dependencies on it. Thus, make sure to only "flush" when we actually had
519 // Compress any bytes left:
520 WriteDeflaterOutput(false);
522 // Pull out any bytes left inside deflater:
526 finished = deflater.Finish(buffer, out compressedBytes);
528 if (compressedBytes > 0)
529 DoWrite(buffer, 0, compressedBytes, false);
535 // Write format footer:
536 if (formatWriter != null && wroteHeader) {
537 byte[] b = formatWriter.GetFooter();
538 _stream.Write(b, 0, b.Length);
542 protected override void Dispose(bool disposing) {
546 PurgeBuffers(disposing);
550 // Close the underlying stream even if PurgeBuffers threw.
551 // Stream.Close() may throw here (may or may not be due to the same error).
552 // In this case, we still need to clean up internal resources, hence the inner finally blocks.
555 if(disposing && !_leaveOpen && _stream != null)
564 if (deflater != null)
570 base.Dispose(disposing);
578 [HostProtection(ExternalThreading=true)]
580 public override IAsyncResult BeginWrite(byte[] array, int offset, int count, AsyncCallback asyncCallback, object asyncState) {
582 EnsureCompressionMode();
584 // We use this checking order for compat to earlier versions:
585 if (asyncOperations != 0 )
586 throw new InvalidOperationException(SR.GetString(SR.InvalidBeginCall));
588 ValidateParameters(array, offset, count);
591 Interlocked.Increment(ref asyncOperations);
595 DeflateStreamAsyncResult userResult = new DeflateStreamAsyncResult(
596 this, asyncState, asyncCallback, array, offset, count);
597 userResult.isWrite = true;
599 m_AsyncWriterDelegate.BeginInvoke(array, offset, count, true, m_CallBack, userResult);
600 userResult.m_CompletedSynchronously &= userResult.IsCompleted;
605 Interlocked.Decrement(ref asyncOperations);
610 // Callback function for asynchrous reading on base stream
611 private void WriteCallback(IAsyncResult asyncResult) {
613 DeflateStreamAsyncResult outerResult = (DeflateStreamAsyncResult) asyncResult.AsyncState;
614 outerResult.m_CompletedSynchronously &= asyncResult.CompletedSynchronously;
618 m_AsyncWriterDelegate.EndInvoke(asyncResult);
620 } catch (Exception exc) {
621 // Defer throwing this until EndWrite where there is user code on the stack:
622 outerResult.InvokeCallback(exc);
625 outerResult.InvokeCallback(null);
628 public override void EndWrite(IAsyncResult asyncResult) {
630 EnsureCompressionMode();
631 CheckEndXxxxLegalStateAndParams(asyncResult);
633 // We checked that this will work in CheckEndXxxxLegalStateAndParams:
634 DeflateStreamAsyncResult deflateStrmAsyncResult = (DeflateStreamAsyncResult) asyncResult;
636 AwaitAsyncResultCompletion(deflateStrmAsyncResult);
638 Exception previousException = deflateStrmAsyncResult.Result as Exception;
639 if (previousException != null) {
640 // Rethrowing will delete the stack trace. Let's help future debuggers:
641 //previousException.Data.Add(OrigStackTrace_ExceptionDataKey, previousException.StackTrace);
642 throw previousException;
646 private void CheckEndXxxxLegalStateAndParams(IAsyncResult asyncResult) {
648 if (asyncOperations != 1)
649 throw new InvalidOperationException(SR.GetString(SR.InvalidEndCall));
651 if (asyncResult == null)
652 throw new ArgumentNullException("asyncResult");
656 DeflateStreamAsyncResult myResult = asyncResult as DeflateStreamAsyncResult;
658 // This should really be an ArgumentException, but we keep this for compat to previous versions:
659 if (myResult == null)
660 throw new ArgumentNullException("asyncResult");
663 private void AwaitAsyncResultCompletion(DeflateStreamAsyncResult asyncResult) {
667 if (!asyncResult.IsCompleted)
668 asyncResult.AsyncWaitHandle.WaitOne();
672 Interlocked.Decrement(ref asyncOperations);
673 asyncResult.Close(); // this will just close the wait handle
677 } // public class DeflateStream
679 } // namespace System.IO.Compression