Initialize the gmime for upstream
[platform/upstream/gmime.git] / gmime / gmime-filter-gzip.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  GMime
3  *  Copyright (C) 2000-2012 Jeffrey Stedfast
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public License
7  *  as published by the Free Software Foundation; either version 2.1
8  *  of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free
17  *  Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
18  *  02110-1301, USA.
19  */
20
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <string.h>
28
29 #include <zlib.h>
30
31 #include "gmime-filter-gzip.h"
32
33 #ifdef ENABLE_WARNINGS
34 #define w(x) x
35 #else
36 #define w(x)
37 #endif /* ENABLE_WARNINGS */
38
39
40 /**
41  * SECTION: gmime-filter-gzip
42  * @title: GMimeFilterGZip
43  * @short_description: GNU Zip compression/decompression
44  * @see_also: #GMimeFilter
45  *
46  * A #GMimeFilter used for compressing or decompressing a stream using
47  * GNU Zip.
48  **/
49
50
51 /* rfc1952 */
52
53 enum {
54         GZIP_FLAG_FTEXT     = (1 << 0),
55         GZIP_FLAG_FHCRC     = (1 << 1),
56         GZIP_FLAG_FEXTRA    = (1 << 2),
57         GZIP_FLAG_FNAME     = (1 << 3),
58         GZIP_FLAG_FCOMMENT  = (1 << 4),
59         GZIP_FLAG_RESERVED0 = (1 << 5),
60         GZIP_FLAG_RESERVED1 = (1 << 6),
61         GZIP_FLAG_RESERVED2 = (1 << 7),
62 };
63
64 enum {
65         GZIP_OS_FAT,
66         GZIP_OS_AMIGA,
67         GZIP_OS_VMS,
68         GZIP_OS_UNIX,
69         GZIP_OS_VM_CMS,
70         GZIP_OS_ATARI_TOS,
71         GZIP_OS_HPFS,
72         GZIP_OS_MACINTOSH,
73         GZIP_OS_ZSYSTEM,
74         GZIP_OS_CPM,
75         GZIP_OS_TOPS20,
76         GZIP_OS_NTFS,
77         GZIP_OS_QDOS,
78         GZIP_OS_ACORN_RISCOS,
79         GZIP_OS_UNKNOWN = 255
80 };
81
82 #define GZIP_FLAG_RESERVED (GZIP_FLAG_RESERVED0 | GZIP_FLAG_RESERVED1 | GZIP_FLAG_RESERVED2)
83
84 /* http://www.gzip.org/zlib/rfc-gzip.html */
85 typedef union {
86         unsigned char buf[10];
87         struct {
88                 guint8 id1;
89                 guint8 id2;
90                 guint8 cm;
91                 guint8 flg;
92                 guint32 mtime;
93                 guint8 xfl;
94                 guint8 os;
95         } v;
96 } gzip_hdr_t;
97
98 typedef union {
99         struct {
100                 guint16 xlen;
101                 guint16 xlen_nread;
102                 guint16 crc16;
103                 
104                 guint8 got_hdr:1;
105                 guint8 is_valid:1;
106                 guint8 got_xlen:1;
107                 guint8 got_fname:1;
108                 guint8 got_fcomment:1;
109                 guint8 got_crc16:1;
110         } unzip;
111         struct {
112                 guint32 wrote_hdr:1;
113         } zip;
114 } gzip_state_t;
115
116 struct _GMimeFilterGZipPrivate {
117         z_stream *stream;
118         
119         gzip_state_t state;
120         gzip_hdr_t hdr;
121         
122         guint32 crc32;
123         guint32 isize;
124 };
125
126 static void g_mime_filter_gzip_class_init (GMimeFilterGZipClass *klass);
127 static void g_mime_filter_gzip_init (GMimeFilterGZip *filter, GMimeFilterGZipClass *klass);
128 static void g_mime_filter_gzip_finalize (GObject *object);
129
130 static GMimeFilter *filter_copy (GMimeFilter *filter);
131 static void filter_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
132                            char **out, size_t *outlen, size_t *outprespace);
133 static void filter_complete (GMimeFilter *filter, char *in, size_t len, size_t prespace,
134                              char **out, size_t *outlen, size_t *outprespace);
135 static void filter_reset (GMimeFilter *filter);
136
137
138 static GMimeFilterClass *parent_class = NULL;
139
140
141 GType
142 g_mime_filter_gzip_get_type (void)
143 {
144         static GType type = 0;
145         
146         if (!type) {
147                 static const GTypeInfo info = {
148                         sizeof (GMimeFilterGZipClass),
149                         NULL, /* base_class_init */
150                         NULL, /* base_class_finalize */
151                         (GClassInitFunc) g_mime_filter_gzip_class_init,
152                         NULL, /* class_finalize */
153                         NULL, /* class_data */
154                         sizeof (GMimeFilterGZip),
155                         0,    /* n_preallocs */
156                         (GInstanceInitFunc) g_mime_filter_gzip_init,
157                 };
158                 
159                 type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterGZip", &info, 0);
160         }
161         
162         return type;
163 }
164
165
166 static void
167 g_mime_filter_gzip_class_init (GMimeFilterGZipClass *klass)
168 {
169         GObjectClass *object_class = G_OBJECT_CLASS (klass);
170         GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
171         
172         parent_class = g_type_class_ref (GMIME_TYPE_FILTER);
173         
174         object_class->finalize = g_mime_filter_gzip_finalize;
175         
176         filter_class->copy = filter_copy;
177         filter_class->filter = filter_filter;
178         filter_class->complete = filter_complete;
179         filter_class->reset = filter_reset;
180 }
181
182 static void
183 g_mime_filter_gzip_init (GMimeFilterGZip *filter, GMimeFilterGZipClass *klass)
184 {
185         filter->priv = g_new0 (struct _GMimeFilterGZipPrivate, 1);
186         filter->priv->stream = g_new0 (z_stream, 1);
187         filter->priv->crc32 = crc32 (0, Z_NULL, 0);
188 }
189
190 static void
191 g_mime_filter_gzip_finalize (GObject *object)
192 {
193         GMimeFilterGZip *gzip = (GMimeFilterGZip *) object;
194         struct _GMimeFilterGZipPrivate *priv = gzip->priv;
195         
196         if (gzip->mode == GMIME_FILTER_GZIP_MODE_ZIP)
197                 deflateEnd (priv->stream);
198         else
199                 inflateEnd (priv->stream);
200         
201         g_free (priv->stream);
202         g_free (priv);
203         
204         G_OBJECT_CLASS (parent_class)->finalize (object);
205 }
206
207
208 static GMimeFilter *
209 filter_copy (GMimeFilter *filter)
210 {
211         GMimeFilterGZip *gzip = (GMimeFilterGZip *) filter;
212         
213         return g_mime_filter_gzip_new (gzip->mode, gzip->level);
214 }
215
216 static void
217 gzip_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
218              char **out, size_t *outlen, size_t *outprespace, int flush)
219 {
220         GMimeFilterGZip *gzip = (GMimeFilterGZip *) filter;
221         struct _GMimeFilterGZipPrivate *priv = gzip->priv;
222         int retval;
223         
224         if (!priv->state.zip.wrote_hdr) {
225                 priv->hdr.v.id1 = 31;
226                 priv->hdr.v.id2 = 139;
227                 priv->hdr.v.cm = Z_DEFLATED;
228                 priv->hdr.v.mtime = 0;
229                 priv->hdr.v.flg = 0;
230                 if (gzip->level == Z_BEST_COMPRESSION)
231                         priv->hdr.v.xfl = 2;
232                 else if (gzip->level == Z_BEST_SPEED)
233                         priv->hdr.v.xfl = 4;
234                 else
235                         priv->hdr.v.xfl = 0;
236                 priv->hdr.v.os = GZIP_OS_UNKNOWN;
237                 
238                 g_mime_filter_set_size (filter, (len * 2) + 22, FALSE);
239                 
240                 memcpy (filter->outbuf, priv->hdr.buf, 10);
241                 
242                 priv->stream->next_out = (unsigned char *) filter->outbuf + 10;
243                 priv->stream->avail_out = filter->outsize - 10;
244                 
245                 priv->state.zip.wrote_hdr = TRUE;
246         } else {
247                 g_mime_filter_set_size (filter, (len * 2) + 12, FALSE);
248                 
249                 priv->stream->next_out = (unsigned char *) filter->outbuf;
250                 priv->stream->avail_out = filter->outsize;
251         }
252         
253         priv->stream->next_in = (unsigned char *) in;
254         priv->stream->avail_in = len;
255         
256         do {
257                 /* FIXME: handle error cases? */
258                 if ((retval = deflate (priv->stream, flush)) != Z_OK)
259                         w(fprintf (stderr, "gzip: %d: %s\n", retval, priv->stream->msg));
260                 
261                 if (flush == Z_FULL_FLUSH) {
262                         size_t outlen;
263                         
264                         outlen = filter->outsize - priv->stream->avail_out;
265                         g_mime_filter_set_size (filter, outlen + (priv->stream->avail_in * 2) + 12, TRUE);
266                         priv->stream->next_out = (unsigned char *) filter->outbuf + outlen;
267                         priv->stream->avail_out = filter->outsize - outlen;
268                         
269                         if (priv->stream->avail_in == 0) {
270                                 guint32 val;
271                                 
272                                 val = GUINT32_TO_LE (priv->crc32);
273                                 memcpy (priv->stream->next_out, &val, 4);
274                                 priv->stream->avail_out -= 4;
275                                 priv->stream->next_out += 4;
276                                 
277                                 val = GUINT32_TO_LE (priv->isize);
278                                 memcpy (priv->stream->next_out, &val, 4);
279                                 priv->stream->avail_out -= 4;
280                                 priv->stream->next_out += 4;
281                                 
282                                 break;
283                         }
284                 } else {
285                         if (priv->stream->avail_in > 0)
286                                 g_mime_filter_backup (filter, (char *) priv->stream->next_in,
287                                                       priv->stream->avail_in);
288                         
289                         break;
290                 }
291         } while (1);
292         
293         priv->crc32 = crc32 (priv->crc32, (unsigned char *) in, len - priv->stream->avail_in);
294         priv->isize += len - priv->stream->avail_in;
295         
296         *out = filter->outbuf;
297         *outlen = filter->outsize - priv->stream->avail_out;
298         *outprespace = filter->outpre;
299 }
300
301 static void
302 gunzip_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
303                char **out, size_t *outlen, size_t *outprespace, int flush)
304 {
305         GMimeFilterGZip *gzip = (GMimeFilterGZip *) filter;
306         struct _GMimeFilterGZipPrivate *priv = gzip->priv;
307         guint16 need, val;
308         int retval;
309         
310         if (!priv->state.unzip.got_hdr) {
311                 if (len < 10) {
312                         g_mime_filter_backup (filter, in, len);
313                         return;
314                 }
315                 
316                 memcpy (priv->hdr.buf, in, 10);
317                 priv->state.unzip.got_hdr = TRUE;
318                 len -= 10;
319                 in += 10;
320                 
321                 priv->state.unzip.is_valid = (priv->hdr.v.id1 == 31 &&
322                                               priv->hdr.v.id2 == 139 &&
323                                               priv->hdr.v.cm == Z_DEFLATED);
324         }
325         
326         if (!priv->state.unzip.is_valid)
327                 return;
328         
329         if (priv->hdr.v.flg & GZIP_FLAG_FEXTRA) {
330                 if (!priv->state.unzip.got_xlen) {
331                         if (len < 2) {
332                                 g_mime_filter_backup (filter, in, len);
333                                 return;
334                         }
335                         
336                         memcpy (&val, in, 2);
337                         priv->state.unzip.xlen = GUINT16_FROM_LE (val);
338                         priv->state.unzip.got_xlen = TRUE;
339                         len -= 2;
340                         in += 2;
341                 }
342                 
343                 if (priv->state.unzip.xlen_nread < priv->state.unzip.xlen) {
344                         need = priv->state.unzip.xlen - priv->state.unzip.xlen_nread;
345                         
346                         if (need < len) {
347                                 priv->state.unzip.xlen_nread += need;
348                                 len -= need;
349                                 in += need;
350                         } else {
351                                 priv->state.unzip.xlen_nread += len;
352                                 return;
353                         }
354                 }
355         }
356         
357         if ((priv->hdr.v.flg & GZIP_FLAG_FNAME) && !priv->state.unzip.got_fname) {
358                 while (*in && len > 0) {
359                         len--;
360                         in++;
361                 }
362                 
363                 if (*in == '\0' && len > 0) {
364                         priv->state.unzip.got_fname = TRUE;
365                         len--;
366                         in++;
367                 } else {
368                         return;
369                 }
370         }
371         
372         if ((priv->hdr.v.flg & GZIP_FLAG_FCOMMENT) && !priv->state.unzip.got_fcomment) {
373                 while (*in && len > 0) {
374                         len--;
375                         in++;
376                 }
377                 
378                 if (*in == '\0' && len > 0) {
379                         priv->state.unzip.got_fcomment = TRUE;
380                         len--;
381                         in++;
382                 } else {
383                         return;
384                 }
385         }
386         
387         if ((priv->hdr.v.flg & GZIP_FLAG_FHCRC) && !priv->state.unzip.got_crc16) {
388                 if (len < 2) {
389                         g_mime_filter_backup (filter, in, len);
390                         return;
391                 }
392                 
393                 memcpy (&val, in, 2);
394                 priv->state.unzip.crc16 = GUINT16_FROM_LE (val);
395                 len -= 2;
396                 in += 2;
397         }
398         
399         if (len == 0)
400                 return;
401         
402         g_mime_filter_set_size (filter, (len * 2) + 12, FALSE);
403         
404         priv->stream->next_in = (unsigned char *) in;
405         priv->stream->avail_in = len - 8;
406         
407         priv->stream->next_out = (unsigned char *) filter->outbuf;
408         priv->stream->avail_out = filter->outsize;
409         
410         do {
411                 /* FIXME: handle error cases? */
412                 /* Note: Z_BUF_ERROR is not really an error unless there is input available */
413                 if ((retval = inflate (priv->stream, flush)) != Z_OK &&
414                     !(retval == Z_BUF_ERROR && !priv->stream->avail_in))
415                         w(fprintf (stderr, "gunzip: %d: %s\n", retval, priv->stream->msg));
416                 
417                 if (flush == Z_FULL_FLUSH) {
418                         size_t outlen;
419                         
420                         if (priv->stream->avail_in == 0) {
421                                 /* FIXME: extract & compare calculated crc32 and isize values? */
422                                 break;
423                         }
424                         
425                         outlen = filter->outsize - priv->stream->avail_out;
426                         g_mime_filter_set_size (filter, outlen + (priv->stream->avail_in * 2) + 12, TRUE);
427                         priv->stream->next_out = (unsigned char *) filter->outbuf + outlen;
428                         priv->stream->avail_out = filter->outsize - outlen;
429                 } else {
430                         priv->stream->avail_in += 8;
431                         
432                         if (priv->stream->avail_in > 0)
433                                 g_mime_filter_backup (filter, (char *) priv->stream->next_in,
434                                                       priv->stream->avail_in);
435                         
436                         break;
437                 }
438         } while (1);
439         
440         /* FIXME: if we keep this, we could check that the gzip'd
441          * stream is sane, but how would we tell our consumer if it
442          * was/wasn't? */
443         /*priv->crc32 = crc32 (priv->crc32, in, len - priv->stream->avail_in - 8);
444           priv->isize += len - priv->stream->avail_in - 8;*/
445         
446         *out = filter->outbuf;
447         *outlen = filter->outsize - priv->stream->avail_out;
448         *outprespace = filter->outpre;
449 }
450
451 static void
452 filter_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
453                char **out, size_t *outlen, size_t *outprespace)
454 {
455         GMimeFilterGZip *gzip = (GMimeFilterGZip *) filter;
456         
457         if (gzip->mode == GMIME_FILTER_GZIP_MODE_ZIP)
458                 gzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_SYNC_FLUSH);
459         else
460                 gunzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_SYNC_FLUSH);
461 }
462
463 static void
464 filter_complete (GMimeFilter *filter, char *in, size_t len, size_t prespace,
465                  char **out, size_t *outlen, size_t *outprespace)
466 {
467         GMimeFilterGZip *gzip = (GMimeFilterGZip *) filter;
468         
469         if (gzip->mode == GMIME_FILTER_GZIP_MODE_ZIP)
470                 gzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_FULL_FLUSH);
471         else
472                 gunzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_FULL_FLUSH);
473 }
474
475 /* should this 'flush' outstanding state/data bytes? */
476 static void
477 filter_reset (GMimeFilter *filter)
478 {
479         GMimeFilterGZip *gzip = (GMimeFilterGZip *) filter;
480         struct _GMimeFilterGZipPrivate *priv = gzip->priv;
481         
482         memset (&priv->state, 0, sizeof (priv->state));
483         
484         if (gzip->mode == GMIME_FILTER_GZIP_MODE_ZIP)
485                 deflateReset (priv->stream);
486         else
487                 inflateReset (priv->stream);
488         
489         priv->crc32 = crc32 (0, Z_NULL, 0);
490         priv->isize = 0;
491 }
492
493
494 /**
495  * g_mime_filter_gzip_new:
496  * @mode: zip or unzip
497  * @level: compression level
498  *
499  * Creates a new gzip (or gunzip) filter.
500  *
501  * Returns: a new gzip (or gunzip) filter.
502  **/
503 GMimeFilter *
504 g_mime_filter_gzip_new (GMimeFilterGZipMode mode, int level)
505 {
506         GMimeFilterGZip *new;
507         int retval;
508         
509         new = g_object_newv (GMIME_TYPE_FILTER_GZIP, 0, NULL);
510         new->mode = mode;
511         new->level = level;
512         
513         if (mode == GMIME_FILTER_GZIP_MODE_ZIP)
514                 retval = deflateInit2 (new->priv->stream, level, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
515         else
516                 retval = inflateInit2 (new->priv->stream, -MAX_WBITS);
517         
518         if (retval != Z_OK) {
519                 g_object_unref (new);
520                 return NULL;
521         }
522         
523         return (GMimeFilter *) new;
524 }