summaryrefslogtreecommitdiff
path: root/src/camel/camel-mime-filter-gzip.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/camel/camel-mime-filter-gzip.c')
-rw-r--r--src/camel/camel-mime-filter-gzip.c482
1 files changed, 482 insertions, 0 deletions
diff --git a/src/camel/camel-mime-filter-gzip.c b/src/camel/camel-mime-filter-gzip.c
new file mode 100644
index 000000000..8e8c9e1fe
--- /dev/null
+++ b/src/camel/camel-mime-filter-gzip.c
@@ -0,0 +1,482 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ */
+
+#include "evolution-data-server-config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <zlib.h>
+
+#include "camel-mime-filter-gzip.h"
+
+#define CAMEL_MIME_FILTER_GZIP_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), CAMEL_TYPE_MIME_FILTER_GZIP, CamelMimeFilterGZipPrivate))
+
+/* 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 {
+ guchar 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 {
+
+ CamelMimeFilterGZipMode mode;
+ gint level;
+
+ z_stream *stream;
+
+ gzip_state_t state;
+ gzip_hdr_t hdr;
+
+ guint32 crc32;
+ guint32 isize;
+};
+
+G_DEFINE_TYPE (CamelMimeFilterGZip, camel_mime_filter_gzip, CAMEL_TYPE_MIME_FILTER)
+
+static void
+gzip_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace,
+ gint flush)
+{
+ CamelMimeFilterGZipPrivate *priv;
+ gint retval;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+
+ 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 (priv->level == Z_BEST_COMPRESSION)
+ priv->hdr.v.xfl = 2;
+ else if (priv->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 (mime_filter, (len * 2) + 22, FALSE);
+
+ memcpy (mime_filter->outbuf, priv->hdr.buf, 10);
+
+ priv->stream->next_out = (Bytef *) mime_filter->outbuf + 10;
+ priv->stream->avail_out = mime_filter->outsize - 10;
+
+ priv->state.zip.wrote_hdr = TRUE;
+ } else {
+ camel_mime_filter_set_size (mime_filter, (len * 2) + 12, FALSE);
+
+ priv->stream->next_out = (Bytef *) mime_filter->outbuf;
+ priv->stream->avail_out = mime_filter->outsize;
+ }
+
+ priv->stream->next_in = (Bytef *) 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) {
+ gsize n;
+
+ n = mime_filter->outsize - priv->stream->avail_out;
+ camel_mime_filter_set_size (mime_filter, n + (priv->stream->avail_in * 2) + 12, TRUE);
+ priv->stream->avail_out = mime_filter->outsize - n;
+ priv->stream->next_out = (Bytef *) mime_filter->outbuf + n;
+
+ 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 (mime_filter, (const gchar *) priv->stream->next_in, priv->stream->avail_in);
+
+ break;
+ }
+ } while (1);
+
+ priv->crc32 = crc32 (priv->crc32, (guchar *) in, len - priv->stream->avail_in);
+ priv->isize += len - priv->stream->avail_in;
+
+ *out = mime_filter->outbuf;
+ *outlen = mime_filter->outsize - priv->stream->avail_out;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+gunzip_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace,
+ gint flush)
+{
+ CamelMimeFilterGZipPrivate *priv;
+ guint16 need, val;
+ gint retval;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+
+ if (!priv->state.unzip.got_hdr) {
+ if (len < 10) {
+ camel_mime_filter_backup (mime_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 (mime_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 (mime_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 (mime_filter, (len * 2) + 12, FALSE);
+
+ priv->stream->next_in = (Bytef *) in;
+ priv->stream->avail_in = len - 8;
+
+ priv->stream->next_out = (Bytef *) mime_filter->outbuf;
+ priv->stream->avail_out = mime_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) {
+ gsize n;
+
+ if (priv->stream->avail_in == 0) {
+ /* FIXME: extract & compare calculated crc32 and isize values? */
+ break;
+ }
+
+ n = mime_filter->outsize - priv->stream->avail_out;
+ camel_mime_filter_set_size (mime_filter, n + (priv->stream->avail_in * 2) + 12, TRUE);
+ priv->stream->avail_out = mime_filter->outsize - n;
+ priv->stream->next_out = (Bytef *) mime_filter->outbuf + n;
+ } else {
+ priv->stream->avail_in += 8;
+
+ if (priv->stream->avail_in > 0)
+ camel_mime_filter_backup (mime_filter, (gchar *) 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 = mime_filter->outbuf;
+ *outlen = mime_filter->outsize - priv->stream->avail_out;
+ *outprespace = mime_filter->outpre;
+}
+
+static void
+mime_filter_gzip_finalize (GObject *object)
+{
+ CamelMimeFilterGZipPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (object);
+
+ if (priv->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
+ deflateEnd (priv->stream);
+ else
+ inflateEnd (priv->stream);
+
+ g_free (priv->stream);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_mime_filter_gzip_parent_class)->finalize (object);
+}
+
+static void
+mime_filter_gzip_filter (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterGZipPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+
+ if (priv->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
+ gzip_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, Z_SYNC_FLUSH);
+ else
+ gunzip_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, Z_SYNC_FLUSH);
+}
+
+static void
+mime_filter_gzip_complete (CamelMimeFilter *mime_filter,
+ const gchar *in,
+ gsize len,
+ gsize prespace,
+ gchar **out,
+ gsize *outlen,
+ gsize *outprespace)
+{
+ CamelMimeFilterGZipPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+
+ if (priv->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
+ gzip_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, Z_FULL_FLUSH);
+ else
+ gunzip_filter (
+ mime_filter, in, len, prespace,
+ out, outlen, outprespace, Z_FULL_FLUSH);
+}
+
+/* should this 'flush' outstanding state/data bytes? */
+static void
+mime_filter_gzip_reset (CamelMimeFilter *mime_filter)
+{
+ CamelMimeFilterGZipPrivate *priv;
+
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+
+ memset (&priv->state, 0, sizeof (priv->state));
+
+ if (priv->mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
+ deflateReset (priv->stream);
+ else
+ inflateReset (priv->stream);
+
+ priv->crc32 = crc32 (0, Z_NULL, 0);
+ priv->isize = 0;
+}
+
+static void
+camel_mime_filter_gzip_class_init (CamelMimeFilterGZipClass *class)
+{
+ GObjectClass *object_class;
+ CamelMimeFilterClass *mime_filter_class;
+
+ g_type_class_add_private (class, sizeof (CamelMimeFilterGZipPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = mime_filter_gzip_finalize;
+
+ mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
+ mime_filter_class->filter = mime_filter_gzip_filter;
+ mime_filter_class->complete = mime_filter_gzip_complete;
+ mime_filter_class->reset = mime_filter_gzip_reset;
+}
+
+static void
+camel_mime_filter_gzip_init (CamelMimeFilterGZip *mime_filter)
+{
+ mime_filter->priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (mime_filter);
+ mime_filter->priv->stream = g_new0 (z_stream, 1);
+ mime_filter->priv->crc32 = crc32 (0, Z_NULL, 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,
+ gint level)
+{
+ CamelMimeFilter *new;
+ CamelMimeFilterGZipPrivate *priv;
+ gint retval;
+
+ new = g_object_new (CAMEL_TYPE_MIME_FILTER_GZIP, NULL);
+ priv = CAMEL_MIME_FILTER_GZIP_GET_PRIVATE (new);
+
+ priv->mode = mode;
+ priv->level = level;
+
+ if (mode == CAMEL_MIME_FILTER_GZIP_MODE_ZIP)
+ retval = deflateInit2 (priv->stream, level, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+ else
+ retval = inflateInit2 (priv->stream, -MAX_WBITS);
+
+ if (retval != Z_OK) {
+ g_object_unref (new);
+ return NULL;
+ }
+
+ return new;
+}