/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors: Jeffrey Stedfast * * Copyright 2001-2004 Ximian, Inc. (www.ximian.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "camel-mime-filter-gzip.h" /* rfc1952 */ enum { GZIP_FLAG_FTEXT = (1 << 0), GZIP_FLAG_FHCRC = (1 << 1), GZIP_FLAG_FEXTRA = (1 << 2), GZIP_FLAG_FNAME = (1 << 3), GZIP_FLAG_FCOMMENT = (1 << 4), GZIP_FLAG_RESERVED0 = (1 << 5), GZIP_FLAG_RESERVED1 = (1 << 6), GZIP_FLAG_RESERVED2 = (1 << 7), }; #define GZIP_FLAG_RESERVED (GZIP_FLAG_RESERVED0 | GZIP_FLAG_RESERVED1 | GZIP_FLAG_RESERVED2) typedef union { unsigned char buf[10]; struct { guint8 id1; guint8 id2; guint8 cm; guint8 flg; guint32 mtime; guint8 xfl; guint8 os; } v; } gzip_hdr_t; typedef union { struct { guint16 xlen; guint16 xlen_nread; guint16 crc16; guint8 got_hdr:1; guint8 is_valid:1; guint8 got_xlen:1; guint8 got_fname:1; guint8 got_fcomment:1; guint8 got_crc16:1; } unzip; struct { guint32 wrote_hdr:1; } zip; } gzip_state_t; struct _CamelMimeFilterGZipPrivate { z_stream *stream; gzip_state_t state; gzip_hdr_t hdr; guint32 crc32; guint32 isize; }; static void camel_mime_filter_gzip_class_init (CamelMimeFilterGZipClass *klass); static void camel_mime_filter_gzip_init (CamelMimeFilterGZip *filter, CamelMimeFilterGZipClass *klass); static void camel_mime_filter_gzip_finalize (CamelObject *object); static void filter_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace); static void filter_complete (CamelMimeFilter *filter, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace); static void filter_reset (CamelMimeFilter *filter); static CamelMimeFilterClass *parent_class = NULL; CamelType camel_mime_filter_gzip_get_type (void) { static CamelType type = CAMEL_INVALID_TYPE; if (type == CAMEL_INVALID_TYPE) { type = camel_type_register (camel_mime_filter_get_type (), "CamelMimeFilterGZip", sizeof (CamelMimeFilterGZip), sizeof (CamelMimeFilterGZipClass), (CamelObjectClassInitFunc) camel_mime_filter_gzip_class_init, NULL, (CamelObjectInitFunc) camel_mime_filter_gzip_init, (CamelObjectFinalizeFunc) camel_mime_filter_gzip_finalize); } return type; } static void camel_mime_filter_gzip_class_init (CamelMimeFilterGZipClass *klass) { CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass; parent_class = CAMEL_MIME_FILTER_CLASS (camel_type_get_global_classfuncs (camel_mime_filter_get_type ())); filter_class->reset = filter_reset; filter_class->filter = filter_filter; filter_class->complete = filter_complete; } static void camel_mime_filter_gzip_init (CamelMimeFilterGZip *filter, CamelMimeFilterGZipClass *klass) { filter->priv = g_new0 (struct _CamelMimeFilterGZipPrivate, 1); filter->priv->stream = g_new0 (z_stream, 1); filter->priv->crc32 = crc32 (0, Z_NULL, 0); } static void camel_mime_filter_gzip_finalize (CamelObject *object) { CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) object; struct _CamelMimeFilterGZipPrivate *priv = gzip->priv; if (gzip->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP) deflateEnd (priv->stream); else inflateEnd (priv->stream); g_free (priv->stream); g_free (priv); } static void gzip_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace, int flush) { CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) filter; struct _CamelMimeFilterGZipPrivate *priv = gzip->priv; int retval; if (!priv->state.zip.wrote_hdr) { priv->hdr.v.id1 = 31; priv->hdr.v.id2 = 139; priv->hdr.v.cm = Z_DEFLATED; priv->hdr.v.mtime = 0; priv->hdr.v.flg = 0; if (gzip->level == Z_BEST_COMPRESSION) priv->hdr.v.xfl = 2; else if (gzip->level == Z_BEST_SPEED) priv->hdr.v.xfl = 4; else priv->hdr.v.xfl = 0; priv->hdr.v.os = 255; camel_mime_filter_set_size (filter, (len * 2) + 22, FALSE); memcpy (filter->outbuf, priv->hdr.buf, 10); priv->stream->next_out = filter->outbuf + 10; priv->stream->avail_out = filter->outsize - 10; priv->state.zip.wrote_hdr = TRUE; } else { camel_mime_filter_set_size (filter, (len * 2) + 12, FALSE); priv->stream->next_out = filter->outbuf; priv->stream->avail_out = filter->outsize; } priv->stream->next_in = in; priv->stream->avail_in = len; do { /* FIXME: handle error cases? */ if ((retval = deflate (priv->stream, flush)) != Z_OK) fprintf (stderr, "gzip: %d: %s\n", retval, priv->stream->msg); if (flush == Z_FULL_FLUSH) { size_t outlen; outlen = filter->outsize - priv->stream->avail_out; camel_mime_filter_set_size (filter, outlen + (priv->stream->avail_in * 2) + 12, TRUE); priv->stream->avail_out = filter->outsize - outlen; priv->stream->next_out = filter->outbuf + outlen; if (priv->stream->avail_in == 0) { guint32 val; val = GUINT32_TO_LE (priv->crc32); memcpy (priv->stream->next_out, &val, 4); priv->stream->avail_out -= 4; priv->stream->next_out += 4; val = GUINT32_TO_LE (priv->isize); memcpy (priv->stream->next_out, &val, 4); priv->stream->avail_out -= 4; priv->stream->next_out += 4; break; } } else { if (priv->stream->avail_in > 0) camel_mime_filter_backup (filter, priv->stream->next_in, priv->stream->avail_in); break; } } while (1); priv->crc32 = crc32 (priv->crc32, in, len - priv->stream->avail_in); priv->isize += len - priv->stream->avail_in; *out = filter->outbuf; *outlen = filter->outsize - priv->stream->avail_out; *outprespace = filter->outpre; } static void gunzip_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace, int flush) { CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) filter; struct _CamelMimeFilterGZipPrivate *priv = gzip->priv; guint16 need, val; int retval; if (!priv->state.unzip.got_hdr) { if (len < 10) { camel_mime_filter_backup (filter, in, len); return; } memcpy (priv->hdr.buf, in, 10); priv->state.unzip.got_hdr = TRUE; len -= 10; in += 10; priv->state.unzip.is_valid = (priv->hdr.v.id1 == 31 && priv->hdr.v.id2 == 139 && priv->hdr.v.cm == Z_DEFLATED); } if (!priv->state.unzip.is_valid) return; if (priv->hdr.v.flg & GZIP_FLAG_FEXTRA) { if (!priv->state.unzip.got_xlen) { if (len < 2) { camel_mime_filter_backup (filter, in, len); return; } memcpy (&val, in, 2); priv->state.unzip.xlen = GUINT16_FROM_LE (val); priv->state.unzip.got_xlen = TRUE; len -= 2; in += 2; } if (priv->state.unzip.xlen_nread < priv->state.unzip.xlen) { need = priv->state.unzip.xlen - priv->state.unzip.xlen_nread; if (need < len) { priv->state.unzip.xlen_nread += need; len -= need; in += need; } else { priv->state.unzip.xlen_nread += len; return; } } } if ((priv->hdr.v.flg & GZIP_FLAG_FNAME) && !priv->state.unzip.got_fname) { while (*in && len > 0) { len--; in++; } if (*in == '\0' && len > 0) { priv->state.unzip.got_fname = TRUE; len--; in++; } else { return; } } if ((priv->hdr.v.flg & GZIP_FLAG_FCOMMENT) && !priv->state.unzip.got_fcomment) { while (*in && len > 0) { len--; in++; } if (*in == '\0' && len > 0) { priv->state.unzip.got_fcomment = TRUE; len--; in++; } else { return; } } if ((priv->hdr.v.flg & GZIP_FLAG_FHCRC) && !priv->state.unzip.got_crc16) { if (len < 2) { camel_mime_filter_backup (filter, in, len); return; } memcpy (&val, in, 2); priv->state.unzip.crc16 = GUINT16_FROM_LE (val); len -= 2; in += 2; } if (len == 0) return; camel_mime_filter_set_size (filter, (len * 2) + 12, FALSE); priv->stream->next_in = in; priv->stream->avail_in = len - 8; priv->stream->next_out = filter->outbuf; priv->stream->avail_out = filter->outsize; do { /* FIXME: handle error cases? */ if ((retval = inflate (priv->stream, flush)) != Z_OK) fprintf (stderr, "gunzip: %d: %s\n", retval, priv->stream->msg); if (flush == Z_FULL_FLUSH) { size_t outlen; if (priv->stream->avail_in == 0) { /* FIXME: extract & compare calculated crc32 and isize values? */ break; } outlen = filter->outsize - priv->stream->avail_out; camel_mime_filter_set_size (filter, outlen + (priv->stream->avail_in * 2) + 12, TRUE); priv->stream->avail_out = filter->outsize - outlen; priv->stream->next_out = filter->outbuf + outlen; } else { priv->stream->avail_in += 8; if (priv->stream->avail_in > 0) camel_mime_filter_backup (filter, priv->stream->next_in, priv->stream->avail_in); break; } } while (1); /* FIXME: if we keep this, we could check that the gzip'd * stream is sane, but how would we tell our consumer if it * was/wasn't? */ /*priv->crc32 = crc32 (priv->crc32, in, len - priv->stream->avail_in - 8); priv->isize += len - priv->stream->avail_in - 8;*/ *out = filter->outbuf; *outlen = filter->outsize - priv->stream->avail_out; *outprespace = filter->outpre; } static void filter_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace) { CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) filter; if (gzip->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP) gzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_SYNC_FLUSH); else gunzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_SYNC_FLUSH); } static void filter_complete (CamelMimeFilter *filter, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace) { CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) filter; if (gzip->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP) gzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_FULL_FLUSH); else gunzip_filter (filter, in, len, prespace, out, outlen, outprespace, Z_FULL_FLUSH); } /* should this 'flush' outstanding state/data bytes? */ static void filter_reset (CamelMimeFilter *filter) { CamelMimeFilterGZip *gzip = (CamelMimeFilterGZip *) filter; struct _CamelMimeFilterGZipPrivate *priv = gzip->priv; memset (&priv->state, 0, sizeof (priv->state)); if (gzip->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP) deflateReset (priv->stream); else inflateReset (priv->stream); priv->crc32 = crc32 (0, Z_NULL, 0); priv->isize = 0; } /** * camel_mime_filter_gzip_new: * @mode: zip or unzip * @level: compression level * * Creates a new gzip (or gunzip) filter. * * Returns a new gzip (or gunzip) filter. **/ CamelMimeFilter * camel_mime_filter_gzip_new (CamelMimeFilterGZipMode mode, int level) { CamelMimeFilterGZip *new; int retval; new = (CamelMimeFilterGZip *) camel_object_new (CAMEL_TYPE_MIME_FILTER_GZIP); new->mode = mode; new->level = level; if (mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP) retval = deflateInit2 (new->priv->stream, level, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); else retval = inflateInit2 (new->priv->stream, -MAX_WBITS); if (retval != Z_OK) { camel_object_unref (new); return NULL; } return (CamelMimeFilter *) new; }