[System.IO.Compression.DeflateStream] Fixed end condition in decompression loop.
[mono.git] / support / zlib-helper.c
1 /*
2  * Used by System.IO.Compression.DeflateStream
3  *
4  * Author:
5  *   Gonzalo Paniagua Javier (gonzalo@novell.com)
6  *
7  * (c) Copyright 2009 Novell, Inc.
8  */
9 #include <config.h>
10 #if defined (HAVE_ZLIB)
11 #include <zlib.h>
12 #else
13 #include "zlib.h"
14 #endif
15
16 #include <glib.h>
17 #include <string.h>
18 #include <stdlib.h>
19
20 #ifndef TRUE
21 #define FALSE 0
22 #define TRUE 1
23 #endif
24
25 #define BUFFER_SIZE 4096
26 #define ARGUMENT_ERROR -10
27 #define IO_ERROR -11
28
29 typedef gint (*read_write_func) (guchar *buffer, gint length, void *gchandle);
30 struct _ZStream {
31         z_stream *stream;
32         guchar *buffer;
33         read_write_func func;
34         void *gchandle;
35         guchar compress;
36         guchar eof;
37         guint32 total_in;
38 };
39 typedef struct _ZStream ZStream;
40
41 ZStream *CreateZStream (gint compress, guchar gzip, read_write_func func, void *gchandle);
42 gint CloseZStream (ZStream *zstream);
43 gint Flush (ZStream *stream);
44 gint ReadZStream (ZStream *stream, guchar *buffer, gint length);
45 gint WriteZStream (ZStream *stream, guchar *buffer, gint length);
46 static gint flush_internal (ZStream *stream, gboolean is_final);
47
48 static void *
49 z_alloc (void *opaque, unsigned int nitems, unsigned int item_size)
50 {
51         return g_malloc0 (nitems * item_size);
52 }
53
54 static void
55 z_free (void *opaque, void *ptr)
56 {
57         g_free (ptr);
58 }
59
60 ZStream *
61 CreateZStream (gint compress, guchar gzip, read_write_func func, void *gchandle)
62 {
63         z_stream *z;
64         gint retval;
65         ZStream *result;
66
67         if (func == NULL)
68                 return NULL;
69
70 #if !defined(ZLIB_VERNUM) || (ZLIB_VERNUM < 0x1204)
71         /* Older versions of zlib do not support raw deflate or gzip */
72         return NULL;
73 #endif
74
75         z = g_new0 (z_stream, 1);
76         if (compress) {
77                 retval = deflateInit2 (z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, gzip ? 31 : -15, 8, Z_DEFAULT_STRATEGY);
78         } else {
79                 retval = inflateInit2 (z, gzip ? 31 : -15);
80         }
81
82         if (retval != Z_OK) {
83                 g_free (z);
84                 return NULL;
85         }
86         z->zalloc = z_alloc;
87         z->zfree = z_free;
88         result = g_new0 (ZStream, 1);
89         result->stream = z;
90         result->func = func;
91         result->gchandle = gchandle;
92         result->compress = compress;
93         result->buffer = g_new (guchar, BUFFER_SIZE);
94         result->stream->next_out = result->buffer;
95         result->stream->avail_out = BUFFER_SIZE;
96         result->stream->total_in = 0;
97         return result;
98 }
99
100 gint
101 CloseZStream (ZStream *zstream)
102 {
103         gint status;
104         gint flush_status;
105
106         if (zstream == NULL)
107                 return ARGUMENT_ERROR;
108
109         status = 0;
110         if (zstream->compress) {
111                 if (zstream->stream->total_in > 0) {
112                         do {
113                                 status = deflate (zstream->stream, Z_FINISH);
114                                 flush_status = flush_internal (zstream, TRUE);
115                         } while (status == Z_OK); /* We want Z_STREAM_END or error here here */
116                         if (status == Z_STREAM_END)
117                                 status = flush_status;
118                 }
119                 deflateEnd (zstream->stream);
120         } else {
121                 inflateEnd (zstream->stream);
122         }
123         g_free (zstream->buffer);
124         g_free (zstream->stream);
125         memset (zstream, 0, sizeof (ZStream));
126         g_free (zstream);
127         return status;
128 }
129
130 static gint
131 write_to_managed (ZStream *stream)
132 {
133         gint n;
134         z_stream *zs;
135
136         zs = stream->stream;
137         if (zs->avail_out != BUFFER_SIZE) {
138                 n = stream->func (stream->buffer, BUFFER_SIZE - zs->avail_out, stream->gchandle);
139                 zs->next_out = stream->buffer;
140                 zs->avail_out = BUFFER_SIZE;
141                 if (n < 0)
142                         return IO_ERROR;
143         }
144         return 0;
145 }
146
147 static gint
148 flush_internal (ZStream *stream, gboolean is_final)
149 {
150         gint status;
151
152         if (!stream->compress)
153                 return 0;
154
155         if (!is_final && stream->stream->avail_in != 0) {
156                 status = deflate (stream->stream, Z_PARTIAL_FLUSH);
157                 if (status != Z_OK && status != Z_STREAM_END)
158                         return status;
159         }
160         return write_to_managed (stream);
161 }
162
163 gint
164 Flush (ZStream *stream)
165 {
166         return flush_internal (stream, FALSE);
167 }
168
169 gint
170 ReadZStream (ZStream *stream, guchar *buffer, gint length)
171 {
172         gint n;
173         gint status;
174         z_stream *zs;
175
176         if (stream == NULL || buffer == NULL || length < 0)
177                 return ARGUMENT_ERROR;
178
179         if (stream->eof)
180                 return 0;
181
182         zs = stream->stream;
183         zs->next_out = buffer;
184         zs->avail_out = length;
185         while (zs->avail_out > 0) {
186                 if (zs->avail_in == 0) {
187                         n = stream->func (stream->buffer, BUFFER_SIZE, stream->gchandle);
188                         stream->total_in += n;
189                         if (n <= 0) {
190                                 stream->eof = TRUE;
191                         }
192                         zs->next_in = stream->buffer;
193                         zs->avail_in = n < 0 ? 0 : n;
194                 }
195
196                 if (zs->avail_in == 0 && (zs->total_in == 0 || stream->total_in == zs->total_in))
197                         return Z_STREAM_END;
198
199                 status = inflate (stream->stream, Z_SYNC_FLUSH);
200                 if (status == Z_STREAM_END) {
201                         stream->eof = TRUE;
202                         break;
203                 } else if (status != Z_OK) {
204                         return status;
205                 }
206         }
207         return length - zs->avail_out;
208 }
209
210 gint
211 WriteZStream (ZStream *stream, guchar *buffer, gint length)
212 {
213         gint n;
214         gint status;
215         z_stream *zs;
216
217         if (stream == NULL || buffer == NULL || length < 0)
218                 return ARGUMENT_ERROR;
219
220         if (stream->eof)
221                 return IO_ERROR;
222
223         zs = stream->stream;
224         zs->next_in = buffer;
225         zs->avail_in = length;
226         while (zs->avail_in > 0) {
227                 if (zs->avail_out == 0) {
228                         zs->next_out = stream->buffer;
229                         zs->avail_out = BUFFER_SIZE;
230                 }
231                 status = deflate (stream->stream, Z_NO_FLUSH);
232                 if (status != Z_OK && status != Z_STREAM_END)
233                         return status;
234
235                 if (zs->avail_out == 0) {
236                         n = write_to_managed (stream);
237                         if (n < 0)
238                                 return n;
239                 }
240         }
241         return length;
242 }
243