summaryrefslogtreecommitdiffstats
path: root/src/lib-compression
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-compression/Makefile.am61
-rw-r--r--src/lib-compression/Makefile.in976
-rw-r--r--src/lib-compression/bench-compression.c168
-rw-r--r--src/lib-compression/compression.c250
-rw-r--r--src/lib-compression/compression.h55
-rw-r--r--src/lib-compression/iostream-lz4.h30
-rw-r--r--src/lib-compression/iostream-zstd-private.h35
-rw-r--r--src/lib-compression/istream-bzlib.c231
-rw-r--r--src/lib-compression/istream-decompress.c258
-rw-r--r--src/lib-compression/istream-lz4.c281
-rw-r--r--src/lib-compression/istream-lzma.c264
-rw-r--r--src/lib-compression/istream-zlib.c431
-rw-r--r--src/lib-compression/istream-zlib.h11
-rw-r--r--src/lib-compression/istream-zstd.c268
-rw-r--r--src/lib-compression/ostream-bzlib.c307
-rw-r--r--src/lib-compression/ostream-lz4.c249
-rw-r--r--src/lib-compression/ostream-zlib.c392
-rw-r--r--src/lib-compression/ostream-zlib.h26
-rw-r--r--src/lib-compression/ostream-zstd.c243
-rw-r--r--src/lib-compression/test-compression.c1122
20 files changed, 5658 insertions, 0 deletions
diff --git a/src/lib-compression/Makefile.am b/src/lib-compression/Makefile.am
new file mode 100644
index 0000000..f689b0c
--- /dev/null
+++ b/src/lib-compression/Makefile.am
@@ -0,0 +1,61 @@
+noinst_LTLIBRARIES = libcompression.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ $(ZSTD_CFLAGS)
+
+libcompression_la_SOURCES = \
+ compression.c \
+ istream-decompress.c \
+ istream-lzma.c \
+ istream-lz4.c \
+ istream-zlib.c \
+ istream-bzlib.c \
+ istream-zstd.c \
+ ostream-lz4.c \
+ ostream-zlib.c \
+ ostream-bzlib.c \
+ ostream-zstd.c
+libcompression_la_LIBADD = \
+ $(COMPRESS_LIBS)
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = \
+ compression.h \
+ iostream-lz4.h \
+ istream-zlib.h \
+ ostream-zlib.h
+
+noinst_HEADERS = \
+ iostream-zstd-private.h
+
+pkglib_LTLIBRARIES = libdovecot-compression.la
+libdovecot_compression_la_SOURCES =
+libdovecot_compression_la_LIBADD = libcompression.la ../lib-dovecot/libdovecot.la $(COMPRESS_LIBS)
+libdovecot_compression_la_DEPENDENCIES = libcompression.la ../lib-dovecot/libdovecot.la
+libdovecot_compression_la_LDFLAGS = -export-dynamic
+
+test_programs = \
+ test-compression
+
+noinst_PROGRAMS = $(test_programs) bench-compression
+
+test_libs = \
+ $(noinst_LTLIBRARIES) \
+ ../lib-test/libtest.la \
+ ../lib/liblib.la
+test_deps = $(test_libs)
+
+test_compression_SOURCES = test-compression.c
+test_compression_LDADD = $(test_libs)
+test_compression_DEPENDENCIES = $(test_deps)
+
+bench_compression_SOURCES = bench-compression.c
+bench_compression_LDADD = $(test_libs)
+bench_compression_DEPENDENCIES = $(test_deps)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/lib-compression/Makefile.in b/src/lib-compression/Makefile.in
new file mode 100644
index 0000000..7badd65
--- /dev/null
+++ b/src/lib-compression/Makefile.in
@@ -0,0 +1,976 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = $(am__EXEEXT_1) bench-compression$(EXEEXT)
+subdir = src/lib-compression
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-compression$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkglibdir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+LTLIBRARIES = $(noinst_LTLIBRARIES) $(pkglib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libcompression_la_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_libcompression_la_OBJECTS = compression.lo istream-decompress.lo \
+ istream-lzma.lo istream-lz4.lo istream-zlib.lo \
+ istream-bzlib.lo istream-zstd.lo ostream-lz4.lo \
+ ostream-zlib.lo ostream-bzlib.lo ostream-zstd.lo
+libcompression_la_OBJECTS = $(am_libcompression_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_libdovecot_compression_la_OBJECTS =
+libdovecot_compression_la_OBJECTS = \
+ $(am_libdovecot_compression_la_OBJECTS)
+libdovecot_compression_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libdovecot_compression_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_bench_compression_OBJECTS = bench-compression.$(OBJEXT)
+bench_compression_OBJECTS = $(am_bench_compression_OBJECTS)
+am_test_compression_OBJECTS = test-compression.$(OBJEXT)
+test_compression_OBJECTS = $(am_test_compression_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/bench-compression.Po \
+ ./$(DEPDIR)/compression.Plo ./$(DEPDIR)/istream-bzlib.Plo \
+ ./$(DEPDIR)/istream-decompress.Plo ./$(DEPDIR)/istream-lz4.Plo \
+ ./$(DEPDIR)/istream-lzma.Plo ./$(DEPDIR)/istream-zlib.Plo \
+ ./$(DEPDIR)/istream-zstd.Plo ./$(DEPDIR)/ostream-bzlib.Plo \
+ ./$(DEPDIR)/ostream-lz4.Plo ./$(DEPDIR)/ostream-zlib.Plo \
+ ./$(DEPDIR)/ostream-zstd.Plo ./$(DEPDIR)/test-compression.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libcompression_la_SOURCES) \
+ $(libdovecot_compression_la_SOURCES) \
+ $(bench_compression_SOURCES) $(test_compression_SOURCES)
+DIST_SOURCES = $(libcompression_la_SOURCES) \
+ $(libdovecot_compression_la_SOURCES) \
+ $(bench_compression_SOURCES) $(test_compression_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libcompression.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ $(ZSTD_CFLAGS)
+
+libcompression_la_SOURCES = \
+ compression.c \
+ istream-decompress.c \
+ istream-lzma.c \
+ istream-lz4.c \
+ istream-zlib.c \
+ istream-bzlib.c \
+ istream-zstd.c \
+ ostream-lz4.c \
+ ostream-zlib.c \
+ ostream-bzlib.c \
+ ostream-zstd.c
+
+libcompression_la_LIBADD = \
+ $(COMPRESS_LIBS)
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = \
+ compression.h \
+ iostream-lz4.h \
+ istream-zlib.h \
+ ostream-zlib.h
+
+noinst_HEADERS = \
+ iostream-zstd-private.h
+
+pkglib_LTLIBRARIES = libdovecot-compression.la
+libdovecot_compression_la_SOURCES =
+libdovecot_compression_la_LIBADD = libcompression.la ../lib-dovecot/libdovecot.la $(COMPRESS_LIBS)
+libdovecot_compression_la_DEPENDENCIES = libcompression.la ../lib-dovecot/libdovecot.la
+libdovecot_compression_la_LDFLAGS = -export-dynamic
+test_programs = \
+ test-compression
+
+test_libs = \
+ $(noinst_LTLIBRARIES) \
+ ../lib-test/libtest.la \
+ ../lib/liblib.la
+
+test_deps = $(test_libs)
+test_compression_SOURCES = test-compression.c
+test_compression_LDADD = $(test_libs)
+test_compression_DEPENDENCIES = $(test_deps)
+bench_compression_SOURCES = bench-compression.c
+bench_compression_LDADD = $(test_libs)
+bench_compression_DEPENDENCIES = $(test_deps)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-compression/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-compression/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \
+ }
+
+uninstall-pkglibLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \
+ done
+
+clean-pkglibLTLIBRARIES:
+ -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES)
+ @list='$(pkglib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libcompression.la: $(libcompression_la_OBJECTS) $(libcompression_la_DEPENDENCIES) $(EXTRA_libcompression_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libcompression_la_OBJECTS) $(libcompression_la_LIBADD) $(LIBS)
+
+libdovecot-compression.la: $(libdovecot_compression_la_OBJECTS) $(libdovecot_compression_la_DEPENDENCIES) $(EXTRA_libdovecot_compression_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libdovecot_compression_la_LINK) -rpath $(pkglibdir) $(libdovecot_compression_la_OBJECTS) $(libdovecot_compression_la_LIBADD) $(LIBS)
+
+bench-compression$(EXEEXT): $(bench_compression_OBJECTS) $(bench_compression_DEPENDENCIES) $(EXTRA_bench_compression_DEPENDENCIES)
+ @rm -f bench-compression$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(bench_compression_OBJECTS) $(bench_compression_LDADD) $(LIBS)
+
+test-compression$(EXEEXT): $(test_compression_OBJECTS) $(test_compression_DEPENDENCIES) $(EXTRA_test_compression_DEPENDENCIES)
+ @rm -f test-compression$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_compression_OBJECTS) $(test_compression_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bench-compression.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compression.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-bzlib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-decompress.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-lz4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-lzma.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-zlib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/istream-zstd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-bzlib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-lz4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-zlib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ostream-zstd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-compression.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibdir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS clean-pkglibLTLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/bench-compression.Po
+ -rm -f ./$(DEPDIR)/compression.Plo
+ -rm -f ./$(DEPDIR)/istream-bzlib.Plo
+ -rm -f ./$(DEPDIR)/istream-decompress.Plo
+ -rm -f ./$(DEPDIR)/istream-lz4.Plo
+ -rm -f ./$(DEPDIR)/istream-lzma.Plo
+ -rm -f ./$(DEPDIR)/istream-zlib.Plo
+ -rm -f ./$(DEPDIR)/istream-zstd.Plo
+ -rm -f ./$(DEPDIR)/ostream-bzlib.Plo
+ -rm -f ./$(DEPDIR)/ostream-lz4.Plo
+ -rm -f ./$(DEPDIR)/ostream-zlib.Plo
+ -rm -f ./$(DEPDIR)/ostream-zstd.Plo
+ -rm -f ./$(DEPDIR)/test-compression.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pkglibLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/bench-compression.Po
+ -rm -f ./$(DEPDIR)/compression.Plo
+ -rm -f ./$(DEPDIR)/istream-bzlib.Plo
+ -rm -f ./$(DEPDIR)/istream-decompress.Plo
+ -rm -f ./$(DEPDIR)/istream-lz4.Plo
+ -rm -f ./$(DEPDIR)/istream-lzma.Plo
+ -rm -f ./$(DEPDIR)/istream-zlib.Plo
+ -rm -f ./$(DEPDIR)/istream-zstd.Plo
+ -rm -f ./$(DEPDIR)/ostream-bzlib.Plo
+ -rm -f ./$(DEPDIR)/ostream-lz4.Plo
+ -rm -f ./$(DEPDIR)/ostream-zlib.Plo
+ -rm -f ./$(DEPDIR)/ostream-zstd.Plo
+ -rm -f ./$(DEPDIR)/test-compression.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES clean-noinstPROGRAMS \
+ clean-pkglibLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-pkginc_libHEADERS \
+ install-pkglibLTLIBRARIES install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-pkginc_libHEADERS uninstall-pkglibLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib-compression/bench-compression.c b/src/lib-compression/bench-compression.c
new file mode 100644
index 0000000..655079d
--- /dev/null
+++ b/src/lib-compression/bench-compression.c
@@ -0,0 +1,168 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "istream.h"
+#include "ostream.h"
+#include "randgen.h"
+#include "time-util.h"
+#include "strnum.h"
+#include "compression.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+
+/**
+ * Generates semi-compressible data in blocks of given size, to mimic emails
+ * remotely and then compresses and decompresses it using each algorithm.
+ * It measures the time spent on this giving some estimate how well the data
+ * compressed and how long it took.
+ */
+
+static void bench_compression_speed(const struct compression_handler *handler,
+ unsigned int level, unsigned long block_count)
+{
+ struct istream *is = i_stream_create_file("decompressed.bin", 1024);
+ struct ostream *os = o_stream_create_file("compressed.bin", 0, 0644, 0);
+ struct ostream *os_compressed = handler->create_ostream(os, level);
+ o_stream_unref(&os);
+
+ const unsigned char *data;
+ uint64_t ts_0, ts_1;
+ size_t siz;
+ double compression_speed, decompression_speed;
+
+ ts_0 = i_nanoseconds();
+
+ while (i_stream_read_more(is, &data, &siz) > 0) {
+ o_stream_nsend(os_compressed, data, siz);
+ i_stream_skip(is, siz);
+ }
+
+ if (is->stream_errno != 0)
+ printf("Error: %s\n", i_stream_get_error(is));
+
+ i_assert(o_stream_finish(os_compressed) == 1);
+ o_stream_unref(&os_compressed);
+ i_stream_unref(&is);
+
+ ts_1 = i_nanoseconds();
+
+ /* check ratio */
+ struct stat st_1, st_2;
+ if (stat("decompressed.bin", &st_1) != 0)
+ i_fatal("stat(decompressed.bin): %m");
+ if (stat("compressed.bin", &st_2) != 0)
+ i_fatal("stat(compressed.bin): %m");
+
+ double ratio = (double)st_2.st_size / (double)st_1.st_size;
+
+ compression_speed = ((double)(ts_1-ts_0))/((double)block_count);
+ compression_speed /= 1000.0L;
+
+ is = i_stream_create_file("compressed.bin", 1024);
+ os = o_stream_create_file("decompressed.bin", 0, 0644, 0);
+ struct istream *is_decompressed = handler->create_istream(is);
+ i_stream_unref(&is);
+
+ ts_0 = i_nanoseconds();
+
+ while (i_stream_read_more(is_decompressed, &data, &siz) > 0) {
+ o_stream_nsend(os, data, siz);
+ i_stream_skip(is_decompressed, siz);
+ }
+
+ if (is_decompressed->stream_errno != 0)
+ printf("Error: %s\n", i_stream_get_error(is_decompressed));
+
+ i_assert(o_stream_finish(os) == 1);
+ o_stream_unref(&os);
+ i_stream_unref(&is_decompressed);
+
+ ts_1 = i_nanoseconds();
+
+ decompression_speed = ((double)(ts_1 - ts_0))/((double)block_count);
+ decompression_speed /= 1000.0L;
+
+ printf("%s\n", handler->name);
+ printf("\tCompression: %0.02lf us/block\n\tSpace Saving: %0.02lf%%\n",
+ compression_speed, (1.0-ratio)*100.0);
+ printf("\tDecompression: %0.02lf us/block\n\n", decompression_speed);
+
+}
+
+static void print_usage(const char *prog)
+{
+ fprintf(stderr, "Usage: %s block_size count level\n", prog);
+ fprintf(stderr, "Runs with 1000 8k blocks using level 6 if nothing given\n");
+ lib_exit(1);
+}
+
+int main(int argc, const char *argv[])
+{
+ unsigned int level = 6;
+ lib_init();
+
+ unsigned long block_size = 8192UL;
+ unsigned long block_count = 1000UL;
+
+ if (argc >= 3) {
+ if (str_to_ulong(argv[1], &block_size) < 0 ||
+ str_to_ulong(argv[2], &block_count) < 0) {
+ fprintf(stderr, "Invalid parameters\n");
+ print_usage(argv[0]);
+ }
+ if (argc == 4 &&
+ str_to_uint(argv[3], &level) < 0) {
+ fprintf(stderr, "Invalid parameters\n");
+ print_usage(argv[0]);
+ }
+ if (argc > 4) {
+ print_usage(argv[0]);
+ }
+ } else if (argc != 1) {
+ print_usage(argv[0]);
+ }
+
+ unsigned char buf[block_size];
+ printf("Input data is %lu blocks of %lu bytes\n\n", block_count, block_size);
+
+ time_t t0 = time(NULL);
+
+ /* create plaintext file */
+ struct ostream *os = o_stream_create_file("decompressed.bin", 0, 0644, 0);
+ for (unsigned long r = 0; r < block_count; r++) {
+ time_t t1 = time(NULL);
+ if (t1 - t0 >= 1) {
+ printf("Building block %8lu / %-8lu\r", r, block_count);
+ fflush(stdout);
+ t0 = t1;
+ }
+ for (size_t i = 0; i < sizeof(buf); i++) {
+ if (i_rand_limit(3) == 0)
+ buf[i] = i_rand_limit(4);
+ else
+ buf[i] = i;
+ }
+ o_stream_nsend(os, buf, sizeof(buf));
+ }
+
+ i_assert(o_stream_finish(os) == 1);
+ o_stream_unref(&os);
+
+ printf("Input data constructed \n");
+
+ for (unsigned int i = 0; compression_handlers[i].name != NULL; i++) T_BEGIN {
+ if (compression_handlers[i].create_istream != NULL &&
+ compression_handlers[i].create_ostream != NULL) {
+ bench_compression_speed(&compression_handlers[i], level,
+ block_count);
+ }
+ } T_END;
+
+ i_unlink("decompressed.bin");
+ i_unlink("compressed.bin");
+
+ lib_deinit();
+}
diff --git a/src/lib-compression/compression.c b/src/lib-compression/compression.c
new file mode 100644
index 0000000..e562e73
--- /dev/null
+++ b/src/lib-compression/compression.c
@@ -0,0 +1,250 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "istream-zlib.h"
+#include "ostream-zlib.h"
+#include "iostream-lz4.h"
+#include "compression.h"
+
+#ifndef HAVE_ZLIB
+# define i_stream_create_gz NULL
+# define o_stream_create_gz NULL
+# define i_stream_create_deflate NULL
+# define o_stream_create_deflate NULL
+# define compression_get_min_level_gz NULL
+# define compression_get_default_level_gz NULL
+# define compression_get_max_level_gz NULL
+#endif
+#ifndef HAVE_BZLIB
+# define i_stream_create_bz2 NULL
+# define o_stream_create_bz2 NULL
+# define compression_get_min_level_bz2 NULL
+# define compression_get_default_level_bz2 NULL
+# define compression_get_max_level_bz2 NULL
+#endif
+#ifndef HAVE_LZMA
+# define i_stream_create_lzma NULL
+#endif
+#ifndef HAVE_LZ4
+# define i_stream_create_lz4 NULL
+# define o_stream_create_lz4 NULL
+# define compression_get_min_level_lz4 NULL
+# define compression_get_default_level_lz4 NULL
+# define compression_get_max_level_lz4 NULL
+#endif
+#ifndef HAVE_ZSTD
+# define i_stream_create_zstd NULL
+# define o_stream_create_zstd NULL
+# define compression_get_min_level_zstd NULL
+# define compression_get_default_level_zstd NULL
+# define compression_get_max_level_zstd 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_bytes(input, &data, &size, 2) <= 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_bytes(input, &data, &size, 4) <= 0)
+ return FALSE;
+ if (memcmp(data, "BZh", 3) != 0)
+ return FALSE;
+ if (data[3] < '1' || data[3] > '9')
+ return FALSE;
+ /* The above is enough to be considered as the bzlib magic.
+ Normally it's followed by data header beginning with 0x31. However,
+ with empty compressed files it's followed by 0x17. */
+ return TRUE;
+}
+
+static bool is_compressed_xz(struct istream *input)
+{
+ const unsigned char *data;
+ size_t size;
+
+ if (i_stream_read_bytes(input, &data, &size, 6) <= 0)
+ return FALSE;
+ return memcmp(data, "\xfd\x37\x7a\x58\x5a\x00", 6) == 0;
+}
+
+static bool is_compressed_lz4(struct istream *input)
+{
+ const unsigned char *data;
+ size_t size;
+
+ if (i_stream_read_bytes(input, &data, &size, IOSTREAM_LZ4_MAGIC_LEN) <= 0)
+ return FALSE;
+ /* there is no standard LZ4 header, so we've created our own */
+ return memcmp(data, IOSTREAM_LZ4_MAGIC, IOSTREAM_LZ4_MAGIC_LEN) == 0;
+}
+
+#define ZSTD_MAGICNUMBER 0xFD2FB528 /* valid since v0.8.0 */
+static bool is_compressed_zstd(struct istream *input)
+{
+ const unsigned char *data;
+ size_t size = 0;
+
+ if (i_stream_read_bytes(input, &data, &size, sizeof(uint32_t)) <= 0)
+ return FALSE;
+ i_assert(size >= sizeof(uint32_t));
+
+ return le32_to_cpu_unaligned(data) == ZSTD_MAGICNUMBER;
+}
+
+int compression_lookup_handler(const char *name,
+ const struct compression_handler **handler_r)
+{
+ unsigned int i;
+
+ for (i = 0; compression_handlers[i].name != NULL; i++) {
+ if (strcmp(name, compression_handlers[i].name) == 0) {
+ if (compression_handlers[i].create_istream == NULL ||
+ compression_handlers[i].create_ostream == NULL) {
+ /* Handler is known but not compiled in */
+ return 0;
+ }
+ (*handler_r) = &compression_handlers[i];
+ return 1;
+ }
+ }
+ return -1;
+}
+
+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;
+}
+
+int compression_lookup_handler_from_ext(const char *path,
+ const struct compression_handler **handler_r)
+{
+ unsigned int i;
+ size_t 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) {
+ if (compression_handlers[i].create_istream == NULL ||
+ compression_handlers[i].create_ostream == NULL) {
+ /* Handler is known but not compiled in */
+ return 0;
+ }
+ (*handler_r) = &compression_handlers[i];
+ return 1;
+ }
+ }
+ return -1;
+}
+
+static int compression_get_min_level_unsupported(void)
+{
+ return -1;
+}
+
+static int compression_get_default_level_unsupported(void)
+{
+ return -1;
+}
+
+static int compression_get_max_level_unsupported(void)
+{
+ return -1;
+}
+
+const struct compression_handler compression_handlers[] = {
+ {
+ .name = "gz",
+ .ext = ".gz",
+ .is_compressed = is_compressed_zlib,
+ .create_istream = i_stream_create_gz,
+ .create_ostream = o_stream_create_gz,
+ .get_min_level = compression_get_min_level_gz,
+ .get_default_level = compression_get_default_level_gz,
+ .get_max_level = compression_get_max_level_gz,
+ },
+ {
+ .name = "bz2",
+ .ext = ".bz2",
+ .is_compressed = is_compressed_bzlib,
+ .create_istream = i_stream_create_bz2,
+ .create_ostream = o_stream_create_bz2,
+ .get_min_level = compression_get_min_level_bz2,
+ .get_default_level = compression_get_default_level_bz2,
+ .get_max_level = compression_get_max_level_bz2,
+ },
+ {
+ .name = "deflate",
+ .ext = NULL,
+ .is_compressed = NULL,
+ .create_istream = i_stream_create_deflate,
+ .create_ostream = o_stream_create_deflate,
+ .get_min_level = compression_get_min_level_gz,
+ .get_default_level = compression_get_default_level_gz,
+ .get_max_level = compression_get_max_level_gz,
+ },
+ {
+ .name = "xz",
+ .ext = ".xz",
+ .is_compressed = is_compressed_xz,
+ .create_istream = i_stream_create_lzma,
+ .create_ostream = NULL,
+ .get_min_level = compression_get_min_level_unsupported,
+ .get_default_level = compression_get_default_level_unsupported,
+ .get_max_level = compression_get_max_level_unsupported,
+ },
+ {
+ .name = "lz4",
+ .ext = ".lz4",
+ .is_compressed = is_compressed_lz4,
+ .create_istream = i_stream_create_lz4,
+ .create_ostream = o_stream_create_lz4,
+ .get_min_level = compression_get_min_level_lz4, /* does not actually support any of this */
+ .get_default_level = compression_get_default_level_lz4,
+ .get_max_level = compression_get_max_level_lz4,
+ },
+ {
+ .name = "zstd",
+ .ext = ".zstd",
+ .is_compressed = is_compressed_zstd,
+ .create_istream = i_stream_create_zstd,
+ .create_ostream = o_stream_create_zstd,
+ .get_min_level = compression_get_min_level_zstd,
+ .get_default_level = compression_get_default_level_zstd,
+ .get_max_level = compression_get_max_level_zstd,
+ },
+ {
+ .name = "unsupported",
+ },
+ {
+ .name = NULL,
+ }
+};
diff --git a/src/lib-compression/compression.h b/src/lib-compression/compression.h
new file mode 100644
index 0000000..60d58bf
--- /dev/null
+++ b/src/lib-compression/compression.h
@@ -0,0 +1,55 @@
+#ifndef COMPRESSION_H
+#define COMPRESSION_H
+
+enum istream_decompress_flags {
+ /* If stream isn't detected to be compressed, return it as passthrough
+ istream. */
+ ISTREAM_DECOMPRESS_FLAG_TRY = BIT(0),
+};
+
+/* Compressed input is always detected once at maximum this many bytes have
+ been read. This value must be smaller than a typical istream max buffer
+ size. */
+#define COMPRESSION_HDR_MAX_SIZE 128
+
+struct compression_handler {
+ const char *name;
+ const char *ext;
+ bool (*is_compressed)(struct istream *input);
+ struct istream *(*create_istream)(struct istream *input);
+ struct ostream *(*create_ostream)(struct ostream *output, int level);
+ /* returns minimum level */
+ int (*get_min_level)(void);
+ /* the default can be -1 (e.g. gz), so the return value of this has to
+ be used as-is. */
+ int (*get_default_level)(void);
+ /* returns maximum level */
+ int (*get_max_level)(void);
+};
+
+extern const struct compression_handler compression_handlers[];
+
+/* Returns 1 if compression handler was found and is usable, 0 if support isn't
+ compiled in, -1 if unknown. */
+int compression_lookup_handler(const char *name,
+ const struct compression_handler **handler_r);
+/* 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, returns the same
+ * values as compression_lookup_handler. */
+int compression_lookup_handler_from_ext(const char *path,
+ const struct compression_handler **handler_r);
+
+/* Automatically detect the compression format. Note that using tee-istream as
+ one of the parent streams is dangerous here: A decompression istream may
+ have to read a lot of data (e.g. 8 kB isn't enough) before it returns even
+ the first byte as output. If the other tee children aren't read forward,
+ this can cause an infinite loop when i_stream_read() is always returning 0.
+ This is why ISTREAM_DECOMPRESS_FLAG_TRY should be used instead of attempting
+ to implement similar functionality with tee-istream. */
+struct istream *
+i_stream_create_decompress(struct istream *input,
+ enum istream_decompress_flags flags);
+
+#endif
diff --git a/src/lib-compression/iostream-lz4.h b/src/lib-compression/iostream-lz4.h
new file mode 100644
index 0000000..a0897f5
--- /dev/null
+++ b/src/lib-compression/iostream-lz4.h
@@ -0,0 +1,30 @@
+#ifndef IOSTREAM_LZ4_H
+#define IOSTREAM_LZ4_H
+
+/*
+ Dovecot's LZ4 compressed files contain:
+
+ IOSTREAM_LZ4_HEADER
+ n x (4 byte big-endian: compressed chunk length, compressed chunk)
+*/
+
+#define IOSTREAM_LZ4_MAGIC "Dovecot-LZ4\x0d\x2a\x9b\xc5"
+#define IOSTREAM_LZ4_MAGIC_LEN (sizeof(IOSTREAM_LZ4_MAGIC)-1)
+
+struct iostream_lz4_header {
+ unsigned char magic[IOSTREAM_LZ4_MAGIC_LEN];
+ /* OSTREAM_LZ4_CHUNK_SIZE in big-endian */
+ unsigned char max_uncompressed_chunk_size[4];
+};
+
+/* How large chunks we're buffering into memory before compressing them */
+#define OSTREAM_LZ4_CHUNK_SIZE (1024*64)
+/* How large chunks we allow in input data before returning a failure.
+ This must be at least OSTREAM_LZ4_CHUNK_SIZE, but for future compatibility
+ should be somewhat higher (but not too high to avoid wasting memory for
+ corrupted files). */
+#define ISTREAM_LZ4_CHUNK_SIZE (1024*1024)
+
+#define IOSTREAM_LZ4_CHUNK_PREFIX_LEN 4 /* big-endian size of chunk */
+
+#endif
diff --git a/src/lib-compression/iostream-zstd-private.h b/src/lib-compression/iostream-zstd-private.h
new file mode 100644
index 0000000..e430bbc
--- /dev/null
+++ b/src/lib-compression/iostream-zstd-private.h
@@ -0,0 +1,35 @@
+#ifndef IOSTREAM_ZSTD_PRIVATE_H
+#define IOSTREAM_ZSTD_PRIVATE_H 1
+
+/* a horrible hack to fix issues when the installed libzstd is lot
+ newer than what we were compiled against. */
+static inline ZSTD_ErrorCode zstd_version_errcode(ZSTD_ErrorCode err)
+{
+#if ZSTD_VERSION_NUMBER < 10301
+ if (ZSTD_versionNumber() > 10300) {
+ /* reinterpret them */
+ if (err == 10)
+ return ZSTD_error_prefix_unknown;
+ if (err == 32)
+ return ZSTD_error_dictionary_wrong;
+ if (err == 62)
+ return ZSTD_error_init_missing;
+ if (err == 64)
+ return ZSTD_error_memory_allocation;
+ return ZSTD_error_GENERIC;
+ }
+#endif
+ return err;
+}
+
+static inline void zstd_version_check(void)
+{
+ /* error codes were pinned on 1.3.1, so we only care about
+ versions before that. */
+ if (ZSTD_VERSION_NUMBER < 10301 || ZSTD_versionNumber() < 10301)
+ if (ZSTD_versionNumber() / 100 != ZSTD_VERSION_NUMBER / 100)
+ i_warning("zstd: Compiled against %u, but %u installed!",
+ ZSTD_VERSION_NUMBER, ZSTD_versionNumber());
+}
+
+#endif
diff --git a/src/lib-compression/istream-bzlib.c b/src/lib-compression/istream-bzlib.c
new file mode 100644
index 0000000..aae1027
--- /dev/null
+++ b/src/lib-compression/istream-bzlib.c
@@ -0,0 +1,231 @@
+/* Copyright (c) 2010-2018 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;
+ struct stat last_parent_statbuf;
+
+ bool hdr_read:1;
+ bool marked:1;
+ bool zs_closed:1;
+};
+
+static void i_stream_bzlib_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct bzlib_istream *zstream = (struct bzlib_istream *)stream;
+
+ if (!zstream->zs_closed) {
+ (void)BZ2_bzDecompressEnd(&zstream->zs);
+ zstream->zs_closed = TRUE;
+ }
+ if (close_parent)
+ i_stream_close(zstream->istream.parent);
+}
+
+static void bzlib_read_error(struct bzlib_istream *zstream, const char *error)
+{
+ io_stream_set_error(&zstream->istream.iostream,
+ "bzlib.read(%s): %s at %"PRIuUOFF_T,
+ i_stream_get_name(&zstream->istream.istream), error,
+ i_stream_get_absolute_offset(&zstream->istream.istream));
+}
+
+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, out_size;
+ int ret;
+
+ high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
+ if (zstream->eof_offset == high_offset) {
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+
+ if (!zstream->marked) {
+ if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size))
+ return -2; /* buffer full */
+ } else {
+ /* try to avoid compressing, so we can quickly seek backwards */
+ if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size))
+ return -2; /* buffer full */
+ }
+
+ if (i_stream_read_more(stream->parent, &data, &size) < 0) {
+ if (stream->parent->stream_errno != 0) {
+ stream->istream.stream_errno =
+ stream->parent->stream_errno;
+ } else {
+ i_assert(stream->parent->eof);
+ bzlib_read_error(zstream, "unexpected EOF");
+ if (!zstream->hdr_read)
+ stream->istream.stream_errno = EINVAL;
+ else
+ stream->istream.stream_errno = EPIPE;
+ }
+ return -1;
+ }
+ if (size == 0) {
+ /* no more input */
+ i_assert(!stream->istream.blocking);
+ return 0;
+ }
+
+ zstream->zs.next_in = (char *)data;
+ zstream->zs.avail_in = size;
+
+ zstream->zs.next_out = (char *)stream->w_buffer + stream->pos;
+ zstream->zs.avail_out = out_size;
+ ret = BZ2_bzDecompress(&zstream->zs);
+ zstream->hdr_read = TRUE;
+
+ out_size -= zstream->zs.avail_out;
+ stream->pos += out_size;
+
+ i_stream_skip(stream->parent, size - zstream->zs.avail_in);
+
+ switch (ret) {
+ case BZ_OK:
+ break;
+ case BZ_PARAM_ERROR:
+ i_unreached();
+ case BZ_DATA_ERROR:
+ bzlib_read_error(zstream, "corrupted data");
+ stream->istream.stream_errno = EINVAL;
+ return -1;
+ case BZ_DATA_ERROR_MAGIC:
+ 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);
+ stream->cached_stream_size = zstream->eof_offset;
+ if (out_size == 0) {
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+ break;
+ default:
+ i_fatal("BZ2_bzDecompress() failed with %d", ret);
+ }
+ if (out_size == 0) {
+ /* read more input */
+ return i_stream_bzlib_read(stream);
+ }
+ return out_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_MAX;
+ 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;
+ stream->high_pos = 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;
+
+ if (i_stream_nonseekable_try_seek(stream, v_offset))
+ return;
+
+ /* have to seek backwards - reset state and retry */
+ i_stream_bzlib_reset(zstream);
+ if (!i_stream_nonseekable_try_seek(stream, v_offset))
+ i_unreached();
+
+ if (mark)
+ zstream->marked = TRUE;
+}
+
+static void i_stream_bzlib_sync(struct istream_private *stream)
+{
+ struct bzlib_istream *zstream = (struct bzlib_istream *) stream;
+ const struct stat *st;
+
+ if (i_stream_stat(stream->parent, FALSE, &st) == 0) {
+ 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)
+{
+ struct bzlib_istream *zstream;
+
+ zstream = i_new(struct bzlib_istream, 1);
+ zstream->eof_offset = UOFF_T_MAX;
+
+ 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.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), 0);
+}
+#endif
diff --git a/src/lib-compression/istream-decompress.c b/src/lib-compression/istream-decompress.c
new file mode 100644
index 0000000..2021a01
--- /dev/null
+++ b/src/lib-compression/istream-decompress.c
@@ -0,0 +1,258 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream-private.h"
+#include "compression.h"
+
+struct decompress_istream {
+ struct istream_private istream;
+ struct istream *compressed_input;
+ struct istream *decompressed_input;
+ enum istream_decompress_flags flags;
+};
+
+static void copy_compressed_input_error(struct decompress_istream *zstream)
+{
+ struct istream_private *stream = &zstream->istream;
+
+ stream->istream.stream_errno = zstream->compressed_input->stream_errno;
+ stream->istream.eof = zstream->compressed_input->eof;
+ if (zstream->compressed_input->stream_errno != 0) {
+ io_stream_set_error(&stream->iostream, "%s",
+ i_stream_get_error(&zstream->compressed_input->real_stream->istream));
+ }
+}
+
+static void copy_decompressed_input_error(struct decompress_istream *zstream)
+{
+ struct istream_private *stream = &zstream->istream;
+
+ stream->istream.stream_errno = zstream->decompressed_input->stream_errno;
+ stream->istream.eof = zstream->decompressed_input->eof;
+ if (zstream->decompressed_input->stream_errno != 0) {
+ io_stream_set_error(&stream->iostream, "%s",
+ i_stream_get_error(&zstream->decompressed_input->real_stream->istream));
+ }
+}
+
+static void
+i_stream_decompress_close(struct iostream_private *_stream, bool close_parent)
+{
+ struct istream_private *stream =
+ container_of(_stream, struct istream_private, iostream);
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+
+ if (zstream->decompressed_input != NULL)
+ i_stream_close(zstream->decompressed_input);
+ if (close_parent)
+ i_stream_close(zstream->compressed_input);
+}
+
+static void
+i_stream_decompress_destroy(struct iostream_private *_stream)
+{
+ struct istream_private *stream =
+ container_of(_stream, struct istream_private, iostream);
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+
+ i_stream_unref(&zstream->decompressed_input);
+ i_stream_unref(&zstream->compressed_input);
+}
+
+static int
+i_stream_decompress_not_compressed(struct decompress_istream *zstream)
+{
+ if ((zstream->flags & ISTREAM_DECOMPRESS_FLAG_TRY) == 0) {
+ zstream->istream.istream.stream_errno = EINVAL;
+ io_stream_set_error(&zstream->istream.iostream,
+ "Stream isn't compressed");
+ return -1;
+ } else {
+ zstream->decompressed_input = zstream->compressed_input;
+ i_stream_ref(zstream->decompressed_input);
+ return 1;
+ }
+}
+
+static int i_stream_decompress_detect(struct decompress_istream *zstream)
+{
+ const struct compression_handler *handler;
+ ssize_t ret;
+
+ ret = i_stream_read(zstream->compressed_input);
+ handler = compression_detect_handler(zstream->compressed_input);
+ if (handler == NULL) {
+ switch (ret) {
+ case -1:
+ if (zstream->compressed_input->stream_errno != 0) {
+ copy_compressed_input_error(zstream);
+ return -1;
+ }
+ /* fall through */
+ case -2:
+ /* we've read a full buffer or we reached EOF -
+ the stream isn't compressed */
+ return i_stream_decompress_not_compressed(zstream);
+ case 0:
+ return 0;
+ default:
+ if (!zstream->istream.istream.blocking)
+ return 0;
+ return i_stream_decompress_detect(zstream);
+ }
+ }
+ if (handler->create_istream == NULL) {
+ zstream->istream.istream.stream_errno = EINVAL;
+ io_stream_set_error(&zstream->istream.iostream,
+ "Compression handler %s not supported", handler->name);
+ return -1;
+ }
+
+ zstream->decompressed_input =
+ handler->create_istream(zstream->compressed_input);
+ return 1;
+}
+
+static ssize_t i_stream_decompress_read(struct istream_private *stream)
+{
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+ ssize_t ret;
+ size_t pos;
+
+ if (zstream->decompressed_input == NULL) {
+ if ((ret = i_stream_decompress_detect(zstream)) <= 0)
+ return ret;
+ }
+
+ i_stream_seek(zstream->decompressed_input, stream->istream.v_offset);
+ stream->pos -= stream->skip;
+ stream->skip = 0;
+
+ stream->buffer = i_stream_get_data(zstream->decompressed_input, &pos);
+ if (pos > stream->pos)
+ ret = 0;
+ else do {
+ ret = i_stream_read_memarea(zstream->decompressed_input);
+ copy_decompressed_input_error(zstream);
+ stream->buffer = i_stream_get_data(zstream->decompressed_input,
+ &pos);
+ } while (pos <= stream->pos && ret > 0);
+ if (ret == -2)
+ return -2;
+
+ if (pos <= stream->pos)
+ ret = ret == 0 ? 0 : -1;
+ else
+ ret = (ssize_t)(pos - stream->pos);
+ stream->pos = pos;
+ i_assert(ret != -1 || stream->istream.eof ||
+ stream->istream.stream_errno != 0);
+ return ret;
+}
+
+static void i_stream_decompress_reset(struct istream_private *stream)
+{
+ stream->skip = stream->pos = 0;
+ stream->istream.v_offset = 0;
+ stream->istream.eof = FALSE;
+}
+
+static void
+i_stream_decompress_seek(struct istream_private *stream,
+ uoff_t v_offset, bool mark)
+{
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+
+ if (zstream->decompressed_input == NULL) {
+ if (!i_stream_nonseekable_try_seek(stream, v_offset))
+ i_panic("seeking backwards before detecting compression format");
+ } else {
+ i_stream_decompress_reset(stream);
+ stream->istream.v_offset = v_offset;
+ if (mark)
+ i_stream_seek_mark(zstream->decompressed_input, v_offset);
+ else
+ i_stream_seek(zstream->decompressed_input, v_offset);
+ copy_decompressed_input_error(zstream);
+ }
+}
+
+static void i_stream_decompress_sync(struct istream_private *stream)
+{
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+
+ i_stream_decompress_reset(stream);
+ if (zstream->decompressed_input != NULL)
+ i_stream_sync(zstream->decompressed_input);
+}
+
+static int i_stream_decompress_stat(struct istream_private *stream, bool exact)
+{
+ struct decompress_istream *zstream =
+ container_of(stream, struct decompress_istream, istream);
+ const struct stat *st;
+
+ if (!exact) {
+ if (i_stream_stat(zstream->compressed_input, exact, &st) < 0) {
+ copy_compressed_input_error(zstream);
+ return -1;
+ }
+ stream->statbuf = *st;
+ return 0;
+ }
+ if (zstream->decompressed_input == NULL) {
+ (void)i_stream_read(&stream->istream);
+ if (zstream->decompressed_input == NULL) {
+ if (stream->istream.stream_errno == 0) {
+ zstream->istream.istream.stream_errno = EINVAL;
+ io_stream_set_error(&zstream->istream.iostream,
+ "Stream compression couldn't be detected during stat");
+ }
+ return -1;
+ }
+ }
+
+ if (i_stream_stat(zstream->decompressed_input, exact, &st) < 0) {
+ copy_decompressed_input_error(zstream);
+ return -1;
+ }
+ i_stream_decompress_reset(stream);
+ stream->statbuf = *st;
+ return 0;
+}
+
+struct istream *
+i_stream_create_decompress(struct istream *input,
+ enum istream_decompress_flags flags)
+{
+ struct decompress_istream *zstream;
+
+ zstream = i_new(struct decompress_istream, 1);
+ zstream->compressed_input = input;
+ zstream->flags = flags;
+ i_stream_ref(input);
+
+ zstream->istream.iostream.close = i_stream_decompress_close;
+ zstream->istream.iostream.destroy = i_stream_decompress_destroy;
+ zstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
+ zstream->istream.read = i_stream_decompress_read;
+ zstream->istream.seek = i_stream_decompress_seek;
+ zstream->istream.sync = i_stream_decompress_sync;
+ zstream->istream.stat = i_stream_decompress_stat;
+
+ zstream->istream.istream.readable_fd = FALSE;
+ zstream->istream.istream.blocking = input->blocking;
+ zstream->istream.istream.seekable = input->seekable;
+
+ struct istream *ret = i_stream_create(&zstream->istream, NULL,
+ i_stream_get_fd(input), 0);
+ /* input isn't used as our parent istream, so need to copy the stream
+ name to preserve it. */
+ i_stream_set_name(ret, i_stream_get_name(input));
+ return ret;
+}
diff --git a/src/lib-compression/istream-lz4.c b/src/lib-compression/istream-lz4.c
new file mode 100644
index 0000000..24d539a
--- /dev/null
+++ b/src/lib-compression/istream-lz4.c
@@ -0,0 +1,281 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#ifdef HAVE_LZ4
+
+#include "buffer.h"
+#include "istream-private.h"
+#include "istream-zlib.h"
+#include "iostream-lz4.h"
+#include <lz4.h>
+
+struct lz4_istream {
+ struct istream_private istream;
+
+ struct stat last_parent_statbuf;
+
+ buffer_t *chunk_buf;
+ uint32_t chunk_size, chunk_left, max_uncompressed_chunk_size;
+
+ bool marked:1;
+ bool header_read:1;
+};
+
+static void i_stream_lz4_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct lz4_istream *zstream = (struct lz4_istream *)stream;
+
+ buffer_free(&zstream->chunk_buf);
+ if (close_parent)
+ i_stream_close(zstream->istream.parent);
+}
+
+static void lz4_read_error(struct lz4_istream *zstream, const char *error)
+{
+ io_stream_set_error(&zstream->istream.iostream,
+ "lz4.read(%s): %s at %"PRIuUOFF_T,
+ i_stream_get_name(&zstream->istream.istream), error,
+ i_stream_get_absolute_offset(&zstream->istream.istream));
+}
+
+static int i_stream_lz4_read_header(struct lz4_istream *zstream)
+{
+ const struct iostream_lz4_header *hdr;
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ ret = i_stream_read_bytes(zstream->istream.parent, &data,
+ &size, sizeof(*hdr));
+ size = I_MIN(size, sizeof(*hdr));
+ buffer_append(zstream->chunk_buf, data, size);
+ i_stream_skip(zstream->istream.parent, size);
+ if (ret < 0 || (ret == 0 && zstream->istream.istream.eof)) {
+ i_assert(ret != -2);
+ if (zstream->istream.istream.stream_errno == 0) {
+ lz4_read_error(zstream, "missing header (not lz4 file?)");
+ zstream->istream.istream.stream_errno = EINVAL;
+ } else
+ zstream->istream.istream.stream_errno =
+ zstream->istream.parent->stream_errno;
+ return ret;
+ }
+ if (zstream->chunk_buf->used < sizeof(*hdr)) {
+ i_assert(!zstream->istream.istream.blocking);
+ return 0;
+ }
+
+ hdr = zstream->chunk_buf->data;
+ if (ret == 0 || memcmp(hdr->magic, IOSTREAM_LZ4_MAGIC,
+ IOSTREAM_LZ4_MAGIC_LEN) != 0) {
+ lz4_read_error(zstream, "wrong magic in header (not lz4 file?)");
+ zstream->istream.istream.stream_errno = EINVAL;
+ return -1;
+ }
+ zstream->max_uncompressed_chunk_size =
+ be32_to_cpu_unaligned(hdr->max_uncompressed_chunk_size);
+ buffer_set_used_size(zstream->chunk_buf, 0);
+ if (zstream->max_uncompressed_chunk_size > ISTREAM_LZ4_CHUNK_SIZE) {
+ lz4_read_error(zstream, t_strdup_printf(
+ "lz4 max chunk size too large (%u > %u)",
+ zstream->max_uncompressed_chunk_size,
+ ISTREAM_LZ4_CHUNK_SIZE));
+ zstream->istream.istream.stream_errno = EINVAL;
+ return -1;
+ }
+ return 1;
+}
+
+static int i_stream_lz4_read_chunk_header(struct lz4_istream *zstream)
+{
+ int ret;
+ size_t size;
+ const unsigned char *data;
+ struct istream_private *stream = &zstream->istream;
+
+ i_assert(zstream->chunk_buf->used <= IOSTREAM_LZ4_CHUNK_PREFIX_LEN);
+ ret = i_stream_read_more(stream->parent, &data, &size);
+ size = I_MIN(size, IOSTREAM_LZ4_CHUNK_PREFIX_LEN - zstream->chunk_buf->used);
+ buffer_append(zstream->chunk_buf, data, size);
+ i_stream_skip(stream->parent, size);
+ if (ret < 0) {
+ i_assert(ret != -2);
+ stream->istream.stream_errno = stream->parent->stream_errno;
+ if (stream->istream.stream_errno == 0) {
+ stream->istream.eof = TRUE;
+ stream->cached_stream_size =
+ stream->istream.v_offset +
+ stream->pos - stream->skip;
+ }
+ return ret;
+ }
+ i_assert(ret != 0 || !stream->istream.blocking);
+ if (ret == 0)
+ return ret;
+ if (zstream->chunk_buf->used < IOSTREAM_LZ4_CHUNK_PREFIX_LEN)
+ return 0;
+ zstream->chunk_size = zstream->chunk_left =
+ be32_to_cpu_unaligned(zstream->chunk_buf->data);
+ if (zstream->chunk_size == 0 ||
+ zstream->chunk_size > ISTREAM_LZ4_CHUNK_SIZE) {
+ lz4_read_error(zstream, t_strdup_printf(
+ "invalid lz4 chunk size: %u", zstream->chunk_size));
+ stream->istream.stream_errno = EINVAL;
+ return -1;
+ }
+ buffer_set_used_size(zstream->chunk_buf, 0);
+ return 1;
+}
+
+static ssize_t i_stream_lz4_read(struct istream_private *stream)
+{
+ struct lz4_istream *zstream = (struct lz4_istream *)stream;
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ /* if we already have max_buffer_size amount of data, fail here */
+ if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream))
+ return -2;
+
+ if (!zstream->header_read) {
+ if ((ret = i_stream_lz4_read_header(zstream)) <= 0) {
+ stream->istream.eof = TRUE;
+ return ret;
+ }
+ zstream->header_read = TRUE;
+ }
+
+ if (zstream->chunk_left == 0) {
+ while ((ret = i_stream_lz4_read_chunk_header(zstream)) == 0) {
+ if (!stream->istream.blocking)
+ return 0;
+ }
+ if (ret < 0)
+ return ret;
+ }
+
+ /* read the whole compressed chunk into memory */
+ while (zstream->chunk_left > 0 &&
+ (ret = i_stream_read_more(zstream->istream.parent, &data, &size)) > 0) {
+ if (size > zstream->chunk_left)
+ size = zstream->chunk_left;
+ buffer_append(zstream->chunk_buf, data, size);
+ i_stream_skip(zstream->istream.parent, size);
+ zstream->chunk_left -= size;
+ }
+ if (zstream->chunk_left > 0) {
+ if (ret == -1 && zstream->istream.parent->stream_errno == 0) {
+ lz4_read_error(zstream, "truncated lz4 chunk");
+ stream->istream.stream_errno = EPIPE;
+ return -1;
+ }
+ zstream->istream.istream.stream_errno =
+ zstream->istream.parent->stream_errno;
+ i_assert(ret != 0 || !stream->istream.blocking);
+ return ret;
+ }
+ /* if we already have max_buffer_size amount of data, fail here */
+ if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream))
+ return -2;
+ if (i_stream_get_data_size(zstream->istream.parent) > 0) {
+ /* Parent stream was only partially consumed. Set the stream's
+ IO as pending to avoid hangs. */
+ i_stream_set_input_pending(&zstream->istream.istream, TRUE);
+ }
+ /* allocate enough space for the old data and the new
+ decompressed chunk. we don't know the original compressed size,
+ so just allocate the max amount of memory. */
+ void *dest = i_stream_alloc(stream, zstream->max_uncompressed_chunk_size);
+ ret = LZ4_decompress_safe(zstream->chunk_buf->data, dest,
+ zstream->chunk_buf->used,
+ zstream->max_uncompressed_chunk_size);
+ i_assert(ret <= (int)zstream->max_uncompressed_chunk_size);
+ if (ret < 0) {
+ lz4_read_error(zstream, "corrupted lz4 chunk");
+ stream->istream.stream_errno = EINVAL;
+ return -1;
+ }
+ i_assert(ret > 0);
+ stream->pos += ret;
+ i_assert(stream->pos <= stream->buffer_size);
+
+ /* we are going to get next chunk after this, so reset here
+ so we can reuse the chunk buf for reading next buffer prefix */
+ if (zstream->chunk_left == 0)
+ buffer_set_used_size(zstream->chunk_buf, 0);
+
+ return ret;
+}
+
+static void i_stream_lz4_reset(struct lz4_istream *zstream)
+{
+ struct istream_private *stream = &zstream->istream;
+
+ i_stream_seek(stream->parent, stream->parent_start_offset);
+ zstream->header_read = FALSE;
+ zstream->chunk_size = zstream->chunk_left = 0;
+
+ stream->parent_expected_offset = stream->parent_start_offset;
+ stream->skip = stream->pos = 0;
+ stream->istream.v_offset = 0;
+ buffer_set_used_size(zstream->chunk_buf, 0);
+}
+
+static void
+i_stream_lz4_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
+{
+ struct lz4_istream *zstream = (struct lz4_istream *) stream;
+
+ if (i_stream_nonseekable_try_seek(stream, v_offset))
+ return;
+
+ /* have to seek backwards - reset state and retry */
+ i_stream_lz4_reset(zstream);
+ if (!i_stream_nonseekable_try_seek(stream, v_offset))
+ i_unreached();
+
+ if (mark)
+ zstream->marked = TRUE;
+}
+
+static void i_stream_lz4_sync(struct istream_private *stream)
+{
+ struct lz4_istream *zstream = (struct lz4_istream *) stream;
+ const struct stat *st;
+
+ if (i_stream_stat(stream->parent, FALSE, &st) == 0) {
+ 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_lz4_reset(zstream);
+}
+
+struct istream *i_stream_create_lz4(struct istream *input)
+{
+ struct lz4_istream *zstream;
+
+ zstream = i_new(struct lz4_istream, 1);
+
+ zstream->istream.iostream.close = i_stream_lz4_close;
+ zstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
+ zstream->istream.read = i_stream_lz4_read;
+ zstream->istream.seek = i_stream_lz4_seek;
+ zstream->istream.sync = i_stream_lz4_sync;
+
+ zstream->istream.istream.readable_fd = FALSE;
+ zstream->istream.istream.blocking = input->blocking;
+ zstream->istream.istream.seekable = input->seekable;
+ zstream->chunk_buf = buffer_create_dynamic(default_pool, 1024);
+
+ return i_stream_create(&zstream->istream, input,
+ i_stream_get_fd(input), 0);
+}
+#endif
diff --git a/src/lib-compression/istream-lzma.c b/src/lib-compression/istream-lzma.c
new file mode 100644
index 0000000..7b0c2a6
--- /dev/null
+++ b/src/lib-compression/istream-lzma.c
@@ -0,0 +1,264 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#ifdef HAVE_LZMA
+
+#include "istream-private.h"
+#include "istream-zlib.h"
+#include <lzma.h>
+
+#define CHUNK_SIZE (1024*64)
+
+#define LZMA_MEMORY_LIMIT (1024*1024*80)
+
+struct lzma_istream {
+ struct istream_private istream;
+
+ lzma_stream strm;
+ uoff_t eof_offset;
+ struct stat last_parent_statbuf;
+
+ bool hdr_read:1;
+ bool marked:1;
+ bool strm_closed:1;
+};
+
+static void i_stream_lzma_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct lzma_istream *zstream = (struct lzma_istream *)stream;
+
+ if (!zstream->strm_closed) {
+ lzma_end(&zstream->strm);
+ zstream->strm_closed = TRUE;
+ }
+ if (close_parent)
+ i_stream_close(zstream->istream.parent);
+}
+
+static void lzma_read_error(struct lzma_istream *zstream, const char *error)
+{
+ io_stream_set_error(&zstream->istream.iostream,
+ "lzma.read(%s): %s at %"PRIuUOFF_T,
+ i_stream_get_name(&zstream->istream.istream), error,
+ i_stream_get_absolute_offset(&zstream->istream.istream));
+}
+
+static int lzma_handle_error(struct lzma_istream *zstream, lzma_ret lzma_err)
+{
+ struct istream_private *stream = &zstream->istream;
+ switch (lzma_err) {
+ case LZMA_OK:
+ break;
+ case LZMA_DATA_ERROR:
+ case LZMA_BUF_ERROR:
+ lzma_read_error(zstream, "corrupted data");
+ stream->istream.stream_errno = EINVAL;
+ return -1;
+ case LZMA_FORMAT_ERROR:
+ lzma_read_error(zstream, "wrong magic in header (not xz file?)");
+ stream->istream.stream_errno = EINVAL;
+ return -1;
+ case LZMA_OPTIONS_ERROR:
+ lzma_read_error(zstream, "Unsupported xz options");
+ stream->istream.stream_errno = EIO;
+ return -1;
+ case LZMA_MEM_ERROR:
+ i_fatal_status(FATAL_OUTOFMEM, "lzma.read(%s): Out of memory",
+ i_stream_get_name(&stream->istream));
+ case LZMA_STREAM_END:
+ break;
+ default:
+ lzma_read_error(zstream, t_strdup_printf(
+ "lzma_code() failed with %d", lzma_err));
+ stream->istream.stream_errno = EIO;
+ return -1;
+ }
+ return 0;
+}
+
+static void lzma_stream_end(struct lzma_istream *zstream)
+{
+ zstream->eof_offset = zstream->istream.istream.v_offset +
+ (zstream->istream.pos - zstream->istream.skip);
+ zstream->istream.cached_stream_size = zstream->eof_offset;
+}
+
+static ssize_t i_stream_lzma_read(struct istream_private *stream)
+{
+ struct lzma_istream *zstream = (struct lzma_istream *)stream;
+ const unsigned char *data;
+ uoff_t high_offset;
+ size_t size, out_size;
+ lzma_ret ret;
+
+ high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
+ if (zstream->eof_offset == high_offset) {
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+
+ if (!zstream->marked) {
+ if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size))
+ return -2; /* buffer full */
+ } else {
+ /* try to avoid compressing, so we can quickly seek backwards */
+ if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size))
+ return -2; /* buffer full */
+ }
+
+ if (i_stream_read_more(stream->parent, &data, &size) < 0) {
+ if (stream->parent->stream_errno != 0) {
+ stream->istream.stream_errno =
+ stream->parent->stream_errno;
+ } else {
+ i_assert(stream->parent->eof);
+ lzma_stream_end(zstream);
+ ret = lzma_code(&zstream->strm, LZMA_FINISH);
+ if (lzma_handle_error(zstream, ret) < 0)
+ ;
+ else if (!zstream->hdr_read) {
+ lzma_read_error(zstream, "file too small (not xz file?)");
+ stream->istream.stream_errno = EINVAL;
+ } else if (ret != LZMA_STREAM_END) {
+ lzma_read_error(zstream, "unexpected EOF");
+ stream->istream.stream_errno = EPIPE;
+ }
+ stream->istream.eof = TRUE;
+ }
+ return -1;
+ }
+ if (size == 0) {
+ /* no more input */
+ i_assert(!stream->istream.blocking);
+ return 0;
+ }
+
+ zstream->strm.next_in = data;
+ zstream->strm.avail_in = size;
+
+ zstream->strm.next_out = stream->w_buffer + stream->pos;
+ zstream->strm.avail_out = out_size;
+ if (!zstream->hdr_read && size > LZMA_STREAM_HEADER_SIZE)
+ zstream->hdr_read = TRUE;
+ ret = lzma_code(&zstream->strm, LZMA_RUN);
+
+ out_size -= zstream->strm.avail_out;
+ stream->pos += out_size;
+
+ size_t bytes_consumed = size - zstream->strm.avail_in;
+ i_stream_skip(stream->parent, bytes_consumed);
+ if (i_stream_get_data_size(stream->parent) > 0 &&
+ (bytes_consumed > 0 || out_size > 0)) {
+ /* Parent stream was only partially consumed. Set the stream's
+ IO as pending to avoid hangs. */
+ i_stream_set_input_pending(&stream->istream, TRUE);
+ }
+
+ if (lzma_handle_error(zstream, ret) < 0) {
+ return -1;
+ } else if (ret == LZMA_STREAM_END) {
+ lzma_stream_end(zstream);
+ if (out_size == 0) {
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+ }
+ if (out_size == 0) {
+ /* read more input */
+ return i_stream_lzma_read(stream);
+ }
+ return out_size;
+}
+
+static void i_stream_lzma_init(struct lzma_istream *zstream)
+{
+ lzma_ret ret;
+
+ ret = lzma_stream_decoder(&zstream->strm, LZMA_MEMORY_LIMIT,
+ LZMA_CONCATENATED);
+ switch (ret) {
+ case LZMA_OK:
+ break;
+ case LZMA_MEM_ERROR:
+ i_fatal_status(FATAL_OUTOFMEM, "lzma: Out of memory");
+ default:
+ i_fatal("lzma_stream_decoder() failed with ret=%d", ret);
+ }
+}
+
+static void i_stream_lzma_reset(struct lzma_istream *zstream)
+{
+ struct istream_private *stream = &zstream->istream;
+
+ i_stream_seek(stream->parent, stream->parent_start_offset);
+ zstream->eof_offset = UOFF_T_MAX;
+ zstream->strm.next_in = NULL;
+ zstream->strm.avail_in = 0;
+
+ stream->parent_expected_offset = stream->parent_start_offset;
+ stream->skip = stream->pos = 0;
+ stream->istream.v_offset = 0;
+
+ lzma_end(&zstream->strm);
+ i_stream_lzma_init(zstream);
+}
+
+static void
+i_stream_lzma_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
+{
+ struct lzma_istream *zstream = (struct lzma_istream *) stream;
+
+ if (i_stream_nonseekable_try_seek(stream, v_offset))
+ return;
+
+ /* have to seek backwards - reset state and retry */
+ i_stream_lzma_reset(zstream);
+ if (!i_stream_nonseekable_try_seek(stream, v_offset))
+ i_unreached();
+
+ if (mark)
+ zstream->marked = TRUE;
+}
+
+static void i_stream_lzma_sync(struct istream_private *stream)
+{
+ struct lzma_istream *zstream = (struct lzma_istream *) stream;
+ const struct stat *st;
+
+ if (i_stream_stat(stream->parent, FALSE, &st) == 0) {
+ 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_lzma_reset(zstream);
+}
+
+struct istream *i_stream_create_lzma(struct istream *input)
+{
+ struct lzma_istream *zstream;
+
+ zstream = i_new(struct lzma_istream, 1);
+ zstream->eof_offset = UOFF_T_MAX;
+
+ i_stream_lzma_init(zstream);
+
+ zstream->istream.iostream.close = i_stream_lzma_close;
+ zstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
+ zstream->istream.read = i_stream_lzma_read;
+ zstream->istream.seek = i_stream_lzma_seek;
+ zstream->istream.sync = i_stream_lzma_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), 0);
+}
+#endif
diff --git a/src/lib-compression/istream-zlib.c b/src/lib-compression/istream-zlib.c
new file mode 100644
index 0000000..3a975c3
--- /dev/null
+++ b/src/lib-compression/istream-zlib.c
@@ -0,0 +1,431 @@
+/* Copyright (c) 2010-2018 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;
+ size_t prev_size;
+ uint32_t crc32;
+ struct stat last_parent_statbuf;
+
+ bool gz:1;
+ bool marked:1;
+ bool header_read:1;
+ bool trailer_read:1;
+ bool zs_closed:1;
+ bool starting_concated_output:1;
+};
+
+static void i_stream_zlib_init(struct zlib_istream *zstream);
+
+static void i_stream_zlib_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct zlib_istream *zstream = (struct zlib_istream *)stream;
+
+ if (!zstream->zs_closed) {
+ (void)inflateEnd(&zstream->zs);
+ zstream->zs_closed = TRUE;
+ }
+ if (close_parent)
+ i_stream_close(zstream->istream.parent);
+}
+
+static void zlib_read_error(struct zlib_istream *zstream, const char *error)
+{
+ io_stream_set_error(&zstream->istream.iostream,
+ "zlib.read(%s): %s at %"PRIuUOFF_T,
+ i_stream_get_name(&zstream->istream.istream), error,
+ i_stream_get_absolute_offset(&zstream->istream.istream));
+}
+
+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_bytes(stream->parent, &data, &size,
+ zstream->prev_size + 1);
+ if (size == zstream->prev_size) {
+ stream->istream.stream_errno = stream->parent->stream_errno;
+ if (ret == -1 && stream->istream.stream_errno == 0) {
+ zlib_read_error(zstream, "missing gz header");
+ stream->istream.stream_errno = EINVAL;
+ }
+ if (ret == -2) {
+ zlib_read_error(zstream, "gz header is too large");
+ stream->istream.stream_errno = EINVAL;
+ ret = -1;
+ }
+ 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 */
+ 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 = le16_to_cpu_unaligned(&data[pos]);
+ 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);
+ zstream->prev_size = 0;
+ return 1;
+}
+
+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_bytes(stream->parent, &data, &size,
+ GZ_TRAILER_SIZE);
+ if (size == zstream->prev_size) {
+ stream->istream.stream_errno = stream->parent->stream_errno;
+ if (ret == -1 && stream->istream.stream_errno == 0) {
+ 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 (le32_to_cpu_unaligned(data) != zstream->crc32) {
+ 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, out_size;
+ int ret;
+
+ high_offset = stream->istream.v_offset + (stream->pos - stream->skip);
+ if (zstream->eof_offset == high_offset) {
+ /* zlib library returned EOF. */
+ if (!zstream->gz) {
+ /* deflate - ignore if there's still more data */
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+ /* gz format - read the trailer */
+ if (!zstream->trailer_read) {
+ do {
+ ret = i_stream_zlib_read_trailer(zstream);
+ } while (ret == 0 && stream->istream.blocking);
+ if (ret <= 0)
+ return ret;
+ }
+ /* See if there's another concatenated gz stream. */
+ if (i_stream_read_eof(stream->parent)) {
+ /* EOF or error */
+ stream->istream.stream_errno =
+ stream->parent->stream_errno;
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+ /* Multiple gz streams concatenated together */
+ zstream->starting_concated_output = TRUE;
+ }
+ if (zstream->starting_concated_output) {
+ /* make sure there actually is something in parent stream.
+ we don't want to reset the stream unless we actually see
+ some concated output. */
+ ret = i_stream_read_more(stream->parent, &data, &size);
+ if (ret <= 0) {
+ if (ret == 0)
+ return 0;
+ if (stream->parent->stream_errno != 0) {
+ stream->istream.stream_errno =
+ stream->parent->stream_errno;
+ }
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+
+ /* gzip file with concatenated content */
+ stream->cached_stream_size = UOFF_T_MAX;
+ zstream->eof_offset = UOFF_T_MAX;
+ zstream->header_read = FALSE;
+ zstream->trailer_read = FALSE;
+ zstream->crc32 = 0;
+ zstream->starting_concated_output = FALSE;
+
+ (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;
+ }
+
+ if (!zstream->marked) {
+ if (!i_stream_try_alloc(stream, CHUNK_SIZE, &out_size))
+ return -2; /* buffer full */
+ } else {
+ /* try to avoid compressing, so we can quickly seek backwards */
+ if (!i_stream_try_alloc_avoid_compress(stream, CHUNK_SIZE, &out_size))
+ return -2; /* buffer full */
+ }
+
+ if (i_stream_read_more(stream->parent, &data, &size) < 0) {
+ if (stream->parent->stream_errno != 0) {
+ stream->istream.stream_errno =
+ stream->parent->stream_errno;
+ } else {
+ i_assert(stream->parent->eof);
+ zlib_read_error(zstream, "unexpected EOF");
+ stream->istream.stream_errno = EPIPE;
+ }
+ return -1;
+ }
+ if (size == 0) {
+ /* no more input */
+ i_assert(!stream->istream.blocking);
+ return 0;
+ }
+
+ zstream->zs.next_in = (void *)data;
+ zstream->zs.avail_in = size;
+
+ zstream->zs.next_out = stream->w_buffer + stream->pos;
+ zstream->zs.avail_out = out_size;
+ ret = inflate(&zstream->zs, Z_SYNC_FLUSH);
+
+ out_size -= zstream->zs.avail_out;
+ zstream->crc32 = crc32_data_more(zstream->crc32,
+ stream->w_buffer + stream->pos,
+ out_size);
+ stream->pos += out_size;
+
+ size_t bytes_consumed = size - zstream->zs.avail_in;
+ i_stream_skip(stream->parent, bytes_consumed);
+ if (i_stream_get_data_size(stream->parent) > 0 &&
+ (bytes_consumed > 0 || out_size > 0)) {
+ /* Parent stream was only partially consumed. Set the stream's
+ IO as pending to avoid hangs. */
+ i_stream_set_input_pending(&stream->istream, TRUE);
+ }
+
+ switch (ret) {
+ case Z_OK:
+ break;
+ case Z_NEED_DICT:
+ zlib_read_error(zstream, "can't read file without dict");
+ stream->istream.stream_errno = EIO;
+ return -1;
+ case Z_DATA_ERROR:
+ 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);
+ stream->cached_stream_size = zstream->eof_offset;
+ zstream->zs.avail_in = 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 (out_size == 0) {
+ /* read more input */
+ return i_stream_zlib_read(stream);
+ }
+ return out_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_MAX;
+ 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;
+ stream->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;
+
+ if (i_stream_nonseekable_try_seek(stream, v_offset))
+ return;
+
+ /* have to seek backwards - reset state and retry */
+ i_stream_zlib_reset(zstream);
+ if (!i_stream_nonseekable_try_seek(stream, v_offset))
+ i_unreached();
+
+ if (mark)
+ zstream->marked = TRUE;
+}
+
+static void i_stream_zlib_sync(struct istream_private *stream)
+{
+ struct zlib_istream *zstream = (struct zlib_istream *) stream;
+ const struct stat *st;
+
+ if (i_stream_stat(stream->parent, FALSE, &st) == 0) {
+ 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)
+{
+ struct zlib_istream *zstream;
+
+ zstream = i_new(struct zlib_istream, 1);
+ zstream->eof_offset = UOFF_T_MAX;
+ zstream->gz = gz;
+
+ 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.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), 0);
+}
+
+struct istream *i_stream_create_gz(struct istream *input)
+{
+ return i_stream_create_zlib(input, TRUE);
+}
+
+struct istream *i_stream_create_deflate(struct istream *input)
+{
+ return i_stream_create_zlib(input, FALSE);
+}
+#endif
diff --git a/src/lib-compression/istream-zlib.h b/src/lib-compression/istream-zlib.h
new file mode 100644
index 0000000..041fbf3
--- /dev/null
+++ b/src/lib-compression/istream-zlib.h
@@ -0,0 +1,11 @@
+#ifndef ISTREAM_ZLIB_H
+#define ISTREAM_ZLIB_H
+
+struct istream *i_stream_create_gz(struct istream *input);
+struct istream *i_stream_create_deflate(struct istream *input);
+struct istream *i_stream_create_bz2(struct istream *input);
+struct istream *i_stream_create_lzma(struct istream *input);
+struct istream *i_stream_create_lz4(struct istream *input);
+struct istream *i_stream_create_zstd(struct istream *input);
+
+#endif
diff --git a/src/lib-compression/istream-zstd.c b/src/lib-compression/istream-zstd.c
new file mode 100644
index 0000000..9e059bc
--- /dev/null
+++ b/src/lib-compression/istream-zstd.c
@@ -0,0 +1,268 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#ifdef HAVE_ZSTD
+
+#include "buffer.h"
+#include "istream-private.h"
+#include "istream-zlib.h"
+
+#include "zstd.h"
+#include "zstd_errors.h"
+#include "iostream-zstd-private.h"
+
+#ifndef HAVE_ZSTD_GETERRORCODE
+ZSTD_ErrorCode ZSTD_getErrorCode(size_t functionResult)
+{
+ ssize_t errcode = (ssize_t)functionResult;
+ if (errcode < 0)
+ return -errcode;
+ return ZSTD_error_no_error;
+}
+#endif
+
+struct zstd_istream {
+ struct istream_private istream;
+
+ ZSTD_DStream *dstream;
+ ZSTD_inBuffer input;
+ ZSTD_outBuffer output;
+
+ struct stat last_parent_statbuf;
+
+ /* ZSTD input size */
+ size_t input_size;
+
+ /* storage for frames */
+ buffer_t *frame_buffer;
+
+ /* storage for data */
+ buffer_t *data_buffer;
+
+ bool hdr_read:1;
+ bool marked:1;
+ bool zs_closed:1;
+ /* is there data remaining */
+ bool remain:1;
+};
+
+static void i_stream_zstd_init(struct zstd_istream *zstream)
+{
+ zstream->dstream = ZSTD_createDStream();
+ if (zstream->dstream == NULL)
+ i_fatal_status(FATAL_OUTOFMEM, "zstd: Out of memory");
+ ZSTD_initDStream(zstream->dstream);
+ zstream->input_size = ZSTD_DStreamInSize();
+ if (zstream->frame_buffer == NULL)
+ zstream->frame_buffer = buffer_create_dynamic(default_pool, ZSTD_DStreamInSize());
+ else
+ buffer_set_used_size(zstream->frame_buffer, 0);
+ if (zstream->data_buffer == NULL)
+ zstream->data_buffer = buffer_create_dynamic(default_pool, ZSTD_DStreamOutSize());
+ else
+ buffer_set_used_size(zstream->data_buffer, 0);
+ zstream->zs_closed = FALSE;
+}
+
+static void i_stream_zstd_deinit(struct zstd_istream *zstream, bool reuse_buffers)
+{
+ (void)ZSTD_freeDStream(zstream->dstream);
+ zstream->dstream = NULL;
+ if (!reuse_buffers) {
+ buffer_free(&zstream->frame_buffer);
+ buffer_free(&zstream->data_buffer);
+ }
+ zstream->zs_closed = TRUE;
+ i_zero(&zstream->input);
+}
+
+static void i_stream_zstd_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct istream_private *_istream =
+ container_of(stream, struct istream_private, iostream);
+ struct zstd_istream *zstream =
+ container_of(_istream, struct zstd_istream, istream);
+ if (!zstream->zs_closed)
+ i_stream_zstd_deinit(zstream, FALSE);
+ buffer_free(&zstream->frame_buffer);
+ if (close_parent)
+ i_stream_close(zstream->istream.parent);
+}
+
+static void i_stream_zstd_read_error(struct zstd_istream *zstream, size_t err)
+{
+ ZSTD_ErrorCode errcode = zstd_version_errcode(ZSTD_getErrorCode(err));
+ const char *error = ZSTD_getErrorName(err);
+ if (errcode == ZSTD_error_memory_allocation)
+ i_fatal_status(FATAL_OUTOFMEM, "zstd.read(%s): Out of memory",
+ i_stream_get_name(&zstream->istream.istream));
+ else if (errcode == ZSTD_error_prefix_unknown ||
+#if HAVE_DECL_ZSTD_ERROR_PARAMETER_UNSUPPORTED == 1
+ errcode == ZSTD_error_parameter_unsupported ||
+#endif
+
+ errcode == ZSTD_error_dictionary_wrong ||
+ errcode == ZSTD_error_init_missing)
+ zstream->istream.istream.stream_errno = EINVAL;
+ else
+ zstream->istream.istream.stream_errno = EIO;
+
+ io_stream_set_error(&zstream->istream.iostream,
+ "zstd.read(%s): %s at %"PRIuUOFF_T,
+ i_stream_get_name(&zstream->istream.istream), error,
+ i_stream_get_absolute_offset(&zstream->istream.istream));
+}
+
+static ssize_t i_stream_zstd_read(struct istream_private *stream)
+{
+ struct zstd_istream *zstream =
+ container_of(stream, struct zstd_istream, istream);
+ const unsigned char *data;
+ size_t size;
+
+ if (stream->istream.eof)
+ return -1;
+
+ for (;;) {
+ if (zstream->data_buffer->used > 0) {
+ if (!i_stream_try_alloc(stream, stream->max_buffer_size, &size))
+ return -2;
+ size = I_MIN(zstream->data_buffer->used, size);
+ memcpy(PTR_OFFSET(stream->w_buffer,stream->pos),
+ zstream->data_buffer->data, size);
+ stream->pos += size;
+ buffer_delete(zstream->data_buffer, 0, size);
+ return size;
+ }
+
+ /* see if we can get more */
+ if (zstream->input.pos == zstream->input.size) {
+ ssize_t ret;
+ buffer_set_used_size(zstream->frame_buffer, 0);
+ /* need to read more */
+ if ((ret = i_stream_read_more(stream->parent, &data, &size)) < 0) {
+ stream->istream.stream_errno =
+ stream->parent->stream_errno;
+ stream->istream.eof = stream->parent->eof;
+ if (stream->istream.stream_errno != 0)
+ ;
+ else if (!zstream->hdr_read)
+ stream->istream.stream_errno = EINVAL;
+ else if (zstream->remain)
+ /* truncated data */
+ stream->istream.stream_errno = EPIPE;
+ return ret;
+ }
+ if (ret == 0)
+ return 0;
+ buffer_append(zstream->frame_buffer, data, size);
+ /* NOTE: All of the parent stream input is skipped
+ over here. This is why there's no need to call
+ i_stream_set_input_pending() here like with other
+ compression istreams. */
+ i_stream_skip(stream->parent, size);
+ zstream->input.src = zstream->frame_buffer->data;
+ zstream->input.size = zstream->frame_buffer->used;
+ zstream->input.pos = 0;
+ }
+
+ i_assert(zstream->input.size > 0);
+ i_assert(zstream->data_buffer->used == 0);
+ zstream->output.dst = buffer_append_space_unsafe(zstream->data_buffer,
+ ZSTD_DStreamOutSize());
+ zstream->output.pos = 0;
+ zstream->output.size = ZSTD_DStreamOutSize();
+
+ size_t zret = ZSTD_decompressStream(zstream->dstream, &zstream->output,
+ &zstream->input);
+ if (ZSTD_isError(zret) != 0) {
+ i_stream_zstd_read_error(zstream, zret);
+ return -1;
+ }
+ /* ZSTD magic number is 4 bytes, but it's only defined after v0.8 */
+ if (!zstream->hdr_read && zstream->input.size > 4)
+ zstream->hdr_read = TRUE;
+ zstream->remain = zret > 0;
+ buffer_set_used_size(zstream->data_buffer, zstream->output.pos);
+ }
+ i_unreached();
+}
+
+static void i_stream_zstd_reset(struct zstd_istream *zstream)
+{
+ struct istream_private *stream = &zstream->istream;
+
+ i_stream_seek(stream->parent, stream->parent_start_offset);
+ stream->parent_expected_offset = stream->parent_start_offset;
+ stream->skip = stream->pos = 0;
+ stream->istream.v_offset = 0;
+ stream->high_pos = 0;
+
+ i_stream_zstd_deinit(zstream, TRUE);
+ i_stream_zstd_init(zstream);
+}
+
+static void
+i_stream_zstd_seek(struct istream_private *stream, uoff_t v_offset, bool mark)
+{
+ struct zstd_istream *zstream =
+ container_of(stream, struct zstd_istream, istream);
+
+ if (i_stream_nonseekable_try_seek(stream, v_offset))
+ return;
+
+ /* have to seek backwards - reset state and retry */
+ i_stream_zstd_reset(zstream);
+ if (!i_stream_nonseekable_try_seek(stream, v_offset))
+ i_unreached();
+
+ if (mark)
+ zstream->marked = TRUE;
+}
+
+static void i_stream_zstd_sync(struct istream_private *stream)
+{
+ struct zstd_istream *zstream =
+ container_of(stream, struct zstd_istream, istream);
+ const struct stat *st;
+
+ if (i_stream_stat(stream->parent, FALSE, &st) == 0) {
+ 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_zstd_reset(zstream);
+}
+
+struct istream *
+i_stream_create_zstd(struct istream *input)
+{
+ struct zstd_istream *zstream;
+
+ zstd_version_check();
+
+ zstream = i_new(struct zstd_istream, 1);
+
+ i_stream_zstd_init(zstream);
+
+ zstream->istream.iostream.close = i_stream_zstd_close;
+ zstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
+ zstream->istream.read = i_stream_zstd_read;
+ zstream->istream.seek = i_stream_zstd_seek;
+ zstream->istream.sync = i_stream_zstd_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), 0);
+}
+
+#endif
diff --git a/src/lib-compression/ostream-bzlib.c b/src/lib-compression/ostream-bzlib.c
new file mode 100644
index 0000000..4d072b2
--- /dev/null
+++ b/src/lib-compression/ostream-bzlib.c
@@ -0,0 +1,307 @@
+/* Copyright (c) 2010-2018 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;
+
+ bool flushed:1;
+};
+
+/* in bzlib, level is actually block size. From bzlib manual:
+
+ The block size affects both the compression ratio achieved,
+ and the amount of memory needed for compression and decompression.
+
+ BlockSize 1 through BlockSize 9 specify the block size to be 100,000 bytes
+ through 900,000 bytes respectively. The default is to use the maximum block
+ size.
+
+ Larger block sizes give rapidly diminishing marginal returns.
+ Most of the compression comes from the first two or three hundred k of
+ block size, a fact worth bearing in mind when using bzip2 on small machines.
+ It is also important to appreciate that the decompression memory
+ requirement is set at compression time by the choice of block size.
+
+ * In general, try and use the largest block size memory constraints
+ allow, since that maximises the compression achieved.
+ * Compression and decompression speed are virtually unaffected by block
+ size.
+
+ Another significant point applies to files which fit in a single block -
+ that means most files you'd encounter using a large block size. The
+ amount of real memory touched is proportional to the size of the file,
+ since the file is smaller than a block. For example, compressing a file
+ 20,000 bytes long with the flag BlockSize 9 will cause the compressor to
+ allocate around 7600k of memory, but only touch 400k + 20000 * 8 = 560 kbytes
+ of it. Similarly, the decompressor will allocate 3700k but only
+ touch 100k + 20000 * 4 = 180 kbytes.
+*/
+
+int compression_get_min_level_bz2(void)
+{
+ return 1;
+}
+
+int compression_get_default_level_bz2(void)
+{
+ /* default is maximum level */
+ return 9;
+}
+
+int compression_get_max_level_bz2(void)
+{
+ return 9;
+}
+
+static void o_stream_bzlib_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream;
+
+ (void)BZ2_bzCompressEnd(&zstream->zs);
+ if (close_parent)
+ o_stream_close(zstream->ostream.parent);
+}
+
+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 ((ret = BZ2_bzCompress(zs, BZ_RUN))) {
+ case BZ_RUN_OK:
+ break;
+ case BZ_MEM_ERROR:
+ i_fatal_status(FATAL_OUTOFMEM, "bzip2.write(%s): Out of memory",
+ o_stream_get_name(&zstream->ostream.ostream));
+ default:
+ i_fatal("BZ2_bzCompress() failed with %d", ret);
+ }
+ }
+ size -= zs->avail_in;
+
+ zstream->flushed = FALSE;
+ return size;
+}
+
+static int o_stream_bzlib_send_flush(struct bzlib_ostream *zstream, bool final)
+{
+ bz_stream *zs = &zstream->zs;
+ size_t len;
+ bool done = FALSE;
+ int ret;
+
+ i_assert(zs->avail_in == 0);
+
+ if (zstream->flushed) {
+ i_assert(zstream->outbuf_used == 0);
+ return 1;
+ }
+
+ if ((ret = o_stream_flush_parent_if_needed(&zstream->ostream)) <= 0)
+ return ret;
+ if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
+ return ret;
+
+ /* do not attempt to finish the stream early */
+ if (!final)
+ return 1;
+
+ 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_RUN_OK:
+ case BZ_FLUSH_OK:
+ case BZ_STREAM_END:
+ done = TRUE;
+ break;
+ case BZ_FINISH_OK:
+ break;
+ case BZ_MEM_ERROR:
+ i_fatal_status(FATAL_OUTOFMEM, "bzip2.write(%s): Out of memory",
+ o_stream_get_name(&zstream->ostream.ostream));
+ default:
+ i_fatal("BZ2_bzCompress() failed with %d", ret);
+ }
+ } while (zs->avail_out != sizeof(zstream->outbuf));
+
+ if (final)
+ zstream->flushed = TRUE;
+ i_assert(zstream->outbuf_used == 0);
+ return 1;
+}
+
+static int o_stream_bzlib_flush(struct ostream_private *stream)
+{
+ struct bzlib_ostream *zstream = (struct bzlib_ostream *)stream;
+ int ret;
+ if ((ret = o_stream_bzlib_send_flush(zstream, stream->finished)) < 0)
+ return -1;
+ else if (ret > 0)
+ return o_stream_flush_parent(stream);
+ return ret;
+}
+
+static size_t
+o_stream_bzlib_get_buffer_used_size(const struct ostream_private *stream)
+{
+ const struct bzlib_ostream *zstream =
+ (const struct bzlib_ostream *)stream;
+
+ /* outbuf has already compressed data that we're trying to send to the
+ parent stream. We're not including bzlib's internal compression
+ buffer size. */
+ return (zstream->outbuf_used - zstream->outbuf_offset) +
+ o_stream_get_buffer_used_size(stream->parent);
+}
+
+static size_t
+o_stream_bzlib_get_buffer_avail_size(const struct ostream_private *stream)
+{
+ /* FIXME: not correct - this is counting compressed size, which may be
+ too larger than uncompressed size in some situations. Fixing would
+ require some kind of additional buffering. */
+ return o_stream_get_buffer_avail_size(stream->parent);
+}
+
+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.get_buffer_used_size =
+ o_stream_bzlib_get_buffer_used_size;
+ zstream->ostream.get_buffer_avail_size =
+ o_stream_bzlib_get_buffer_avail_size;
+ 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,
+ o_stream_get_fd(output));
+}
+#endif
diff --git a/src/lib-compression/ostream-lz4.c b/src/lib-compression/ostream-lz4.c
new file mode 100644
index 0000000..360f475
--- /dev/null
+++ b/src/lib-compression/ostream-lz4.c
@@ -0,0 +1,249 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#ifdef HAVE_LZ4
+
+#include "ostream-private.h"
+#include "ostream-zlib.h"
+#include "iostream-lz4.h"
+#include <lz4.h>
+
+#define CHUNK_SIZE OSTREAM_LZ4_CHUNK_SIZE
+
+struct lz4_ostream {
+ struct ostream_private ostream;
+
+ unsigned char compressbuf[CHUNK_SIZE];
+ unsigned int compressbuf_offset;
+
+ /* chunk size, followed by compressed data */
+ unsigned char outbuf[IOSTREAM_LZ4_CHUNK_PREFIX_LEN +
+ LZ4_COMPRESSBOUND(CHUNK_SIZE)];
+ unsigned int outbuf_offset, outbuf_used;
+};
+
+/* There is no actual compression level in LZ4, so for legacy
+ reasons we allow 1-9 to avoid breaking anyone's config. */
+int compression_get_min_level_lz4(void)
+{
+ return 1;
+}
+
+int compression_get_default_level_lz4(void)
+{
+ return 1;
+}
+
+int compression_get_max_level_lz4(void)
+{
+ return 9;
+}
+
+static void o_stream_lz4_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct lz4_ostream *zstream = (struct lz4_ostream *)stream;
+
+ if (close_parent)
+ o_stream_close(zstream->ostream.parent);
+}
+
+static int o_stream_lz4_send_outbuf(struct lz4_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 int o_stream_lz4_compress(struct lz4_ostream *zstream)
+{
+ uint32_t chunk_size;
+ int ret;
+
+ if (zstream->compressbuf_offset == 0)
+ return 1;
+ if ((ret = o_stream_lz4_send_outbuf(zstream)) <= 0)
+ return ret;
+
+ i_assert(zstream->outbuf_offset == 0);
+ i_assert(zstream->outbuf_used == 0);
+
+#if defined(HAVE_LZ4_COMPRESS_DEFAULT)
+ int max_dest_size = LZ4_compressBound(zstream->compressbuf_offset);
+ i_assert(max_dest_size >= 0);
+ if (max_dest_size == 0) {
+ io_stream_set_error(&zstream->ostream.iostream,
+ "lz4-compress: input size %u too large (> %u)",
+ zstream->compressbuf_offset, LZ4_MAX_INPUT_SIZE);
+ zstream->ostream.ostream.stream_errno = EINVAL;
+ return -1;
+ }
+ ret = LZ4_compress_default((void *)zstream->compressbuf,
+ (void *)(zstream->outbuf +
+ IOSTREAM_LZ4_CHUNK_PREFIX_LEN),
+ zstream->compressbuf_offset,
+ max_dest_size);
+#else
+ ret = LZ4_compress((void *)zstream->compressbuf,
+ (void *)(zstream->outbuf +
+ IOSTREAM_LZ4_CHUNK_PREFIX_LEN),
+ zstream->compressbuf_offset);
+#endif /* defined(HAVE_LZ4_COMPRESS_DEFAULT) */
+ i_assert(ret > 0 && (unsigned int)ret <= sizeof(zstream->outbuf) -
+ IOSTREAM_LZ4_CHUNK_PREFIX_LEN);
+ zstream->outbuf_used = IOSTREAM_LZ4_CHUNK_PREFIX_LEN + ret;
+ chunk_size = zstream->outbuf_used - IOSTREAM_LZ4_CHUNK_PREFIX_LEN;
+ zstream->outbuf[0] = (chunk_size & 0xff000000) >> 24;
+ zstream->outbuf[1] = (chunk_size & 0x00ff0000) >> 16;
+ zstream->outbuf[2] = (chunk_size & 0x0000ff00) >> 8;
+ zstream->outbuf[3] = (chunk_size & 0x000000ff);
+ zstream->compressbuf_offset = 0;
+ return 1;
+}
+
+static ssize_t
+o_stream_lz4_send_chunk(struct lz4_ostream *zstream,
+ const void *data, size_t size)
+{
+ size_t max_size;
+ ssize_t added_bytes = 0;
+ int ret;
+
+ i_assert(zstream->outbuf_used == 0);
+
+ do {
+ max_size = I_MIN(size, sizeof(zstream->compressbuf) -
+ zstream->compressbuf_offset);
+ memcpy(zstream->compressbuf + zstream->compressbuf_offset,
+ data, max_size);
+ zstream->compressbuf_offset += max_size;
+
+ data = CONST_PTR_OFFSET(data, max_size);
+ size -= max_size;
+ added_bytes += max_size;
+
+ if (zstream->compressbuf_offset == sizeof(zstream->compressbuf)) {
+ ret = o_stream_lz4_compress(zstream);
+ if (ret <= 0)
+ return added_bytes != 0 ? added_bytes : ret;
+ }
+ } while (size > 0);
+
+ return added_bytes;
+}
+
+static int o_stream_lz4_flush(struct ostream_private *stream)
+{
+ struct lz4_ostream *zstream = (struct lz4_ostream *)stream;
+
+ if (o_stream_lz4_compress(zstream) < 0)
+ return -1;
+ if (o_stream_lz4_send_outbuf(zstream) < 0)
+ return -1;
+
+ return o_stream_flush_parent(stream);
+}
+
+static size_t
+o_stream_lz4_get_buffer_used_size(const struct ostream_private *stream)
+{
+ const struct lz4_ostream *zstream =
+ (const struct lz4_ostream *)stream;
+
+ /* outbuf has already compressed data that we're trying to send to the
+ parent stream. compressbuf isn't included in the return value,
+ because it needs to be filled up or flushed. */
+ return (zstream->outbuf_used - zstream->outbuf_offset) +
+ o_stream_get_buffer_used_size(stream->parent);
+}
+
+static size_t
+o_stream_lz4_get_buffer_avail_size(const struct ostream_private *stream)
+{
+ const struct lz4_ostream *zstream =
+ (const struct lz4_ostream *)stream;
+
+ /* We're only guaranteed to accept data to compressbuf. The parent
+ stream might have space, but since compressed data gets written
+ there it's not really known how much we can actually write there. */
+ return sizeof(zstream->compressbuf) - zstream->compressbuf_offset;
+}
+
+static ssize_t
+o_stream_lz4_sendv(struct ostream_private *stream,
+ const struct const_iovec *iov, unsigned int iov_count)
+{
+ struct lz4_ostream *zstream = (struct lz4_ostream *)stream;
+ ssize_t ret, bytes = 0;
+ unsigned int i;
+
+ if ((ret = o_stream_lz4_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_lz4_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;
+ return bytes;
+}
+
+struct ostream *o_stream_create_lz4(struct ostream *output, int level)
+{
+ struct iostream_lz4_header *hdr;
+ struct lz4_ostream *zstream;
+
+ i_assert(level >= 1 && level <= 9);
+
+ zstream = i_new(struct lz4_ostream, 1);
+ zstream->ostream.sendv = o_stream_lz4_sendv;
+ zstream->ostream.flush = o_stream_lz4_flush;
+ zstream->ostream.get_buffer_used_size =
+ o_stream_lz4_get_buffer_used_size;
+ zstream->ostream.get_buffer_avail_size =
+ o_stream_lz4_get_buffer_avail_size;
+ zstream->ostream.iostream.close = o_stream_lz4_close;
+
+ i_assert(sizeof(zstream->outbuf) >= sizeof(*hdr));
+ hdr = (void *)zstream->outbuf;
+ memcpy(hdr->magic, IOSTREAM_LZ4_MAGIC, sizeof(hdr->magic));
+ hdr->max_uncompressed_chunk_size[0] =
+ (OSTREAM_LZ4_CHUNK_SIZE & 0xff000000) >> 24;
+ hdr->max_uncompressed_chunk_size[1] =
+ (OSTREAM_LZ4_CHUNK_SIZE & 0x00ff0000) >> 16;
+ hdr->max_uncompressed_chunk_size[2] =
+ (OSTREAM_LZ4_CHUNK_SIZE & 0x0000ff00) >> 8;
+ hdr->max_uncompressed_chunk_size[3] =
+ (OSTREAM_LZ4_CHUNK_SIZE & 0x000000ff);
+ zstream->outbuf_used = sizeof(*hdr);
+ return o_stream_create(&zstream->ostream, output,
+ o_stream_get_fd(output));
+}
+#endif
diff --git a/src/lib-compression/ostream-zlib.c b/src/lib-compression/ostream-zlib.c
new file mode 100644
index 0000000..7267917
--- /dev/null
+++ b/src/lib-compression/ostream-zlib.c
@@ -0,0 +1,392 @@
+/* Copyright (c) 2010-2018 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;
+ unsigned int header_bytes_left;
+
+ uint32_t crc, bytes32;
+
+ bool gz:1;
+ bool flushed:1;
+};
+
+int compression_get_min_level_gz(void)
+{
+ return Z_NO_COMPRESSION;
+}
+
+int compression_get_default_level_gz(void)
+{
+ return Z_DEFAULT_COMPRESSION;
+}
+
+int compression_get_max_level_gz(void)
+{
+ return Z_BEST_COMPRESSION;
+}
+
+static void o_stream_zlib_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct zlib_ostream *zstream = (struct zlib_ostream *)stream;
+
+ i_assert(zstream->ostream.finished ||
+ zstream->ostream.ostream.stream_errno != 0 ||
+ zstream->ostream.error_handling_disabled);
+ (void)deflateEnd(&zstream->zs);
+ if (close_parent)
+ o_stream_close(zstream->ostream.parent);
+}
+
+static int o_stream_zlib_send_gz_header(struct zlib_ostream *zstream)
+{
+ size_t header_send_offset =
+ sizeof(zstream->gz_header) - zstream->header_bytes_left;
+ ssize_t ret;
+
+ i_assert(zstream->header_bytes_left <= sizeof(zstream->gz_header));
+ ret = o_stream_send(zstream->ostream.parent,
+ zstream->gz_header + header_send_offset,
+ zstream->header_bytes_left);
+ if (ret < 0) {
+ o_stream_copy_error_from_parent(&zstream->ostream);
+ return -1;
+ }
+ i_assert((size_t)ret <= zstream->header_bytes_left);
+ zstream->header_bytes_left -= ret;
+ return zstream->header_bytes_left == 0 ? 1 : 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_bytes_left > 0) {
+ if ((ret = o_stream_zlib_send_gz_header(zstream)) <= 0)
+ return ret;
+ }
+
+ 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;
+ }
+ }
+
+ ret = deflate(zs, flush);
+ switch (ret) {
+ case Z_OK:
+ case Z_BUF_ERROR:
+ break;
+ case Z_MEM_ERROR:
+ i_fatal_status(FATAL_OUTOFMEM, "zlib: Out of memory");
+ case Z_STREAM_ERROR:
+ i_assert(zstream->gz);
+ i_panic("zlib.write(%s) failed: Can't write more data to .gz after flushing",
+ o_stream_get_name(&zstream->ostream.ostream));
+ default:
+ i_panic("zlib.write(%s) failed with unexpected code %d",
+ o_stream_get_name(&zstream->ostream.ostream), ret);
+ }
+ }
+ size -= zs->avail_in;
+
+ zstream->crc = crc32_data_more(zstream->crc, data, size);
+ zstream->bytes32 += size;
+ zstream->flushed = FALSE;
+ return size;
+}
+
+static int
+o_stream_zlib_send_flush(struct zlib_ostream *zstream, bool final)
+{
+ z_stream *zs = &zstream->zs;
+ size_t len;
+ bool done = FALSE;
+ int ret, flush;
+
+ i_assert(zs->avail_in == 0);
+
+ if (zstream->flushed) {
+ i_assert(zstream->outbuf_used == 0);
+ return 1;
+ }
+
+ if ((ret = o_stream_flush_parent_if_needed(&zstream->ostream)) <= 0)
+ return ret;
+ if (zstream->header_bytes_left > 0) {
+ if ((ret = o_stream_zlib_send_gz_header(zstream)) <= 0)
+ return ret;
+ }
+
+ if ((ret = o_stream_zlib_send_outbuf(zstream)) <= 0)
+ return ret;
+
+ flush = final ? Z_FINISH :
+ (!zstream->gz ? Z_SYNC_FLUSH : Z_NO_FLUSH);
+
+ 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, flush)) {
+ case Z_OK:
+ case Z_BUF_ERROR:
+ break;
+ case Z_STREAM_END:
+ done = TRUE;
+ break;
+ case Z_MEM_ERROR:
+ i_fatal_status(FATAL_OUTOFMEM, "zlib: Out of memory");
+ default:
+ i_unreached();
+ }
+ } while (zs->avail_out != sizeof(zstream->outbuf));
+
+ if (final) {
+ if (o_stream_zlib_send_gz_trailer(zstream) < 0)
+ return -1;
+ }
+ if (final)
+ zstream->flushed = TRUE;
+ i_assert(zstream->outbuf_used == 0);
+ return 1;
+}
+
+static int o_stream_zlib_flush(struct ostream_private *stream)
+{
+ struct zlib_ostream *zstream = (struct zlib_ostream *)stream;
+ int ret;
+ if ((ret = o_stream_zlib_send_flush(zstream, stream->finished)) < 0)
+ return -1;
+ else if (ret > 0)
+ return o_stream_flush_parent(stream);
+ return ret;
+}
+
+static size_t
+o_stream_zlib_get_buffer_used_size(const struct ostream_private *stream)
+{
+ const struct zlib_ostream *zstream =
+ (const struct zlib_ostream *)stream;
+
+ /* outbuf has already compressed data that we're trying to send to the
+ parent stream. We're not including zlib's internal compression
+ buffer size. */
+ return (zstream->outbuf_used - zstream->outbuf_offset) +
+ o_stream_get_buffer_used_size(stream->parent);
+}
+
+static size_t
+o_stream_zlib_get_buffer_avail_size(const struct ostream_private *stream)
+{
+ /* FIXME: not correct - this is counting compressed size, which may be
+ too larger than uncompressed size in some situations. Fixing would
+ require some kind of additional buffering. */
+ return o_stream_get_buffer_avail_size(stream->parent);
+}
+
+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, FALSE) < 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;
+
+ /* accepted range is 0..9 and -1 is default compression */
+ 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.get_buffer_used_size =
+ o_stream_zlib_get_buffer_used_size;
+ zstream->ostream.get_buffer_avail_size =
+ o_stream_zlib_get_buffer_avail_size;
+ zstream->ostream.iostream.close = o_stream_zlib_close;
+ zstream->crc = 0;
+ zstream->gz = gz;
+ if (gz)
+ zstream->header_bytes_left = sizeof(zstream->gz_header);
+
+ 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,
+ o_stream_get_fd(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
diff --git a/src/lib-compression/ostream-zlib.h b/src/lib-compression/ostream-zlib.h
new file mode 100644
index 0000000..ac038b5
--- /dev/null
+++ b/src/lib-compression/ostream-zlib.h
@@ -0,0 +1,26 @@
+#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);
+struct ostream *o_stream_create_lz4(struct ostream *output, int level);
+struct ostream *o_stream_create_zstd(struct ostream *output, int level);
+
+int compression_get_min_level_gz(void);
+int compression_get_default_level_gz(void);
+int compression_get_max_level_gz(void);
+
+int compression_get_min_level_bz2(void);
+int compression_get_default_level_bz2(void);
+int compression_get_max_level_bz2(void);
+
+int compression_get_min_level_lz4(void);
+int compression_get_default_level_lz4(void);
+int compression_get_max_level_lz4(void);
+
+int compression_get_min_level_zstd(void);
+int compression_get_default_level_zstd(void);
+int compression_get_max_level_zstd(void);
+
+#endif
diff --git a/src/lib-compression/ostream-zstd.c b/src/lib-compression/ostream-zstd.c
new file mode 100644
index 0000000..e40380b
--- /dev/null
+++ b/src/lib-compression/ostream-zstd.c
@@ -0,0 +1,243 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+
+#ifdef HAVE_ZSTD
+
+#include "ostream.h"
+#include "ostream-private.h"
+#include "ostream-zlib.h"
+
+#include "zstd.h"
+#include "zstd_errors.h"
+#include "iostream-zstd-private.h"
+
+struct zstd_ostream {
+ struct ostream_private ostream;
+
+ ZSTD_CStream *cstream;
+ ZSTD_outBuffer output;
+
+ unsigned char *outbuf;
+
+ bool flushed:1;
+ bool closed:1;
+ bool finished:1;
+};
+
+int compression_get_min_level_zstd(void)
+{
+#if HAVE_DECL_ZSTD_MINCLEVEL == 1
+ return ZSTD_minCLevel();
+#else
+ return 1;
+#endif
+}
+
+int compression_get_default_level_zstd(void)
+{
+#ifdef ZSTD_CLEVEL_DEFAULT
+ return ZSTD_CLEVEL_DEFAULT;
+#else
+ /* Documentation says 3 is default */
+ return 3;
+#endif
+}
+
+int compression_get_max_level_zstd(void)
+{
+ return ZSTD_maxCLevel();
+}
+
+static void o_stream_zstd_write_error(struct zstd_ostream *zstream, size_t err)
+{
+ ZSTD_ErrorCode errcode = zstd_version_errcode(ZSTD_getErrorCode(err));
+ const char *error = ZSTD_getErrorName(err);
+ if (errcode == ZSTD_error_memory_allocation)
+ i_fatal_status(FATAL_OUTOFMEM, "zstd.write(%s): Out of memory",
+ o_stream_get_name(&zstream->ostream.ostream));
+ else if (errcode == ZSTD_error_prefix_unknown ||
+#if HAVE_DECL_ZSTD_ERROR_PARAMETER_UNSUPPORTED == 1
+ errcode == ZSTD_error_parameter_unsupported ||
+#endif
+ errcode == ZSTD_error_dictionary_wrong ||
+ errcode == ZSTD_error_init_missing)
+ zstream->ostream.ostream.stream_errno = EINVAL;
+ else
+ zstream->ostream.ostream.stream_errno = EIO;
+
+ io_stream_set_error(&zstream->ostream.iostream,
+ "zstd.write(%s): %s at %"PRIuUOFF_T,
+ o_stream_get_name(&zstream->ostream.ostream), error,
+ zstream->ostream.ostream.offset);
+}
+
+static ssize_t o_stream_zstd_send_outbuf(struct zstd_ostream *zstream)
+{
+ ssize_t ret;
+ /* nothing to send */
+ if (zstream->output.pos == 0)
+ return 1;
+ ret = o_stream_send(zstream->ostream.parent, zstream->output.dst,
+ zstream->output.pos);
+ if (ret < 0) {
+ o_stream_copy_error_from_parent(&zstream->ostream);
+ return -1;
+ } else {
+ memmove(zstream->outbuf, zstream->outbuf+ret, zstream->output.pos-ret);
+ zstream->output.pos -= ret;
+ }
+ if (zstream->output.pos > 0)
+ return 0;
+ return 1;
+}
+
+static ssize_t
+o_stream_zstd_sendv(struct ostream_private *stream,
+ const struct const_iovec *iov, unsigned int iov_count)
+{
+ struct zstd_ostream *zstream =
+ container_of(stream, struct zstd_ostream, ostream);
+ ssize_t total = 0;
+ size_t ret;
+
+ for (unsigned int i = 0; i < iov_count; i++) {
+ /* does it actually fit there */
+ ZSTD_inBuffer input = {
+ .src = iov[i].iov_base,
+ .pos = 0,
+ .size = iov[i].iov_len
+ };
+ bool flush_attempted = FALSE;
+ for (;;) {
+ size_t prev_pos = input.pos;
+ ret = ZSTD_compressStream(zstream->cstream, &zstream->output,
+ &input);
+ if (ZSTD_isError(ret) != 0) {
+ o_stream_zstd_write_error(zstream, ret);
+ return -1;
+ }
+ size_t new_input_size = input.pos - prev_pos;
+ if (new_input_size == 0 && flush_attempted) {
+ /* non-blocking output buffer full */
+ return total;
+ }
+ stream->ostream.offset += new_input_size;
+ total += new_input_size;
+ if (input.pos == input.size)
+ break;
+ /* output buffer full. try to flush it. */
+ if (o_stream_zstd_send_outbuf(zstream) < 0)
+ return -1;
+ flush_attempted = TRUE;
+ }
+ }
+ if (o_stream_zstd_send_outbuf(zstream) < 0)
+ return -1;
+ return total;
+}
+
+static int o_stream_zstd_send_flush(struct zstd_ostream *zstream, bool final)
+{
+ int ret;
+
+ if (zstream->flushed) {
+ i_assert(zstream->output.pos == 0);
+ return 1;
+ }
+
+ if ((ret = o_stream_flush_parent_if_needed(&zstream->ostream)) <= 0)
+ return ret;
+
+ if (zstream->output.pos == 0)
+ ZSTD_flushStream(zstream->cstream, &zstream->output);
+
+ if ((ret = o_stream_zstd_send_outbuf(zstream)) <= 0)
+ return ret;
+
+ if (!final)
+ return 1;
+
+ if (!zstream->finished) {
+ ret = ZSTD_endStream(zstream->cstream, &zstream->output);
+ if (ZSTD_isError(ret) != 0) {
+ o_stream_zstd_write_error(zstream, ret);
+ return -1;
+ }
+ zstream->finished = TRUE;
+ }
+
+ if ((ret = o_stream_zstd_send_outbuf(zstream)) <= 0)
+ return ret;
+
+ if (final)
+ zstream->flushed = TRUE;
+ i_assert(zstream->output.pos == 0);
+ return 1;
+}
+
+static int o_stream_zstd_flush(struct ostream_private *stream)
+{
+ struct zstd_ostream *zstream =
+ container_of(stream, struct zstd_ostream, ostream);
+
+ int ret;
+ if ((ret = o_stream_zstd_send_flush(zstream, stream->finished)) < 0)
+ return -1;
+ else if (ret > 0)
+ return o_stream_flush_parent(stream);
+ return ret;
+}
+
+static void o_stream_zstd_close(struct iostream_private *stream,
+ bool close_parent)
+{
+ struct ostream_private *_ostream =
+ container_of(stream, struct ostream_private, iostream);
+ struct zstd_ostream *zstream =
+ container_of(_ostream, struct zstd_ostream, ostream);
+
+ i_assert(zstream->ostream.finished ||
+ zstream->ostream.ostream.stream_errno != 0 ||
+ zstream->ostream.error_handling_disabled);
+ if (zstream->cstream != NULL) {
+ ZSTD_freeCStream(zstream->cstream);
+ zstream->cstream = NULL;
+ }
+ i_free(zstream->outbuf);
+ i_zero(&zstream->output);
+ if (close_parent)
+ o_stream_close(zstream->ostream.parent);
+}
+
+struct ostream *
+o_stream_create_zstd(struct ostream *output, int level)
+{
+ struct zstd_ostream *zstream;
+ size_t ret;
+
+ i_assert(level >= compression_get_min_level_zstd() &&
+ level <= compression_get_max_level_zstd());
+
+ zstd_version_check();
+
+ zstream = i_new(struct zstd_ostream, 1);
+ zstream->ostream.sendv = o_stream_zstd_sendv;
+ zstream->ostream.flush = o_stream_zstd_flush;
+ zstream->ostream.iostream.close = o_stream_zstd_close;
+ zstream->cstream = ZSTD_createCStream();
+ if (zstream->cstream == NULL)
+ i_fatal_status(FATAL_OUTOFMEM, "zstd: Out of memory");
+ ret = ZSTD_initCStream(zstream->cstream, level);
+ if (ZSTD_isError(ret) != 0)
+ o_stream_zstd_write_error(zstream, ret);
+ else {
+ zstream->outbuf = i_malloc(ZSTD_CStreamOutSize());
+ zstream->output.dst = zstream->outbuf;
+ zstream->output.size = ZSTD_CStreamOutSize();
+ }
+ return o_stream_create(&zstream->ostream, output,
+ o_stream_get_fd(output));
+}
+
+#endif
diff --git a/src/lib-compression/test-compression.c b/src/lib-compression/test-compression.c
new file mode 100644
index 0000000..f690c7b
--- /dev/null
+++ b/src/lib-compression/test-compression.c
@@ -0,0 +1,1122 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "istream.h"
+#include "iostream-temp.h"
+#include "ostream.h"
+#include "sha1.h"
+#include "randgen.h"
+#include "test-common.h"
+#include "compression.h"
+#include "iostream-lz4.h"
+
+#include "hex-binary.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+static void test_compression_handler_detect(const struct compression_handler *handler)
+{
+ const unsigned char test_data[] = {'h','e','l','l','o',' ',
+ 'w','o','r','l','d','\n'};
+ const unsigned char *data;
+ size_t size;
+ buffer_t *buffer;
+ struct ostream *test_output;
+ struct ostream *output;
+
+ struct istream *test_input;
+ struct istream *input;
+
+ /* write some amount of data */
+ test_begin(t_strdup_printf("compression handler %s (detect)", handler->name));
+
+ buffer = buffer_create_dynamic(default_pool, 1024);
+
+ test_output = test_ostream_create(buffer);
+ output = handler->create_ostream(test_output, 1);
+ o_stream_unref(&test_output);
+
+ /* write data at once */
+ test_assert(o_stream_send(output, test_data, sizeof(test_data)) == sizeof(test_data));
+ test_assert(o_stream_finish(output) == 1);
+ o_stream_unref(&output);
+
+ test_input = test_istream_create_data(buffer->data, buffer->used);
+ handler = compression_detect_handler(test_input);
+ i_stream_seek(test_input, 0);
+ test_assert(handler != NULL);
+ if (handler != NULL) {
+ input = handler->create_istream(test_input);
+ i_stream_unref(&test_input);
+
+ test_assert(i_stream_read_more(input, &data, &size) > 0);
+ test_assert(size == sizeof(test_data) &&
+ memcmp(data, test_data, size) == 0);
+
+ i_stream_unref(&input);
+ } else {
+ i_stream_unref(&test_input);
+ }
+
+ buffer_free(&buffer);
+ test_end();
+}
+
+static void
+test_compression_handler_short(const struct compression_handler *handler,
+ bool autodetect)
+{
+ const unsigned char *data;
+ size_t len, size;
+ buffer_t *test_data;
+ buffer_t *buffer;
+ struct ostream *test_output;
+ struct ostream *output;
+
+ struct istream *test_input;
+ struct istream *input;
+
+ /* write some amount of data */
+ test_begin(t_strdup_printf("compression handler %s (small, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
+ len = i_rand_minmax(1, 1024);
+ test_data = buffer_create_dynamic(default_pool, len);
+ random_fill(buffer_append_space_unsafe(test_data, len), len);
+ buffer_set_used_size(test_data, len);
+ buffer_append(test_data, "hello. world.\n", 14);
+
+ buffer = buffer_create_dynamic(default_pool, 1024);
+ test_output = test_ostream_create(buffer);
+ output = handler->create_ostream(test_output, 1);
+ o_stream_unref(&test_output);
+
+ /* write data at once */
+ test_assert(o_stream_send(output, test_data->data, test_data->used) == (ssize_t)test_data->used);
+ test_assert(o_stream_finish(output) == 1);
+ o_stream_unref(&output);
+
+ /* read data at once */
+ test_input = test_istream_create_data(buffer->data, buffer->used);
+ input = !autodetect ? handler->create_istream(test_input) :
+ i_stream_create_decompress(test_input, 0);
+ i_stream_unref(&test_input);
+
+ test_assert(i_stream_read_more(input, &data, &size) > 0);
+ test_assert(size == test_data->used &&
+ memcmp(data, test_data->data, size) ==0);
+
+ i_stream_unref(&input);
+
+ buffer_free(&buffer);
+ buffer_free(&test_data);
+
+ test_end();
+}
+
+static void
+test_compression_handler_empty(const struct compression_handler *handler,
+ bool autodetect)
+{
+ buffer_t *buffer;
+ struct ostream *test_output;
+ struct ostream *output;
+
+ struct istream *test_input;
+ struct istream *input;
+
+ /* create stream and finish it without writing anything */
+ test_begin(t_strdup_printf("compression handler %s (empty, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
+ buffer = buffer_create_dynamic(default_pool, 128);
+ test_output = test_ostream_create(buffer);
+ output = handler->create_ostream(test_output, 1);
+ o_stream_unref(&test_output);
+ test_assert(o_stream_finish(output) == 1);
+ o_stream_unref(&output);
+
+ /* read the input */
+ test_input = test_istream_create_data(buffer->data, buffer->used);
+ input = !autodetect ? handler->create_istream(test_input) :
+ i_stream_create_decompress(test_input, 0);
+ i_stream_unref(&test_input);
+
+ test_assert(i_stream_read(input) == -1);
+ test_assert(i_stream_get_data_size(input) == 0);
+ i_stream_unref(&input);
+
+ buffer_free(&buffer);
+
+ test_end();
+}
+
+static void
+test_compression_handler_seek(const struct compression_handler *handler,
+ bool autodetect)
+{
+ const unsigned char *data,*ptr;
+ size_t len, size, pos;
+ buffer_t *test_data;
+ buffer_t *buffer;
+ struct ostream *test_output;
+ struct ostream *output;
+
+ struct istream *test_input;
+ struct istream *input;
+
+ /* write some amount of data */
+ test_begin(t_strdup_printf("compression handler %s (seek, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
+ len = i_rand_minmax(1024, 2048);
+ test_data = buffer_create_dynamic(default_pool, len);
+ random_fill(buffer_append_space_unsafe(test_data, len), len);
+ buffer_set_used_size(test_data, len);
+ buffer_append(test_data, "hello. world.\n", 14);
+
+ buffer = buffer_create_dynamic(default_pool, 1024);
+ test_output = test_ostream_create(buffer);
+ output = handler->create_ostream(test_output, 1);
+ o_stream_unref(&test_output);
+
+ /* write data at once */
+ test_assert(o_stream_send(output, test_data->data, test_data->used) == (ssize_t)test_data->used);
+ test_assert(o_stream_finish(output) == 1);
+ o_stream_unref(&output);
+
+ test_input = test_istream_create_data(buffer->data, buffer->used);
+ input = !autodetect ? handler->create_istream(test_input) :
+ i_stream_create_decompress(test_input, 0);
+ i_stream_unref(&test_input);
+
+ /* seek forward */
+ i_stream_seek(input, test_data->used - 14); /* should read 'hello. world.\n' */
+
+ test_assert(i_stream_read_more(input, &data, &size) > 0);
+ test_assert(size >= 14 && memcmp(data, "hello. world.\n", 14) == 0);
+ i_stream_skip(input, size);
+
+ ptr = test_data->data;
+
+ /* seek to random positions and see that we get correct data */
+ for (unsigned int i = 0; i < 1000; i++) {
+ pos = i_rand_limit(test_data->used);
+ i_stream_seek(input, pos);
+ size = 0;
+ test_assert_idx(i_stream_read_more(input, &data, &size) > 0, i);
+ test_assert_idx(size > 0 && memcmp(data,ptr+pos,size) == 0, i);
+ }
+
+ i_stream_unref(&input);
+
+ buffer_free(&buffer);
+ buffer_free(&test_data);
+
+ test_end();
+}
+
+static void
+test_compression_handler_reset(const struct compression_handler *handler,
+ bool autodetect)
+{
+ const unsigned char *data;
+ size_t len, size;
+ buffer_t *test_data;
+ buffer_t *buffer;
+ struct ostream *test_output;
+ struct ostream *output;
+
+ struct istream *test_input;
+ struct istream *input;
+
+ /* write some amount of data */
+ test_begin(t_strdup_printf("compression handler %s (reset, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
+ len = i_rand_minmax(1024, 2048);
+ test_data = buffer_create_dynamic(default_pool, len);
+ random_fill(buffer_append_space_unsafe(test_data, len), len);
+ buffer_set_used_size(test_data, len);
+ buffer_append(test_data, "hello. world.\n", 14);
+
+ buffer = buffer_create_dynamic(default_pool, 1024);
+ test_output = test_ostream_create(buffer);
+ output = handler->create_ostream(test_output, 1);
+ o_stream_unref(&test_output);
+
+ /* write data at once */
+ test_assert(o_stream_send(output, test_data->data, test_data->used) == (ssize_t)test_data->used);
+ test_assert(o_stream_finish(output) == 1);
+ o_stream_unref(&output);
+
+ test_input = test_istream_create_data(buffer->data, buffer->used);
+ input = !autodetect ? handler->create_istream(test_input) :
+ i_stream_create_decompress(test_input, 0);
+ i_stream_unref(&test_input);
+
+ /* seek forward */
+ i_stream_seek(input, test_data->used - 14); /* should read 'hello. world.\n' */
+
+ test_assert(i_stream_read_more(input, &data, &size) > 0);
+ test_assert(size >= 14 && memcmp(data, "hello. world.\n", 14) == 0);
+ i_stream_skip(input, size);
+
+ /* reset */
+ i_stream_sync(input);
+
+ /* see that we still get data, at start */
+ size = 0;
+ test_assert(i_stream_read_more(input, &data, &size) > 0);
+ test_assert(size > 0 && memcmp(data, test_data->data, size) == 0);
+
+ i_stream_unref(&input);
+
+ buffer_free(&buffer);
+ buffer_free(&test_data);
+
+ test_end();
+}
+
+static void
+test_compression_handler(const struct compression_handler *handler,
+ bool autodetect)
+{
+ const char *path = "test-compression.tmp";
+ struct istream *file_input, *input;
+ struct ostream *file_output, *output;
+ unsigned char buf[IO_BLOCK_SIZE];
+ const unsigned char *data;
+ size_t size;
+ uoff_t stream_size;
+ struct sha1_ctxt sha1;
+ unsigned char output_sha1[SHA1_RESULTLEN], input_sha1[SHA1_RESULTLEN];
+ unsigned int i;
+ int fd;
+ ssize_t ret;
+
+ test_begin(t_strdup_printf("compression handler %s (autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
+
+ /* write compressed data */
+ fd = open(path, O_TRUNC | O_CREAT | O_RDWR, 0600);
+ if (fd == -1)
+ i_fatal("creat(%s) failed: %m", path);
+ file_output = o_stream_create_fd_file(fd, 0, FALSE);
+ output = handler->create_ostream(file_output, 1);
+ sha1_init(&sha1);
+
+ /* 1) write lots of easily compressible data */
+ memset(buf, 0, sizeof(buf));
+ for (i = 0; i < 1024*1024*4 / sizeof(buf); i++) {
+ sha1_loop(&sha1, buf, sizeof(buf));
+ test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf));
+ }
+
+ /* 2) write uncompressible data */
+ for (i = 0; i < 1024*128 / sizeof(buf); i++) {
+ random_fill(buf, sizeof(buf));
+ sha1_loop(&sha1, buf, sizeof(buf));
+ test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf));
+ }
+ /* make sure the input size isn't multiple of something simple */
+ random_fill(buf, sizeof(buf));
+ sha1_loop(&sha1, buf, sizeof(buf) - 5);
+ test_assert(o_stream_send(output, buf, sizeof(buf) - 5) == sizeof(buf) - 5);
+
+ /* 3) write semi-compressible data */
+ for (i = 0; i < sizeof(buf); i++) {
+ if (i_rand_limit(3) == 0)
+ buf[i] = i_rand_limit(4);
+ else
+ buf[i] = i & UCHAR_MAX;
+ }
+ for (i = 0; i < 1024*128 / sizeof(buf); i++) {
+ sha1_loop(&sha1, buf, sizeof(buf));
+ test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf));
+ }
+
+ test_assert(o_stream_finish(output) > 0);
+ uoff_t uncompressed_size = output->offset;
+ o_stream_destroy(&output);
+ uoff_t compressed_size = file_output->offset;
+ o_stream_destroy(&file_output);
+ sha1_result(&sha1, output_sha1);
+
+ /* read and uncompress the data */
+ file_input = i_stream_create_fd(fd, IO_BLOCK_SIZE);
+ input = !autodetect ? handler->create_istream(file_input) :
+ i_stream_create_decompress(file_input, 0);
+
+ test_assert(i_stream_get_size(input, FALSE, &stream_size) == 1);
+ test_assert(stream_size == compressed_size);
+
+ test_assert(i_stream_get_size(input, TRUE, &stream_size) == 1);
+ test_assert(stream_size == uncompressed_size);
+
+ sha1_init(&sha1);
+ for (bool seeked = FALSE;;) {
+ sha1_init(&sha1);
+ while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
+ sha1_loop(&sha1, data, size);
+ i_stream_skip(input, size);
+ }
+ test_assert(ret == -1);
+ test_assert(input->stream_errno == 0);
+ sha1_result(&sha1, input_sha1);
+ test_assert(memcmp(input_sha1, output_sha1, sizeof(input_sha1)) == 0);
+ if (seeked)
+ break;
+ seeked = TRUE;
+ i_stream_seek(input, 1);
+ (void)i_stream_read(input);
+ i_stream_seek(input, 0);
+ }
+ i_stream_destroy(&input);
+ i_stream_destroy(&file_input);
+
+ i_unlink(path);
+ i_close_fd(&fd);
+
+ test_end();
+}
+
+static void
+test_compression_handler_partial_parent_write(const struct compression_handler *handler,
+ bool autodetect)
+{
+ test_begin(t_strdup_printf("compression handler %s (partial parent writes, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
+
+ int ret;
+ buffer_t *buffer = t_buffer_create(64);
+ buffer_t *compressed_data = t_buffer_create(256);
+ struct ostream *os = test_ostream_create_nonblocking(buffer, 64);
+ struct ostream *os_compressed = handler->create_ostream(os, 9);
+ o_stream_unref(&os);
+
+ unsigned char input_buffer[64];
+ /* create unlikely compressible data */
+ random_fill(input_buffer, sizeof(input_buffer));
+
+ for (unsigned int i = 0; i < 10; i++) {
+ /* write it to stream */
+ test_assert_idx(o_stream_send(os_compressed, input_buffer, sizeof(input_buffer)) == sizeof(input_buffer), i);
+
+ while ((ret = o_stream_flush(os_compressed)) == 0) {
+ /* flush buffer */
+ if (buffer->used > 0)
+ buffer_append(compressed_data, buffer->data, buffer->used);
+ buffer_set_used_size(buffer, 0);
+ }
+ if (buffer->used > 0)
+ buffer_append(compressed_data, buffer->data, buffer->used);
+ buffer_set_used_size(buffer, 0);
+ test_assert_idx(ret == 1, i);
+ }
+ test_assert(o_stream_finish(os_compressed) == 1);
+ o_stream_unref(&os_compressed);
+ if (buffer->used > 0)
+ buffer_append(compressed_data, buffer->data, buffer->used);
+
+ struct istream *is = test_istream_create_data(compressed_data->data, compressed_data->used);
+ struct istream *is_decompressed =
+ !autodetect ? handler->create_istream(is) :
+ i_stream_create_decompress(is, 0);
+ i_stream_unref(&is);
+
+ const unsigned char *data;
+ size_t siz;
+ buffer_t *decompressed_data = t_buffer_create(sizeof(input_buffer)*10);
+
+ while(i_stream_read_more(is_decompressed, &data, &siz) > 0) {
+ buffer_append(decompressed_data, data, siz);
+ i_stream_skip(is_decompressed, siz);
+ }
+ test_assert(decompressed_data->used == sizeof(input_buffer)*10);
+ for(siz = 0; siz < decompressed_data->used; siz+=sizeof(input_buffer)) {
+ test_assert(decompressed_data->used - siz >= sizeof(input_buffer) &&
+ memcmp(CONST_PTR_OFFSET(decompressed_data->data, siz),
+ input_buffer, sizeof(input_buffer)) == 0);
+ }
+
+ i_stream_unref(&is_decompressed);
+
+ test_end();
+}
+
+static void
+test_compression_handler_random_io(const struct compression_handler *handler,
+ bool autodetect)
+{
+ unsigned char in_buf[8192];
+ size_t in_buf_size;
+ buffer_t *enc_buf, *dec_buf;
+ unsigned int i, j;
+ int ret;
+
+ enc_buf = buffer_create_dynamic(default_pool, sizeof(in_buf));
+ dec_buf = buffer_create_dynamic(default_pool, sizeof(in_buf));
+
+ test_begin(t_strdup_printf("compression handler %s (random I/O, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
+
+ for (i = 0; !test_has_failed() && i < 300; i++) {
+ struct istream *input1, *input2;
+ struct ostream *output1, *output2;
+ struct istream *top_input;
+ const unsigned char *data;
+ size_t size, in_pos, out_pos;
+
+ /* Initialize test data (semi-compressible) */
+ in_buf_size = i_rand_limit(sizeof(in_buf));
+ for (j = 0; j < in_buf_size; j++) {
+ if (i_rand_limit(3) == 0)
+ in_buf[j] = i_rand_limit(256);
+ else
+ in_buf[j] = (unsigned char)j;
+ }
+
+ /* Reset encode output buffer */
+ buffer_set_used_size(enc_buf, 0);
+
+ /* Create input stream for test data */
+ input1 = test_istream_create_data(in_buf, in_buf_size);
+ i_stream_set_name(input1, "[data]");
+
+ /* Create output stream for compressed data */
+ output1 = test_ostream_create_nonblocking(enc_buf,
+ i_rand_minmax(1, 512));
+
+ /* Create compressor output stream */
+ output2 = handler->create_ostream(output1, i_rand_minmax(1, 6));
+
+ /* Compress the data incrementally */
+ in_pos = out_pos = 0;
+ ret = 0;
+ test_istream_set_size(input1, in_pos);
+ while (ret == 0) {
+ enum ostream_send_istream_result res;
+
+ res = o_stream_send_istream(output2, input1);
+ switch(res) {
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ ret = -1;
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ out_pos += i_rand_limit(512);
+ test_ostream_set_max_output_size(
+ output1, out_pos);
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ in_pos += i_rand_limit(512);
+ if (in_pos > in_buf_size)
+ in_pos = in_buf_size;
+ test_istream_set_size(input1, in_pos);
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ /* finish it */
+ ret = o_stream_finish(output2);
+ break;
+ }
+ }
+
+ /* Clean up */
+ i_stream_unref(&input1);
+ o_stream_unref(&output1);
+ o_stream_unref(&output2);
+
+ /* Reset decode output buffer */
+ buffer_set_used_size(dec_buf, 0);
+
+ /* Create input stream for compressed data */
+ input1 = i_stream_create_from_buffer(enc_buf);
+ i_stream_set_name(input1, "[compressed-data]");
+
+ /* Create decompressor stream */
+ input2 = !autodetect ? handler->create_istream(input1) :
+ i_stream_create_decompress(input1, 0);
+ i_stream_set_name(input2, "[decompressor]");
+
+ /* Assign random buffer sizes */
+ i_stream_set_max_buffer_size(input2, i_rand_minmax(1, 512));
+
+ /* Read the outer stream in full with random increments. */
+ top_input = input2;
+ while ((ret = i_stream_read_more(
+ top_input, &data, &size)) > 0) {
+ size_t ch = i_rand_limit(512);
+
+ size = I_MIN(size, ch);
+ buffer_append(dec_buf, data, size);
+ i_stream_skip(top_input, size);
+ }
+ if (ret < 0 && top_input->stream_errno == 0) {
+ data = i_stream_get_data(top_input, &size);
+ if (size > 0) {
+ buffer_append(dec_buf, data, size);
+ i_stream_skip(top_input, size);
+ }
+ }
+
+ /* Assert stream status */
+ test_assert_idx(ret < 0 && top_input->stream_errno == 0, i);
+ /* Assert input/output equality */
+ test_assert_idx(dec_buf->used == in_buf_size &&
+ memcmp(in_buf, dec_buf->data, in_buf_size) == 0,
+ i);
+
+ if (top_input->stream_errno != 0) {
+ i_error("%s: %s", i_stream_get_name(input1),
+ i_stream_get_error(input1));
+ i_error("%s: %s", i_stream_get_name(input2),
+ i_stream_get_error(input2));
+ }
+
+ if (test_has_failed()) {
+ i_info("Test parameters: size=%zu",
+ in_buf_size);
+ }
+
+ /* Clean up */
+ i_stream_unref(&input1);
+ i_stream_unref(&input2);
+ }
+ test_end();
+
+ buffer_free(&enc_buf);
+ buffer_free(&dec_buf);
+}
+
+static void
+test_compression_handler_large_random_io(const struct compression_handler *handler,
+ bool autodetect)
+{
+#define RANDOMNESS_SIZE (1024*1024)
+ unsigned char *randomness;
+ struct istream *input, *dec_input;
+ struct ostream *temp_output, *output;
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ test_begin(t_strdup_printf("compression handler %s (large random io, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
+ randomness = i_malloc(RANDOMNESS_SIZE);
+ random_fill(randomness, RANDOMNESS_SIZE);
+
+ /* write 1 MB of randomness to buffer */
+ input = i_stream_create_from_data(randomness, RANDOMNESS_SIZE);
+
+ temp_output = iostream_temp_create(".temp.", 0);
+ output = handler->create_ostream(temp_output, i_rand_minmax(1, 6));
+
+ switch (o_stream_send_istream(output, input)) {
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ test_assert(FALSE);
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ test_assert(o_stream_finish(output) == 1);
+ break;
+ }
+ test_assert(output->offset == RANDOMNESS_SIZE);
+ test_assert(output->stream_errno == 0);
+ i_stream_unref(&input);
+
+ input = iostream_temp_finish(&temp_output, SIZE_MAX);
+ o_stream_unref(&output);
+
+ /* verify that reading the input works */
+
+ dec_input = !autodetect ? handler->create_istream(input) :
+ i_stream_create_decompress(input, 0);
+
+ while ((ret = i_stream_read_more(dec_input, &data, &size)) > 0) {
+ test_assert(memcmp(data, randomness + dec_input->v_offset, size) == 0);
+ i_stream_skip(dec_input, size);
+ }
+ test_assert(ret == -1);
+ test_assert(dec_input->v_offset == RANDOMNESS_SIZE);
+ test_assert(dec_input->stream_errno == 0);
+
+ i_stream_unref(&dec_input);
+ i_stream_unref(&input);
+ i_free(randomness);
+ test_end();
+}
+
+static void
+test_compression_handler_errors(const struct compression_handler *handler,
+ bool autodetect)
+{
+ test_begin(t_strdup_printf("compression handler %s (errors, autodetect=%s)",
+ handler->name, autodetect ? "yes" : "no"));
+
+ /* test that zero stream reading errors out */
+ struct istream *is = test_istream_create("");
+ struct istream *input =
+ !autodetect ? handler->create_istream(is) :
+ i_stream_create_decompress(is, 0);
+ i_stream_unref(&is);
+ test_assert(i_stream_read(input) == -1 && input->eof);
+ i_stream_unref(&input);
+
+ /* test that garbage isn't considered valid */
+ is = test_istream_create("dedededededededededededededede"
+ "dedededeededdedededededededede"
+ "dedededededededededededededede");
+ is->blocking = TRUE;
+ input = !autodetect ? handler->create_istream(is) :
+ i_stream_create_decompress(is, 0);
+ i_stream_unref(&is);
+ test_assert(i_stream_read(input) == -1 && input->eof);
+ i_stream_unref(&input);
+
+ /* test that truncated data is not considered valid */
+ buffer_t *odata = buffer_create_dynamic(pool_datastack_create(), 65535);
+ unsigned char buf[IO_BLOCK_SIZE];
+ struct ostream *os = test_ostream_create(odata);
+ struct ostream *output = handler->create_ostream(os, 1);
+ o_stream_unref(&os);
+
+ for (unsigned int i = 0; i < 10; i++) {
+ random_fill(buf, sizeof(buf));
+ test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf));
+ }
+
+ test_assert(o_stream_finish(output) == 1);
+ o_stream_unref(&output);
+
+ /* truncate buffer */
+ is = test_istream_create_data(odata->data, odata->used - sizeof(buf)*2 - 1);
+ input = !autodetect ? handler->create_istream(is) :
+ i_stream_create_decompress(is, 0);
+ i_stream_unref(&is);
+
+ const unsigned char *data ATTR_UNUSED;
+ size_t size;
+ while (i_stream_read_more(input, &data, &size) > 0)
+ i_stream_skip(input, size);
+
+ test_assert(input->stream_errno == EPIPE);
+ i_stream_unref(&input);
+
+ /* Cannot do the next check if we don't know if it's compressed. */
+ if (handler->is_compressed != NULL) {
+ /* test incrementally reading up to 32 bytes of plaintext data
+ that should not match any handlers' header */
+ for (size_t i = 0; i < 32; i++) {
+ is = test_istream_create_data("dededededededededededededededede", i);
+ input = !autodetect ? handler->create_istream(is) :
+ i_stream_create_decompress(is, 0);
+ i_stream_unref(&is);
+ while (i_stream_read_more(input, &data, &size) >= 0) {
+ test_assert_idx(size == 0, i);
+ i_stream_skip(input, size);
+ }
+ test_assert_idx(input->stream_errno == EINVAL, i);
+ i_stream_unref(&input);
+ }
+ }
+
+ test_end();
+}
+
+static void test_compression_int(bool autodetect)
+{
+ unsigned int i;
+
+ for (i = 0; compression_handlers[i].name != NULL; i++) {
+ if (compression_handlers[i].create_istream != NULL &&
+ compression_handlers[i].create_ostream != NULL &&
+ (!autodetect ||
+ compression_handlers[i].is_compressed != NULL)) T_BEGIN {
+ if (compression_handlers[i].is_compressed != NULL &&
+ !autodetect)
+ test_compression_handler_detect(&compression_handlers[i]);
+ test_compression_handler_short(&compression_handlers[i], autodetect);
+ test_compression_handler_empty(&compression_handlers[i], autodetect);
+ test_compression_handler(&compression_handlers[i], autodetect);
+ test_compression_handler_seek(&compression_handlers[i], autodetect);
+ test_compression_handler_reset(&compression_handlers[i], autodetect);
+ test_compression_handler_partial_parent_write(&compression_handlers[i], autodetect);
+ test_compression_handler_random_io(&compression_handlers[i], autodetect);
+ test_compression_handler_large_random_io(&compression_handlers[i], autodetect);
+ test_compression_handler_errors(&compression_handlers[i], autodetect);
+ } T_END;
+ }
+}
+
+static void test_compression(void)
+{
+ test_compression_int(FALSE);
+ test_compression_int(TRUE);
+}
+
+static void test_istream_decompression_try(void)
+{
+ const char *tests[] = {
+ "",
+ "1",
+ "12",
+ "12345678901234567890123456789012345678901234567890",
+ };
+ struct istream *is, *input;
+ const unsigned char *data;
+ size_t size;
+
+ test_begin("istream-decompression try");
+
+ for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
+ size_t test_len = strlen(tests[i]);
+ is = i_stream_create_from_data(tests[i], test_len);
+ input = i_stream_create_decompress(is, ISTREAM_DECOMPRESS_FLAG_TRY);
+ i_stream_unref(&is);
+
+ ssize_t ret = i_stream_read(input);
+ test_assert_idx((test_len == 0 && ret == -1) ||
+ (test_len > 0 && ret == (ssize_t)test_len), i);
+ data = i_stream_get_data(input, &size);
+ test_assert_idx(size == test_len &&
+ memcmp(data, tests[i], size) == 0, i);
+
+ i_stream_skip(input, size);
+ test_assert_idx(i_stream_read(input) == -1, i);
+ test_assert_idx(input->stream_errno == 0, i);
+ i_stream_unref(&input);
+ }
+ test_end();
+}
+
+static void test_gz(const char *str1, const char *str2, bool autodetect)
+{
+ const struct compression_handler *gz;
+ struct ostream *buf_output, *output;
+ struct istream *test_input, *input;
+ buffer_t *buf = t_buffer_create(512);
+
+ if (compression_lookup_handler("gz", &gz) <= 0 )
+ return; /* not compiled in or unkown*/
+
+ /* write concated output */
+ buf_output = o_stream_create_buffer(buf);
+ o_stream_set_finish_via_child(buf_output, FALSE);
+
+ output = gz->create_ostream(buf_output, 6);
+ o_stream_nsend_str(output, str1);
+ test_assert(o_stream_finish(output) > 0);
+ o_stream_destroy(&output);
+
+ if (str2[0] != '\0') {
+ output = gz->create_ostream(buf_output, 6);
+ o_stream_nsend_str(output, "world");
+ test_assert(o_stream_finish(output) > 0);
+ o_stream_destroy(&output);
+ }
+
+ o_stream_destroy(&buf_output);
+
+ /* read concated input */
+ const unsigned char *data;
+ size_t size;
+ test_input = test_istream_create_data(buf->data, buf->used);
+ test_istream_set_allow_eof(test_input, FALSE);
+ input = !autodetect ? gz->create_istream(test_input) :
+ i_stream_create_decompress(test_input, 0);
+ for (size_t i = 0; i <= buf->used; i++) {
+ test_istream_set_size(test_input, i);
+ test_assert(i_stream_read(input) >= 0);
+ }
+ test_istream_set_allow_eof(test_input, TRUE);
+ test_assert(i_stream_read(input) == -1);
+ test_assert(input->stream_errno == 0);
+
+ data = i_stream_get_data(input, &size);
+ test_assert(size == strlen(str1)+strlen(str2) &&
+ memcmp(data, str1, strlen(str1)) == 0 &&
+ memcmp(data+strlen(str1), str2, strlen(str2)) == 0);
+ i_stream_unref(&input);
+ i_stream_unref(&test_input);
+}
+
+static void test_gz_concat(void)
+{
+ test_begin("gz concat (autodetect=no)");
+ test_gz("hello", "world", FALSE);
+ test_end();
+
+ test_begin("gz concat (autodetect=yes)");
+ test_gz("hello", "world", TRUE);
+ test_end();
+}
+
+static void test_gz_no_concat(void)
+{
+ test_begin("gz no concat (autodetect=no)");
+ test_gz("hello", "", FALSE);
+ test_end();
+
+ test_begin("gz no concat (autodetect=yes)");
+ test_gz("hello", "", TRUE);
+ test_end();
+}
+
+static void test_gz_header_int(bool autodetect)
+{
+ const struct compression_handler *gz;
+ const char *input_strings[] = {
+ "\x1F\x8B",
+ "\x1F\x8B\x01\x02"/* GZ_FLAG_FHCRC */"\xFF\xFF\x01\x01\x01\x01",
+ "\x1F\x8B\x01\x04"/* GZ_FLAG_FEXTRA */"\xFF\xFF\x01\x01\x01\x01",
+ "\x1F\x8B\x01\x08"/* GZ_FLAG_FNAME */"\x01\x01\x01\x01\x01\x01",
+ "\x1F\x8B\x01\x10"/* GZ_FLAG_FCOMMENT */"\x01\x01\x01\x01\x01\x01",
+ "\x1F\x8B\x01\x0C"/* GZ_FLAG_FEXTRA | GZ_FLAG_FNAME */"\xFF\xFF\x01\x01\x01\x01",
+ };
+ struct istream *file_input, *input;
+ if (compression_lookup_handler("gz", &gz) <= 0 )
+ return; /* not compiled in or unkown*/
+
+ test_begin(t_strdup_printf(
+ "gz header (autodetect=%s)", autodetect ? "yes" : "no"));
+ for (unsigned int i = 0; i < N_ELEMENTS(input_strings); i++) {
+ file_input = test_istream_create_data(input_strings[i],
+ strlen(input_strings[i]));
+ file_input->blocking = TRUE;
+ input = !autodetect ? gz->create_istream(file_input) :
+ i_stream_create_decompress(file_input, 0);
+ test_assert_idx(i_stream_read(input) == -1, i);
+ test_assert_idx(input->stream_errno == EINVAL, i);
+ i_stream_unref(&input);
+ i_stream_unref(&file_input);
+ }
+ test_end();
+}
+
+static void test_gz_header(void)
+{
+ test_gz_header_int(FALSE);
+ test_gz_header_int(TRUE);
+}
+
+static void test_gz_large_header_int(bool autodetect)
+{
+ const struct compression_handler *gz;
+ static const unsigned char gz_input[] = {
+ 0x1f, 0x8b, 0x08, 0x08,
+ 'a','a','a','a','a','a','a','a','a','a','a',
+ 0
+ };
+ struct istream *file_input, *input;
+ size_t i;
+
+ if (compression_lookup_handler("gz", &gz) <= 0 )
+ return; /* not compiled in or unkown*/
+
+ test_begin(t_strdup_printf(
+ "gz large header (autodetect=%s)", autodetect ? "yes" : "no"));
+
+ /* max buffer size smaller than gz header */
+ for (i = 1; i < sizeof(gz_input); i++) {
+ file_input = test_istream_create_data(gz_input, sizeof(gz_input));
+ test_istream_set_size(file_input, i);
+ test_istream_set_max_buffer_size(file_input, i);
+
+ input = !autodetect ? gz->create_istream(file_input) :
+ i_stream_create_decompress(file_input, 0);
+ test_assert_idx(i_stream_read(input) == 0, i);
+ test_assert_idx(i_stream_read(input) == -1 &&
+ input->stream_errno == EINVAL, i);
+ i_stream_unref(&input);
+ i_stream_unref(&file_input);
+ }
+
+ /* max buffer size is exactly the gz header */
+ file_input = test_istream_create_data(gz_input, sizeof(gz_input));
+ input = !autodetect ? gz->create_istream(file_input) :
+ i_stream_create_decompress(file_input, 0);
+ test_istream_set_size(file_input, i);
+ test_istream_set_allow_eof(file_input, FALSE);
+ test_istream_set_max_buffer_size(file_input, i);
+ test_assert(i_stream_read(input) == 0);
+ i_stream_unref(&input);
+ i_stream_unref(&file_input);
+
+ test_end();
+}
+
+static void test_gz_large_header(void)
+{
+ test_gz_large_header_int(FALSE);
+ test_gz_large_header_int(TRUE);
+}
+
+static void test_lz4_small_header(void)
+{
+ const struct compression_handler *lz4;
+ struct iostream_lz4_header lz4_input;
+ struct istream *file_input, *input;
+
+ if (compression_lookup_handler("lz4", &lz4) <= 0)
+ return; /* not compiled in or unkown */
+
+ test_begin("lz4 small header");
+
+ memcpy(lz4_input.magic, IOSTREAM_LZ4_MAGIC, IOSTREAM_LZ4_MAGIC_LEN);
+ lz4_input.max_uncompressed_chunk_size[0] = 0;
+ lz4_input.max_uncompressed_chunk_size[1] = 1;
+ lz4_input.max_uncompressed_chunk_size[2] = 0;
+ lz4_input.max_uncompressed_chunk_size[3] = 0;
+
+ /* truncated header */
+ file_input = i_stream_create_from_data(&lz4_input, sizeof(lz4_input)-1);
+ input = lz4->create_istream(file_input);
+ test_assert(i_stream_read(input) == -1 &&
+ input->stream_errno == EINVAL);
+ i_stream_unref(&input);
+ i_stream_unref(&file_input);
+
+ /* partial initial header read */
+ file_input = test_istream_create_data(&lz4_input, sizeof(lz4_input));
+ file_input->blocking = TRUE;
+
+ test_istream_set_max_buffer_size(file_input, sizeof(lz4_input)-1);
+ (void)i_stream_read(file_input);
+ test_istream_set_max_buffer_size(file_input, sizeof(lz4_input));
+
+ input = lz4->create_istream(file_input);
+ test_assert(i_stream_read(input) == -1 &&
+ input->stream_errno == 0);
+ i_stream_unref(&input);
+ i_stream_unref(&file_input);
+
+ test_end();
+}
+
+static void test_uncompress_file(const char *path)
+{
+ const struct compression_handler *handler;
+ struct istream *input, *file_input;
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ ret = compression_lookup_handler_from_ext(path, &handler);
+ if (ret < 0)
+ i_fatal("Can't detect compression algorithm from path %s", path);
+ else if (ret == 0)
+ i_fatal("Support not compiled in for %s", handler->name);
+
+ file_input = i_stream_create_file(path, IO_BLOCK_SIZE);
+ input = handler->create_istream(file_input);
+ while (i_stream_read_more(input, &data, &size) > 0) {
+ if (write(STDOUT_FILENO, data, size) < 0)
+ break;
+ i_stream_skip(input, size);
+ }
+ i_stream_destroy(&input);
+}
+
+static void test_compress_file(const char *in_path, const char *out_path)
+{
+ const struct compression_handler *handler;
+ struct istream *input, *file_input;
+ struct ostream *output, *file_output;
+ int fd_in, fd_out;
+ struct sha1_ctxt sha1;
+ unsigned char output_sha1[SHA1_RESULTLEN], input_sha1[SHA1_RESULTLEN];
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ ret = compression_lookup_handler_from_ext(out_path, &handler);
+ if (ret < 0)
+ i_fatal("Can't detect compression algorithm from path %s", out_path);
+ if (ret == 0)
+ i_fatal("Support not compiled in for %s", handler->name);
+
+ /* write the compressed output file */
+ fd_in = open(in_path, O_RDONLY);
+ if (fd_in == -1)
+ i_fatal("open(%s) failed: %m", in_path);
+ fd_out = open(out_path, O_TRUNC | O_CREAT | O_RDWR, 0600);
+ if (fd_out == -1)
+ i_fatal("creat(%s) failed: %m", out_path);
+
+ sha1_init(&sha1);
+ file_output = o_stream_create_fd_file(fd_out, 0, FALSE);
+ output = handler->create_ostream(file_output, 1);
+ input = i_stream_create_fd_autoclose(&fd_in, IO_BLOCK_SIZE);
+ while (i_stream_read_more(input, &data, &size) > 0) {
+ sha1_loop(&sha1, data, size);
+ o_stream_nsend(output, data, size);
+ i_stream_skip(input, size);
+ }
+ if (o_stream_finish(output) < 0) {
+ i_fatal("write(%s) failed: %s",
+ out_path, o_stream_get_error(output));
+ }
+ i_stream_destroy(&input);
+ o_stream_destroy(&output);
+ o_stream_destroy(&file_output);
+ sha1_result(&sha1, output_sha1);
+
+ /* verify that we can read the compressed file */
+ sha1_init(&sha1);
+ file_input = i_stream_create_fd(fd_out, IO_BLOCK_SIZE);
+ input = handler->create_istream(file_input);
+ while (i_stream_read_more(input, &data, &size) > 0) {
+ sha1_loop(&sha1, data, size);
+ i_stream_skip(input, size);
+ }
+ i_stream_destroy(&input);
+ i_stream_destroy(&file_input);
+ sha1_result(&sha1, input_sha1);
+
+ if (memcmp(input_sha1, output_sha1, sizeof(input_sha1)) != 0)
+ i_fatal("Decompression couldn't get the original input");
+ i_close_fd(&fd_out);
+}
+
+static void test_compression_ext(void)
+{
+ const struct compression_handler *handler;
+ test_begin("compression handler by extension");
+
+ for (unsigned int i = 0; compression_handlers[i].name != NULL; i++) {
+ if (compression_handlers[i].ext != NULL) {
+ const char *path =
+ t_strconcat("file", compression_handlers[i].ext, NULL);
+ int ret = compression_lookup_handler_from_ext(path, &handler);
+ test_assert(ret == 0 || handler == &compression_handlers[i]);
+ }
+ }
+
+ test_end();
+}
+
+int main(int argc, char *argv[])
+{
+ static void (*const test_functions[])(void) = {
+ test_compression,
+ test_istream_decompression_try,
+ test_gz_concat,
+ test_gz_no_concat,
+ test_gz_header,
+ test_gz_large_header,
+ test_lz4_small_header,
+ test_compression_ext,
+ NULL
+ };
+ if (argc == 2) {
+ test_uncompress_file(argv[1]);
+ return 0;
+ }
+ if (argc == 3) {
+ test_compress_file(argv[1], argv[2]);
+ return 0;
+ }
+ return test_run(test_functions);
+}