changeset 14739:378ba560ea9f

Moved zlib/bzlib code to lib-compression library.
author Timo Sirainen <tss@iki.fi>
date Wed, 01 Aug 2012 18:30:40 +0300
parents c3a781ef0aeb
children 7ece66101bbd
files Makefile.am configure.in dovecot-config.in.in dovecot.m4 src/Makefile.am src/lib-compression/Makefile.am src/lib-compression/compression.c src/lib-compression/compression.h src/lib-compression/istream-bzlib.c src/lib-compression/istream-zlib.c src/lib-compression/istream-zlib.h src/lib-compression/ostream-bzlib.c src/lib-compression/ostream-zlib.c src/lib-compression/ostream-zlib.h src/plugins/imap-zlib/Makefile.am src/plugins/imap-zlib/imap-zlib-plugin.c src/plugins/zlib/Makefile.am src/plugins/zlib/istream-bzlib.c src/plugins/zlib/istream-zlib.c src/plugins/zlib/istream-zlib.h src/plugins/zlib/ostream-bzlib.c src/plugins/zlib/ostream-zlib.c src/plugins/zlib/ostream-zlib.h src/plugins/zlib/zlib-plugin.c src/plugins/zlib/zlib-plugin.h
diffstat 25 files changed, 1611 insertions(+), 1571 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.am	Tue Jul 31 19:32:03 2012 +0300
+++ b/Makefile.am	Wed Aug 01 18:30:40 2012 +0300
@@ -66,6 +66,7 @@
 	-e "s|^\(LIBDOVECOT_LOGIN\)=.*$$|\1=-ldovecot-login|" \
 	-e "s|^\(LIBDOVECOT_SQL\)=.*$$|\1=-ldovecot-sql|" \
 	-e "s|^\(LIBDOVECOT_SSL\)=.*$$|\1=-ldovecot-ssl|" \
+	-e "s|^\(LIBDOVECOT_COMPRESS\)=.*$$|\1=-ldovecot-compress|" \
 	-e "s|^\(LIBDOVECOT_LDA\)=.*$$|\1=-ldovecot-lda|" \
 	-e "s|^\(LIBDOVECOT_STORAGE\)=.*$$|\1=-ldovecot-storage|" \
 	-e "s|^\(LIBDOVECOT_INCLUDE\)=.*$$|\1=-I$(pkgincludedir)|" \
--- a/configure.in	Tue Jul 31 19:32:03 2012 +0300
+++ b/configure.in	Wed Aug 01 18:30:40 2012 +0300
@@ -2515,6 +2515,7 @@
   LIBDOVECOT_STORAGE_DEPS='$(top_builddir)/src/lib-storage/libdovecot-storage.la $(top_builddir)/src/lib-imap-storage/libimap-storage.la'
   LIBDOVECOT_LOGIN='$(top_builddir)/src/login-common/libdovecot-login.la'
   LIBDOVECOT_SSL='$(top_builddir)/src/lib-master/libmaster_ssl.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la'
+  LIBDOVECOT_COMPRESS='$(top_builddir)/src/lib-compression/libcompression.la'
   LIBDOVECOT_LDA='$(top_builddir)/src/lib-lda/libdovecot-lda.la'
 else
   LIBDOVECOT_DEPS='$(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-fs/libfs.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib/liblib.la'
@@ -2524,6 +2525,7 @@
   LIBDOVECOT_STORAGE_DEPS="$LIBDOVECOT_STORAGE_FIRST $LINKED_STORAGE_LIBS $LIBDOVECOT_STORAGE_LAST"
   LIBDOVECOT_LOGIN='$(top_builddir)/src/login-common/liblogin.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la'
   LIBDOVECOT_SSL='$(top_builddir)/src/lib-master/libmaster_ssl.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la'
+  LIBDOVECOT_COMPRESS='$(top_builddir)/src/lib-compression/libcompression.la'
   LIBDOVECOT_LDA='$(top_builddir)/src/lib-lda/liblda.la'
 fi
 LIBDOVECOT_STORAGE="$LIBDOVECOT_STORAGE_DEPS $LINKED_STORAGE_LDADD"
@@ -2535,6 +2537,7 @@
 AC_SUBST(LIBDOVECOT_LOGIN)
 AC_SUBST(LIBDOVECOT_SQL)
 AC_SUBST(LIBDOVECOT_SSL)
+AC_SUBST(LIBDOVECOT_COMPRESS)
 AC_SUBST(LIBDOVECOT_LDA)
 
 dnl **
@@ -2576,25 +2579,27 @@
 dnl ** Plugins
 dnl **
 
+COMPRESS_LIBS=
 if test "$want_zlib" != "no"; then
   AC_CHECK_HEADER(zlib.h, [
     have_zlib=yes
-    have_zlib_plugin=yes
+    have_compress_lib=yes
     AC_DEFINE(HAVE_ZLIB,, Define if you have zlib library)
+    COMPRESS_LIBS="$COMPRESS_LIBS -lz"
   ], [
     if test "$want_zlib" = "yes"; then
       AC_ERROR([Can't build with zlib support: zlib.h not found])
     fi
   ])
 fi
-AM_CONDITIONAL(BUILD_ZLIB, test "$have_zlib" = "yes")
 
 if test "$want_bzlib" != "no"; then
   AC_CHECK_HEADER(bzlib.h, [
     AC_CHECK_LIB(bz2, BZ2_bzdopen, [
       have_bzlib=yes
-      have_zlib_plugin=yes
+      have_compress_lib=yes
       AC_DEFINE(HAVE_BZLIB,, Define if you have bzlib library)
+      COMPRESS_LIBS="$COMPRESS_LIBS -lbz2"
     ], [
       if test "$want_bzlib" = "yes"; then
 	AC_ERROR([Can't build with bzlib support: libbz2 not found])
@@ -2606,8 +2611,8 @@
     fi
   ])
 fi
-AM_CONDITIONAL(BUILD_BZLIB, test "$have_bzlib" = "yes")
-AM_CONDITIONAL(BUILD_ZLIB_PLUGIN, test "$have_zlib_plugin" = "yes")
+AC_SUBST(COMPRESS_LIBS)
+AM_CONDITIONAL(BUILD_ZLIB_PLUGIN, test "$have_compress_lib" = "yes")
 
 RPCGEN=${RPCGEN-rpcgen}
 if ! $RPCGEN -c /dev/null > /dev/null; then
@@ -2747,6 +2752,7 @@
 src/lib-sql/Makefile
 src/lib-auth/Makefile
 src/lib-charset/Makefile
+src/lib-compression/Makefile
 src/lib-dict/Makefile
 src/lib-dns/Makefile
 src/lib-fs/Makefile
--- a/dovecot-config.in.in	Tue Jul 31 19:32:03 2012 +0300
+++ b/dovecot-config.in.in	Wed Aug 01 18:30:40 2012 +0300
@@ -2,11 +2,13 @@
 DOVECOT_LIBS="@LIBS@"
 DOVECOT_SSL_LIBS="@SSL_LIBS@"
 DOVECOT_SQL_LIBS="@SQL_LIBS@"
+DOVECOT_COMPRESS_LIBS="@COMPRESS_LIBS@"
 
 LIBDOVECOT="@LIBDOVECOT@ @MODULE_LIBS@"
 LIBDOVECOT_LOGIN="@LIBDOVECOT_LOGIN@ @SSL_LIBS@"
 LIBDOVECOT_SQL="@LIBDOVECOT_SQL@"
 LIBDOVECOT_SSL="@LIBDOVECOT_SSL@"
+LIBDOVECOT_COMPRESS="@LIBDOVECOT_COMPRESS@"
 LIBDOVECOT_LDA="@LIBDOVECOT_LDA@"
 LIBDOVECOT_STORAGE="@LIBDOVECOT_STORAGE@"
 
@@ -14,10 +16,11 @@
 LIBDOVECOT_LOGIN_DEPS="@LIBDOVECOT_LOGIN@"
 LIBDOVECOT_SQL_DEPS="@LIBDOVECOT_SQL@"
 LIBDOVECOT_SSL_DEPS="@LIBDOVECOT_SSL@"
+LIBDOVECOT_COMPRESS_DEPS="@LIBDOVECOT_COMPRESS@"
 LIBDOVECOT_LDA_DEPS="@LIBDOVECOT_LDA@"
 LIBDOVECOT_STORAGE_DEPS="@LIBDOVECOT_STORAGE_DEPS@"
 
-LIBDOVECOT_INCLUDE="-I$(incdir) -I$(incdir)/src/lib -I$(incdir)/src/lib-dict -I$(incdir)/src/lib-dns -I$(incdir)/src/lib-mail -I$(incdir)/src/lib-imap -I$(incdir)/src/lib-fs -I$(incdir)/src/lib-charset -I$(incdir)/src/lib-auth -I$(incdir)/src/lib-master -I$(incdir)/src/lib-ssl-iostream -I$(incdir)/src/lib-settings"
+LIBDOVECOT_INCLUDE="-I$(incdir) -I$(incdir)/src/lib -I$(incdir)/src/lib-dict -I$(incdir)/src/lib-dns -I$(incdir)/src/lib-mail -I$(incdir)/src/lib-imap -I$(incdir)/src/lib-fs -I$(incdir)/src/lib-charset -I$(incdir)/src/lib-auth -I$(incdir)/src/lib-master -I$(incdir)/src/lib-ssl-iostream -I$(incdir)/src/lib-compression -I$(incdir)/src/lib-settings"
 LIBDOVECOT_LDA_INCLUDE="-I$(incdir)/src/lib-lda -I$(incdir)/src/lda"
 LIBDOVECOT_STORAGE_INCLUDE="-I$(incdir)/src/lib-index -I$(incdir)/src/lib-storage -I$(incdir)/src/lib-storage/index -I$(incdir)/src/lib-storage/index/raw"
 LIBDOVECOT_LOGIN_INCLUDE="-I$(incdir)/src/login-common"
--- a/dovecot.m4	Tue Jul 31 19:32:03 2012 +0300
+++ b/dovecot.m4	Wed Aug 01 18:30:40 2012 +0300
@@ -6,7 +6,7 @@
 # unlimited permission to copy and/or distribute it, with or without
 # modifications, as long as this notice is preserved.
 
-# serial 7
+# serial 8
 
 AC_DEFUN([DC_DOVECOT_MODULEDIR],[
 	AC_ARG_WITH(moduledir,
@@ -84,9 +84,9 @@
 	fi
 
 	AX_SUBST_L([DISTCHECK_CONFIGURE_FLAGS], [dovecotdir], [dovecot_moduledir], [dovecot_pkgincludedir], [dovecot_pkglibexecdir], [dovecot_pkglibdir], [dovecot_docdir])
-	AX_SUBST_L([DOVECOT_CFLAGS], [DOVECOT_LIBS], [DOVECOT_SSL_LIBS], [DOVECOT_SQL_LIBS])
-	AX_SUBST_L([LIBDOVECOT], [LIBDOVECOT_LOGIN], [LIBDOVECOT_SQL], [LIBDOVECOT_SSL], [LIBDOVECOT_LDA], [LIBDOVECOT_STORAGE])
-	AX_SUBST_L([LIBDOVECOT_DEPS], [LIBDOVECOT_LOGIN_DEPS], [LIBDOVECOT_SQL_DEPS], [LIBDOVECOT_SSL_DEPS], [LIBDOVECOT_LDA_DEPS], [LIBDOVECOT_STORAGE_DEPS])
+	AX_SUBST_L([DOVECOT_CFLAGS], [DOVECOT_LIBS], [DOVECOT_SSL_LIBS], [DOVECOT_SQL_LIBS], [DOVECOT_COMPRESS_LIBS])
+	AX_SUBST_L([LIBDOVECOT], [LIBDOVECOT_LOGIN], [LIBDOVECOT_SQL], [LIBDOVECOT_SSL], [LIBDOVECOT_COMPRESS], [LIBDOVECOT_LDA], [LIBDOVECOT_STORAGE])
+	AX_SUBST_L([LIBDOVECOT_DEPS], [LIBDOVECOT_LOGIN_DEPS], [LIBDOVECOT_SQL_DEPS], [LIBDOVECOT_SSL_DEPS], [LIBDOVECOT_COMPRESS_DEPS], [LIBDOVECOT_LDA_DEPS], [LIBDOVECOT_STORAGE_DEPS])
 	AX_SUBST_L([LIBDOVECOT_INCLUDE], [LIBDOVECOT_LDA_INCLUDE], [LIBDOVECOT_SERVICE_INCLUDE], [LIBDOVECOT_STORAGE_INCLUDE], [LIBDOVECOT_LOGIN_INCLUDE], [LIBDOVECOT_CONFIG_INCLUDE], [LIBDOVECOT_IMAP_INCLUDE])
 
 	DC_PLUGIN_DEPS
--- a/src/Makefile.am	Tue Jul 31 19:32:03 2012 +0300
+++ b/src/Makefile.am	Wed Aug 01 18:30:40 2012 +0300
@@ -16,6 +16,7 @@
 	lib-test \
 	$(LIBDOVECOT_SUBDIRS) \
 	lib-imap-client \
+	lib-compression \
 	lib-dovecot \
 	lib-index \
 	lib-storage \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-compression/Makefile.am	Wed Aug 01 18:30:40 2012 +0300
@@ -0,0 +1,25 @@
+noinst_LTLIBRARIES = libcompression.la
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-test
+
+libcompression_la_SOURCES = \
+	compression.c \
+	istream-zlib.c \
+	istream-bzlib.c \
+	ostream-zlib.c \
+	ostream-bzlib.c
+libcompression_la_LIBADD = \
+	$(COMPRESS_LIBS)
+
+pkginc_libdir = $(pkgincludedir)
+	compression.h \
+	istream-zlib.h \
+	ostream-zlib.h
+
+pkglib_LTLIBRARIES = libdovecot-compression.la
+libdovecot_compression_la_SOURCES = 
+libdovecot_compression_la_LIBADD = libcompression.la ../lib/liblib.la $(MODULE_LIBS) $(COMPRESS_LIBS)
+libdovecot_compression_la_DEPENDENCIES = libcompression.la
+libdovecot_compression_la_LDFLAGS = -export-dynamic
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-compression/compression.c	Wed Aug 01 18:30:40 2012 +0300
@@ -0,0 +1,99 @@
+/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "istream-zlib.h"
+#include "ostream-zlib.h"
+#include "compression.h"
+
+#ifndef HAVE_ZLIB
+#  define i_stream_create_gz NULL
+#  define o_stream_create_gz NULL
+#endif
+#ifndef HAVE_BZLIB
+#  define i_stream_create_bz2 NULL
+#  define o_stream_create_bz2 NULL
+#endif
+
+static bool is_compressed_zlib(struct istream *input)
+{
+	const unsigned char *data;
+	size_t size;
+
+	/* Peek in to the stream and see if it looks like it's compressed
+	   (based on its header). This also means that users can try to exploit
+	   security holes in the uncompression library by APPENDing a specially
+	   crafted mail. So let's hope zlib is free of holes. */
+	if (i_stream_read_data(input, &data, &size, 1) <= 0)
+		return FALSE;
+	i_assert(size >= 2);
+
+	return data[0] == 31 && data[1] == 139;
+}
+
+static bool is_compressed_bzlib(struct istream *input)
+{
+	const unsigned char *data;
+	size_t size;
+
+	if (i_stream_read_data(input, &data, &size, 4+6 - 1) <= 0)
+		return FALSE;
+	if (data[0] != 'B' || data[1] != 'Z')
+		return FALSE;
+	if (data[2] != 'h' && data[2] != '0')
+		return FALSE;
+	if (data[3] < '1' || data[3] > '9')
+		return FALSE;
+	return memcmp(data + 4, "\x31\x41\x59\x26\x53\x59", 6) == 0;
+}
+
+const struct compression_handler *compression_lookup_handler(const char *name)
+{
+	unsigned int i;
+
+	for (i = 0; compression_handlers[i].name != NULL; i++) {
+		if (strcmp(name, compression_handlers[i].name) == 0)
+			return &compression_handlers[i];
+	}
+	return NULL;
+}
+
+const struct compression_handler *
+compression_detect_handler(struct istream *input)
+{
+	unsigned int i;
+
+	for (i = 0; compression_handlers[i].name != NULL; i++) {
+		if (compression_handlers[i].is_compressed != NULL &&
+		    compression_handlers[i].is_compressed(input))
+			return &compression_handlers[i];
+	}
+	return NULL;
+}
+
+const struct compression_handler *
+compression_lookup_handler_from_ext(const char *path)
+{
+	unsigned int i, len, path_len = strlen(path);
+
+	for (i = 0; compression_handlers[i].name != NULL; i++) {
+		if (compression_handlers[i].ext == NULL)
+			continue;
+
+		len = strlen(compression_handlers[i].ext);
+		if (path_len > len &&
+		    strcmp(path + path_len - len, compression_handlers[i].ext) == 0)
+			return &compression_handlers[i];
+	}
+	return NULL;
+}
+
+const struct compression_handler compression_handlers[] = {
+	{ "gz", ".gz", is_compressed_zlib,
+	  i_stream_create_gz, o_stream_create_gz },
+	{ "bz2", ".bz2", is_compressed_bzlib,
+	  i_stream_create_bz2, o_stream_create_bz2 },
+	{ "deflate", NULL, NULL,
+	  i_stream_create_deflate, o_stream_create_deflate },
+	{ NULL, NULL, NULL, NULL, NULL }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-compression/compression.h	Wed Aug 01 18:30:40 2012 +0300
@@ -0,0 +1,24 @@
+#ifndef COMPRESSION_H
+#define COMPRESSION_H
+
+struct compression_handler {
+	const char *name;
+	const char *ext;
+	bool (*is_compressed)(struct istream *input);
+	struct istream *(*create_istream)(struct istream *input,
+					  bool log_errors);
+	struct ostream *(*create_ostream)(struct ostream *output, int level);
+};
+
+extern const struct compression_handler compression_handlers[];
+
+/* Lookup handler by its name (gz, bz2) */
+const struct compression_handler *compression_lookup_handler(const char *name);
+/* Detect handler by looking at the first few bytes of the input stream. */
+const struct compression_handler *
+compression_detect_handler(struct istream *input);
+/* Lookup handler based on filename extension in the path */
+const struct compression_handler *
+compression_lookup_handler_from_ext(const char *path);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-compression/istream-bzlib.c	Wed Aug 01 18:30:40 2012 +0300
@@ -0,0 +1,345 @@
+/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#ifdef HAVE_BZLIB
+
+#include "istream-private.h"
+#include "istream-zlib.h"
+#include <bzlib.h>
+
+#define CHUNK_SIZE (1024*64)
+
+struct bzlib_istream {
+	struct istream_private istream;
+
+	bz_stream zs;
+	uoff_t eof_offset, stream_size;
+	size_t prev_size, high_pos;
+	struct stat last_parent_statbuf;
+
+	unsigned int log_errors:1;
+	unsigned int marked:1;
+	unsigned int zs_closed:1;
+};
+
+static void i_stream_bzlib_close(struct iostream_private *stream)
+{
+	struct bzlib_istream *zstream = (struct bzlib_istream *)stream;
+
+	if (!zstream->zs_closed) {
+		(void)BZ2_bzDecompressEnd(&zstream->zs);
+		zstream->zs_closed = TRUE;
+	}
+}
+
+static void bzlib_read_error(struct bzlib_istream *zstream, const char *error)
+{
+	i_error("bzlib.read(%s): %s at %"PRIuUOFF_T,
+		i_stream_get_name(&zstream->istream.istream), error,
+		zstream->istream.abs_start_offset +
+		zstream->istream.istream.v_offset);
+}
+
+static ssize_t i_stream_bzlib_read(struct istream_private *stream)
+{
+	struct bzlib_istream *zstream = (struct bzlib_istream *)stream;
+	const unsigned char *data;
+	uoff_t high_offset;
+	size_t size;
+	int ret;
+
+	high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
+	if (zstream->eof_offset == high_offset) {
+		i_assert(zstream->high_pos == 0 ||
+			 zstream->high_pos == stream->pos);
+		stream->istream.eof = TRUE;
+		return -1;
+	}
+
+	if (stream->pos < zstream->high_pos) {
+		/* we're here because we seeked back within the read buffer. */
+		ret = zstream->high_pos - stream->pos;
+		stream->pos = zstream->high_pos;
+		zstream->high_pos = 0;
+
+		if (zstream->eof_offset != (uoff_t)-1) {
+			high_offset = stream->istream.v_offset +
+				(stream->pos - stream->skip);
+			i_assert(zstream->eof_offset == high_offset);
+			stream->istream.eof = TRUE;
+		}
+		return ret;
+	}
+	zstream->high_pos = 0;
+
+	if (stream->pos + CHUNK_SIZE > stream->buffer_size) {
+		/* try to keep at least CHUNK_SIZE available */
+		if (!zstream->marked && stream->skip > 0) {
+			/* don't try to keep anything cached if we don't
+			   have a seek mark. */
+			i_stream_compress(stream);
+		}
+		if (stream->max_buffer_size == 0 ||
+		    stream->buffer_size < stream->max_buffer_size)
+			i_stream_grow_buffer(stream, CHUNK_SIZE);
+
+		if (stream->pos == stream->buffer_size) {
+			if (stream->skip > 0) {
+				/* lose our buffer cache */
+				i_stream_compress(stream);
+			}
+
+			if (stream->pos == stream->buffer_size)
+				return -2; /* buffer full */
+		}
+	}
+
+	if (zstream->zs.avail_in == 0) {
+		/* need to read more data. try to read a full CHUNK_SIZE */
+		i_stream_skip(stream->parent, zstream->prev_size);
+		if (i_stream_read_data(stream->parent, &data, &size,
+				       CHUNK_SIZE-1) == -1 && size == 0) {
+			if (stream->parent->stream_errno != 0) {
+				stream->istream.stream_errno =
+					stream->parent->stream_errno;
+			} else {
+				i_assert(stream->parent->eof);
+				if (zstream->log_errors) {
+					bzlib_read_error(zstream,
+							 "unexpected EOF");
+				}
+				stream->istream.stream_errno = EINVAL;
+			}
+			return -1;
+		}
+		zstream->prev_size = size;
+		if (size == 0) {
+			/* no more input */
+			i_assert(!stream->istream.blocking);
+			return 0;
+		}
+
+		zstream->zs.next_in = (char *)data;
+		zstream->zs.avail_in = size;
+	}
+
+	size = stream->buffer_size - stream->pos;
+	zstream->zs.next_out = (char *)stream->w_buffer + stream->pos;
+	zstream->zs.avail_out = size;
+	ret = BZ2_bzDecompress(&zstream->zs);
+
+	size -= zstream->zs.avail_out;
+	stream->pos += size;
+
+	switch (ret) {
+	case BZ_OK:
+		break;
+	case BZ_PARAM_ERROR:
+		i_unreached();
+	case BZ_DATA_ERROR:
+		if (zstream->log_errors)
+			bzlib_read_error(zstream, "corrupted data");
+		stream->istream.stream_errno = EINVAL;
+		return -1;
+	case BZ_DATA_ERROR_MAGIC:
+		if (zstream->log_errors) {
+			bzlib_read_error(zstream,
+				"wrong magic in header (not bz2 file?)");
+		}
+		stream->istream.stream_errno = EINVAL;
+		return -1;
+	case BZ_MEM_ERROR:
+		i_fatal_status(FATAL_OUTOFMEM, "bzlib.read(%s): Out of memory",
+			       i_stream_get_name(&stream->istream));
+	case BZ_STREAM_END:
+		zstream->eof_offset = stream->istream.v_offset +
+			(stream->pos - stream->skip);
+		zstream->stream_size = zstream->eof_offset;
+		if (size == 0) {
+			stream->istream.eof = TRUE;
+			return -1;
+		}
+		break;
+	default:
+		i_fatal("BZ2_bzDecompress() failed with %d", ret);
+	}
+	if (size == 0) {
+		/* read more input */
+		return i_stream_bzlib_read(stream);
+	}
+	return size;
+}
+
+static void i_stream_bzlib_init(struct bzlib_istream *zstream)
+{
+	int ret;
+
+	ret = BZ2_bzDecompressInit(&zstream->zs, 0, 0);
+	switch (ret) {
+	case BZ_OK:
+		break;
+	case BZ_MEM_ERROR:
+		i_fatal_status(FATAL_OUTOFMEM, "bzlib: Out of memory");
+	case BZ_CONFIG_ERROR:
+		i_fatal("Wrong bzlib library version (broken compilation)");
+	case BZ_PARAM_ERROR:
+		i_fatal("bzlib: Invalid parameters");
+	default:
+		i_fatal("BZ2_bzDecompressInit() failed with %d", ret);
+	}
+}
+
+static void i_stream_bzlib_reset(struct bzlib_istream *zstream)
+{
+	struct istream_private *stream = &zstream->istream;
+
+	i_stream_seek(stream->parent, stream->parent_start_offset);
+	zstream->eof_offset = (uoff_t)-1;
+	zstream->zs.next_in = NULL;
+	zstream->zs.avail_in = 0;
+
+	stream->parent_expected_offset = stream->parent_start_offset;
+	stream->skip = stream->pos = 0;
+	stream->istream.v_offset = 0;
+	zstream->high_pos = 0;
+	zstream->prev_size = 0;
+
+	(void)BZ2_bzDecompressEnd(&zstream->zs);
+	i_stream_bzlib_init(zstream);
+}
+
+static void
+i_stream_bzlib_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
+{
+	struct bzlib_istream *zstream = (struct bzlib_istream *) stream;
+	uoff_t start_offset = stream->istream.v_offset - stream->skip;
+
+	if (v_offset < start_offset) {
+		/* have to seek backwards */
+		i_stream_bzlib_reset(zstream);
+		start_offset = 0;
+	} else if (zstream->high_pos != 0) {
+		stream->pos = zstream->high_pos;
+		zstream->high_pos = 0;
+	}
+
+	if (v_offset <= start_offset + stream->pos) {
+		/* seeking backwards within what's already cached */
+		stream->skip = v_offset - start_offset;
+		stream->istream.v_offset = v_offset;
+		zstream->high_pos = stream->pos;
+		stream->pos = stream->skip;
+	} else {
+		/* read and cache forward */
+		do {
+			size_t avail = stream->pos - stream->skip;
+
+			if (stream->istream.v_offset + avail >= v_offset) {
+				i_stream_skip(&stream->istream,
+					      v_offset -
+					      stream->istream.v_offset);
+				break;
+			}
+
+			i_stream_skip(&stream->istream, avail);
+		} while (i_stream_read(&stream->istream) >= 0);
+
+		if (stream->istream.v_offset != v_offset) {
+			/* some failure, we've broken it */
+			if (stream->istream.stream_errno != 0) {
+				i_error("bzlib_istream.seek(%s) failed: %s",
+					i_stream_get_name(&stream->istream),
+					strerror(stream->istream.stream_errno));
+				i_stream_close(&stream->istream);
+			} else {
+				/* unexpected EOF. allow it since we may just
+				   want to check if there's anything.. */
+				i_assert(stream->istream.eof);
+			}
+		}
+	}
+
+	if (mark)
+		zstream->marked = TRUE;
+}
+
+static const struct stat *
+i_stream_bzlib_stat(struct istream_private *stream, bool exact)
+{
+	struct bzlib_istream *zstream = (struct bzlib_istream *) stream;
+	const struct stat *st;
+	size_t size;
+
+	st = i_stream_stat(stream->parent, exact);
+	if (st == NULL)
+		return NULL;
+
+	/* when exact=FALSE always return the parent stat's size, even if we
+	   know the exact value. this is necessary because otherwise e.g. mbox
+	   code can see two different values and think that a compressed mbox
+	   file keeps changing. */
+	if (!exact)
+		return st;
+
+	stream->statbuf = *st;
+	if (zstream->stream_size == (uoff_t)-1) {
+		uoff_t old_offset = stream->istream.v_offset;
+
+		do {
+			size = i_stream_get_data_size(&stream->istream);
+			i_stream_skip(&stream->istream, size);
+		} while (i_stream_read(&stream->istream) > 0);
+
+		i_stream_seek(&stream->istream, old_offset);
+		if (zstream->stream_size == (uoff_t)-1)
+			return NULL;
+	}
+	stream->statbuf.st_size = zstream->stream_size;
+	return &stream->statbuf;
+}
+
+static void i_stream_bzlib_sync(struct istream_private *stream)
+{
+	struct bzlib_istream *zstream = (struct bzlib_istream *) stream;
+	const struct stat *st;
+
+	st = i_stream_stat(stream->parent, FALSE);
+	if (st != NULL) {
+		if (memcmp(&zstream->last_parent_statbuf,
+			   st, sizeof(*st)) == 0) {
+			/* a compressed file doesn't change unexpectedly,
+			   don't clear our caches unnecessarily */
+			return;
+		}
+		zstream->last_parent_statbuf = *st;
+	}
+	i_stream_bzlib_reset(zstream);
+}
+
+struct istream *i_stream_create_bz2(struct istream *input, bool log_errors)
+{
+	struct bzlib_istream *zstream;
+
+	zstream = i_new(struct bzlib_istream, 1);
+	zstream->eof_offset = (uoff_t)-1;
+	zstream->stream_size = (uoff_t)-1;
+	zstream->log_errors = log_errors;
+
+	i_stream_bzlib_init(zstream);
+
+	zstream->istream.iostream.close = i_stream_bzlib_close;
+	zstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
+	zstream->istream.read = i_stream_bzlib_read;
+	zstream->istream.seek = i_stream_bzlib_seek;
+	zstream->istream.stat = i_stream_bzlib_stat;
+	zstream->istream.sync = i_stream_bzlib_sync;
+
+	zstream->istream.istream.readable_fd = FALSE;
+	zstream->istream.istream.blocking = input->blocking;
+	zstream->istream.istream.seekable = input->seekable;
+
+	return i_stream_create(&zstream->istream, input,
+			       i_stream_get_fd(input));
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-compression/istream-zlib.c	Wed Aug 01 18:30:40 2012 +0300
@@ -0,0 +1,516 @@
+/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#ifdef HAVE_ZLIB
+
+#include "crc32.h"
+#include "istream-private.h"
+#include "istream-zlib.h"
+#include <zlib.h>
+
+#define CHUNK_SIZE (1024*64)
+
+#define GZ_HEADER_MIN_SIZE 10
+#define GZ_TRAILER_SIZE 8
+
+#define GZ_MAGIC1	0x1f
+#define GZ_MAGIC2	0x8b
+#define GZ_FLAG_FHCRC	0x02
+#define GZ_FLAG_FEXTRA	0x04
+#define GZ_FLAG_FNAME	0x08
+#define GZ_FLAG_FCOMMENT 0x10
+
+struct zlib_istream {
+	struct istream_private istream;
+
+	z_stream zs;
+	uoff_t eof_offset, stream_size;
+	size_t prev_size, high_pos;
+	uint32_t crc32;
+	struct stat last_parent_statbuf;
+
+	unsigned int gz:1;
+	unsigned int log_errors:1;
+	unsigned int marked:1;
+	unsigned int header_read:1;
+	unsigned int trailer_read:1;
+	unsigned int zs_closed:1;
+};
+
+static void i_stream_zlib_init(struct zlib_istream *zstream);
+
+static void i_stream_zlib_close(struct iostream_private *stream)
+{
+	struct zlib_istream *zstream = (struct zlib_istream *)stream;
+
+	if (!zstream->zs_closed) {
+		(void)inflateEnd(&zstream->zs);
+		zstream->zs_closed = TRUE;
+	}
+}
+
+static void zlib_read_error(struct zlib_istream *zstream, const char *error)
+{
+	i_error("zlib.read(%s): %s at %"PRIuUOFF_T,
+		i_stream_get_name(&zstream->istream.istream), error,
+		zstream->istream.abs_start_offset +
+		zstream->istream.istream.v_offset);
+}
+
+static int i_stream_zlib_read_header(struct istream_private *stream)
+{
+	struct zlib_istream *zstream = (struct zlib_istream *)stream;
+	const unsigned char *data;
+	size_t size;
+	unsigned int pos, fextra_size;
+	int ret;
+
+	ret = i_stream_read_data(stream->parent, &data, &size,
+				 zstream->prev_size);
+	if (size == zstream->prev_size) {
+		if (ret == -1) {
+			if (zstream->log_errors)
+				zlib_read_error(zstream, "missing gz header");
+			stream->istream.stream_errno = EINVAL;
+		}
+		return ret;
+	}
+	zstream->prev_size = size;
+
+	if (size < GZ_HEADER_MIN_SIZE)
+		return 0;
+	pos = GZ_HEADER_MIN_SIZE;
+
+	if (data[0] != GZ_MAGIC1 || data[1] != GZ_MAGIC2) {
+		/* missing gzip magic header */
+		if (zstream->log_errors) {
+			zlib_read_error(zstream, "wrong magic in header "
+					"(not gz file?)");
+		}
+		stream->istream.stream_errno = EINVAL;
+		return -1;
+	}
+	if ((data[3] & GZ_FLAG_FEXTRA) != 0) {
+		if (pos + 2 < size)
+			return 0;
+
+		fextra_size = data[pos] + (data[pos+1] << 8);
+		pos += 2;
+		if (pos + fextra_size < size)
+			return 0;
+		pos += fextra_size;
+	}
+	if ((data[3] & GZ_FLAG_FNAME) != 0) {
+		do {
+			if (pos == size)
+				return 0;
+		} while (data[pos++] != '\0');
+	}
+	if ((data[3] & GZ_FLAG_FCOMMENT) != 0) {
+		do {
+			if (pos == size)
+				return 0;
+		} while (data[pos++] != '\0');
+	}
+	if ((data[3] & GZ_FLAG_FHCRC) != 0) {
+		if (pos + 2 < size)
+			return 0;
+		pos += 2;
+	}
+	i_stream_skip(stream->parent, pos);
+	return 1;
+}
+
+static uint32_t data_get_uint32(const unsigned char *data)
+{
+	return data[0] | (data[1] << 8) | (data[2] << 16) |
+		((uint32_t)data[3] << 24);
+}
+
+static int i_stream_zlib_read_trailer(struct zlib_istream *zstream)
+{
+	struct istream_private *stream = &zstream->istream;
+	const unsigned char *data;
+	size_t size;
+	int ret;
+
+	ret = i_stream_read_data(stream->parent, &data, &size,
+				 GZ_TRAILER_SIZE-1);
+	if (size == zstream->prev_size) {
+		if (ret == -1) {
+			if (zstream->log_errors)
+				zlib_read_error(zstream, "missing gz trailer");
+			stream->istream.stream_errno = EINVAL;
+		}
+		return ret;
+	}
+	zstream->prev_size = size;
+
+	if (size < GZ_TRAILER_SIZE)
+		return 0;
+
+	if (data_get_uint32(data) != zstream->crc32) {
+		if (zstream->log_errors) {
+			zlib_read_error(zstream,
+					"gz trailer has wrong CRC value");
+		}
+		stream->istream.stream_errno = EINVAL;
+		return -1;
+	}
+	i_stream_skip(stream->parent, GZ_TRAILER_SIZE);
+	zstream->prev_size = 0;
+	zstream->trailer_read = TRUE;
+	return 1;
+}
+
+static ssize_t i_stream_zlib_read(struct istream_private *stream)
+{
+	struct zlib_istream *zstream = (struct zlib_istream *)stream;
+	const unsigned char *data;
+	uoff_t high_offset;
+	size_t size;
+	int ret;
+
+	high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
+	if (zstream->eof_offset == high_offset) {
+		i_assert(zstream->high_pos == 0 ||
+			 zstream->high_pos == stream->pos);
+		if (!zstream->trailer_read) {
+			do {
+				ret = i_stream_zlib_read_trailer(zstream);
+			} while (ret == 0 && stream->istream.blocking);
+			if (ret <= 0)
+				return ret;
+		}
+		if (!zstream->gz || i_stream_is_eof(stream->parent)) {
+			stream->istream.eof = TRUE;
+			return -1;
+		}
+		/* gzip file with concatenated content */
+		zstream->eof_offset = (uoff_t)-1;
+		zstream->stream_size = (uoff_t)-1;
+		zstream->header_read = FALSE;
+		zstream->trailer_read = FALSE;
+		zstream->crc32 = 0;
+
+		(void)inflateEnd(&zstream->zs);
+		i_stream_zlib_init(zstream);
+	}
+
+	if (!zstream->header_read) {
+		do {
+			ret = i_stream_zlib_read_header(stream);
+		} while (ret == 0 && stream->istream.blocking);
+		if (ret <= 0)
+			return ret;
+		zstream->header_read = TRUE;
+		zstream->prev_size = 0;
+	}
+
+	if (stream->pos < zstream->high_pos) {
+		/* we're here because we seeked back within the read buffer. */
+		ret = zstream->high_pos - stream->pos;
+		stream->pos = zstream->high_pos;
+		zstream->high_pos = 0;
+		if (zstream->trailer_read) {
+			high_offset = stream->istream.v_offset +
+				(stream->pos - stream->skip);
+			i_assert(zstream->eof_offset == high_offset);
+			stream->istream.eof = TRUE;
+		}
+		return ret;
+	}
+	zstream->high_pos = 0;
+
+	if (stream->pos + CHUNK_SIZE > stream->buffer_size) {
+		/* try to keep at least CHUNK_SIZE available */
+		if (!zstream->marked && stream->skip > 0) {
+			/* don't try to keep anything cached if we don't
+			   have a seek mark. */
+			i_stream_compress(stream);
+		}
+		if (stream->max_buffer_size == 0 ||
+		    stream->buffer_size < stream->max_buffer_size)
+			i_stream_grow_buffer(stream, CHUNK_SIZE);
+
+		if (stream->pos == stream->buffer_size) {
+			if (stream->skip > 0) {
+				/* lose our buffer cache */
+				i_stream_compress(stream);
+			}
+
+			if (stream->pos == stream->buffer_size)
+				return -2; /* buffer full */
+		}
+	}
+
+	if (zstream->zs.avail_in == 0) {
+		/* need to read more data. try to read a full CHUNK_SIZE */
+		i_stream_skip(stream->parent, zstream->prev_size);
+		if (i_stream_read_data(stream->parent, &data, &size,
+				       CHUNK_SIZE-1) == -1 && size == 0) {
+			if (stream->parent->stream_errno != 0) {
+				stream->istream.stream_errno =
+					stream->parent->stream_errno;
+			} else {
+				i_assert(stream->parent->eof);
+				if (zstream->log_errors) {
+					zlib_read_error(zstream,
+							"unexpected EOF");
+				}
+				stream->istream.stream_errno = EPIPE;
+			}
+			return -1;
+		}
+		zstream->prev_size = size;
+		if (size == 0) {
+			/* no more input */
+			i_assert(!stream->istream.blocking);
+			return 0;
+		}
+
+		zstream->zs.next_in = (void *)data;
+		zstream->zs.avail_in = size;
+	}
+
+	size = stream->buffer_size - stream->pos;
+	zstream->zs.next_out = stream->w_buffer + stream->pos;
+	zstream->zs.avail_out = size;
+	ret = inflate(&zstream->zs, Z_SYNC_FLUSH);
+
+	size -= zstream->zs.avail_out;
+	zstream->crc32 = crc32_data_more(zstream->crc32,
+					 stream->w_buffer + stream->pos, size);
+	stream->pos += size;
+
+	switch (ret) {
+	case Z_OK:
+		break;
+	case Z_NEED_DICT:
+		if (zstream->log_errors)
+			zlib_read_error(zstream, "can't read file without dict");
+		stream->istream.stream_errno = EINVAL;
+		return -1;
+	case Z_DATA_ERROR:
+		if (zstream->log_errors)
+			zlib_read_error(zstream, "corrupted data");
+		stream->istream.stream_errno = EINVAL;
+		return -1;
+	case Z_MEM_ERROR:
+		i_fatal_status(FATAL_OUTOFMEM, "zlib.read(%s): Out of memory",
+			       i_stream_get_name(&stream->istream));
+	case Z_STREAM_END:
+		zstream->eof_offset = stream->istream.v_offset +
+			(stream->pos - stream->skip);
+		zstream->stream_size = zstream->eof_offset;
+		i_stream_skip(stream->parent,
+			      zstream->prev_size - zstream->zs.avail_in);
+		zstream->zs.avail_in = 0;
+		zstream->prev_size = 0;
+
+		if (!zstream->trailer_read) {
+			/* try to read and verify the trailer, we might not
+			   be called again. */
+			if (i_stream_zlib_read_trailer(zstream) < 0)
+				return -1;
+		}
+		break;
+	default:
+		i_fatal("inflate() failed with %d", ret);
+	}
+	if (size == 0) {
+		/* read more input */
+		return i_stream_zlib_read(stream);
+	}
+	return size;
+}
+
+static void i_stream_zlib_init(struct zlib_istream *zstream)
+{
+	int ret;
+
+	ret = inflateInit2(&zstream->zs, -15);
+	switch (ret) {
+	case Z_OK:
+		break;
+	case Z_MEM_ERROR:
+		i_fatal_status(FATAL_OUTOFMEM, "zlib: Out of memory");
+	case Z_VERSION_ERROR:
+		i_fatal("Wrong zlib library version (broken compilation)");
+	case Z_STREAM_ERROR:
+		i_fatal("zlib: Invalid parameters");
+	default:
+		i_fatal("inflateInit() failed with %d", ret);
+	}
+	zstream->header_read = !zstream->gz;
+	zstream->trailer_read = !zstream->gz;
+}
+
+static void i_stream_zlib_reset(struct zlib_istream *zstream)
+{
+	struct istream_private *stream = &zstream->istream;
+
+	i_stream_seek(stream->parent, stream->parent_start_offset);
+	zstream->eof_offset = (uoff_t)-1;
+	zstream->crc32 = 0;
+
+	zstream->zs.next_in = NULL;
+	zstream->zs.avail_in = 0;
+
+	stream->parent_expected_offset = stream->parent_start_offset;
+	stream->skip = stream->pos = 0;
+	stream->istream.v_offset = 0;
+	zstream->high_pos = 0;
+	zstream->prev_size = 0;
+
+	(void)inflateEnd(&zstream->zs);
+	i_stream_zlib_init(zstream);
+}
+
+static void
+i_stream_zlib_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
+{
+	struct zlib_istream *zstream = (struct zlib_istream *) stream;
+	uoff_t start_offset = stream->istream.v_offset - stream->skip;
+
+	if (v_offset < start_offset) {
+		/* have to seek backwards */
+		i_stream_zlib_reset(zstream);
+		start_offset = 0;
+	} else if (zstream->high_pos != 0) {
+		stream->pos = zstream->high_pos;
+		zstream->high_pos = 0;
+	}
+
+	if (v_offset <= start_offset + stream->pos) {
+		/* seeking backwards within what's already cached */
+		stream->skip = v_offset - start_offset;
+		stream->istream.v_offset = v_offset;
+		zstream->high_pos = stream->pos;
+		stream->pos = stream->skip;
+	} else {
+		/* read and cache forward */
+		do {
+			size_t avail = stream->pos - stream->skip;
+
+			if (stream->istream.v_offset + avail >= v_offset) {
+				i_stream_skip(&stream->istream,
+					      v_offset -
+					      stream->istream.v_offset);
+				break;
+			}
+
+			i_stream_skip(&stream->istream, avail);
+		} while (i_stream_read(&stream->istream) >= 0);
+
+		if (stream->istream.v_offset != v_offset) {
+			/* some failure, we've broken it */
+			if (stream->istream.stream_errno != 0) {
+				i_error("zlib_istream.seek(%s) failed: %s",
+					i_stream_get_name(&stream->istream),
+					strerror(stream->istream.stream_errno));
+				i_stream_close(&stream->istream);
+			} else {
+				/* unexpected EOF. allow it since we may just
+				   want to check if there's anything.. */
+				i_assert(stream->istream.eof);
+			}
+		}
+	}
+
+	if (mark)
+		zstream->marked = TRUE;
+}
+
+static const struct stat *
+i_stream_zlib_stat(struct istream_private *stream, bool exact)
+{
+	struct zlib_istream *zstream = (struct zlib_istream *) stream;
+	const struct stat *st;
+	size_t size;
+
+	st = i_stream_stat(stream->parent, exact);
+	if (st == NULL)
+		return NULL;
+
+	/* when exact=FALSE always return the parent stat's size, even if we
+	   know the exact value. this is necessary because otherwise e.g. mbox
+	   code can see two different values and think that a compressed mbox
+	   file keeps changing. */
+	if (!exact)
+		return st;
+
+	stream->statbuf = *st;
+	if (zstream->stream_size == (uoff_t)-1) {
+		uoff_t old_offset = stream->istream.v_offset;
+
+		do {
+			size = i_stream_get_data_size(&stream->istream);
+			i_stream_skip(&stream->istream, size);
+		} while (i_stream_read(&stream->istream) > 0);
+
+		i_stream_seek(&stream->istream, old_offset);
+		if (zstream->stream_size == (uoff_t)-1)
+			return NULL;
+	}
+	stream->statbuf.st_size = zstream->stream_size;
+	return &stream->statbuf;
+}
+
+static void i_stream_zlib_sync(struct istream_private *stream)
+{
+	struct zlib_istream *zstream = (struct zlib_istream *) stream;
+	const struct stat *st;
+
+	st = i_stream_stat(stream->parent, FALSE);
+	if (st != NULL) {
+		if (memcmp(&zstream->last_parent_statbuf,
+			   st, sizeof(*st)) == 0) {
+			/* a compressed file doesn't change unexpectedly,
+			   don't clear our caches unnecessarily */
+			return;
+		}
+		zstream->last_parent_statbuf = *st;
+	}
+	i_stream_zlib_reset(zstream);
+}
+
+static struct istream *
+i_stream_create_zlib(struct istream *input, bool gz, bool log_errors)
+{
+	struct zlib_istream *zstream;
+
+	zstream = i_new(struct zlib_istream, 1);
+	zstream->eof_offset = (uoff_t)-1;
+	zstream->stream_size = (uoff_t)-1;
+	zstream->gz = gz;
+	zstream->log_errors = log_errors;
+
+	i_stream_zlib_init(zstream);
+
+	zstream->istream.iostream.close = i_stream_zlib_close;
+	zstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
+	zstream->istream.read = i_stream_zlib_read;
+	zstream->istream.seek = i_stream_zlib_seek;
+	zstream->istream.stat = i_stream_zlib_stat;
+	zstream->istream.sync = i_stream_zlib_sync;
+
+	zstream->istream.istream.readable_fd = FALSE;
+	zstream->istream.istream.blocking = input->blocking;
+	zstream->istream.istream.seekable = input->seekable;
+
+	return i_stream_create(&zstream->istream, input,
+			       i_stream_get_fd(input));
+}
+
+struct istream *i_stream_create_gz(struct istream *input, bool log_errors)
+{
+	return i_stream_create_zlib(input, TRUE, log_errors);
+}
+
+struct istream *i_stream_create_deflate(struct istream *input, bool log_errors)
+{
+	return i_stream_create_zlib(input, FALSE, log_errors);
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-compression/istream-zlib.h	Wed Aug 01 18:30:40 2012 +0300
@@ -0,0 +1,8 @@
+#ifndef ISTREAM_ZLIB_H
+#define ISTREAM_ZLIB_H
+
+struct istream *i_stream_create_gz(struct istream *input, bool log_errors);
+struct istream *i_stream_create_deflate(struct istream *input, bool log_errors);
+struct istream *i_stream_create_bz2(struct istream *input, bool log_errors);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-compression/ostream-bzlib.c	Wed Aug 01 18:30:40 2012 +0300
@@ -0,0 +1,222 @@
+/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#ifdef HAVE_BZLIB
+
+#include "ostream-private.h"
+#include "ostream-zlib.h"
+#include <bzlib.h>
+
+#define CHUNK_SIZE (1024*64)
+
+struct bzlib_ostream {
+	struct ostream_private ostream;
+	bz_stream zs;
+
+	char outbuf[CHUNK_SIZE];
+	unsigned int outbuf_offset, outbuf_used;
+
+	unsigned int flushed:1;
+};
+
+static void o_stream_bzlib_close(struct iostream_private *stream)
+{
+	struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream;
+
+	(void)o_stream_flush(&zstream->ostream.ostream);
+	(void)BZ2_bzCompressEnd(&zstream->zs);
+}
+
+static int o_stream_zlib_send_outbuf(struct bzlib_ostream *zstream)
+{
+	ssize_t ret;
+	size_t size;
+
+	if (zstream->outbuf_used == 0)
+		return 1;
+
+	size = zstream->outbuf_used - zstream->outbuf_offset;
+	i_assert(size > 0);
+	ret = o_stream_send(zstream->ostream.parent,
+			    zstream->outbuf + zstream->outbuf_offset, size);
+	if (ret < 0) {
+		o_stream_copy_error_from_parent(&zstream->ostream);
+		return -1;
+	}
+	if ((size_t)ret != size) {
+		zstream->outbuf_offset += ret;
+		return 0;
+	}
+	zstream->outbuf_offset = 0;
+	zstream->outbuf_used = 0;
+	return 1;
+}
+
+static ssize_t
+o_stream_bzlib_send_chunk(struct bzlib_ostream *zstream,
+			  const void *data, size_t size)
+{
+	bz_stream *zs = &zstream->zs;
+	int ret;
+
+	i_assert(zstream->outbuf_used == 0);
+
+	zs->next_in = (void *)data;
+	zs->avail_in = size;
+	while (zs->avail_in > 0) {
+		if (zs->avail_out == 0) {
+			/* previous block was compressed. send it and start
+			   compression for a new block. */
+			zs->next_out = zstream->outbuf;
+			zs->avail_out = sizeof(zstream->outbuf);
+
+			zstream->outbuf_used = sizeof(zstream->outbuf);
+			if ((ret = o_stream_zlib_send_outbuf(zstream)) < 0)
+				return -1;
+			if (ret == 0) {
+				/* parent stream's buffer full */
+				break;
+			}
+		}
+
+		switch (BZ2_bzCompress(zs, BZ_RUN)) {
+		case BZ_RUN_OK:
+			break;
+		default:
+			i_unreached();
+		}
+	}
+	size -= zs->avail_in;
+
+	zstream->flushed = FALSE;
+	return size;
+}
+
+static int o_stream_bzlib_send_flush(struct bzlib_ostream *zstream)
+{
+	bz_stream *zs = &zstream->zs;
+	unsigned int len;
+	bool done = FALSE;
+	int ret;
+
+	if (zs->avail_in != 0) {
+		i_assert(zstream->ostream.ostream.last_failed_errno != 0);
+		zstream->ostream.ostream.stream_errno =
+			zstream->ostream.ostream.last_failed_errno;
+		return -1;
+	}
+
+	if (zstream->flushed)
+		return 0;
+
+	if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
+		return ret;
+
+	i_assert(zstream->outbuf_used == 0);
+	do {
+		len = sizeof(zstream->outbuf) - zs->avail_out;
+		if (len != 0) {
+			zs->next_out = zstream->outbuf;
+			zs->avail_out = sizeof(zstream->outbuf);
+
+			zstream->outbuf_used = len;
+			if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
+				return ret;
+			if (done)
+				break;
+		}
+
+		ret = BZ2_bzCompress(zs, BZ_FINISH);
+		switch (ret) {
+		case BZ_STREAM_END:
+			done = TRUE;
+			break;
+		case BZ_FINISH_OK:
+			break;
+		default:
+			i_unreached();
+		}
+	} while (zs->avail_out != sizeof(zstream->outbuf));
+
+	zstream->flushed = TRUE;
+	return 0;
+}
+
+static int o_stream_bzlib_flush(struct ostream_private *stream)
+{
+	struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream;
+	int ret;
+
+	if (o_stream_bzlib_send_flush(zstream) < 0)
+		return -1;
+
+	ret = o_stream_flush(stream->parent);
+	if (ret < 0)
+		o_stream_copy_error_from_parent(stream);
+	return ret;
+}
+
+static ssize_t
+o_stream_bzlib_sendv(struct ostream_private *stream,
+		    const struct const_iovec *iov, unsigned int iov_count)
+{
+	struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream;
+	ssize_t ret, bytes = 0;
+	unsigned int i;
+
+	if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0) {
+		/* error / we still couldn't flush existing data to
+		   parent stream. */
+		return ret;
+	}
+
+	for (i = 0; i < iov_count; i++) {
+		ret = o_stream_bzlib_send_chunk(zstream, iov[i].iov_base,
+						iov[i].iov_len);
+		if (ret < 0)
+			return -1;
+		bytes += ret;
+		if ((size_t)ret != iov[i].iov_len)
+			break;
+	}
+	stream->ostream.offset += bytes;
+
+	/* avail_in!=0 check is used to detect errors. if it's non-zero here
+	   it simply means we didn't send all the data */
+	zstream->zs.avail_in = 0;
+	return bytes;
+}
+
+struct ostream *o_stream_create_bz2(struct ostream *output, int level)
+{
+	struct bzlib_ostream *zstream;
+	int ret;
+
+	i_assert(level >= 1 && level <= 9);
+
+	zstream = i_new(struct bzlib_ostream, 1);
+	zstream->ostream.sendv = o_stream_bzlib_sendv;
+	zstream->ostream.flush = o_stream_bzlib_flush;
+	zstream->ostream.iostream.close = o_stream_bzlib_close;
+
+	ret = BZ2_bzCompressInit(&zstream->zs, level, 0, 0);
+	switch (ret) {
+	case BZ_OK:
+		break;
+	case BZ_MEM_ERROR:
+		i_fatal_status(FATAL_OUTOFMEM,
+			       "bzlib: Out of memory");
+	case BZ_CONFIG_ERROR:
+		i_fatal("Wrong bzlib library version (broken compilation)");
+	case BZ_PARAM_ERROR:
+		i_fatal("bzlib: Invalid parameters");
+	default:
+		i_fatal("BZ2_bzCompressInit() failed with %d", ret);
+	}
+
+	zstream->zs.next_out = zstream->outbuf;
+	zstream->zs.avail_out = sizeof(zstream->outbuf);
+	return o_stream_create(&zstream->ostream, output);
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-compression/ostream-zlib.c	Wed Aug 01 18:30:40 2012 +0300
@@ -0,0 +1,325 @@
+/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#ifdef HAVE_ZLIB
+
+#include "crc32.h"
+#include "ostream-private.h"
+#include "ostream-zlib.h"
+#include <zlib.h>
+
+#define CHUNK_SIZE (1024*32)
+#define ZLIB_OS_CODE 0x03  /* Unix */
+
+struct zlib_ostream {
+	struct ostream_private ostream;
+	z_stream zs;
+
+	unsigned char gz_header[10];
+	unsigned char outbuf[CHUNK_SIZE];
+	unsigned int outbuf_offset, outbuf_used;
+
+	uint32_t crc, bytes32;
+
+	unsigned int gz:1;
+	unsigned int header_sent:1;
+	unsigned int flushed:1;
+};
+
+static void o_stream_zlib_close(struct iostream_private *stream)
+{
+	struct zlib_ostream *zstream = (struct zlib_ostream *)stream;
+
+	(void)o_stream_flush(&zstream->ostream.ostream);
+	(void)deflateEnd(&zstream->zs);
+}
+
+static int o_stream_zlib_send_gz_header(struct zlib_ostream *zstream)
+{
+	ssize_t ret;
+
+	ret = o_stream_send(zstream->ostream.parent, zstream->gz_header,
+			    sizeof(zstream->gz_header));
+	if ((size_t)ret != sizeof(zstream->gz_header)) {
+		o_stream_copy_error_from_parent(&zstream->ostream);
+		return -1;
+	}
+	zstream->header_sent = TRUE;
+	return 0;
+}
+
+static int o_stream_zlib_lsb_uint32(struct ostream *output, uint32_t num)
+{
+	unsigned char buf[sizeof(uint32_t)];
+	unsigned int i;
+
+	for (i = 0; i < sizeof(buf); i++) {
+		buf[i] = num & 0xff;
+		num >>= 8;
+	}
+	if (o_stream_send(output, buf, sizeof(buf)) != sizeof(buf))
+		return -1;
+	return 0;
+}
+
+static int o_stream_zlib_send_gz_trailer(struct zlib_ostream *zstream)
+{
+	struct ostream *output = zstream->ostream.parent;
+
+	if (!zstream->gz)
+		return 0;
+
+	if (o_stream_zlib_lsb_uint32(output, zstream->crc) < 0 ||
+	    o_stream_zlib_lsb_uint32(output, zstream->bytes32) < 0) {
+		o_stream_copy_error_from_parent(&zstream->ostream);
+		return -1;
+	}
+	return 0;
+}
+
+static int o_stream_zlib_send_outbuf(struct zlib_ostream *zstream)
+{
+	ssize_t ret;
+	size_t size;
+
+	if (zstream->outbuf_used == 0)
+		return 1;
+
+	size = zstream->outbuf_used - zstream->outbuf_offset;
+	i_assert(size > 0);
+	ret = o_stream_send(zstream->ostream.parent,
+			    zstream->outbuf + zstream->outbuf_offset, size);
+	if (ret < 0) {
+		o_stream_copy_error_from_parent(&zstream->ostream);
+		return -1;
+	}
+	if ((size_t)ret != size) {
+		zstream->outbuf_offset += ret;
+		return 0;
+	}
+	zstream->outbuf_offset = 0;
+	zstream->outbuf_used = 0;
+	return 1;
+}
+
+static ssize_t
+o_stream_zlib_send_chunk(struct zlib_ostream *zstream,
+			 const void *data, size_t size)
+{
+	z_stream *zs = &zstream->zs;
+	int ret, flush;
+
+	i_assert(zstream->outbuf_used == 0);
+
+	flush = zstream->ostream.corked || zstream->gz ?
+		Z_NO_FLUSH : Z_SYNC_FLUSH;
+
+	if (!zstream->header_sent) {
+		if (o_stream_zlib_send_gz_header(zstream) < 0)
+			return -1;
+	}
+
+	zs->next_in = (void *)data;
+	zs->avail_in = size;
+	while (zs->avail_in > 0) {
+		if (zs->avail_out == 0) {
+			/* previous block was compressed. send it and start
+			   compression for a new block. */
+			zs->next_out = zstream->outbuf;
+			zs->avail_out = sizeof(zstream->outbuf);
+
+			zstream->outbuf_used = sizeof(zstream->outbuf);
+			if ((ret = o_stream_zlib_send_outbuf(zstream)) < 0)
+				return -1;
+			if (ret == 0) {
+				/* parent stream's buffer full */
+				break;
+			}
+		}
+
+		switch (deflate(zs, flush)) {
+		case Z_OK:
+		case Z_BUF_ERROR:
+			break;
+		default:
+			i_unreached();
+		}
+	}
+	size -= zs->avail_in;
+
+	zstream->crc = crc32_data_more(zstream->crc, data, size);
+	zstream->bytes32 += size;
+	zstream->flushed = flush == Z_SYNC_FLUSH && zs->avail_in == 0 &&
+		zs->avail_out == sizeof(zstream->outbuf);
+	return size;
+}
+
+static int o_stream_zlib_send_flush(struct zlib_ostream *zstream)
+{
+	z_stream *zs = &zstream->zs;
+	unsigned int len;
+	bool done = FALSE;
+	int ret;
+
+	if (zs->avail_in != 0) {
+		i_assert(zstream->ostream.ostream.last_failed_errno != 0);
+		zstream->ostream.ostream.stream_errno =
+			zstream->ostream.ostream.last_failed_errno;
+		return -1;
+	}
+
+	if (zstream->flushed)
+		return 0;
+	if (!zstream->header_sent) {
+		if (o_stream_zlib_send_gz_header(zstream) < 0)
+			return -1;
+	}
+
+	if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
+		return ret;
+
+	i_assert(zstream->outbuf_used == 0);
+	do {
+		len = sizeof(zstream->outbuf) - zs->avail_out;
+		if (len != 0) {
+			zs->next_out = zstream->outbuf;
+			zs->avail_out = sizeof(zstream->outbuf);
+
+			zstream->outbuf_used = len;
+			if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
+				return ret;
+			if (done)
+				break;
+		}
+
+		switch (deflate(zs, zstream->gz ? Z_FINISH : Z_SYNC_FLUSH)) {
+		case Z_OK:
+		case Z_BUF_ERROR:
+			break;
+		case Z_STREAM_END:
+			done = TRUE;
+			break;
+		default:
+			i_unreached();
+		}
+	} while (zs->avail_out != sizeof(zstream->outbuf));
+
+	if (o_stream_zlib_send_gz_trailer(zstream) < 0)
+		return -1;
+	zstream->flushed = TRUE;
+	return 0;
+}
+
+static int o_stream_zlib_flush(struct ostream_private *stream)
+{
+	struct zlib_ostream *zstream = (struct zlib_ostream *)stream;
+	int ret;
+
+	if (o_stream_zlib_send_flush(zstream) < 0)
+		return -1;
+
+	ret = o_stream_flush(stream->parent);
+	if (ret < 0)
+		o_stream_copy_error_from_parent(stream);
+	return ret;
+}
+
+static ssize_t
+o_stream_zlib_sendv(struct ostream_private *stream,
+		    const struct const_iovec *iov, unsigned int iov_count)
+{
+	struct zlib_ostream *zstream = (struct zlib_ostream *)stream;
+	ssize_t ret, bytes = 0;
+	unsigned int i;
+
+	if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0) {
+		/* error / we still couldn't flush existing data to
+		   parent stream. */
+		return ret;
+	}
+
+	for (i = 0; i < iov_count; i++) {
+		ret = o_stream_zlib_send_chunk(zstream, iov[i].iov_base,
+					       iov[i].iov_len);
+		if (ret < 0)
+			return -1;
+		bytes += ret;
+		if ((size_t)ret != iov[i].iov_len)
+			break;
+	}
+	stream->ostream.offset += bytes;
+
+	if (!zstream->ostream.corked && i == iov_count) {
+		if (o_stream_zlib_send_flush(zstream) < 0)
+			return -1;
+	}
+	/* avail_in!=0 check is used to detect errors. if it's non-zero here
+	   it simply means we didn't send all the data */
+	zstream->zs.avail_in = 0;
+	return bytes;
+}
+
+static void o_stream_zlib_init_gz_header(struct zlib_ostream *zstream,
+					 int level, int strategy)
+{
+	unsigned char *hdr = zstream->gz_header;
+
+	hdr[0] = 0x1f;
+	hdr[1] = 0x8b;
+	hdr[2] = Z_DEFLATED;
+	hdr[8] = level == 9 ? 2 :
+		(strategy >= Z_HUFFMAN_ONLY ||
+		 (level != Z_DEFAULT_COMPRESSION && level < 2) ? 4 : 0);
+	hdr[9] = ZLIB_OS_CODE;
+	i_assert(sizeof(zstream->gz_header) == 10);
+}
+
+static struct ostream *
+o_stream_create_zlib(struct ostream *output, int level, bool gz)
+{
+	const int strategy = Z_DEFAULT_STRATEGY;
+	struct zlib_ostream *zstream;
+	int ret;
+
+	i_assert(level >= 1 && level <= 9);
+
+	zstream = i_new(struct zlib_ostream, 1);
+	zstream->ostream.sendv = o_stream_zlib_sendv;
+	zstream->ostream.flush = o_stream_zlib_flush;
+	zstream->ostream.iostream.close = o_stream_zlib_close;
+	zstream->crc = 0;
+	zstream->gz = gz;
+	if (!gz)
+		zstream->header_sent = TRUE;
+
+	o_stream_zlib_init_gz_header(zstream, level, strategy);
+	ret = deflateInit2(&zstream->zs, level, Z_DEFLATED, -15, 8, strategy);
+	switch (ret) {
+	case Z_OK:
+		break;
+	case Z_MEM_ERROR:
+		i_fatal_status(FATAL_OUTOFMEM, "deflateInit(): Out of memory");
+	case Z_VERSION_ERROR:
+		i_fatal("Wrong zlib library version (broken compilation)");
+	case Z_STREAM_ERROR:
+		i_fatal("Invalid compression level %d", level);
+	default:
+		i_fatal("deflateInit() failed with %d", ret);
+	}
+
+	zstream->zs.next_out = zstream->outbuf;
+	zstream->zs.avail_out = sizeof(zstream->outbuf);
+	return o_stream_create(&zstream->ostream, output);
+}
+
+struct ostream *o_stream_create_gz(struct ostream *output, int level)
+{
+	return o_stream_create_zlib(output, level, TRUE);
+}
+
+struct ostream *o_stream_create_deflate(struct ostream *output, int level)
+{
+	return o_stream_create_zlib(output, level, FALSE);
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-compression/ostream-zlib.h	Wed Aug 01 18:30:40 2012 +0300
@@ -0,0 +1,8 @@
+#ifndef OSTREAM_ZLIB_H
+#define OSTREAM_ZLIB_H
+
+struct ostream *o_stream_create_gz(struct ostream *output, int level);
+struct ostream *o_stream_create_deflate(struct ostream *output, int level);
+struct ostream *o_stream_create_bz2(struct ostream *output, int level);
+
+#endif
--- a/src/plugins/imap-zlib/Makefile.am	Tue Jul 31 19:32:03 2012 +0300
+++ b/src/plugins/imap-zlib/Makefile.am	Wed Aug 01 18:30:40 2012 +0300
@@ -1,11 +1,11 @@
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-compression \
 	-I$(top_srcdir)/src/lib-mail \
 	-I$(top_srcdir)/src/lib-imap \
 	-I$(top_srcdir)/src/lib-index \
 	-I$(top_srcdir)/src/lib-storage \
-	-I$(top_srcdir)/src/imap \
-	-I$(top_srcdir)/src/plugins/zlib
+	-I$(top_srcdir)/src/imap
 
 imap_moduledir = $(moduledir)
 
@@ -15,10 +15,8 @@
 imap_module_LTLIBRARIES = \
 	lib30_imap_zlib_plugin.la
 
-if DOVECOT_PLUGIN_DEPS
 lib30_imap_zlib_plugin_la_LIBADD = \
-	../zlib/lib20_zlib_plugin.la
-endif
+	../../lib-compression/libcompression.la
 
 lib30_imap_zlib_plugin_la_SOURCES = \
 	imap-zlib-plugin.c
--- a/src/plugins/imap-zlib/imap-zlib-plugin.c	Tue Jul 31 19:32:03 2012 +0300
+++ b/src/plugins/imap-zlib/imap-zlib-plugin.c	Wed Aug 01 18:30:40 2012 +0300
@@ -6,7 +6,7 @@
 #include "ostream.h"
 #include "module-context.h"
 #include "imap-commands.h"
-#include "zlib-plugin.h"
+#include "compression.h"
 #include "imap-zlib-plugin.h"
 
 #include <stdlib.h>
@@ -19,7 +19,7 @@
 struct zlib_client {
 	union imap_module_context module_ctx;
 
-	const struct zlib_handler *handler;
+	const struct compression_handler *handler;
 };
 
 const char *imap_zlib_plugin_version = DOVECOT_VERSION;
@@ -65,7 +65,7 @@
 {
 	struct client *client = cmd->client;
 	struct zlib_client *zclient = IMAP_ZLIB_IMAP_CONTEXT(client);
-	const struct zlib_handler *handler;
+	const struct compression_handler *handler;
 	const struct imap_arg *args;
 	struct istream *old_input;
 	struct ostream *old_output;
@@ -93,7 +93,7 @@
 		return TRUE;
 	}
 
-	handler = zlib_find_zlib_handler(t_str_lcase(mechanism));
+	handler = compression_lookup_handler(t_str_lcase(mechanism));
 	if (handler == NULL || handler->create_istream == NULL) {
 		client_send_tagline(cmd, "NO Unknown compression mechanism.");
 		return TRUE;
@@ -129,7 +129,7 @@
 	struct zlib_client *zclient;
 
 	if (mail_user_is_plugin_loaded(client->user, imap_zlib_module) &&
-	    zlib_find_zlib_handler("deflate") != NULL) {
+	    compression_lookup_handler("deflate") != NULL) {
 		zclient = p_new(client->pool, struct zlib_client, 1);
 		MODULE_CONTEXT_SET(client, imap_zlib_imap_module, zclient);
 
@@ -156,5 +156,4 @@
 	imap_client_created_hook_set(next_hook_client_created);
 }
 
-const char *imap_zlib_plugin_dependencies[] = { "zlib", NULL };
 const char imap_zlib_plugin_binary_dependency[] = "imap";
--- a/src/plugins/zlib/Makefile.am	Tue Jul 31 19:32:03 2012 +0300
+++ b/src/plugins/zlib/Makefile.am	Wed Aug 01 18:30:40 2012 +0300
@@ -4,6 +4,7 @@
 	-I$(top_srcdir)/src/lib \
 	-I$(top_srcdir)/src/lib-master \
 	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-compression \
 	-I$(top_srcdir)/src/lib-index \
 	-I$(top_srcdir)/src/lib-storage \
 	-I$(top_srcdir)/src/lib-storage/index \
@@ -17,26 +18,13 @@
 module_LTLIBRARIES = \
 	lib20_zlib_plugin.la
 
-if BUILD_ZLIB
-ZLIB_LIB = -lz
-endif
-if BUILD_BZLIB
-BZLIB_LIB = -lbz2
-endif
-
 lib20_zlib_plugin_la_LIBADD = \
-	$(ZLIB_LIB) $(BZLIB_LIB)
+	../../lib-compression/libcompression.la
 
 lib20_zlib_plugin_la_SOURCES = \
-	istream-bzlib.c \
-	istream-zlib.c \
-	ostream-zlib.c \
-	ostream-bzlib.c \
 	zlib-plugin.c
 
 noinst_HEADERS = \
-	istream-zlib.h \
-	ostream-zlib.h \
 	zlib-plugin.h
 
 doveadm_module_LTLIBRARIES = \
--- a/src/plugins/zlib/istream-bzlib.c	Tue Jul 31 19:32:03 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,345 +0,0 @@
-/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-
-#ifdef HAVE_BZLIB
-
-#include "istream-private.h"
-#include "istream-zlib.h"
-#include <bzlib.h>
-
-#define CHUNK_SIZE (1024*64)
-
-struct bzlib_istream {
-	struct istream_private istream;
-
-	bz_stream zs;
-	uoff_t eof_offset, stream_size;
-	size_t prev_size, high_pos;
-	struct stat last_parent_statbuf;
-
-	unsigned int log_errors:1;
-	unsigned int marked:1;
-	unsigned int zs_closed:1;
-};
-
-static void i_stream_bzlib_close(struct iostream_private *stream)
-{
-	struct bzlib_istream *zstream = (struct bzlib_istream *)stream;
-
-	if (!zstream->zs_closed) {
-		(void)BZ2_bzDecompressEnd(&zstream->zs);
-		zstream->zs_closed = TRUE;
-	}
-}
-
-static void bzlib_read_error(struct bzlib_istream *zstream, const char *error)
-{
-	i_error("bzlib.read(%s): %s at %"PRIuUOFF_T,
-		i_stream_get_name(&zstream->istream.istream), error,
-		zstream->istream.abs_start_offset +
-		zstream->istream.istream.v_offset);
-}
-
-static ssize_t i_stream_bzlib_read(struct istream_private *stream)
-{
-	struct bzlib_istream *zstream = (struct bzlib_istream *)stream;
-	const unsigned char *data;
-	uoff_t high_offset;
-	size_t size;
-	int ret;
-
-	high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
-	if (zstream->eof_offset == high_offset) {
-		i_assert(zstream->high_pos == 0 ||
-			 zstream->high_pos == stream->pos);
-		stream->istream.eof = TRUE;
-		return -1;
-	}
-
-	if (stream->pos < zstream->high_pos) {
-		/* we're here because we seeked back within the read buffer. */
-		ret = zstream->high_pos - stream->pos;
-		stream->pos = zstream->high_pos;
-		zstream->high_pos = 0;
-
-		if (zstream->eof_offset != (uoff_t)-1) {
-			high_offset = stream->istream.v_offset +
-				(stream->pos - stream->skip);
-			i_assert(zstream->eof_offset == high_offset);
-			stream->istream.eof = TRUE;
-		}
-		return ret;
-	}
-	zstream->high_pos = 0;
-
-	if (stream->pos + CHUNK_SIZE > stream->buffer_size) {
-		/* try to keep at least CHUNK_SIZE available */
-		if (!zstream->marked && stream->skip > 0) {
-			/* don't try to keep anything cached if we don't
-			   have a seek mark. */
-			i_stream_compress(stream);
-		}
-		if (stream->max_buffer_size == 0 ||
-		    stream->buffer_size < stream->max_buffer_size)
-			i_stream_grow_buffer(stream, CHUNK_SIZE);
-
-		if (stream->pos == stream->buffer_size) {
-			if (stream->skip > 0) {
-				/* lose our buffer cache */
-				i_stream_compress(stream);
-			}
-
-			if (stream->pos == stream->buffer_size)
-				return -2; /* buffer full */
-		}
-	}
-
-	if (zstream->zs.avail_in == 0) {
-		/* need to read more data. try to read a full CHUNK_SIZE */
-		i_stream_skip(stream->parent, zstream->prev_size);
-		if (i_stream_read_data(stream->parent, &data, &size,
-				       CHUNK_SIZE-1) == -1 && size == 0) {
-			if (stream->parent->stream_errno != 0) {
-				stream->istream.stream_errno =
-					stream->parent->stream_errno;
-			} else {
-				i_assert(stream->parent->eof);
-				if (zstream->log_errors) {
-					bzlib_read_error(zstream,
-							 "unexpected EOF");
-				}
-				stream->istream.stream_errno = EINVAL;
-			}
-			return -1;
-		}
-		zstream->prev_size = size;
-		if (size == 0) {
-			/* no more input */
-			i_assert(!stream->istream.blocking);
-			return 0;
-		}
-
-		zstream->zs.next_in = (char *)data;
-		zstream->zs.avail_in = size;
-	}
-
-	size = stream->buffer_size - stream->pos;
-	zstream->zs.next_out = (char *)stream->w_buffer + stream->pos;
-	zstream->zs.avail_out = size;
-	ret = BZ2_bzDecompress(&zstream->zs);
-
-	size -= zstream->zs.avail_out;
-	stream->pos += size;
-
-	switch (ret) {
-	case BZ_OK:
-		break;
-	case BZ_PARAM_ERROR:
-		i_unreached();
-	case BZ_DATA_ERROR:
-		if (zstream->log_errors)
-			bzlib_read_error(zstream, "corrupted data");
-		stream->istream.stream_errno = EINVAL;
-		return -1;
-	case BZ_DATA_ERROR_MAGIC:
-		if (zstream->log_errors) {
-			bzlib_read_error(zstream,
-				"wrong magic in header (not bz2 file?)");
-		}
-		stream->istream.stream_errno = EINVAL;
-		return -1;
-	case BZ_MEM_ERROR:
-		i_fatal_status(FATAL_OUTOFMEM, "bzlib.read(%s): Out of memory",
-			       i_stream_get_name(&stream->istream));
-	case BZ_STREAM_END:
-		zstream->eof_offset = stream->istream.v_offset +
-			(stream->pos - stream->skip);
-		zstream->stream_size = zstream->eof_offset;
-		if (size == 0) {
-			stream->istream.eof = TRUE;
-			return -1;
-		}
-		break;
-	default:
-		i_fatal("BZ2_bzDecompress() failed with %d", ret);
-	}
-	if (size == 0) {
-		/* read more input */
-		return i_stream_bzlib_read(stream);
-	}
-	return size;
-}
-
-static void i_stream_bzlib_init(struct bzlib_istream *zstream)
-{
-	int ret;
-
-	ret = BZ2_bzDecompressInit(&zstream->zs, 0, 0);
-	switch (ret) {
-	case BZ_OK:
-		break;
-	case BZ_MEM_ERROR:
-		i_fatal_status(FATAL_OUTOFMEM, "bzlib: Out of memory");
-	case BZ_CONFIG_ERROR:
-		i_fatal("Wrong bzlib library version (broken compilation)");
-	case BZ_PARAM_ERROR:
-		i_fatal("bzlib: Invalid parameters");
-	default:
-		i_fatal("BZ2_bzDecompressInit() failed with %d", ret);
-	}
-}
-
-static void i_stream_bzlib_reset(struct bzlib_istream *zstream)
-{
-	struct istream_private *stream = &zstream->istream;
-
-	i_stream_seek(stream->parent, stream->parent_start_offset);
-	zstream->eof_offset = (uoff_t)-1;
-	zstream->zs.next_in = NULL;
-	zstream->zs.avail_in = 0;
-
-	stream->parent_expected_offset = stream->parent_start_offset;
-	stream->skip = stream->pos = 0;
-	stream->istream.v_offset = 0;
-	zstream->high_pos = 0;
-	zstream->prev_size = 0;
-
-	(void)BZ2_bzDecompressEnd(&zstream->zs);
-	i_stream_bzlib_init(zstream);
-}
-
-static void
-i_stream_bzlib_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
-{
-	struct bzlib_istream *zstream = (struct bzlib_istream *) stream;
-	uoff_t start_offset = stream->istream.v_offset - stream->skip;
-
-	if (v_offset < start_offset) {
-		/* have to seek backwards */
-		i_stream_bzlib_reset(zstream);
-		start_offset = 0;
-	} else if (zstream->high_pos != 0) {
-		stream->pos = zstream->high_pos;
-		zstream->high_pos = 0;
-	}
-
-	if (v_offset <= start_offset + stream->pos) {
-		/* seeking backwards within what's already cached */
-		stream->skip = v_offset - start_offset;
-		stream->istream.v_offset = v_offset;
-		zstream->high_pos = stream->pos;
-		stream->pos = stream->skip;
-	} else {
-		/* read and cache forward */
-		do {
-			size_t avail = stream->pos - stream->skip;
-
-			if (stream->istream.v_offset + avail >= v_offset) {
-				i_stream_skip(&stream->istream,
-					      v_offset -
-					      stream->istream.v_offset);
-				break;
-			}
-
-			i_stream_skip(&stream->istream, avail);
-		} while (i_stream_read(&stream->istream) >= 0);
-
-		if (stream->istream.v_offset != v_offset) {
-			/* some failure, we've broken it */
-			if (stream->istream.stream_errno != 0) {
-				i_error("bzlib_istream.seek(%s) failed: %s",
-					i_stream_get_name(&stream->istream),
-					strerror(stream->istream.stream_errno));
-				i_stream_close(&stream->istream);
-			} else {
-				/* unexpected EOF. allow it since we may just
-				   want to check if there's anything.. */
-				i_assert(stream->istream.eof);
-			}
-		}
-	}
-
-	if (mark)
-		zstream->marked = TRUE;
-}
-
-static const struct stat *
-i_stream_bzlib_stat(struct istream_private *stream, bool exact)
-{
-	struct bzlib_istream *zstream = (struct bzlib_istream *) stream;
-	const struct stat *st;
-	size_t size;
-
-	st = i_stream_stat(stream->parent, exact);
-	if (st == NULL)
-		return NULL;
-
-	/* when exact=FALSE always return the parent stat's size, even if we
-	   know the exact value. this is necessary because otherwise e.g. mbox
-	   code can see two different values and think that a compressed mbox
-	   file keeps changing. */
-	if (!exact)
-		return st;
-
-	stream->statbuf = *st;
-	if (zstream->stream_size == (uoff_t)-1) {
-		uoff_t old_offset = stream->istream.v_offset;
-
-		do {
-			size = i_stream_get_data_size(&stream->istream);
-			i_stream_skip(&stream->istream, size);
-		} while (i_stream_read(&stream->istream) > 0);
-
-		i_stream_seek(&stream->istream, old_offset);
-		if (zstream->stream_size == (uoff_t)-1)
-			return NULL;
-	}
-	stream->statbuf.st_size = zstream->stream_size;
-	return &stream->statbuf;
-}
-
-static void i_stream_bzlib_sync(struct istream_private *stream)
-{
-	struct bzlib_istream *zstream = (struct bzlib_istream *) stream;
-	const struct stat *st;
-
-	st = i_stream_stat(stream->parent, FALSE);
-	if (st != NULL) {
-		if (memcmp(&zstream->last_parent_statbuf,
-			   st, sizeof(*st)) == 0) {
-			/* a compressed file doesn't change unexpectedly,
-			   don't clear our caches unnecessarily */
-			return;
-		}
-		zstream->last_parent_statbuf = *st;
-	}
-	i_stream_bzlib_reset(zstream);
-}
-
-struct istream *i_stream_create_bz2(struct istream *input, bool log_errors)
-{
-	struct bzlib_istream *zstream;
-
-	zstream = i_new(struct bzlib_istream, 1);
-	zstream->eof_offset = (uoff_t)-1;
-	zstream->stream_size = (uoff_t)-1;
-	zstream->log_errors = log_errors;
-
-	i_stream_bzlib_init(zstream);
-
-	zstream->istream.iostream.close = i_stream_bzlib_close;
-	zstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
-	zstream->istream.read = i_stream_bzlib_read;
-	zstream->istream.seek = i_stream_bzlib_seek;
-	zstream->istream.stat = i_stream_bzlib_stat;
-	zstream->istream.sync = i_stream_bzlib_sync;
-
-	zstream->istream.istream.readable_fd = FALSE;
-	zstream->istream.istream.blocking = input->blocking;
-	zstream->istream.istream.seekable = input->seekable;
-
-	return i_stream_create(&zstream->istream, input,
-			       i_stream_get_fd(input));
-}
-#endif
--- a/src/plugins/zlib/istream-zlib.c	Tue Jul 31 19:32:03 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,516 +0,0 @@
-/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-
-#ifdef HAVE_ZLIB
-
-#include "crc32.h"
-#include "istream-private.h"
-#include "istream-zlib.h"
-#include <zlib.h>
-
-#define CHUNK_SIZE (1024*64)
-
-#define GZ_HEADER_MIN_SIZE 10
-#define GZ_TRAILER_SIZE 8
-
-#define GZ_MAGIC1	0x1f
-#define GZ_MAGIC2	0x8b
-#define GZ_FLAG_FHCRC	0x02
-#define GZ_FLAG_FEXTRA	0x04
-#define GZ_FLAG_FNAME	0x08
-#define GZ_FLAG_FCOMMENT 0x10
-
-struct zlib_istream {
-	struct istream_private istream;
-
-	z_stream zs;
-	uoff_t eof_offset, stream_size;
-	size_t prev_size, high_pos;
-	uint32_t crc32;
-	struct stat last_parent_statbuf;
-
-	unsigned int gz:1;
-	unsigned int log_errors:1;
-	unsigned int marked:1;
-	unsigned int header_read:1;
-	unsigned int trailer_read:1;
-	unsigned int zs_closed:1;
-};
-
-static void i_stream_zlib_init(struct zlib_istream *zstream);
-
-static void i_stream_zlib_close(struct iostream_private *stream)
-{
-	struct zlib_istream *zstream = (struct zlib_istream *)stream;
-
-	if (!zstream->zs_closed) {
-		(void)inflateEnd(&zstream->zs);
-		zstream->zs_closed = TRUE;
-	}
-}
-
-static void zlib_read_error(struct zlib_istream *zstream, const char *error)
-{
-	i_error("zlib.read(%s): %s at %"PRIuUOFF_T,
-		i_stream_get_name(&zstream->istream.istream), error,
-		zstream->istream.abs_start_offset +
-		zstream->istream.istream.v_offset);
-}
-
-static int i_stream_zlib_read_header(struct istream_private *stream)
-{
-	struct zlib_istream *zstream = (struct zlib_istream *)stream;
-	const unsigned char *data;
-	size_t size;
-	unsigned int pos, fextra_size;
-	int ret;
-
-	ret = i_stream_read_data(stream->parent, &data, &size,
-				 zstream->prev_size);
-	if (size == zstream->prev_size) {
-		if (ret == -1) {
-			if (zstream->log_errors)
-				zlib_read_error(zstream, "missing gz header");
-			stream->istream.stream_errno = EINVAL;
-		}
-		return ret;
-	}
-	zstream->prev_size = size;
-
-	if (size < GZ_HEADER_MIN_SIZE)
-		return 0;
-	pos = GZ_HEADER_MIN_SIZE;
-
-	if (data[0] != GZ_MAGIC1 || data[1] != GZ_MAGIC2) {
-		/* missing gzip magic header */
-		if (zstream->log_errors) {
-			zlib_read_error(zstream, "wrong magic in header "
-					"(not gz file?)");
-		}
-		stream->istream.stream_errno = EINVAL;
-		return -1;
-	}
-	if ((data[3] & GZ_FLAG_FEXTRA) != 0) {
-		if (pos + 2 < size)
-			return 0;
-
-		fextra_size = data[pos] + (data[pos+1] << 8);
-		pos += 2;
-		if (pos + fextra_size < size)
-			return 0;
-		pos += fextra_size;
-	}
-	if ((data[3] & GZ_FLAG_FNAME) != 0) {
-		do {
-			if (pos == size)
-				return 0;
-		} while (data[pos++] != '\0');
-	}
-	if ((data[3] & GZ_FLAG_FCOMMENT) != 0) {
-		do {
-			if (pos == size)
-				return 0;
-		} while (data[pos++] != '\0');
-	}
-	if ((data[3] & GZ_FLAG_FHCRC) != 0) {
-		if (pos + 2 < size)
-			return 0;
-		pos += 2;
-	}
-	i_stream_skip(stream->parent, pos);
-	return 1;
-}
-
-static uint32_t data_get_uint32(const unsigned char *data)
-{
-	return data[0] | (data[1] << 8) | (data[2] << 16) |
-		((uint32_t)data[3] << 24);
-}
-
-static int i_stream_zlib_read_trailer(struct zlib_istream *zstream)
-{
-	struct istream_private *stream = &zstream->istream;
-	const unsigned char *data;
-	size_t size;
-	int ret;
-
-	ret = i_stream_read_data(stream->parent, &data, &size,
-				 GZ_TRAILER_SIZE-1);
-	if (size == zstream->prev_size) {
-		if (ret == -1) {
-			if (zstream->log_errors)
-				zlib_read_error(zstream, "missing gz trailer");
-			stream->istream.stream_errno = EINVAL;
-		}
-		return ret;
-	}
-	zstream->prev_size = size;
-
-	if (size < GZ_TRAILER_SIZE)
-		return 0;
-
-	if (data_get_uint32(data) != zstream->crc32) {
-		if (zstream->log_errors) {
-			zlib_read_error(zstream,
-					"gz trailer has wrong CRC value");
-		}
-		stream->istream.stream_errno = EINVAL;
-		return -1;
-	}
-	i_stream_skip(stream->parent, GZ_TRAILER_SIZE);
-	zstream->prev_size = 0;
-	zstream->trailer_read = TRUE;
-	return 1;
-}
-
-static ssize_t i_stream_zlib_read(struct istream_private *stream)
-{
-	struct zlib_istream *zstream = (struct zlib_istream *)stream;
-	const unsigned char *data;
-	uoff_t high_offset;
-	size_t size;
-	int ret;
-
-	high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
-	if (zstream->eof_offset == high_offset) {
-		i_assert(zstream->high_pos == 0 ||
-			 zstream->high_pos == stream->pos);
-		if (!zstream->trailer_read) {
-			do {
-				ret = i_stream_zlib_read_trailer(zstream);
-			} while (ret == 0 && stream->istream.blocking);
-			if (ret <= 0)
-				return ret;
-		}
-		if (!zstream->gz || i_stream_is_eof(stream->parent)) {
-			stream->istream.eof = TRUE;
-			return -1;
-		}
-		/* gzip file with concatenated content */
-		zstream->eof_offset = (uoff_t)-1;
-		zstream->stream_size = (uoff_t)-1;
-		zstream->header_read = FALSE;
-		zstream->trailer_read = FALSE;
-		zstream->crc32 = 0;
-
-		(void)inflateEnd(&zstream->zs);
-		i_stream_zlib_init(zstream);
-	}
-
-	if (!zstream->header_read) {
-		do {
-			ret = i_stream_zlib_read_header(stream);
-		} while (ret == 0 && stream->istream.blocking);
-		if (ret <= 0)
-			return ret;
-		zstream->header_read = TRUE;
-		zstream->prev_size = 0;
-	}
-
-	if (stream->pos < zstream->high_pos) {
-		/* we're here because we seeked back within the read buffer. */
-		ret = zstream->high_pos - stream->pos;
-		stream->pos = zstream->high_pos;
-		zstream->high_pos = 0;
-		if (zstream->trailer_read) {
-			high_offset = stream->istream.v_offset +
-				(stream->pos - stream->skip);
-			i_assert(zstream->eof_offset == high_offset);
-			stream->istream.eof = TRUE;
-		}
-		return ret;
-	}
-	zstream->high_pos = 0;
-
-	if (stream->pos + CHUNK_SIZE > stream->buffer_size) {
-		/* try to keep at least CHUNK_SIZE available */
-		if (!zstream->marked && stream->skip > 0) {
-			/* don't try to keep anything cached if we don't
-			   have a seek mark. */
-			i_stream_compress(stream);
-		}
-		if (stream->max_buffer_size == 0 ||
-		    stream->buffer_size < stream->max_buffer_size)
-			i_stream_grow_buffer(stream, CHUNK_SIZE);
-
-		if (stream->pos == stream->buffer_size) {
-			if (stream->skip > 0) {
-				/* lose our buffer cache */
-				i_stream_compress(stream);
-			}
-
-			if (stream->pos == stream->buffer_size)
-				return -2; /* buffer full */
-		}
-	}
-
-	if (zstream->zs.avail_in == 0) {
-		/* need to read more data. try to read a full CHUNK_SIZE */
-		i_stream_skip(stream->parent, zstream->prev_size);
-		if (i_stream_read_data(stream->parent, &data, &size,
-				       CHUNK_SIZE-1) == -1 && size == 0) {
-			if (stream->parent->stream_errno != 0) {
-				stream->istream.stream_errno =
-					stream->parent->stream_errno;
-			} else {
-				i_assert(stream->parent->eof);
-				if (zstream->log_errors) {
-					zlib_read_error(zstream,
-							"unexpected EOF");
-				}
-				stream->istream.stream_errno = EPIPE;
-			}
-			return -1;
-		}
-		zstream->prev_size = size;
-		if (size == 0) {
-			/* no more input */
-			i_assert(!stream->istream.blocking);
-			return 0;
-		}
-
-		zstream->zs.next_in = (void *)data;
-		zstream->zs.avail_in = size;
-	}
-
-	size = stream->buffer_size - stream->pos;
-	zstream->zs.next_out = stream->w_buffer + stream->pos;
-	zstream->zs.avail_out = size;
-	ret = inflate(&zstream->zs, Z_SYNC_FLUSH);
-
-	size -= zstream->zs.avail_out;
-	zstream->crc32 = crc32_data_more(zstream->crc32,
-					 stream->w_buffer + stream->pos, size);
-	stream->pos += size;
-
-	switch (ret) {
-	case Z_OK:
-		break;
-	case Z_NEED_DICT:
-		if (zstream->log_errors)
-			zlib_read_error(zstream, "can't read file without dict");
-		stream->istream.stream_errno = EINVAL;
-		return -1;
-	case Z_DATA_ERROR:
-		if (zstream->log_errors)
-			zlib_read_error(zstream, "corrupted data");
-		stream->istream.stream_errno = EINVAL;
-		return -1;
-	case Z_MEM_ERROR:
-		i_fatal_status(FATAL_OUTOFMEM, "zlib.read(%s): Out of memory",
-			       i_stream_get_name(&stream->istream));
-	case Z_STREAM_END:
-		zstream->eof_offset = stream->istream.v_offset +
-			(stream->pos - stream->skip);
-		zstream->stream_size = zstream->eof_offset;
-		i_stream_skip(stream->parent,
-			      zstream->prev_size - zstream->zs.avail_in);
-		zstream->zs.avail_in = 0;
-		zstream->prev_size = 0;
-
-		if (!zstream->trailer_read) {
-			/* try to read and verify the trailer, we might not
-			   be called again. */
-			if (i_stream_zlib_read_trailer(zstream) < 0)
-				return -1;
-		}
-		break;
-	default:
-		i_fatal("inflate() failed with %d", ret);
-	}
-	if (size == 0) {
-		/* read more input */
-		return i_stream_zlib_read(stream);
-	}
-	return size;
-}
-
-static void i_stream_zlib_init(struct zlib_istream *zstream)
-{
-	int ret;
-
-	ret = inflateInit2(&zstream->zs, -15);
-	switch (ret) {
-	case Z_OK:
-		break;
-	case Z_MEM_ERROR:
-		i_fatal_status(FATAL_OUTOFMEM, "zlib: Out of memory");
-	case Z_VERSION_ERROR:
-		i_fatal("Wrong zlib library version (broken compilation)");
-	case Z_STREAM_ERROR:
-		i_fatal("zlib: Invalid parameters");
-	default:
-		i_fatal("inflateInit() failed with %d", ret);
-	}
-	zstream->header_read = !zstream->gz;
-	zstream->trailer_read = !zstream->gz;
-}
-
-static void i_stream_zlib_reset(struct zlib_istream *zstream)
-{
-	struct istream_private *stream = &zstream->istream;
-
-	i_stream_seek(stream->parent, stream->parent_start_offset);
-	zstream->eof_offset = (uoff_t)-1;
-	zstream->crc32 = 0;
-
-	zstream->zs.next_in = NULL;
-	zstream->zs.avail_in = 0;
-
-	stream->parent_expected_offset = stream->parent_start_offset;
-	stream->skip = stream->pos = 0;
-	stream->istream.v_offset = 0;
-	zstream->high_pos = 0;
-	zstream->prev_size = 0;
-
-	(void)inflateEnd(&zstream->zs);
-	i_stream_zlib_init(zstream);
-}
-
-static void
-i_stream_zlib_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
-{
-	struct zlib_istream *zstream = (struct zlib_istream *) stream;
-	uoff_t start_offset = stream->istream.v_offset - stream->skip;
-
-	if (v_offset < start_offset) {
-		/* have to seek backwards */
-		i_stream_zlib_reset(zstream);
-		start_offset = 0;
-	} else if (zstream->high_pos != 0) {
-		stream->pos = zstream->high_pos;
-		zstream->high_pos = 0;
-	}
-
-	if (v_offset <= start_offset + stream->pos) {
-		/* seeking backwards within what's already cached */
-		stream->skip = v_offset - start_offset;
-		stream->istream.v_offset = v_offset;
-		zstream->high_pos = stream->pos;
-		stream->pos = stream->skip;
-	} else {
-		/* read and cache forward */
-		do {
-			size_t avail = stream->pos - stream->skip;
-
-			if (stream->istream.v_offset + avail >= v_offset) {
-				i_stream_skip(&stream->istream,
-					      v_offset -
-					      stream->istream.v_offset);
-				break;
-			}
-
-			i_stream_skip(&stream->istream, avail);
-		} while (i_stream_read(&stream->istream) >= 0);
-
-		if (stream->istream.v_offset != v_offset) {
-			/* some failure, we've broken it */
-			if (stream->istream.stream_errno != 0) {
-				i_error("zlib_istream.seek(%s) failed: %s",
-					i_stream_get_name(&stream->istream),
-					strerror(stream->istream.stream_errno));
-				i_stream_close(&stream->istream);
-			} else {
-				/* unexpected EOF. allow it since we may just
-				   want to check if there's anything.. */
-				i_assert(stream->istream.eof);
-			}
-		}
-	}
-
-	if (mark)
-		zstream->marked = TRUE;
-}
-
-static const struct stat *
-i_stream_zlib_stat(struct istream_private *stream, bool exact)
-{
-	struct zlib_istream *zstream = (struct zlib_istream *) stream;
-	const struct stat *st;
-	size_t size;
-
-	st = i_stream_stat(stream->parent, exact);
-	if (st == NULL)
-		return NULL;
-
-	/* when exact=FALSE always return the parent stat's size, even if we
-	   know the exact value. this is necessary because otherwise e.g. mbox
-	   code can see two different values and think that a compressed mbox
-	   file keeps changing. */
-	if (!exact)
-		return st;
-
-	stream->statbuf = *st;
-	if (zstream->stream_size == (uoff_t)-1) {
-		uoff_t old_offset = stream->istream.v_offset;
-
-		do {
-			size = i_stream_get_data_size(&stream->istream);
-			i_stream_skip(&stream->istream, size);
-		} while (i_stream_read(&stream->istream) > 0);
-
-		i_stream_seek(&stream->istream, old_offset);
-		if (zstream->stream_size == (uoff_t)-1)
-			return NULL;
-	}
-	stream->statbuf.st_size = zstream->stream_size;
-	return &stream->statbuf;
-}
-
-static void i_stream_zlib_sync(struct istream_private *stream)
-{
-	struct zlib_istream *zstream = (struct zlib_istream *) stream;
-	const struct stat *st;
-
-	st = i_stream_stat(stream->parent, FALSE);
-	if (st != NULL) {
-		if (memcmp(&zstream->last_parent_statbuf,
-			   st, sizeof(*st)) == 0) {
-			/* a compressed file doesn't change unexpectedly,
-			   don't clear our caches unnecessarily */
-			return;
-		}
-		zstream->last_parent_statbuf = *st;
-	}
-	i_stream_zlib_reset(zstream);
-}
-
-static struct istream *
-i_stream_create_zlib(struct istream *input, bool gz, bool log_errors)
-{
-	struct zlib_istream *zstream;
-
-	zstream = i_new(struct zlib_istream, 1);
-	zstream->eof_offset = (uoff_t)-1;
-	zstream->stream_size = (uoff_t)-1;
-	zstream->gz = gz;
-	zstream->log_errors = log_errors;
-
-	i_stream_zlib_init(zstream);
-
-	zstream->istream.iostream.close = i_stream_zlib_close;
-	zstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
-	zstream->istream.read = i_stream_zlib_read;
-	zstream->istream.seek = i_stream_zlib_seek;
-	zstream->istream.stat = i_stream_zlib_stat;
-	zstream->istream.sync = i_stream_zlib_sync;
-
-	zstream->istream.istream.readable_fd = FALSE;
-	zstream->istream.istream.blocking = input->blocking;
-	zstream->istream.istream.seekable = input->seekable;
-
-	return i_stream_create(&zstream->istream, input,
-			       i_stream_get_fd(input));
-}
-
-struct istream *i_stream_create_gz(struct istream *input, bool log_errors)
-{
-	return i_stream_create_zlib(input, TRUE, log_errors);
-}
-
-struct istream *i_stream_create_deflate(struct istream *input, bool log_errors)
-{
-	return i_stream_create_zlib(input, FALSE, log_errors);
-}
-#endif
--- a/src/plugins/zlib/istream-zlib.h	Tue Jul 31 19:32:03 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-#ifndef ISTREAM_ZLIB_H
-#define ISTREAM_ZLIB_H
-
-struct istream *i_stream_create_gz(struct istream *input, bool log_errors);
-struct istream *i_stream_create_deflate(struct istream *input, bool log_errors);
-struct istream *i_stream_create_bz2(struct istream *input, bool log_errors);
-
-#endif
--- a/src/plugins/zlib/ostream-bzlib.c	Tue Jul 31 19:32:03 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-
-#ifdef HAVE_BZLIB
-
-#include "ostream-private.h"
-#include "ostream-zlib.h"
-#include <bzlib.h>
-
-#define CHUNK_SIZE (1024*64)
-
-struct bzlib_ostream {
-	struct ostream_private ostream;
-	bz_stream zs;
-
-	char outbuf[CHUNK_SIZE];
-	unsigned int outbuf_offset, outbuf_used;
-
-	unsigned int flushed:1;
-};
-
-static void o_stream_bzlib_close(struct iostream_private *stream)
-{
-	struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream;
-
-	(void)o_stream_flush(&zstream->ostream.ostream);
-	(void)BZ2_bzCompressEnd(&zstream->zs);
-}
-
-static int o_stream_zlib_send_outbuf(struct bzlib_ostream *zstream)
-{
-	ssize_t ret;
-	size_t size;
-
-	if (zstream->outbuf_used == 0)
-		return 1;
-
-	size = zstream->outbuf_used - zstream->outbuf_offset;
-	i_assert(size > 0);
-	ret = o_stream_send(zstream->ostream.parent,
-			    zstream->outbuf + zstream->outbuf_offset, size);
-	if (ret < 0) {
-		o_stream_copy_error_from_parent(&zstream->ostream);
-		return -1;
-	}
-	if ((size_t)ret != size) {
-		zstream->outbuf_offset += ret;
-		return 0;
-	}
-	zstream->outbuf_offset = 0;
-	zstream->outbuf_used = 0;
-	return 1;
-}
-
-static ssize_t
-o_stream_bzlib_send_chunk(struct bzlib_ostream *zstream,
-			  const void *data, size_t size)
-{
-	bz_stream *zs = &zstream->zs;
-	int ret;
-
-	i_assert(zstream->outbuf_used == 0);
-
-	zs->next_in = (void *)data;
-	zs->avail_in = size;
-	while (zs->avail_in > 0) {
-		if (zs->avail_out == 0) {
-			/* previous block was compressed. send it and start
-			   compression for a new block. */
-			zs->next_out = zstream->outbuf;
-			zs->avail_out = sizeof(zstream->outbuf);
-
-			zstream->outbuf_used = sizeof(zstream->outbuf);
-			if ((ret = o_stream_zlib_send_outbuf(zstream)) < 0)
-				return -1;
-			if (ret == 0) {
-				/* parent stream's buffer full */
-				break;
-			}
-		}
-
-		switch (BZ2_bzCompress(zs, BZ_RUN)) {
-		case BZ_RUN_OK:
-			break;
-		default:
-			i_unreached();
-		}
-	}
-	size -= zs->avail_in;
-
-	zstream->flushed = FALSE;
-	return size;
-}
-
-static int o_stream_bzlib_send_flush(struct bzlib_ostream *zstream)
-{
-	bz_stream *zs = &zstream->zs;
-	unsigned int len;
-	bool done = FALSE;
-	int ret;
-
-	if (zs->avail_in != 0) {
-		i_assert(zstream->ostream.ostream.last_failed_errno != 0);
-		zstream->ostream.ostream.stream_errno =
-			zstream->ostream.ostream.last_failed_errno;
-		return -1;
-	}
-
-	if (zstream->flushed)
-		return 0;
-
-	if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
-		return ret;
-
-	i_assert(zstream->outbuf_used == 0);
-	do {
-		len = sizeof(zstream->outbuf) - zs->avail_out;
-		if (len != 0) {
-			zs->next_out = zstream->outbuf;
-			zs->avail_out = sizeof(zstream->outbuf);
-
-			zstream->outbuf_used = len;
-			if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
-				return ret;
-			if (done)
-				break;
-		}
-
-		ret = BZ2_bzCompress(zs, BZ_FINISH);
-		switch (ret) {
-		case BZ_STREAM_END:
-			done = TRUE;
-			break;
-		case BZ_FINISH_OK:
-			break;
-		default:
-			i_unreached();
-		}
-	} while (zs->avail_out != sizeof(zstream->outbuf));
-
-	zstream->flushed = TRUE;
-	return 0;
-}
-
-static int o_stream_bzlib_flush(struct ostream_private *stream)
-{
-	struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream;
-	int ret;
-
-	if (o_stream_bzlib_send_flush(zstream) < 0)
-		return -1;
-
-	ret = o_stream_flush(stream->parent);
-	if (ret < 0)
-		o_stream_copy_error_from_parent(stream);
-	return ret;
-}
-
-static ssize_t
-o_stream_bzlib_sendv(struct ostream_private *stream,
-		    const struct const_iovec *iov, unsigned int iov_count)
-{
-	struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream;
-	ssize_t ret, bytes = 0;
-	unsigned int i;
-
-	if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0) {
-		/* error / we still couldn't flush existing data to
-		   parent stream. */
-		return ret;
-	}
-
-	for (i = 0; i < iov_count; i++) {
-		ret = o_stream_bzlib_send_chunk(zstream, iov[i].iov_base,
-						iov[i].iov_len);
-		if (ret < 0)
-			return -1;
-		bytes += ret;
-		if ((size_t)ret != iov[i].iov_len)
-			break;
-	}
-	stream->ostream.offset += bytes;
-
-	/* avail_in!=0 check is used to detect errors. if it's non-zero here
-	   it simply means we didn't send all the data */
-	zstream->zs.avail_in = 0;
-	return bytes;
-}
-
-struct ostream *o_stream_create_bz2(struct ostream *output, int level)
-{
-	struct bzlib_ostream *zstream;
-	int ret;
-
-	i_assert(level >= 1 && level <= 9);
-
-	zstream = i_new(struct bzlib_ostream, 1);
-	zstream->ostream.sendv = o_stream_bzlib_sendv;
-	zstream->ostream.flush = o_stream_bzlib_flush;
-	zstream->ostream.iostream.close = o_stream_bzlib_close;
-
-	ret = BZ2_bzCompressInit(&zstream->zs, level, 0, 0);
-	switch (ret) {
-	case BZ_OK:
-		break;
-	case BZ_MEM_ERROR:
-		i_fatal_status(FATAL_OUTOFMEM,
-			       "bzlib: Out of memory");
-	case BZ_CONFIG_ERROR:
-		i_fatal("Wrong bzlib library version (broken compilation)");
-	case BZ_PARAM_ERROR:
-		i_fatal("bzlib: Invalid parameters");
-	default:
-		i_fatal("BZ2_bzCompressInit() failed with %d", ret);
-	}
-
-	zstream->zs.next_out = zstream->outbuf;
-	zstream->zs.avail_out = sizeof(zstream->outbuf);
-	return o_stream_create(&zstream->ostream, output);
-}
-#endif
--- a/src/plugins/zlib/ostream-zlib.c	Tue Jul 31 19:32:03 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,325 +0,0 @@
-/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-
-#ifdef HAVE_ZLIB
-
-#include "crc32.h"
-#include "ostream-private.h"
-#include "ostream-zlib.h"
-#include <zlib.h>
-
-#define CHUNK_SIZE (1024*32)
-#define ZLIB_OS_CODE 0x03  /* Unix */
-
-struct zlib_ostream {
-	struct ostream_private ostream;
-	z_stream zs;
-
-	unsigned char gz_header[10];
-	unsigned char outbuf[CHUNK_SIZE];
-	unsigned int outbuf_offset, outbuf_used;
-
-	uint32_t crc, bytes32;
-
-	unsigned int gz:1;
-	unsigned int header_sent:1;
-	unsigned int flushed:1;
-};
-
-static void o_stream_zlib_close(struct iostream_private *stream)
-{
-	struct zlib_ostream *zstream = (struct zlib_ostream *)stream;
-
-	(void)o_stream_flush(&zstream->ostream.ostream);
-	(void)deflateEnd(&zstream->zs);
-}
-
-static int o_stream_zlib_send_gz_header(struct zlib_ostream *zstream)
-{
-	ssize_t ret;
-
-	ret = o_stream_send(zstream->ostream.parent, zstream->gz_header,
-			    sizeof(zstream->gz_header));
-	if ((size_t)ret != sizeof(zstream->gz_header)) {
-		o_stream_copy_error_from_parent(&zstream->ostream);
-		return -1;
-	}
-	zstream->header_sent = TRUE;
-	return 0;
-}
-
-static int o_stream_zlib_lsb_uint32(struct ostream *output, uint32_t num)
-{
-	unsigned char buf[sizeof(uint32_t)];
-	unsigned int i;
-
-	for (i = 0; i < sizeof(buf); i++) {
-		buf[i] = num & 0xff;
-		num >>= 8;
-	}
-	if (o_stream_send(output, buf, sizeof(buf)) != sizeof(buf))
-		return -1;
-	return 0;
-}
-
-static int o_stream_zlib_send_gz_trailer(struct zlib_ostream *zstream)
-{
-	struct ostream *output = zstream->ostream.parent;
-
-	if (!zstream->gz)
-		return 0;
-
-	if (o_stream_zlib_lsb_uint32(output, zstream->crc) < 0 ||
-	    o_stream_zlib_lsb_uint32(output, zstream->bytes32) < 0) {
-		o_stream_copy_error_from_parent(&zstream->ostream);
-		return -1;
-	}
-	return 0;
-}
-
-static int o_stream_zlib_send_outbuf(struct zlib_ostream *zstream)
-{
-	ssize_t ret;
-	size_t size;
-
-	if (zstream->outbuf_used == 0)
-		return 1;
-
-	size = zstream->outbuf_used - zstream->outbuf_offset;
-	i_assert(size > 0);
-	ret = o_stream_send(zstream->ostream.parent,
-			    zstream->outbuf + zstream->outbuf_offset, size);
-	if (ret < 0) {
-		o_stream_copy_error_from_parent(&zstream->ostream);
-		return -1;
-	}
-	if ((size_t)ret != size) {
-		zstream->outbuf_offset += ret;
-		return 0;
-	}
-	zstream->outbuf_offset = 0;
-	zstream->outbuf_used = 0;
-	return 1;
-}
-
-static ssize_t
-o_stream_zlib_send_chunk(struct zlib_ostream *zstream,
-			 const void *data, size_t size)
-{
-	z_stream *zs = &zstream->zs;
-	int ret, flush;
-
-	i_assert(zstream->outbuf_used == 0);
-
-	flush = zstream->ostream.corked || zstream->gz ?
-		Z_NO_FLUSH : Z_SYNC_FLUSH;
-
-	if (!zstream->header_sent) {
-		if (o_stream_zlib_send_gz_header(zstream) < 0)
-			return -1;
-	}
-
-	zs->next_in = (void *)data;
-	zs->avail_in = size;
-	while (zs->avail_in > 0) {
-		if (zs->avail_out == 0) {
-			/* previous block was compressed. send it and start
-			   compression for a new block. */
-			zs->next_out = zstream->outbuf;
-			zs->avail_out = sizeof(zstream->outbuf);
-
-			zstream->outbuf_used = sizeof(zstream->outbuf);
-			if ((ret = o_stream_zlib_send_outbuf(zstream)) < 0)
-				return -1;
-			if (ret == 0) {
-				/* parent stream's buffer full */
-				break;
-			}
-		}
-
-		switch (deflate(zs, flush)) {
-		case Z_OK:
-		case Z_BUF_ERROR:
-			break;
-		default:
-			i_unreached();
-		}
-	}
-	size -= zs->avail_in;
-
-	zstream->crc = crc32_data_more(zstream->crc, data, size);
-	zstream->bytes32 += size;
-	zstream->flushed = flush == Z_SYNC_FLUSH && zs->avail_in == 0 &&
-		zs->avail_out == sizeof(zstream->outbuf);
-	return size;
-}
-
-static int o_stream_zlib_send_flush(struct zlib_ostream *zstream)
-{
-	z_stream *zs = &zstream->zs;
-	unsigned int len;
-	bool done = FALSE;
-	int ret;
-
-	if (zs->avail_in != 0) {
-		i_assert(zstream->ostream.ostream.last_failed_errno != 0);
-		zstream->ostream.ostream.stream_errno =
-			zstream->ostream.ostream.last_failed_errno;
-		return -1;
-	}
-
-	if (zstream->flushed)
-		return 0;
-	if (!zstream->header_sent) {
-		if (o_stream_zlib_send_gz_header(zstream) < 0)
-			return -1;
-	}
-
-	if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
-		return ret;
-
-	i_assert(zstream->outbuf_used == 0);
-	do {
-		len = sizeof(zstream->outbuf) - zs->avail_out;
-		if (len != 0) {
-			zs->next_out = zstream->outbuf;
-			zs->avail_out = sizeof(zstream->outbuf);
-
-			zstream->outbuf_used = len;
-			if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
-				return ret;
-			if (done)
-				break;
-		}
-
-		switch (deflate(zs, zstream->gz ? Z_FINISH : Z_SYNC_FLUSH)) {
-		case Z_OK:
-		case Z_BUF_ERROR:
-			break;
-		case Z_STREAM_END:
-			done = TRUE;
-			break;
-		default:
-			i_unreached();
-		}
-	} while (zs->avail_out != sizeof(zstream->outbuf));
-
-	if (o_stream_zlib_send_gz_trailer(zstream) < 0)
-		return -1;
-	zstream->flushed = TRUE;
-	return 0;
-}
-
-static int o_stream_zlib_flush(struct ostream_private *stream)
-{
-	struct zlib_ostream *zstream = (struct zlib_ostream *)stream;
-	int ret;
-
-	if (o_stream_zlib_send_flush(zstream) < 0)
-		return -1;
-
-	ret = o_stream_flush(stream->parent);
-	if (ret < 0)
-		o_stream_copy_error_from_parent(stream);
-	return ret;
-}
-
-static ssize_t
-o_stream_zlib_sendv(struct ostream_private *stream,
-		    const struct const_iovec *iov, unsigned int iov_count)
-{
-	struct zlib_ostream *zstream = (struct zlib_ostream *)stream;
-	ssize_t ret, bytes = 0;
-	unsigned int i;
-
-	if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0) {
-		/* error / we still couldn't flush existing data to
-		   parent stream. */
-		return ret;
-	}
-
-	for (i = 0; i < iov_count; i++) {
-		ret = o_stream_zlib_send_chunk(zstream, iov[i].iov_base,
-					       iov[i].iov_len);
-		if (ret < 0)
-			return -1;
-		bytes += ret;
-		if ((size_t)ret != iov[i].iov_len)
-			break;
-	}
-	stream->ostream.offset += bytes;
-
-	if (!zstream->ostream.corked && i == iov_count) {
-		if (o_stream_zlib_send_flush(zstream) < 0)
-			return -1;
-	}
-	/* avail_in!=0 check is used to detect errors. if it's non-zero here
-	   it simply means we didn't send all the data */
-	zstream->zs.avail_in = 0;
-	return bytes;
-}
-
-static void o_stream_zlib_init_gz_header(struct zlib_ostream *zstream,
-					 int level, int strategy)
-{
-	unsigned char *hdr = zstream->gz_header;
-
-	hdr[0] = 0x1f;
-	hdr[1] = 0x8b;
-	hdr[2] = Z_DEFLATED;
-	hdr[8] = level == 9 ? 2 :
-		(strategy >= Z_HUFFMAN_ONLY ||
-		 (level != Z_DEFAULT_COMPRESSION && level < 2) ? 4 : 0);
-	hdr[9] = ZLIB_OS_CODE;
-	i_assert(sizeof(zstream->gz_header) == 10);
-}
-
-static struct ostream *
-o_stream_create_zlib(struct ostream *output, int level, bool gz)
-{
-	const int strategy = Z_DEFAULT_STRATEGY;
-	struct zlib_ostream *zstream;
-	int ret;
-
-	i_assert(level >= 1 && level <= 9);
-
-	zstream = i_new(struct zlib_ostream, 1);
-	zstream->ostream.sendv = o_stream_zlib_sendv;
-	zstream->ostream.flush = o_stream_zlib_flush;
-	zstream->ostream.iostream.close = o_stream_zlib_close;
-	zstream->crc = 0;
-	zstream->gz = gz;
-	if (!gz)
-		zstream->header_sent = TRUE;
-
-	o_stream_zlib_init_gz_header(zstream, level, strategy);
-	ret = deflateInit2(&zstream->zs, level, Z_DEFLATED, -15, 8, strategy);
-	switch (ret) {
-	case Z_OK:
-		break;
-	case Z_MEM_ERROR:
-		i_fatal_status(FATAL_OUTOFMEM, "deflateInit(): Out of memory");
-	case Z_VERSION_ERROR:
-		i_fatal("Wrong zlib library version (broken compilation)");
-	case Z_STREAM_ERROR:
-		i_fatal("Invalid compression level %d", level);
-	default:
-		i_fatal("deflateInit() failed with %d", ret);
-	}
-
-	zstream->zs.next_out = zstream->outbuf;
-	zstream->zs.avail_out = sizeof(zstream->outbuf);
-	return o_stream_create(&zstream->ostream, output);
-}
-
-struct ostream *o_stream_create_gz(struct ostream *output, int level)
-{
-	return o_stream_create_zlib(output, level, TRUE);
-}
-
-struct ostream *o_stream_create_deflate(struct ostream *output, int level)
-{
-	return o_stream_create_zlib(output, level, FALSE);
-}
-#endif
--- a/src/plugins/zlib/ostream-zlib.h	Tue Jul 31 19:32:03 2012 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-#ifndef OSTREAM_ZLIB_H
-#define OSTREAM_ZLIB_H
-
-struct ostream *o_stream_create_gz(struct ostream *output, int level);
-struct ostream *o_stream_create_deflate(struct ostream *output, int level);
-struct ostream *o_stream_create_bz2(struct ostream *output, int level);
-
-#endif
--- a/src/plugins/zlib/zlib-plugin.c	Tue Jul 31 19:32:03 2012 +0300
+++ b/src/plugins/zlib/zlib-plugin.c	Wed Aug 01 18:30:40 2012 +0300
@@ -10,8 +10,7 @@
 #include "maildir/maildir-storage.h"
 #include "index-storage.h"
 #include "index-mail.h"
-#include "istream-zlib.h"
-#include "ostream-zlib.h"
+#include "compression.h"
 #include "zlib-plugin.h"
 
 #include <stdlib.h>
@@ -26,15 +25,6 @@
 #define ZLIB_USER_CONTEXT(obj) \
 	MODULE_CONTEXT(obj, zlib_user_module)
 
-#ifndef HAVE_ZLIB
-#  define i_stream_create_gz NULL
-#  define o_stream_create_gz NULL
-#endif
-#ifndef HAVE_BZLIB
-#  define i_stream_create_bz2 NULL
-#  define o_stream_create_bz2 NULL
-#endif
-
 #define MAX_INBUF_SIZE (1024*1024)
 
 struct zlib_transaction_context {
@@ -46,7 +36,7 @@
 struct zlib_user {
 	union mail_user_module_context module_ctx;
 
-	const struct zlib_handler *save_handler;
+	const struct compression_handler *save_handler;
 	unsigned int save_level;
 };
 
@@ -58,84 +48,13 @@
 				  &mail_storage_module_register);
 static MODULE_CONTEXT_DEFINE_INIT(zlib_mail_module, &mail_module_register);
 
-static bool is_compressed_zlib(struct istream *input)
-{
-	const unsigned char *data;
-	size_t size;
-
-	/* Peek in to the stream and see if it looks like it's compressed
-	   (based on its header). This also means that users can try to exploit
-	   security holes in the uncompression library by APPENDing a specially
-	   crafted mail. So let's hope zlib is free of holes. */
-	if (i_stream_read_data(input, &data, &size, 1) <= 0)
-		return FALSE;
-	i_assert(size >= 2);
-
-	return data[0] == 31 && data[1] == 139;
-}
-
-static bool is_compressed_bzlib(struct istream *input)
-{
-	const unsigned char *data;
-	size_t size;
-
-	if (i_stream_read_data(input, &data, &size, 4+6 - 1) <= 0)
-		return FALSE;
-	if (data[0] != 'B' || data[1] != 'Z')
-		return FALSE;
-	if (data[2] != 'h' && data[2] != '0')
-		return FALSE;
-	if (data[3] < '1' || data[3] > '9')
-		return FALSE;
-	return memcmp(data + 4, "\x31\x41\x59\x26\x53\x59", 6) == 0;
-}
-
-const struct zlib_handler *zlib_find_zlib_handler(const char *name)
-{
-	unsigned int i;
-
-	for (i = 0; zlib_handlers[i].name != NULL; i++) {
-		if (strcmp(name, zlib_handlers[i].name) == 0)
-			return &zlib_handlers[i];
-	}
-	return NULL;
-}
-
-static const struct zlib_handler *zlib_get_zlib_handler(struct istream *input)
-{
-	unsigned int i;
-
-	for (i = 0; zlib_handlers[i].name != NULL; i++) {
-		if (zlib_handlers[i].is_compressed != NULL &&
-		    zlib_handlers[i].is_compressed(input))
-			return &zlib_handlers[i];
-	}
-	return NULL;
-}
-
-static const struct zlib_handler *zlib_get_zlib_handler_ext(const char *name)
-{
-	unsigned int i, len, name_len = strlen(name);
-
-	for (i = 0; zlib_handlers[i].name != NULL; i++) {
-		if (zlib_handlers[i].ext == NULL)
-			continue;
-
-		len = strlen(zlib_handlers[i].ext);
-		if (name_len > len &&
-		    strcmp(name + name_len - len, zlib_handlers[i].ext) == 0)
-			return &zlib_handlers[i];
-	}
-	return NULL;
-}
-
 static int zlib_istream_opened(struct mail *_mail, struct istream **stream)
 {
 	struct zlib_user *zuser = ZLIB_USER_CONTEXT(_mail->box->storage->user);
 	struct mail_private *mail = (struct mail_private *)_mail;
 	union mail_module_context *zmail = ZLIB_MAIL_CONTEXT(mail);
 	struct istream *input;
-	const struct zlib_handler *handler;
+	const struct compression_handler *handler;
 
 	/* don't uncompress input when we are reading a mail that we're just
 	   in the middle of saving, and we didn't do the compression ourself.
@@ -144,7 +63,7 @@
 	if (_mail->saving && zuser->save_handler == NULL)
 		return zmail->super.istream_opened(_mail, stream);
 
-	handler = zlib_get_zlib_handler(*stream);
+	handler = compression_detect_handler(*stream);
 	if (handler != NULL) {
 		if (handler->create_istream == NULL) {
 			mail_storage_set_critical(_mail->box->storage,
@@ -253,7 +172,7 @@
 	if (mail_get_stream(ctx->dest_mail, NULL, NULL, &input) < 0)
 		return -1;
 
-	if (zlib_get_zlib_handler(input) != NULL) {
+	if (compression_detect_handler(input) != NULL) {
 		mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
 			"Saving mails compressed by client isn't supported");
 		return -1;
@@ -299,12 +218,12 @@
 
 static int zlib_mailbox_open_input(struct mailbox *box)
 {
-	const struct zlib_handler *handler;
+	const struct compression_handler *handler;
 	struct istream *input;
 	struct stat st;
 	int fd;
 
-	handler = zlib_get_zlib_handler_ext(box->name);
+	handler = compression_lookup_handler_from_ext(box->name);
 	if (handler == NULL || handler->create_istream == NULL)
 		return 0;
 
@@ -373,7 +292,7 @@
 
 	name = mail_user_plugin_getenv(user, "zlib_save");
 	if (name != NULL && *name != '\0') {
-		zuser->save_handler = zlib_find_zlib_handler(name);
+		zuser->save_handler = compression_lookup_handler(name);
 		if (zuser->save_handler == NULL)
 			i_error("zlib_save: Unknown handler: %s", name);
 	}
@@ -405,13 +324,3 @@
 {
 	mail_storage_hooks_remove(&zlib_mail_storage_hooks);
 }
-
-const struct zlib_handler zlib_handlers[] = {
-	{ "gz", ".gz", is_compressed_zlib,
-	  i_stream_create_gz, o_stream_create_gz },
-	{ "bz2", ".bz2", is_compressed_bzlib,
-	  i_stream_create_bz2, o_stream_create_bz2 },
-	{ "deflate", NULL, NULL,
-	  i_stream_create_deflate, o_stream_create_deflate },
-	{ NULL, NULL, NULL, NULL, NULL }
-};
--- a/src/plugins/zlib/zlib-plugin.h	Tue Jul 31 19:32:03 2012 +0300
+++ b/src/plugins/zlib/zlib-plugin.h	Wed Aug 01 18:30:40 2012 +0300
@@ -1,19 +1,6 @@
 #ifndef ZLIB_PLUGIN_H
 #define ZLIB_PLUGIN_H
 
-struct zlib_handler {
-	const char *name;
-	const char *ext;
-	bool (*is_compressed)(struct istream *input);
-	struct istream *(*create_istream)(struct istream *input,
-					  bool log_errors);
-	struct ostream *(*create_ostream)(struct ostream *output, int level);
-};
-
-extern const struct zlib_handler zlib_handlers[];
-
-const struct zlib_handler *zlib_find_zlib_handler(const char *name);
-
 void zlib_plugin_init(struct module *module);
 void zlib_plugin_deinit(void);