Initial commit
[mono.git] / mcs / class / referencesource / System / sys / system / IO / compression / DeflateStream.cs
1 ///----------- ----------- ----------- ----------- ----------- ----------- -----------
2 /// <copyright file="DeflateStream.cs" company="Microsoft">
3 ///     Copyright (c) Microsoft Corporation.  All rights reserved.
4 /// </copyright>                               
5 ///
6 ///----------- ----------- ----------- ----------- ----------- ----------- -----------
7 ///
8
9 using System.Diagnostics;
10 using System.Threading;
11 using System.Security.Permissions;
12 using System.Diagnostics.Contracts;
13
14 namespace System.IO.Compression {
15    
16     public class DeflateStream : Stream {
17
18         internal const int DefaultBufferSize = 8192;
19
20         internal delegate void AsyncWriteDelegate(byte[] array, int offset, int count, bool isAsync);
21
22         //private const String OrigStackTrace_ExceptionDataKey = "ORIGINAL_STACK_TRACE";
23
24         private Stream _stream;
25         private CompressionMode _mode;
26         private bool _leaveOpen;
27         private Inflater inflater;
28         private IDeflater deflater;        
29         private byte[] buffer;
30         
31         private int asyncOperations; 
32         private readonly AsyncCallback m_CallBack;
33         private readonly AsyncWriteDelegate m_AsyncWriterDelegate;
34
35         private IFileFormatWriter formatWriter;
36         private bool wroteHeader;
37         private bool wroteBytes;
38
39         private enum WorkerType : byte { Managed, ZLib, Unknown };
40         private static volatile WorkerType deflaterType = WorkerType.Unknown;
41
42
43         public DeflateStream(Stream stream, CompressionMode mode)
44             : this(stream, mode, false) {
45         }              
46         
47         public DeflateStream(Stream stream, CompressionMode mode, bool leaveOpen) {
48            
49             if(stream == null )
50                 throw new ArgumentNullException("stream");
51
52             if (CompressionMode.Compress != mode && CompressionMode.Decompress != mode)
53                 throw new ArgumentException(SR.GetString(SR.ArgumentOutOfRange_Enum), "mode");
54
55             _stream = stream;
56             _mode = mode;
57             _leaveOpen = leaveOpen;
58
59             switch (_mode) {
60
61                 case CompressionMode.Decompress:
62
63                     if (!_stream.CanRead) {
64                         throw new ArgumentException(SR.GetString(SR.NotReadableStream), "stream");
65                     }
66
67                     inflater = new Inflater();
68
69                     m_CallBack = new AsyncCallback(ReadCallback); 
70                     break;
71
72                 case CompressionMode.Compress:
73
74                     if (!_stream.CanWrite) {
75                         throw new ArgumentException(SR.GetString(SR.NotWriteableStream), "stream");
76                     }
77
78                     deflater = CreateDeflater(null);
79                     
80                     m_AsyncWriterDelegate = new AsyncWriteDelegate(this.InternalWrite);
81                     m_CallBack = new AsyncCallback(WriteCallback); 
82
83                     break;                        
84
85             }  // switch (_mode)
86
87             buffer = new byte[DefaultBufferSize];
88         }
89
90         // Implies mode = Compress
91         public DeflateStream(Stream stream, CompressionLevel compressionLevel)
92
93             : this(stream, compressionLevel, false) {
94         }
95
96         // Implies mode = Compress
97         public DeflateStream(Stream stream, CompressionLevel compressionLevel, bool leaveOpen) {
98
99             if (stream == null)
100                 throw new ArgumentNullException("stream");
101
102             if (!stream.CanWrite)
103                 throw new ArgumentException(SR.GetString(SR.NotWriteableStream), "stream");
104
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.
107             
108             Contract.EndContractBlock();
109
110             _stream = stream;
111             _mode = CompressionMode.Compress;
112             _leaveOpen = leaveOpen;
113
114             deflater = CreateDeflater(compressionLevel);
115
116             m_AsyncWriterDelegate = new AsyncWriteDelegate(this.InternalWrite);
117             m_CallBack = new AsyncCallback(WriteCallback);
118            
119             buffer = new byte[DefaultBufferSize];
120         }        
121
122         private static IDeflater CreateDeflater(CompressionLevel? compressionLevel) {
123
124             switch (GetDeflaterType()) {
125
126                 case WorkerType.Managed:
127                     return new DeflaterManaged();
128
129                 case WorkerType.ZLib:
130                     if (compressionLevel.HasValue)
131                         return new DeflaterZLib(compressionLevel.Value);
132                     else
133                         return new DeflaterZLib();
134
135                 default:
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.");
139             }
140         }
141
142         #if !SILVERLIGHT
143         [System.Security.SecuritySafeCritical]
144         #endif
145         private static WorkerType GetDeflaterType() {
146
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.
150
151             if (WorkerType.Unknown != deflaterType)
152                 return deflaterType;
153                         
154             #if !SILVERLIGHT  // Do this on Desktop.  CLRConfig doesn't exist on CoreSys nor Silverlight.
155
156             // CLRConfig is internal in mscorlib and is a friend
157             if (System.CLRConfig.CheckLegacyManagedDeflateStream())
158                 return (deflaterType = WorkerType.Managed);
159
160             #endif
161
162             #if !SILVERLIGHT || FEATURE_NETCORE  // Only skip this for Silverlight, which doesn't ship ZLib.
163
164             if (!CompatibilitySwitches.IsNetFx45LegacyManagedDeflateStream)
165                 return (deflaterType = WorkerType.ZLib);
166
167             #endif
168
169             return (deflaterType = WorkerType.Managed);
170         }
171
172         internal void SetFileFormatReader(IFileFormatReader reader) {
173             if (reader != null) {
174                 inflater.SetFileFormatReader(reader);
175             }
176         }
177
178         internal void SetFileFormatWriter(IFileFormatWriter writer) {
179             if (writer != null) {
180                 formatWriter = writer;
181             }
182         }
183
184         public Stream BaseStream {
185             get {
186                 return _stream;
187             }
188         }
189
190         public override bool CanRead { 
191             get {
192                 if( _stream == null) {
193                     return false;      
194                 }
195
196                 return (_mode == CompressionMode.Decompress && _stream.CanRead);
197             }
198         }
199
200         public override bool CanWrite { 
201             get {
202                 if( _stream == null) {
203                     return false;                
204                 }
205                 
206                 return (_mode == CompressionMode.Compress && _stream.CanWrite);
207             }
208         }
209
210         public override bool CanSeek { 
211             get {                
212                 return false;
213             }
214         }
215
216         public override long Length { 
217             get {
218                 throw new NotSupportedException(SR.GetString(SR.NotSupported));            
219             }
220         }
221
222         public override long Position { 
223             get {
224                 throw new NotSupportedException(SR.GetString(SR.NotSupported));            
225             } 
226             
227             set {
228                 throw new NotSupportedException(SR.GetString(SR.NotSupported));            
229             }
230         }
231         
232         public override void Flush() {            
233             EnsureNotDisposed();
234             return;
235         }
236
237         public override long Seek(long offset, SeekOrigin origin) {
238             throw new NotSupportedException(SR.GetString(SR.NotSupported));            
239         }
240
241         public override void SetLength(long value) {
242             throw new NotSupportedException(SR.GetString(SR.NotSupported));            
243         }
244
245         public override int Read(byte[] array, int offset, int count) {
246
247             EnsureDecompressionMode();
248             ValidateParameters(array, offset, count);
249             EnsureNotDisposed();
250             
251             int bytesRead;
252             int currentOffset = offset;
253             int remainingCount = count;
254
255             while(true) {
256                 bytesRead = inflater.Inflate(array, currentOffset, remainingCount); 
257                 currentOffset += bytesRead;
258                 remainingCount -= bytesRead;
259
260                 if( remainingCount == 0) {
261                     break;
262                 }
263                
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!");
267                     break;                    
268                 }                
269
270                 Debug.Assert(inflater.NeedsInput(), "We can only run into this case if we are short of input");
271
272                 int bytes = _stream.Read(buffer, 0, buffer.Length);
273                 if( bytes == 0) {
274                     break;      //Do we want to throw an exception here?
275                 }
276
277                 inflater.SetInput(buffer, 0 , bytes);
278             }
279
280             return count - remainingCount;
281         }        
282
283         private void ValidateParameters(byte[] array, int offset, int count) {
284
285             if (array==null)
286                 throw new ArgumentNullException("array");           
287             
288             if (offset < 0)
289                 throw new ArgumentOutOfRangeException("offset");          
290
291             if (count < 0)
292                 throw new ArgumentOutOfRangeException("count");           
293
294             if (array.Length - offset < count)
295                 throw new ArgumentException(SR.GetString(SR.InvalidArgumentOffsetCount));           
296         }
297
298         private void EnsureNotDisposed() {
299
300             if (_stream == null)
301                 throw new ObjectDisposedException(null, SR.GetString(SR.ObjectDisposed_StreamClosed));           
302         }
303
304         private void EnsureDecompressionMode() {
305
306             if( _mode != CompressionMode.Decompress)
307                 throw new InvalidOperationException(SR.GetString(SR.CannotReadFromDeflateStream));           
308         }
309
310         private void EnsureCompressionMode() {
311
312             if( _mode != CompressionMode.Compress)
313                 throw new InvalidOperationException(SR.GetString(SR.CannotWriteToDeflateStream));           
314         }
315
316 #if !FEATURE_NETCORE
317         [HostProtection(ExternalThreading=true)]
318 #endif
319         public override IAsyncResult BeginRead(byte[] array, int offset, int count, AsyncCallback asyncCallback, object asyncState) {
320             
321             EnsureDecompressionMode();
322
323             // We use this checking order for compat to earlier versions:
324             if (asyncOperations != 0)
325                 throw new InvalidOperationException(SR.GetString(SR.InvalidBeginCall));
326
327             ValidateParameters(array, offset, count);
328             EnsureNotDisposed();                       
329
330             Interlocked.Increment(ref asyncOperations);
331
332             try {                
333
334                 DeflateStreamAsyncResult userResult = new DeflateStreamAsyncResult(
335                                                             this, asyncState, asyncCallback, array, offset, count);
336                 userResult.isWrite = false;
337
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);                      
344                     return userResult;
345                 }
346
347                 if (inflater.Finished() ) {  
348                     // end of compression stream
349                     userResult.InvokeCallback(true, (object) 0);  
350                     return userResult;
351                 }
352                     
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;            
357                 
358                 return userResult;
359
360             } catch {
361                 Interlocked.Decrement( ref asyncOperations);
362                 throw;
363             }
364         }
365
366         // callback function for asynchrous reading on base stream
367         private void ReadCallback(IAsyncResult baseStreamResult) {
368
369             DeflateStreamAsyncResult outerResult = (DeflateStreamAsyncResult) baseStreamResult.AsyncState;
370             outerResult.m_CompletedSynchronously &= baseStreamResult.CompletedSynchronously;
371             int bytesRead = 0;
372
373             try {
374
375                 EnsureNotDisposed();
376
377                 bytesRead = _stream.EndRead(baseStreamResult);
378
379                 if (bytesRead <= 0 ) {
380                     // This indicates the base stream has received EOF
381                     outerResult.InvokeCallback((object) 0);  
382                     return;
383                 }
384     
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); 
388
389                 if (bytesRead == 0 && !inflater.Finished()) {  
390
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);                   
395
396                 } else {
397                     outerResult.InvokeCallback((object) bytesRead);              
398                 }
399             } catch (Exception exc) {
400                 // Defer throwing this until EndRead where we will likely have user code on the stack.
401                 outerResult.InvokeCallback(exc); 
402                 return;
403             }
404         }
405
406         public override int EndRead(IAsyncResult asyncResult) {
407
408             EnsureDecompressionMode();
409             CheckEndXxxxLegalStateAndParams(asyncResult);
410
411             // We checked that this will work in CheckEndXxxxLegalStateAndParams:
412             DeflateStreamAsyncResult deflateStrmAsyncResult = (DeflateStreamAsyncResult) asyncResult;
413
414             AwaitAsyncResultCompletion(deflateStrmAsyncResult);
415
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;
421             }
422
423             return (int) deflateStrmAsyncResult.Result;
424         }
425
426         public override void Write(byte[] array, int offset, int count) {
427             EnsureCompressionMode();
428             ValidateParameters(array, offset, count);
429             EnsureNotDisposed();
430             InternalWrite(array, offset, count, false);
431         }
432
433         // isAsync always seems to be false. why do we have it?
434         internal void InternalWrite(byte[] array, int offset, int count, bool isAsync) {
435
436             DoMaintenance(array, offset, count);            
437
438             // Write compressed the bytes we already passed to the deflater:
439
440             WriteDeflaterOutput(isAsync);
441
442             // Pass new bytes through deflater and write them too:
443
444             deflater.SetInput(array, offset, count);
445             WriteDeflaterOutput(isAsync);
446         }
447
448
449         private void WriteDeflaterOutput(bool isAsync) {
450
451             while (!deflater.NeedsInput()) {
452
453                 int compressedBytes = deflater.GetDeflateOutput(buffer);
454                 if (compressedBytes > 0)
455                     DoWrite(buffer, 0, compressedBytes, isAsync);
456             }
457         }
458
459         private void DoWrite(byte[] array, int offset, int count, bool isAsync) {
460             Debug.Assert(array != null);
461             Debug.Assert(count != 0);
462
463             if (isAsync) {
464                 IAsyncResult result = _stream.BeginWrite(array, offset, count, null, null);
465                 _stream.EndWrite(result);
466
467             } else {
468                 _stream.Write(array, offset, count);
469             }
470         }
471
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) {
475
476             // If no bytes written, do nothing:
477             if (count <= 0)
478                 return;
479
480             // Note that stream contains more than zero data bytes:
481             wroteBytes = true;
482
483             // If no header/footer formatter present, nothing else to do:
484             if (formatWriter == null)
485                 return;            
486
487             // If formatter has not yet written a header, do it now:
488             if (!wroteHeader) {
489                 byte[] b = formatWriter.GetHeader();
490                 _stream.Write(b, 0, b.Length);
491                 wroteHeader = true;
492             }
493             
494             // Inform formatter of the data bytes written:
495             formatWriter.UpdateWithBytesRead(array, offset, count);            
496         }
497
498         // This is called by Dispose:
499         private void PurgeBuffers(bool disposing) {
500
501             if (!disposing)
502                 return;
503
504             if (_stream == null)
505                 return;
506
507             Flush();
508
509             if (_mode != CompressionMode.Compress)
510                 return;
511
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
516             // some input:
517             if (wroteBytes) {
518
519                 // Compress any bytes left:                        
520                 WriteDeflaterOutput(false);
521
522                 // Pull out any bytes left inside deflater:
523                 bool finished;
524                 do {
525                     int compressedBytes;
526                     finished = deflater.Finish(buffer, out compressedBytes);
527
528                     if (compressedBytes > 0)
529                         DoWrite(buffer, 0, compressedBytes, false);
530
531                 } while (!finished);
532
533             }
534
535             // Write format footer:
536             if (formatWriter != null && wroteHeader) {
537                 byte[] b = formatWriter.GetFooter();
538                 _stream.Write(b, 0, b.Length);
539             }            
540         }
541
542         protected override void Dispose(bool disposing) {
543
544             try {
545
546                 PurgeBuffers(disposing);
547
548             } finally {
549
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.
553                 try {                    
554
555                     if(disposing && !_leaveOpen && _stream != null) 
556                         _stream.Close();                    
557
558                 } finally {
559
560                     _stream = null;
561
562                     try {
563
564                         if (deflater != null)
565                             deflater.Dispose();
566
567                     } finally {
568
569                         deflater = null;
570                         base.Dispose(disposing);
571                     }
572                 }  // finally
573             }  // finally
574         }  // Dispose
575
576
577 #if !FEATURE_NETCORE
578         [HostProtection(ExternalThreading=true)]
579 #endif
580         public override IAsyncResult BeginWrite(byte[] array, int offset, int count, AsyncCallback asyncCallback, object asyncState) {
581
582             EnsureCompressionMode();            
583
584             // We use this checking order for compat to earlier versions:
585             if (asyncOperations != 0 )
586                 throw new InvalidOperationException(SR.GetString(SR.InvalidBeginCall));
587
588             ValidateParameters(array, offset, count);
589             EnsureNotDisposed();
590            
591             Interlocked.Increment(ref asyncOperations);
592
593             try {                
594
595                 DeflateStreamAsyncResult userResult = new DeflateStreamAsyncResult(
596                                                             this, asyncState, asyncCallback, array, offset, count);
597                 userResult.isWrite = true;
598
599                 m_AsyncWriterDelegate.BeginInvoke(array, offset, count, true, m_CallBack, userResult);
600                 userResult.m_CompletedSynchronously &= userResult.IsCompleted;            
601                                 
602                 return userResult;
603
604             } catch {
605                 Interlocked.Decrement(ref asyncOperations);
606                 throw;
607             }
608         }
609
610         // Callback function for asynchrous reading on base stream
611         private void WriteCallback(IAsyncResult asyncResult) {
612
613             DeflateStreamAsyncResult outerResult = (DeflateStreamAsyncResult) asyncResult.AsyncState;
614             outerResult.m_CompletedSynchronously &= asyncResult.CompletedSynchronously;            
615
616             try {
617
618                 m_AsyncWriterDelegate.EndInvoke(asyncResult);
619
620             } catch (Exception exc) {
621                 // Defer throwing this until EndWrite where there is user code on the stack:
622                 outerResult.InvokeCallback(exc); 
623                 return;
624             }
625             outerResult.InvokeCallback(null);              
626         }
627
628         public override void EndWrite(IAsyncResult asyncResult) {
629
630             EnsureCompressionMode();
631             CheckEndXxxxLegalStateAndParams(asyncResult);
632
633             // We checked that this will work in CheckEndXxxxLegalStateAndParams:
634             DeflateStreamAsyncResult deflateStrmAsyncResult = (DeflateStreamAsyncResult) asyncResult;
635
636             AwaitAsyncResultCompletion(deflateStrmAsyncResult);
637
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;
643             }
644         }
645
646         private void CheckEndXxxxLegalStateAndParams(IAsyncResult asyncResult) {
647
648             if (asyncOperations != 1)
649                 throw new InvalidOperationException(SR.GetString(SR.InvalidEndCall));
650
651             if (asyncResult == null)
652                 throw new ArgumentNullException("asyncResult");
653
654             EnsureNotDisposed();
655             
656             DeflateStreamAsyncResult myResult = asyncResult as DeflateStreamAsyncResult;
657
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");
661         }
662
663         private void AwaitAsyncResultCompletion(DeflateStreamAsyncResult asyncResult) {
664
665             try {
666
667                 if (!asyncResult.IsCompleted)
668                     asyncResult.AsyncWaitHandle.WaitOne();
669
670             } finally {
671
672                 Interlocked.Decrement(ref asyncOperations);
673                 asyncResult.Close();  // this will just close the wait handle
674             }
675         }
676
677     }  // public class DeflateStream
678
679 }  // namespace System.IO.Compression
680