Merge pull request #1063 from esdrubal/bug18482
[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                 [MonoTODO]
85                 public DeflateStream (Stream stream, CompressionLevel compressionLevel)
86                         : this (stream, CompressionMode.Compress)
87                 {
88                         throw new NotImplementedException ();
89                 }
90                 
91                 [MonoTODO]
92                 public DeflateStream (Stream stream, CompressionLevel compressionLevel, bool leaveOpen)
93                         : this (stream, CompressionMode.Compress, leaveOpen)
94                 {
95                         throw new NotImplementedException ();
96                 }
97 #endif
98
99                 protected override void Dispose (bool disposing)
100                 {
101                         native.Dispose (disposing);
102
103                         if (disposing && !disposed) {
104                                 disposed = true;
105
106                                 if (!leaveOpen) {
107                                         Stream st = base_stream;
108                                         if (st != null)
109                                                 st.Close ();
110                                         base_stream = null;
111                                 }
112                         }
113
114                         base.Dispose (disposing);
115                 }
116
117                 unsafe int ReadInternal (byte[] array, int offset, int count)
118                 {
119                         if (count == 0)
120                                 return 0;
121
122                         fixed (byte *b = array) {
123                                 IntPtr ptr = new IntPtr (b + offset);
124                                 return native.ReadZStream (ptr, count);
125                         }
126                 }
127
128                 public override int Read (byte[] dest, int dest_offset, int count)
129                 {
130                         if (disposed)
131                                 throw new ObjectDisposedException (GetType ().FullName);
132                         if (dest == null)
133                                 throw new ArgumentNullException ("Destination array is null.");
134                         if (!CanRead)
135                                 throw new InvalidOperationException ("Stream does not support reading.");
136                         int len = dest.Length;
137                         if (dest_offset < 0 || count < 0)
138                                 throw new ArgumentException ("Dest or count is negative.");
139                         if (dest_offset > len)
140                                 throw new ArgumentException ("destination offset is beyond array size");
141                         if ((dest_offset + count) > len)
142                                 throw new ArgumentException ("Reading would overrun buffer");
143
144                         return ReadInternal (dest, dest_offset, count);
145                 }
146
147                 unsafe void WriteInternal (byte[] array, int offset, int count)
148                 {
149                         if (count == 0)
150                                 return;
151
152                         fixed (byte *b = array) {
153                                 IntPtr ptr = new IntPtr (b + offset);
154                                 native.WriteZStream (ptr, count);
155                         }
156                 }
157
158                 public override void Write (byte[] src, int src_offset, int count)
159                 {
160                         if (disposed)
161                                 throw new ObjectDisposedException (GetType ().FullName);
162
163                         if (src == null)
164                                 throw new ArgumentNullException ("src");
165
166                         if (src_offset < 0)
167                                 throw new ArgumentOutOfRangeException ("src_offset");
168
169                         if (count < 0)
170                                 throw new ArgumentOutOfRangeException ("count");
171
172                         if (!CanWrite)
173                                 throw new NotSupportedException ("Stream does not support writing");
174
175                         WriteInternal (src, src_offset, count);
176                 }
177
178                 public override void Flush ()
179                 {
180                         if (disposed)
181                                 throw new ObjectDisposedException (GetType ().FullName);
182
183                         if (CanWrite) {
184                                 native.Flush ();
185                         }
186                 }
187
188                 public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
189                                                         AsyncCallback cback, object state)
190                 {
191                         if (disposed)
192                                 throw new ObjectDisposedException (GetType ().FullName);
193
194                         if (!CanRead)
195                                 throw new NotSupportedException ("This stream does not support reading");
196
197                         if (buffer == null)
198                                 throw new ArgumentNullException ("buffer");
199
200                         if (count < 0)
201                                 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
202
203                         if (offset < 0)
204                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
205
206                         if (count + offset > buffer.Length)
207                                 throw new ArgumentException ("Buffer too small. count/offset wrong.");
208
209                         ReadMethod r = new ReadMethod (ReadInternal);
210                         return r.BeginInvoke (buffer, offset, count, cback, state);
211                 }
212
213                 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
214                                                         AsyncCallback cback, object state)
215                 {
216                         if (disposed)
217                                 throw new ObjectDisposedException (GetType ().FullName);
218
219                         if (!CanWrite)
220                                 throw new InvalidOperationException ("This stream does not support writing");
221
222                         if (buffer == null)
223                                 throw new ArgumentNullException ("buffer");
224
225                         if (count < 0)
226                                 throw new ArgumentOutOfRangeException ("count", "Must be >= 0");
227
228                         if (offset < 0)
229                                 throw new ArgumentOutOfRangeException ("offset", "Must be >= 0");
230
231                         if (count + offset > buffer.Length)
232                                 throw new ArgumentException ("Buffer too small. count/offset wrong.");
233
234                         WriteMethod w = new WriteMethod (WriteInternal);
235                         return w.BeginInvoke (buffer, offset, count, cback, state);                     
236                 }
237
238                 public override int EndRead(IAsyncResult async_result)
239                 {
240                         if (async_result == null)
241                                 throw new ArgumentNullException ("async_result");
242
243                         AsyncResult ares = async_result as AsyncResult;
244                         if (ares == null)
245                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
246
247                         ReadMethod r = ares.AsyncDelegate as ReadMethod;
248                         if (r == null)
249                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
250
251                         return r.EndInvoke (async_result);
252                 }
253
254                 public override void EndWrite (IAsyncResult async_result)
255                 {
256                         if (async_result == null)
257                                 throw new ArgumentNullException ("async_result");
258
259                         AsyncResult ares = async_result as AsyncResult;
260                         if (ares == null)
261                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
262
263                         WriteMethod w = ares.AsyncDelegate as WriteMethod;
264                         if (w == null)
265                                 throw new ArgumentException ("Invalid IAsyncResult", "async_result");
266
267                         w.EndInvoke (async_result);
268                         return;
269                 }
270
271                 public override long Seek (long offset, SeekOrigin origin)
272                 {
273                         throw new NotSupportedException();
274                 }
275
276                 public override void SetLength (long value)
277                 {
278                         throw new NotSupportedException();
279                 }
280
281                 public Stream BaseStream {
282                         get { return base_stream; }
283                 }
284
285                 public override bool CanRead {
286                         get { return !disposed && mode == CompressionMode.Decompress && base_stream.CanRead; }
287                 }
288
289                 public override bool CanSeek {
290                         get { return false; }
291                 }
292
293                 public override bool CanWrite {
294                         get { return !disposed && mode == CompressionMode.Compress && base_stream.CanWrite; }
295                 }
296
297                 public override long Length {
298                         get { throw new NotSupportedException(); }
299                 }
300
301                 public override long Position {
302                         get { throw new NotSupportedException(); }
303                         set { throw new NotSupportedException(); }
304                 }
305         }
306
307         class DeflateStreamNative
308         {
309                 const int BufferSize = 4096;
310
311                 [UnmanagedFunctionPointer (CallingConvention.Cdecl)]
312                 delegate int UnmanagedReadOrWrite (IntPtr buffer, int length, IntPtr data);
313
314                 UnmanagedReadOrWrite feeder; // This will be passed to unmanaged code and used there
315
316                 Stream base_stream;
317                 IntPtr z_stream;
318                 GCHandle data;
319                 bool disposed;
320                 byte [] io_buffer;
321
322                 private DeflateStreamNative ()
323                 {
324                 }
325
326                 public static DeflateStreamNative Create (Stream compressedStream, CompressionMode mode, bool gzip)
327                 {
328                         var dsn = new DeflateStreamNative ();
329                         dsn.data = GCHandle.Alloc (dsn);
330                         dsn.feeder = mode == CompressionMode.Compress ? new UnmanagedReadOrWrite (UnmanagedWrite) : new UnmanagedReadOrWrite (UnmanagedRead);
331                         dsn.z_stream = CreateZStream (mode, gzip, dsn.feeder, GCHandle.ToIntPtr (dsn.data));
332                         if (dsn.z_stream == IntPtr.Zero) {
333                                 dsn.Dispose (true);
334                                 return null;
335                         }
336
337                         dsn.base_stream = compressedStream;
338                         return dsn;
339                 }
340
341                 ~DeflateStreamNative ()
342                 {
343                         Dispose (false);
344                 }
345
346                 public void Dispose (bool disposing)
347                 {
348                         if (disposing && !disposed) {
349                                 disposed = true;
350                                 GC.SuppressFinalize (this);
351                         
352                                 io_buffer = null;
353                         
354                                 IntPtr zz = z_stream;
355                                 z_stream = IntPtr.Zero;
356                                 if (zz != IntPtr.Zero)
357                                         CloseZStream (zz); // This will Flush() the remaining output if any
358                         }
359
360                         if (data.IsAllocated) {
361                                 data.Free ();
362                         }
363                 }
364
365                 public void Flush ()
366                 {
367                         var res = Flush (z_stream);
368                         CheckResult (res, "Flush");
369                 }
370
371                 public int ReadZStream (IntPtr buffer, int length)
372                 {
373                         var res = ReadZStream (z_stream, buffer, length);
374                         CheckResult (res, "ReadInternal");
375                         return res;
376                 }
377
378                 public void WriteZStream (IntPtr buffer, int length)
379                 {
380                         var res = WriteZStream (z_stream, buffer, length);
381                         CheckResult (res, "WriteInternal");
382                 }
383
384 #if MONOTOUCH
385                 [MonoPInvokeCallback (typeof (UnmanagedReadOrWrite))]
386 #endif
387                 static int UnmanagedRead (IntPtr buffer, int length, IntPtr data)
388                 {
389                         GCHandle s = GCHandle.FromIntPtr (data);
390                         var self = s.Target as DeflateStreamNative;
391                         if (self == null)
392                                 return -1;
393                         return self.UnmanagedRead (buffer, length);
394                 }
395
396                 int UnmanagedRead (IntPtr buffer, int length)
397                 {
398                         int total = 0;
399                         int n = 1;
400                         while (length > 0 && n > 0) {
401                                 if (io_buffer == null)
402                                         io_buffer = new byte [BufferSize];
403
404                                 int count = Math.Min (length, io_buffer.Length);
405                                 n = base_stream.Read (io_buffer, 0, count);
406                                 if (n > 0) {
407                                         Marshal.Copy (io_buffer, 0, buffer, n);
408                                         unsafe {
409                                                 buffer = new IntPtr ((byte *) buffer.ToPointer () + n);
410                                         }
411                                         length -= n;
412                                         total += n;
413                                 }
414                         }
415                         return total;
416                 }
417
418 #if MONOTOUCH
419                 [MonoPInvokeCallback (typeof (UnmanagedReadOrWrite))]
420 #endif
421                 static int UnmanagedWrite (IntPtr buffer, int length, IntPtr data)
422                 {
423                         GCHandle s = GCHandle.FromIntPtr (data);
424                         var self = s.Target as DeflateStreamNative;
425                         if (self == null)
426                                 return -1;
427                         return self.UnmanagedWrite (buffer, length);
428                 }
429
430                 int UnmanagedWrite (IntPtr buffer, int length)
431                 {
432                         int total = 0;
433                         while (length > 0) {
434                                 if (io_buffer == null)
435                                         io_buffer = new byte [BufferSize];
436
437                                 int count = Math.Min (length, io_buffer.Length);
438                                 Marshal.Copy (buffer, io_buffer, 0, count);
439                                 base_stream.Write (io_buffer, 0, count);
440                                 unsafe {
441                                         buffer = new IntPtr ((byte *) buffer.ToPointer () + count);
442                                 }
443                                 length -= count;
444                                 total += count;
445                         }
446                         return total;
447                 }
448
449                 static void CheckResult (int result, string where)
450                 {
451                         if (result >= 0)
452                                 return;
453
454                         string error;
455                         switch (result) {
456                         case -1: // Z_ERRNO
457                                 error = "Unknown error"; // Marshal.GetLastWin32() ?
458                                 break;
459                         case -2: // Z_STREAM_ERROR
460                                 error = "Internal error";
461                                 break;
462                         case -3: // Z_DATA_ERROR
463                                 error = "Corrupted data";
464                                 break;
465                         case -4: // Z_MEM_ERROR
466                                 error = "Not enough memory";
467                                 break;
468                         case -5: // Z_BUF_ERROR
469                                 error = "Internal error (no progress possible)";
470                                 break;
471                         case -6: // Z_VERSION_ERROR
472                                 error = "Invalid version";
473                                 break;
474                         case -10:
475                                 error = "Invalid argument(s)";
476                                 break;
477                         case -11:
478                                 error = "IO error";
479                                 break;
480                         default:
481                                 error = "Unknown error";
482                                 break;
483                         }
484
485                         throw new IOException (error + " " + where);
486                 }
487
488 #if MONOTOUCH || MONODROID
489                 const string LIBNAME = "__Internal";
490 #else
491                 const string LIBNAME = "MonoPosixHelper";
492 #endif
493
494                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
495                 static extern IntPtr CreateZStream (CompressionMode compress, bool gzip, UnmanagedReadOrWrite feeder, IntPtr data);
496
497                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
498                 static extern int CloseZStream (IntPtr stream);
499
500                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
501                 static extern int Flush (IntPtr stream);
502
503                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
504                 static extern int ReadZStream (IntPtr stream, IntPtr buffer, int length);
505
506                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
507                 static extern int WriteZStream (IntPtr stream, IntPtr buffer, int length);
508         }
509 }
510