Merge pull request #960 from ermshiperete/ShowHelp
[mono.git] / mcs / class / System / System.IO.Compression / DeflateStream.cs
1 /* -*- Mode: csharp; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 // 
3 // DeflateStream.cs
4 //
5 // Authors:
6 //      Christopher James Lahey <clahey@ximian.com>
7 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
8 //  Marek Safar (marek.safar@gmail.com)
9 //
10 // (c) Copyright 2004,2009 Novell, Inc. <http://www.novell.com>
11 // Copyright (C) 2013 Xamarin Inc (http://www.xamarin.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.IO;
35 using System.Runtime.InteropServices;
36 using System.Runtime.Remoting.Messaging;
37
38 #if MONOTOUCH
39 using MonoTouch;
40 #endif
41
42 namespace System.IO.Compression
43 {
44         public class DeflateStream : Stream
45         {
46                 delegate int ReadMethod (byte[] array, int offset, int count);
47                 delegate void WriteMethod (byte[] array, int offset, int count);
48
49                 Stream base_stream;
50                 CompressionMode mode;
51                 bool leaveOpen;
52                 bool disposed;
53                 DeflateStreamNative native;
54
55                 public DeflateStream (Stream compressedStream, CompressionMode mode) :
56                         this (compressedStream, mode, false, false)
57                 {
58                 }
59
60                 public DeflateStream (Stream compressedStream, CompressionMode mode, bool leaveOpen) :
61                         this (compressedStream, mode, leaveOpen, false)
62                 {
63                 }
64
65                 internal DeflateStream (Stream compressedStream, CompressionMode mode, bool leaveOpen, bool gzip)
66                 {
67                         if (compressedStream == null)
68                                 throw new ArgumentNullException ("compressedStream");
69
70                         if (mode != CompressionMode.Compress && mode != CompressionMode.Decompress)
71                                 throw new ArgumentException ("mode");
72
73                         this.base_stream = compressedStream;
74
75                         this.native = DeflateStreamNative.Create (compressedStream, mode, gzip);
76                         if (this.native == null) {
77                                 throw new NotImplementedException ("Failed to initialize zlib. You probably have an old zlib installed. Version 1.2.0.4 or later is required.");
78                         }
79                         this.mode = mode;
80                         this.leaveOpen = leaveOpen;
81                 }
82                 
83 #if NET_4_5
84                 public DeflateStream (Stream stream, CompressionLevel compressionLevel)
85                         : this (stream, compressionLevel, false, false)
86                 {
87                 }
88                 
89                 public DeflateStream (Stream stream, CompressionLevel compressionLevel, bool leaveOpen)
90                         : this (stream, compressionLevel, leaveOpen, false)
91                 {
92                 }
93
94                 internal DeflateStream (Stream stream, CompressionLevel compressionLevel, bool leaveOpen, bool gzip)
95                         : this (stream, CompressionMode.Compress, leaveOpen, gzip)
96                 {
97                 }               
98 #endif
99
100                 protected override void Dispose (bool disposing)
101                 {
102                         native.Dispose (disposing);
103
104                         if (disposing && !disposed) {
105                                 disposed = true;
106
107                                 if (!leaveOpen) {
108                                         Stream st = base_stream;
109                                         if (st != null)
110                                                 st.Close ();
111                                         base_stream = null;
112                                 }
113                         }
114
115                         base.Dispose (disposing);
116                 }
117
118                 unsafe int ReadInternal (byte[] array, int offset, int count)
119                 {
120                         if (count == 0)
121                                 return 0;
122
123                         fixed (byte *b = array) {
124                                 IntPtr ptr = new IntPtr (b + offset);
125                                 return native.ReadZStream (ptr, count);
126                         }
127                 }
128
129                 public override int Read (byte[] dest, int dest_offset, int count)
130                 {
131                         if (disposed)
132                                 throw new ObjectDisposedException (GetType ().FullName);
133                         if (dest == null)
134                                 throw new ArgumentNullException ("Destination array is null.");
135                         if (!CanRead)
136                                 throw new InvalidOperationException ("Stream does not support reading.");
137                         int len = dest.Length;
138                         if (dest_offset < 0 || count < 0)
139                                 throw new ArgumentException ("Dest or count is negative.");
140                         if (dest_offset > len)
141                                 throw new ArgumentException ("destination offset is beyond array size");
142                         if ((dest_offset + count) > len)
143                                 throw new ArgumentException ("Reading would overrun buffer");
144
145                         return ReadInternal (dest, dest_offset, count);
146                 }
147
148                 unsafe void WriteInternal (byte[] array, int offset, int count)
149                 {
150                         if (count == 0)
151                                 return;
152
153                         fixed (byte *b = array) {
154                                 IntPtr ptr = new IntPtr (b + offset);
155                                 native.WriteZStream (ptr, count);
156                         }
157                 }
158
159                 public override void Write (byte[] src, int src_offset, int count)
160                 {
161                         if (disposed)
162                                 throw new ObjectDisposedException (GetType ().FullName);
163
164                         if (src == null)
165                                 throw new ArgumentNullException ("src");
166
167                         if (src_offset < 0)
168                                 throw new ArgumentOutOfRangeException ("src_offset");
169
170                         if (count < 0)
171                                 throw new ArgumentOutOfRangeException ("count");
172
173                         if (!CanWrite)
174                                 throw new NotSupportedException ("Stream does not support writing");
175
176                         WriteInternal (src, src_offset, count);
177                 }
178
179                 public override void Flush ()
180                 {
181                         if (disposed)
182                                 throw new ObjectDisposedException (GetType ().FullName);
183
184                         if (CanWrite) {
185                                 native.Flush ();
186                         }
187                 }
188
189                 public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
190                                                         AsyncCallback cback, object state)
191                 {
192                         if (disposed)
193                                 throw new ObjectDisposedException (GetType ().FullName);
194
195                         if (!CanRead)
196                                 throw new NotSupportedException ("This stream does not support reading");
197
198                         if (buffer == null)
199                                 throw new ArgumentNullException ("buffer");
200
201                         if (count < 0)
202                                 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
203
204                         if (offset < 0)
205                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
206
207                         if (count + offset > buffer.Length)
208                                 throw new ArgumentException ("Buffer too small. count/offset wrong.");
209
210                         ReadMethod r = new ReadMethod (ReadInternal);
211                         return r.BeginInvoke (buffer, offset, count, cback, state);
212                 }
213
214                 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
215                                                         AsyncCallback cback, object state)
216                 {
217                         if (disposed)
218                                 throw new ObjectDisposedException (GetType ().FullName);
219
220                         if (!CanWrite)
221                                 throw new InvalidOperationException ("This stream does not support writing");
222
223                         if (buffer == null)
224                                 throw new ArgumentNullException ("buffer");
225
226                         if (count < 0)
227                                 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
228
229                         if (offset < 0)
230                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
231
232                         if (count + offset > buffer.Length)
233                                 throw new ArgumentException ("Buffer too small. count/offset wrong.");
234
235                         WriteMethod w = new WriteMethod (WriteInternal);
236                         return w.BeginInvoke (buffer, offset, count, cback, state);                     
237                 }
238
239                 public override int EndRead(IAsyncResult async_result)
240                 {
241                         if (async_result == null)
242                                 throw new ArgumentNullException ("async_result");
243
244                         AsyncResult ares = async_result as AsyncResult;
245                         if (ares == null)
246                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
247
248                         ReadMethod r = ares.AsyncDelegate as ReadMethod;
249                         if (r == null)
250                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
251
252                         return r.EndInvoke (async_result);
253                 }
254
255                 public override void EndWrite (IAsyncResult async_result)
256                 {
257                         if (async_result == null)
258                                 throw new ArgumentNullException ("async_result");
259
260                         AsyncResult ares = async_result as AsyncResult;
261                         if (ares == null)
262                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
263
264                         WriteMethod w = ares.AsyncDelegate as WriteMethod;
265                         if (w == null)
266                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
267
268                         w.EndInvoke (async_result);
269                         return;
270                 }
271
272                 public override long Seek (long offset, SeekOrigin origin)
273                 {
274                         throw new NotSupportedException();
275                 }
276
277                 public override void SetLength (long value)
278                 {
279                         throw new NotSupportedException();
280                 }
281
282                 public Stream BaseStream {
283                         get { return base_stream; }
284                 }
285
286                 public override bool CanRead {
287                         get { return !disposed && mode == CompressionMode.Decompress && base_stream.CanRead; }
288                 }
289
290                 public override bool CanSeek {
291                         get { return false; }
292                 }
293
294                 public override bool CanWrite {
295                         get { return !disposed && mode == CompressionMode.Compress && base_stream.CanWrite; }
296                 }
297
298                 public override long Length {
299                         get { throw new NotSupportedException(); }
300                 }
301
302                 public override long Position {
303                         get { throw new NotSupportedException(); }
304                         set { throw new NotSupportedException(); }
305                 }
306         }
307
308         class DeflateStreamNative
309         {
310                 const int BufferSize = 4096;
311
312                 [UnmanagedFunctionPointer (CallingConvention.Cdecl)]
313                 delegate int UnmanagedReadOrWrite (IntPtr buffer, int length, IntPtr data);
314
315                 UnmanagedReadOrWrite feeder; // This will be passed to unmanaged code and used there
316
317                 Stream base_stream;
318                 IntPtr z_stream;
319                 GCHandle data;
320                 bool disposed;
321                 byte [] io_buffer;
322
323                 private DeflateStreamNative ()
324                 {
325                 }
326
327                 public static DeflateStreamNative Create (Stream compressedStream, CompressionMode mode, bool gzip)
328                 {
329                         var dsn = new DeflateStreamNative ();
330                         dsn.data = GCHandle.Alloc (dsn);
331                         dsn.feeder = mode == CompressionMode.Compress ? new UnmanagedReadOrWrite (UnmanagedWrite) : new UnmanagedReadOrWrite (UnmanagedRead);
332                         dsn.z_stream = CreateZStream (mode, gzip, dsn.feeder, GCHandle.ToIntPtr (dsn.data));
333                         if (dsn.z_stream == IntPtr.Zero) {
334                                 dsn.Dispose (true);
335                                 return null;
336                         }
337
338                         dsn.base_stream = compressedStream;
339                         return dsn;
340                 }
341
342                 ~DeflateStreamNative ()
343                 {
344                         Dispose (false);
345                 }
346
347                 public void Dispose (bool disposing)
348                 {
349                         if (disposing && !disposed) {
350                                 disposed = true;
351                                 GC.SuppressFinalize (this);
352                         
353                                 io_buffer = null;
354                         
355                                 IntPtr zz = z_stream;
356                                 z_stream = IntPtr.Zero;
357                                 if (zz != IntPtr.Zero)
358                                         CloseZStream (zz); // This will Flush() the remaining output if any
359                         }
360
361                         if (data.IsAllocated) {
362                                 data.Free ();
363                         }
364                 }
365
366                 public void Flush ()
367                 {
368                         var res = Flush (z_stream);
369                         CheckResult (res, "Flush");
370                 }
371
372                 public int ReadZStream (IntPtr buffer, int length)
373                 {
374                         var res = ReadZStream (z_stream, buffer, length);
375                         CheckResult (res, "ReadInternal");
376                         return res;
377                 }
378
379                 public void WriteZStream (IntPtr buffer, int length)
380                 {
381                         var res = WriteZStream (z_stream, buffer, length);
382                         CheckResult (res, "WriteInternal");
383                 }
384
385 #if MONOTOUCH
386                 [MonoPInvokeCallback (typeof (UnmanagedReadOrWrite))]
387 #endif
388                 static int UnmanagedRead (IntPtr buffer, int length, IntPtr data)
389                 {
390                         GCHandle s = GCHandle.FromIntPtr (data);
391                         var self = s.Target as DeflateStreamNative;
392                         if (self == null)
393                                 return -1;
394                         return self.UnmanagedRead (buffer, length);
395                 }
396
397                 int UnmanagedRead (IntPtr buffer, int length)
398                 {
399                         if (io_buffer == null)
400                                 io_buffer = new byte [BufferSize];
401
402                         int count = Math.Min (length, io_buffer.Length);
403                         int n = base_stream.Read (io_buffer, 0, count);
404                         if (n > 0)
405                                 Marshal.Copy (io_buffer, 0, buffer, n);
406
407                         return n;
408                 }
409
410 #if MONOTOUCH
411                 [MonoPInvokeCallback (typeof (UnmanagedReadOrWrite))]
412 #endif
413                 static int UnmanagedWrite (IntPtr buffer, int length, IntPtr data)
414                 {
415                         GCHandle s = GCHandle.FromIntPtr (data);
416                         var self = s.Target as DeflateStreamNative;
417                         if (self == null)
418                                 return -1;
419                         return self.UnmanagedWrite (buffer, length);
420                 }
421
422                 int UnmanagedWrite (IntPtr buffer, int length)
423                 {
424                         int total = 0;
425                         while (length > 0) {
426                                 if (io_buffer == null)
427                                         io_buffer = new byte [BufferSize];
428
429                                 int count = Math.Min (length, io_buffer.Length);
430                                 Marshal.Copy (buffer, io_buffer, 0, count);
431                                 base_stream.Write (io_buffer, 0, count);
432                                 unsafe {
433                                         buffer = new IntPtr ((byte *) buffer.ToPointer () + count);
434                                 }
435                                 length -= count;
436                                 total += count;
437                         }
438                         return total;
439                 }
440
441                 static void CheckResult (int result, string where)
442                 {
443                         if (result >= 0)
444                                 return;
445
446                         string error;
447                         switch (result) {
448                         case -1: // Z_ERRNO
449                                 error = "Unknown error"; // Marshal.GetLastWin32() ?
450                                 break;
451                         case -2: // Z_STREAM_ERROR
452                                 error = "Internal error";
453                                 break;
454                         case -3: // Z_DATA_ERROR
455                                 error = "Corrupted data";
456                                 break;
457                         case -4: // Z_MEM_ERROR
458                                 error = "Not enough memory";
459                                 break;
460                         case -5: // Z_BUF_ERROR
461                                 error = "Internal error (no progress possible)";
462                                 break;
463                         case -6: // Z_VERSION_ERROR
464                                 error = "Invalid version";
465                                 break;
466                         case -10:
467                                 error = "Invalid argument(s)";
468                                 break;
469                         case -11:
470                                 error = "IO error";
471                                 break;
472                         default:
473                                 error = "Unknown error";
474                                 break;
475                         }
476
477                         throw new IOException (error + " " + where);
478                 }
479
480 #if MONOTOUCH || MONODROID
481                 const string LIBNAME = "__Internal";
482 #else
483                 const string LIBNAME = "MonoPosixHelper";
484 #endif
485
486                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
487                 static extern IntPtr CreateZStream (CompressionMode compress, bool gzip, UnmanagedReadOrWrite feeder, IntPtr data);
488
489                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
490                 static extern int CloseZStream (IntPtr stream);
491
492                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
493                 static extern int Flush (IntPtr stream);
494
495                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
496                 static extern int ReadZStream (IntPtr stream, IntPtr buffer, int length);
497
498                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
499                 static extern int WriteZStream (IntPtr stream, IntPtr buffer, int length);
500         }
501 }
502