Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-mime-filter-gzip.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *
5  *  Copyright 2001-2004 Ximian, Inc. (www.ximian.com)
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU Lesser General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdio.h>
29 #include <string.h>
30
31 #include <zlib.h>
32
33 #include "camel-mime-filter-gzip.h"
34
35
36 /* rfc1952 */
37
38 enum {
39         GZIP_FLAG_FTEXT     = (1 << 0),
40         GZIP_FLAG_FHCRC     = (1 << 1),
41         GZIP_FLAG_FEXTRA    = (1 << 2),
42         GZIP_FLAG_FNAME     = (1 << 3),
43         GZIP_FLAG_FCOMMENT  = (1 << 4),
44         GZIP_FLAG_RESERVED0 = (1 << 5),
45         GZIP_FLAG_RESERVED1 = (1 << 6),
46         GZIP_FLAG_RESERVED2 = (1 << 7),
47 };
48
49 #define GZIP_FLAG_RESERVED (GZIP_FLAG_RESERVED0 | GZIP_FLAG_RESERVED1 | GZIP_FLAG_RESERVED2)
50
51 typedef union {
52         unsigned char buf[10];
53         struct {
54                 guint8 id1;
55                 guint8 id2;
56                 guint8 cm;
57                 guint8 flg;
58                 guint32 mtime;
59                 guint8 xfl;
60                 guint8 os;
61         } v;
62 } gzip_hdr_t;
63
64 typedef union {
65         struct {
66                 guint16 xlen;
67                 guint16 xlen_nread;
68                 guint16 crc16;
69                 
70                 guint8 got_hdr:1;
71                 guint8 is_valid:1;
72                 guint8 got_xlen:1;
73                 guint8 got_fname:1;
74                 guint8 got_fcomment:1;
75                 guint8 got_crc16:1;
76         } unzip;
77         struct {
78                 guint32 wrote_hdr:1;
79         } zip;
80 } gzip_state_t;
81
82 struct _CamelMimeFilterGZipPrivate {
83         z_stream *stream;
84         
85         gzip_state_t state;
86         gzip_hdr_t hdr;
87         
88         guint32 crc32;
89         guint32 isize;
90 };
91
92 static void camel_mime_filter_gzip_class_init (CamelMimeFilterGZipClass *klass);
93 static void camel_mime_filter_gzip_init (CamelMimeFilterGZip *filter, CamelMimeFilterGZipClass *klass);
94 static void camel_mime_filter_gzip_finalize (CamelObject *object);
95
96 static void filter_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
97                            char **out, size_t *outlen, size_t *outprespace);
98 static void filter_complete (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
99                              char **out, size_t *outlen, size_t *outprespace);
100 static void filter_reset (CamelMimeFilter *filter);
101
102
103 static CamelMimeFilterClass *parent_class = NULL;
104
105
106 CamelType
107 camel_mime_filter_gzip_get_type (void)
108 {
109         static CamelType type = CAMEL_INVALID_TYPE;
110         
111         if (type == CAMEL_INVALID_TYPE) {
112                 type = camel_type_register (camel_mime_filter_get_type (),
113                                             "CamelMimeFilterGZip",
114                                             sizeof (CamelMimeFilterGZip),
115                                             sizeof (CamelMimeFilterGZipClass),
116                                             (CamelObjectClassInitFunc) camel_mime_filter_gzip_class_init,
117                                             NULL,
118                                             (CamelObjectInitFunc) camel_mime_filter_gzip_init,
119                                             (CamelObjectFinalizeFunc) camel_mime_filter_gzip_finalize);
120         }
121         
122         return type;
123 }
124
125
126 static void
127 camel_mime_filter_gzip_class_init (CamelMimeFilterGZipClass *klass)
128 {
129         CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass;
130         
131         parent_class = CAMEL_MIME_FILTER_CLASS (camel_type_get_global_classfuncs (camel_mime_filter_get_type ()));
132         
133         filter_class->reset = filter_reset;
134         filter_class->filter = filter_filter;
135         filter_class->complete = filter_complete;
136 }
137
138 static void
139 camel_mime_filter_gzip_init (CamelMimeFilterGZip *filter, CamelMimeFilterGZipClass *klass)
140 {
141         filter->priv = g_new0 (struct _CamelMimeFilterGZipPrivate, 1);
142         filter->priv->stream = g_new0 (z_stream, 1);
143         filter->priv->crc32 = crc32 (0, Z_NULL, 0);
144 }
145
146 static void
147 camel_mime_filter_gzip_finalize (CamelObject *object)
148 {
149         CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) object;
150         struct _CamelMimeFilterGZipPrivate *priv = gzip->priv;
151         
152         if (gzip->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
153                 deflateEnd (priv->stream);
154         else
155                 inflateEnd (priv->stream);
156         
157         g_free (priv->stream);
158         g_free (priv);
159 }
160
161
162 static void
163 gzip_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
164              char **out, size_t *outlen, size_t *outprespace, int flush)
165 {
166         CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) filter;
167         struct _CamelMimeFilterGZipPrivate *priv = gzip->priv;
168         int retval;
169         
170         if (!priv->state.zip.wrote_hdr) {
171                 priv->hdr.v.id1 = 31;
172                 priv->hdr.v.id2 = 139;
173                 priv->hdr.v.cm = Z_DEFLATED;
174                 priv->hdr.v.mtime = 0;
175                 priv->hdr.v.flg = 0;
176                 if (gzip->level == Z_BEST_COMPRESSION)
177                         priv->hdr.v.xfl = 2;
178                 else if (gzip->level == Z_BEST_SPEED)
179                         priv->hdr.v.xfl = 4;
180                 else
181                         priv->hdr.v.xfl = 0;
182                 priv->hdr.v.os = 255;
183                 
184                 camel_mime_filter_set_size (filter, (len * 2) + 22, FALSE);
185                 
186                 memcpy (filter->outbuf, priv->hdr.buf, 10);
187                 
188                 priv->stream->next_out = filter->outbuf + 10;
189                 priv->stream->avail_out = filter->outsize - 10;
190                 
191                 priv->state.zip.wrote_hdr = TRUE;
192         } else {
193                 camel_mime_filter_set_size (filter, (len * 2) + 12, FALSE);
194                 
195                 priv->stream->next_out = filter->outbuf;
196                 priv->stream->avail_out = filter->outsize;
197         }
198         
199         priv->stream->next_in = in;
200         priv->stream->avail_in = len;
201         
202         do {
203                 /* FIXME: handle error cases? */
204                 if ((retval = deflate (priv->stream, flush)) != Z_OK)
205                         fprintf (stderr, "gzip: %d: %s\n", retval, priv->stream->msg);
206                 
207                 if (flush == Z_FULL_FLUSH) {
208                         size_t n;
209                         
210                         n = filter->outsize - priv->stream->avail_out;
211                         camel_mime_filter_set_size (filter, n + (priv->stream->avail_in * 2) + 12, TRUE);
212                         priv->stream->avail_out = filter->outsize - n;
213                         priv->stream->next_out = filter->outbuf + n;
214                         
215                         if (priv->stream->avail_in == 0) {
216                                 guint32 val;
217                                 
218                                 val = GUINT32_TO_LE (priv->crc32);
219                                 memcpy (priv->stream->next_out, &val, 4);
220                                 priv->stream->avail_out -= 4;
221                                 priv->stream->next_out += 4;
222                                 
223                                 val = GUINT32_TO_LE (priv->isize);
224                                 memcpy (priv->stream->next_out, &val, 4);
225                                 priv->stream->avail_out -= 4;
226                                 priv->stream->next_out += 4;
227                                 
228                                 break;
229                         }
230                 } else {
231                         if (priv->stream->avail_in > 0)
232                                 camel_mime_filter_backup (filter, priv->stream->next_in, priv->stream->avail_in);
233                         
234                         break;
235                 }
236         } while (1);
237         
238         priv->crc32 = crc32 (priv->crc32, in, len - priv->stream->avail_in);
239         priv->isize += len - priv->stream->avail_in;
240         
241         *out = filter->outbuf;
242         *outlen = filter->outsize - priv->stream->avail_out;
243         *outprespace = filter->outpre;
244 }
245
246 static void
247 gunzip_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
248                char **out, size_t *outlen, size_t *outprespace, int flush)
249 {
250         CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) filter;
251         struct _CamelMimeFilterGZipPrivate *priv = gzip->priv;
252         guint16 need, val;
253         int retval;
254         
255         if (!priv->state.unzip.got_hdr) {
256                 if (len < 10) {
257                         camel_mime_filter_backup (filter, in, len);
258                         return;
259                 }
260                 
261                 memcpy (priv->hdr.buf, in, 10);
262                 priv->state.unzip.got_hdr = TRUE;
263                 len -= 10;
264                 in += 10;
265                 
266                 priv->state.unzip.is_valid = (priv->hdr.v.id1 == 31 &&
267                                               priv->hdr.v.id2 == 139 &&
268                                               priv->hdr.v.cm == Z_DEFLATED);
269         }
270         
271         if (!priv->state.unzip.is_valid)
272                 return;
273         
274         if (priv->hdr.v.flg & GZIP_FLAG_FEXTRA) {
275                 if (!priv->state.unzip.got_xlen) {
276                         if (len < 2) {
277                                 camel_mime_filter_backup (filter, in, len);
278                                 return;
279                         }
280                         
281                         memcpy (&val, in, 2);
282                         priv->state.unzip.xlen = GUINT16_FROM_LE (val);
283                         priv->state.unzip.got_xlen = TRUE;
284                         len -= 2;
285                         in += 2;
286                 }
287                 
288                 if (priv->state.unzip.xlen_nread < priv->state.unzip.xlen) {
289                         need = priv->state.unzip.xlen - priv->state.unzip.xlen_nread;
290                         
291                         if (need < len) {
292                                 priv->state.unzip.xlen_nread += need;
293                                 len -= need;
294                                 in += need;
295                         } else {
296                                 priv->state.unzip.xlen_nread += len;
297                                 return;
298                         }
299                 }
300         }
301         
302         if ((priv->hdr.v.flg & GZIP_FLAG_FNAME) && !priv->state.unzip.got_fname) {
303                 while (*in && len > 0) {
304                         len--;
305                         in++;
306                 }
307                 
308                 if (*in == '\0' && len > 0) {
309                         priv->state.unzip.got_fname = TRUE;
310                         len--;
311                         in++;
312                 } else {
313                         return;
314                 }
315         }
316         
317         if ((priv->hdr.v.flg & GZIP_FLAG_FCOMMENT) && !priv->state.unzip.got_fcomment) {
318                 while (*in && len > 0) {
319                         len--;
320                         in++;
321                 }
322                 
323                 if (*in == '\0' && len > 0) {
324                         priv->state.unzip.got_fcomment = TRUE;
325                         len--;
326                         in++;
327                 } else {
328                         return;
329                 }
330         }
331         
332         if ((priv->hdr.v.flg & GZIP_FLAG_FHCRC) && !priv->state.unzip.got_crc16) {
333                 if (len < 2) {
334                         camel_mime_filter_backup (filter, in, len);
335                         return;
336                 }
337                 
338                 memcpy (&val, in, 2);
339                 priv->state.unzip.crc16 = GUINT16_FROM_LE (val);
340                 len -= 2;
341                 in += 2;
342         }
343         
344         if (len == 0)
345                 return;
346         
347         camel_mime_filter_set_size (filter, (len * 2) + 12, FALSE);
348         
349         priv->stream->next_in = in;
350         priv->stream->avail_in = len - 8;
351         
352         priv->stream->next_out = filter->outbuf;
353         priv->stream->avail_out = filter->outsize;
354         
355         do {
356                 /* FIXME: handle error cases? */
357                 if ((retval = inflate (priv->stream, flush)) != Z_OK)
358                         fprintf (stderr, "gunzip: %d: %s\n", retval, priv->stream->msg);
359                 
360                 if (flush == Z_FULL_FLUSH) {
361                         size_t n;
362                         
363                         if (priv->stream->avail_in == 0) {
364                                 /* FIXME: extract & compare calculated crc32 and isize values? */
365                                 break;
366                         }
367                         
368                         n = filter->outsize - priv->stream->avail_out;
369                         camel_mime_filter_set_size (filter, n + (priv->stream->avail_in * 2) + 12, TRUE);
370                         priv->stream->avail_out = filter->outsize - n;
371                         priv->stream->next_out = filter->outbuf + n;
372                 } else {
373                         priv->stream->avail_in += 8;
374                         
375                         if (priv->stream->avail_in > 0)
376                                 camel_mime_filter_backup (filter, priv->stream->next_in, priv->stream->avail_in);
377                         
378                         break;
379                 }
380         } while (1);
381         
382         /* FIXME: if we keep this, we could check that the gzip'd
383          * stream is sane, but how would we tell our consumer if it
384          * was/wasn't? */
385         /*priv->crc32 = crc32 (priv->crc32, in, len - priv->stream->avail_in - 8);
386           priv->isize += len - priv->stream->avail_in - 8;*/
387         
388         *out = filter->outbuf;
389         *outlen = filter->outsize - priv->stream->avail_out;
390         *outprespace = filter->outpre;
391 }
392
393 static void
394 filter_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
395                char **out, size_t *outlen, size_t *outprespace)
396 {
397         CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) filter;
398         
399         if (gzip->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
400                 gzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_SYNC_FLUSH);
401         else
402                 gunzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_SYNC_FLUSH);
403 }
404
405 static void
406 filter_complete (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
407                  char **out, size_t *outlen, size_t *outprespace)
408 {
409         CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) filter;
410         
411         if (gzip->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
412                 gzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_FULL_FLUSH);
413         else
414                 gunzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_FULL_FLUSH);
415 }
416
417 /* should this 'flush' outstanding state/data bytes? */
418 static void
419 filter_reset (CamelMimeFilter *filter)
420 {
421         CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) filter;
422         struct _CamelMimeFilterGZipPrivate *priv = gzip->priv;
423         
424         memset (&priv->state, 0, sizeof (priv->state));
425         
426         if (gzip->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
427                 deflateReset (priv->stream);
428         else
429                 inflateReset (priv->stream);
430         
431         priv->crc32 = crc32 (0, Z_NULL, 0);
432         priv->isize = 0;
433 }
434
435
436 /**
437  * camel_mime_filter_gzip_new:
438  * @mode: zip or unzip
439  * @level: compression level
440  *
441  * Creates a new gzip (or gunzip) filter.
442  *
443  * Returns a new gzip (or gunzip) filter.
444  **/
445 CamelMimeFilter *
446 camel_mime_filter_gzip_new (CamelMimeFilterGZipMode mode, int level)
447 {
448         CamelMimeFilterGZip *new;
449         int retval;
450         
451         new = (CamelMimeFilterGZip *) camel_object_new (CAMEL_TYPE_MIME_FILTER_GZIP);
452         new->mode = mode;
453         new->level = level;
454         
455         if (mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
456                 retval = deflateInit2 (new->priv->stream, level, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
457         else
458                 retval = inflateInit2 (new->priv->stream, -MAX_WBITS);
459         
460         if (retval != Z_OK) {
461                 camel_object_unref (new);
462                 return NULL;
463         }
464         
465         return (CamelMimeFilter *) new;
466 }