/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Authors: Jeffrey Stedfast <fejj@ximian.com>
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <zlib.h>
#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;
}