/* $Cambridge: hermes/src/prayer/shared/gzip.c,v 1.3 2008/09/16 09:59:58 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2008 */
/* See the file NOTICE for conditions of use and distribution. */

/* Code stolen from zlib: gzio.c and gzutil.c */

#include "shared.h"

#ifndef GZIP_ENABLE

struct buffer *gzip(struct buffer *input)
{
    log_fatal("gzip() called without gzip support enabled");
    return (NIL);
}
#else
#include <zlib.h>

#define DEF_MEM_LEVEL     8
#define Z_BUFSIZE     16384
#define OS_CODE        0x03     /* assume Unix */

#define ALLOC(size) malloc(size)
#define TRYFREE(p) {if (p) free(p);}
#define F_OPEN(name, mode) fopen((name), (mode))

static int gz_magic[2] = { 0x1f, 0x8b };        /* gzip magic header */

typedef struct gz_stream {
    z_stream stream;
    int z_err;                  /* error code for last stream operation */
    int z_eof;                  /* set if end of input file */
    Byte *inbuf;                /* input buffer */
    Byte *outbuf;               /* output buffer */
    uLong crc;                  /* crc32 of uncompressed data */
    char *msg;                  /* error message */
    int transparent;            /* 1 if input file is not a .gz file */
    struct buffer *output;
} gz_stream;

/* ====================================================================== */

/* Utility functions */

static void putLong(struct buffer *b, uLong x)
{
    int n;
    for (n = 0; n < 4; n++) {
        bputc(b, (int) (x & 0xff));
        x >>= 8;
    }
}

static void bwrite(struct buffer *b, unsigned char *x, unsigned long len)
{
    while (len > 0) {
        bputc(b, *x++);
        len--;
    }
}

static int do_flush(gzFile file, int flush)
{
    uInt len;
    int done = 0;
    gz_stream *s = (gz_stream *) file;

    s->stream.avail_in = 0;     /* should be zero already anyway */

    for (;;) {
        len = Z_BUFSIZE - s->stream.avail_out;

        if (len != 0) {
            bwrite(s->output, s->outbuf, len);
            s->stream.next_out = s->outbuf;
            s->stream.avail_out = Z_BUFSIZE;
        }
        if (done)
            break;
        s->z_err = deflate(&(s->stream), flush);

        /* Ignore the second of two consecutive flushes: */
        if (len == 0 && s->z_err == Z_BUF_ERROR)
            s->z_err = Z_OK;

        /* deflate has finished flushing only when it hasn't used up
         * all the available space in the output buffer: 
         */
        done = (s->stream.avail_out != 0 || s->z_err == Z_STREAM_END);

        if (s->z_err != Z_OK && s->z_err != Z_STREAM_END)
            break;
    }
    return s->z_err == Z_STREAM_END ? Z_OK : s->z_err;
}

static int destroy(gz_stream * s)
{
    int err = Z_OK;

    if (!s)
        return Z_STREAM_ERROR;

    TRYFREE(s->msg);

    if (s->stream.state != NULL)
        err = deflateEnd(&(s->stream));

    if (s->z_err < 0)
        err = s->z_err;

    TRYFREE(s->inbuf);
    TRYFREE(s->outbuf);
    TRYFREE(s);

    return (err);
}

/* ====================================================================== */

static gzFile my_gzopen(struct buffer *output)
{
    int err;
    int level = Z_DEFAULT_COMPRESSION;  /* compression level */
    int strategy = Z_DEFAULT_STRATEGY;  /* compression strategy */
    gz_stream *s;

    s = (gz_stream *) ALLOC(sizeof(gz_stream));
    if (!s)
        return Z_NULL;

    s->stream.zalloc = (alloc_func) 0;
    s->stream.zfree = (free_func) 0;
    s->stream.opaque = (voidpf) 0;
    s->stream.next_in = s->inbuf = Z_NULL;
    s->stream.next_out = s->outbuf = Z_NULL;
    s->stream.avail_in = s->stream.avail_out = 0;
    s->z_err = Z_OK;
    s->z_eof = 0;
    s->crc = crc32(0L, Z_NULL, 0);
    s->msg = NULL;
    s->transparent = 0;
    s->output = output;

    err = deflateInit2(&(s->stream), level,
                       Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, strategy);
    /* windowBits is passed < 0 to suppress zlib header */

    s->stream.next_out = s->outbuf = (Byte *) ALLOC(Z_BUFSIZE);

    if (err != Z_OK || s->outbuf == Z_NULL)
        return destroy(s), (gzFile) Z_NULL;

    s->stream.avail_out = Z_BUFSIZE;
    errno = 0;

    /* Write a very simple .gz header:
     */
    bprintf(s->output, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1],
            Z_DEFLATED, 0 /*flags */ , 0, 0, 0, 0 /*time */ ,
            0 /*xflags */ ,
            OS_CODE);

    return (gzFile) s;
}

static int my_gzwrite(gzFile file, const voidp buf, unsigned len)
{
    gz_stream *s = (gz_stream *) file;

    s->stream.next_in = (Bytef *) buf;
    s->stream.avail_in = len;

    while (s->stream.avail_in != 0) {

        if (s->stream.avail_out == 0) {

            s->stream.next_out = s->outbuf;
            bwrite(s->output, s->outbuf, Z_BUFSIZE);
            s->stream.avail_out = Z_BUFSIZE;
        }
        s->z_err = deflate(&(s->stream), Z_NO_FLUSH);
        if (s->z_err != Z_OK)
            break;
    }
    s->crc = crc32(s->crc, (const Bytef *) buf, len);

    return (int) (len - s->stream.avail_in);
}

static int my_gzclose(gzFile file)
{
    int err;
    gz_stream *s = (gz_stream *) file;

    if (s == NULL)
        return Z_STREAM_ERROR;

    err = do_flush(file, Z_FINISH);
    if (err != Z_OK)
        return destroy((gz_stream *) file);

    putLong(s->output, s->crc);
    putLong(s->output, s->stream.total_in);

    return destroy((gz_stream *) file);
}

/* ====================================================================== */

struct buffer *gzip(struct buffer *input)
{
    struct pool *pool = input->pool;
    struct buffer *output = buffer_create(pool, 0);
    gz_stream *stream = my_gzopen(output);
    unsigned char *src;
    unsigned long srcl;

    buffer_rewind(input);

    while (buffer_fetch_block(input, &src, &srcl))
        my_gzwrite(stream, src, srcl);

    my_gzclose(stream);

    return (output);
}
#endif
