summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve/util
diff options
context:
space:
mode:
Diffstat (limited to 'pigeonhole/src/lib-sieve/util')
-rw-r--r--pigeonhole/src/lib-sieve/util/Makefile.am51
-rw-r--r--pigeonhole/src/lib-sieve/util/Makefile.in810
-rw-r--r--pigeonhole/src/lib-sieve/util/edit-mail.c2251
-rw-r--r--pigeonhole/src/lib-sieve/util/edit-mail.h47
-rw-r--r--pigeonhole/src/lib-sieve/util/mail-raw.c247
-rw-r--r--pigeonhole/src/lib-sieve/util/mail-raw.h27
-rw-r--r--pigeonhole/src/lib-sieve/util/rfc2822.c277
-rw-r--r--pigeonhole/src/lib-sieve/util/rfc2822.h46
-rw-r--r--pigeonhole/src/lib-sieve/util/test-edit-mail.c842
-rw-r--r--pigeonhole/src/lib-sieve/util/test-rfc2822.c197
10 files changed, 4795 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/util/Makefile.am b/pigeonhole/src/lib-sieve/util/Makefile.am
new file mode 100644
index 0000000..36cad8a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/Makefile.am
@@ -0,0 +1,51 @@
+noinst_LTLIBRARIES = libsieve_util.la
+
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ $(LIBDOVECOT_SERVICE_INCLUDE) \
+ -DMODULEDIR=\""$(dovecot_moduledir)"\"
+
+libsieve_util_la_DEPENDENCIES = $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_DEPS)
+
+libsieve_util_la_SOURCES = \
+ mail-raw.c \
+ edit-mail.c \
+ rfc2822.c
+
+headers = \
+ mail-raw.h \
+ edit-mail.h \
+ rfc2822.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(headers)
+
+test_programs = \
+ test-edit-mail \
+ test-rfc2822
+
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+ libsieve_util.la \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+test_deps = \
+ libsieve_util.la \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+test_edit_mail_SOURCES = test-edit-mail.c
+test_edit_mail_LDADD = $(test_libs)
+test_edit_mail_DEPENDENCIES = $(test_deps)
+
+test_rfc2822_SOURCES = test-rfc2822.c
+test_rfc2822_LDADD = $(test_libs)
+test_rfc2822_DEPENDENCIES = $(test_deps)
+
+check: check-am check-test
+check-test: all-am
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
diff --git a/pigeonhole/src/lib-sieve/util/Makefile.in b/pigeonhole/src/lib-sieve/util/Makefile.in
new file mode 100644
index 0000000..2053bd8
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/Makefile.in
@@ -0,0 +1,810 @@
+# 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)
+subdir = src/lib-sieve/util
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-edit-mail$(EXEEXT) test-rfc2822$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_util_la_LIBADD =
+am_libsieve_util_la_OBJECTS = mail-raw.lo edit-mail.lo rfc2822.lo
+libsieve_util_la_OBJECTS = $(am_libsieve_util_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_test_edit_mail_OBJECTS = test-edit-mail.$(OBJEXT)
+test_edit_mail_OBJECTS = $(am_test_edit_mail_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = libsieve_util.la $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_test_rfc2822_OBJECTS = test-rfc2822.$(OBJEXT)
+test_rfc2822_OBJECTS = $(am_test_rfc2822_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)/edit-mail.Plo \
+ ./$(DEPDIR)/mail-raw.Plo ./$(DEPDIR)/rfc2822.Plo \
+ ./$(DEPDIR)/test-edit-mail.Po ./$(DEPDIR)/test-rfc2822.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 = $(libsieve_util_la_SOURCES) $(test_edit_mail_SOURCES) \
+ $(test_rfc2822_SOURCES)
+DIST_SOURCES = $(libsieve_util_la_SOURCES) $(test_edit_mail_SOURCES) \
+ $(test_rfc2822_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+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)$(pkginc_libdir)"
+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@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+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@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+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_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@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+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@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_util.la
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ $(LIBDOVECOT_SERVICE_INCLUDE) \
+ -DMODULEDIR=\""$(dovecot_moduledir)"\"
+
+libsieve_util_la_DEPENDENCIES = $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_DEPS)
+libsieve_util_la_SOURCES = \
+ mail-raw.c \
+ edit-mail.c \
+ rfc2822.c
+
+headers = \
+ mail-raw.h \
+ edit-mail.h \
+ rfc2822.h
+
+pkginc_libdir = $(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(headers)
+test_programs = \
+ test-edit-mail \
+ test-rfc2822
+
+test_libs = \
+ libsieve_util.la \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+
+test_deps = \
+ libsieve_util.la \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+test_edit_mail_SOURCES = test-edit-mail.c
+test_edit_mail_LDADD = $(test_libs)
+test_edit_mail_DEPENDENCIES = $(test_deps)
+test_rfc2822_SOURCES = test-rfc2822.c
+test_rfc2822_LDADD = $(test_libs)
+test_rfc2822_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-sieve/util/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/util/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}; \
+ }
+
+libsieve_util.la: $(libsieve_util_la_OBJECTS) $(libsieve_util_la_DEPENDENCIES) $(EXTRA_libsieve_util_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_util_la_OBJECTS) $(libsieve_util_la_LIBADD) $(LIBS)
+
+test-edit-mail$(EXEEXT): $(test_edit_mail_OBJECTS) $(test_edit_mail_DEPENDENCIES) $(EXTRA_test_edit_mail_DEPENDENCIES)
+ @rm -f test-edit-mail$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_edit_mail_OBJECTS) $(test_edit_mail_LDADD) $(LIBS)
+
+test-rfc2822$(EXEEXT): $(test_rfc2822_OBJECTS) $(test_rfc2822_DEPENDENCIES) $(EXTRA_test_rfc2822_DEPENDENCIES)
+ @rm -f test-rfc2822$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_rfc2822_OBJECTS) $(test_rfc2822_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edit-mail.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-raw.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rfc2822.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-edit-mail.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-rfc2822.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
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(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 mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/edit-mail.Plo
+ -rm -f ./$(DEPDIR)/mail-raw.Plo
+ -rm -f ./$(DEPDIR)/rfc2822.Plo
+ -rm -f ./$(DEPDIR)/test-edit-mail.Po
+ -rm -f ./$(DEPDIR)/test-rfc2822.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-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)/edit-mail.Plo
+ -rm -f ./$(DEPDIR)/mail-raw.Plo
+ -rm -f ./$(DEPDIR)/rfc2822.Plo
+ -rm -f ./$(DEPDIR)/test-edit-mail.Po
+ -rm -f ./$(DEPDIR)/test-rfc2822.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
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS 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-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
+
+.PRECIOUS: Makefile
+
+
+check: check-am check-test
+check-test: all-am
+ 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/pigeonhole/src/lib-sieve/util/edit-mail.c b/pigeonhole/src/lib-sieve/util/edit-mail.c
new file mode 100644
index 0000000..5034e58
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/edit-mail.c
@@ -0,0 +1,2251 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "mempool.h"
+#include "llist.h"
+#include "istream-private.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "message-parser.h"
+#include "message-header-encode.h"
+#include "message-header-decode.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "index-mail.h"
+#include "raw-storage.h"
+
+#include "rfc2822.h"
+
+#include "edit-mail.h"
+
+/*
+ * Forward declarations
+ */
+
+struct _header_field_index;
+struct _header_field;
+struct _header_index;
+struct _header;
+
+static struct mail_vfuncs edit_mail_vfuncs;
+
+struct edit_mail_istream;
+struct istream *edit_mail_istream_create(struct edit_mail *edmail);
+
+static struct _header_index *
+edit_mail_header_clone(struct edit_mail *edmail, struct _header *header);
+
+/*
+ * Raw storage
+ */
+
+static struct mail_user *edit_mail_user = NULL;
+static unsigned int edit_mail_refcount = 0;
+
+static struct mail_user *edit_mail_raw_storage_get(struct mail_user *mail_user)
+{
+ if (edit_mail_user == NULL) {
+ void **sets =
+ master_service_settings_get_others(master_service);
+
+ edit_mail_user = raw_storage_create_from_set(
+ mail_user->set_info, sets[0]);
+ }
+
+ edit_mail_refcount++;
+
+ return edit_mail_user;
+}
+
+static void edit_mail_raw_storage_drop(void)
+{
+ i_assert(edit_mail_refcount > 0);
+
+ if (--edit_mail_refcount != 0)
+ return;
+
+ mail_user_unref(&edit_mail_user);
+ edit_mail_user = NULL;
+}
+
+/*
+ * Headers
+ */
+
+struct _header_field {
+ struct _header *header;
+
+ unsigned int refcount;
+
+ char *data;
+ size_t size;
+ size_t virtual_size;
+ uoff_t offset;
+ unsigned int lines;
+
+ uoff_t body_offset;
+
+ char *utf8_value;
+};
+
+struct _header_field_index {
+ struct _header_field_index *prev, *next;
+
+ struct _header_field *field;
+ struct _header_index *header;
+};
+
+struct _header {
+ unsigned int refcount;
+
+ char *name;
+};
+
+struct _header_index {
+ struct _header_index *prev, *next;
+
+ struct _header *header;
+
+ struct _header_field_index *first, *last;
+
+ unsigned int count;
+};
+
+static inline struct _header *_header_create(const char *name)
+{
+ struct _header *header;
+
+ header = i_new(struct _header, 1);
+ header->name = i_strdup(name);
+ header->refcount = 1;
+
+ return header;
+}
+
+static inline void _header_ref(struct _header *header)
+{
+ header->refcount++;
+}
+
+static inline void _header_unref(struct _header *header)
+{
+ i_assert(header->refcount > 0);
+ if (--header->refcount != 0)
+ return;
+
+ i_free(header->name);
+ i_free(header);
+}
+
+static inline struct _header_field *_header_field_create(struct _header *header)
+{
+ struct _header_field *hfield;
+
+ hfield = i_new(struct _header_field, 1);
+ hfield->refcount = 1;
+ hfield->header = header;
+ if (header != NULL)
+ _header_ref(header);
+
+ return hfield;
+}
+
+static inline void _header_field_ref(struct _header_field *hfield)
+{
+ hfield->refcount++;
+}
+
+static inline void _header_field_unref(struct _header_field *hfield)
+{
+ i_assert(hfield->refcount > 0);
+ if (--hfield->refcount != 0)
+ return;
+
+ if (hfield->header != NULL)
+ _header_unref(hfield->header);
+
+ if (hfield->data != NULL)
+ i_free(hfield->data);
+ if (hfield->utf8_value != NULL)
+ i_free(hfield->utf8_value);
+ i_free(hfield);
+}
+
+/*
+ * Edit mail object
+ */
+
+struct edit_mail {
+ struct mail_private mail;
+ struct mail_private *wrapped;
+
+ struct edit_mail *parent;
+ unsigned int refcount;
+
+ struct istream *wrapped_stream;
+ struct istream *stream;
+
+ struct _header_index *headers_head, *headers_tail;
+ struct _header_field_index *header_fields_head, *header_fields_tail;
+ struct message_size hdr_size, body_size;
+
+ struct message_size wrapped_hdr_size, wrapped_body_size;
+
+ struct _header_field_index *header_fields_appended;
+ struct message_size appended_hdr_size;
+
+ bool modified:1;
+ bool snapshot_modified:1;
+ bool crlf:1;
+ bool eoh_crlf:1;
+ bool headers_parsed:1;
+ bool destroying_stream:1;
+};
+
+struct edit_mail *edit_mail_wrap(struct mail *mail)
+{
+ struct mail_private *mailp = (struct mail_private *) mail;
+ struct edit_mail *edmail;
+ struct mail_user *raw_mail_user;
+ struct mailbox *raw_box = NULL;
+ struct mailbox_transaction_context *raw_trans;
+ struct message_size hdr_size, body_size;
+ struct istream *wrapped_stream;
+ uoff_t size_diff;
+ pool_t pool;
+
+ if (mail_get_stream(mail, &hdr_size, &body_size, &wrapped_stream) < 0)
+ return NULL;
+
+ /* Create dummy raw mailbox for our wrapper */
+
+ raw_mail_user = edit_mail_raw_storage_get(mail->box->storage->user);
+
+ if (raw_mailbox_alloc_stream(raw_mail_user, wrapped_stream, (time_t)-1,
+ "editor@example.com", &raw_box) < 0) {
+ i_error("edit-mail: failed to open raw box: %s",
+ mailbox_get_last_internal_error(raw_box, NULL));
+ mailbox_free(&raw_box);
+ edit_mail_raw_storage_drop();
+ return NULL;
+ }
+
+ raw_trans = mailbox_transaction_begin(raw_box, 0, __func__);
+
+ /* Create the wrapper mail */
+
+ pool = pool_alloconly_create("edit_mail", 1024);
+ edmail = p_new(pool, struct edit_mail, 1);
+ edmail->refcount = 1;
+ edmail->mail.pool = pool;
+
+ edmail->wrapped = mailp;
+ edmail->wrapped_hdr_size = hdr_size;
+ edmail->wrapped_body_size = body_size;
+
+ edmail->wrapped_stream = wrapped_stream;
+ i_stream_ref(edmail->wrapped_stream);
+
+ /* Determine whether we should use CRLF or LF for the physical message
+ */
+ size_diff = ((hdr_size.virtual_size + body_size.virtual_size) -
+ (hdr_size.physical_size + body_size.physical_size));
+ if (size_diff == 0 || size_diff <= (hdr_size.lines + body_size.lines)/2)
+ edmail->crlf = edmail->eoh_crlf = TRUE;
+
+ array_create(&edmail->mail.module_contexts, pool, sizeof(void *), 5);
+
+ edmail->mail.v = edit_mail_vfuncs;
+ edmail->mail.mail.seq = 1;
+ edmail->mail.mail.box = raw_box;
+ edmail->mail.mail.transaction = raw_trans;
+ edmail->mail.wanted_fields = mailp->wanted_fields;
+ edmail->mail.wanted_headers = mailp->wanted_headers;
+
+ return edmail;
+}
+
+struct edit_mail *edit_mail_snapshot(struct edit_mail *edmail)
+{
+ struct _header_field_index *field_idx, *field_idx_new;
+ struct edit_mail *edmail_new;
+ pool_t pool;
+
+ if (!edmail->snapshot_modified)
+ return edmail;
+
+ pool = pool_alloconly_create("edit_mail", 1024);
+ edmail_new = p_new(pool, struct edit_mail, 1);
+ edmail_new->refcount = 1;
+ edmail_new->mail.pool = pool;
+
+ edmail_new->wrapped = edmail->wrapped;
+ edmail_new->wrapped_hdr_size = edmail->wrapped_hdr_size;
+ edmail_new->wrapped_body_size = edmail->wrapped_body_size;
+ edmail_new->hdr_size = edmail->hdr_size;
+ edmail_new->body_size = edmail->body_size;
+ edmail_new->appended_hdr_size = edmail->appended_hdr_size;
+
+ edmail_new->wrapped_stream = edmail->wrapped_stream;
+ i_stream_ref(edmail_new->wrapped_stream);
+
+ edmail_new->crlf = edmail->crlf;
+ edmail_new->eoh_crlf = edmail->eoh_crlf;
+
+ array_create(&edmail_new->mail.module_contexts, pool,
+ sizeof(void *), 5);
+
+ edmail_new->mail.v = edit_mail_vfuncs;
+ edmail_new->mail.mail.seq = 1;
+ edmail_new->mail.mail.box = edmail->mail.mail.box;
+ edmail_new->mail.mail.transaction = edmail->mail.mail.transaction;
+ edmail_new->mail.wanted_fields = edmail->mail.wanted_fields;
+ edmail_new->mail.wanted_headers = edmail->mail.wanted_headers;
+
+ edmail_new->stream = NULL;
+
+ if (edmail->modified) {
+ field_idx = edmail->header_fields_head;
+ while (field_idx != NULL) {
+ struct _header_field_index *next = field_idx->next;
+
+ field_idx_new = i_new(struct _header_field_index, 1);
+
+ field_idx_new->header = edit_mail_header_clone(
+ edmail_new, field_idx->header->header);
+
+ field_idx_new->field = field_idx->field;
+ _header_field_ref(field_idx_new->field);
+
+ DLLIST2_APPEND(&edmail_new->header_fields_head,
+ &edmail_new->header_fields_tail,
+ field_idx_new);
+
+ field_idx_new->header->count++;
+ if (field_idx->header->first == field_idx)
+ field_idx_new->header->first = field_idx_new;
+ if (field_idx->header->last == field_idx)
+ field_idx_new->header->last = field_idx_new;
+
+ if (field_idx == edmail->header_fields_appended) {
+ edmail_new->header_fields_appended =
+ field_idx_new;
+ }
+
+ field_idx = next;
+ }
+
+ edmail_new->modified = TRUE;
+ }
+
+ edmail_new->headers_parsed = edmail->headers_parsed;
+ edmail_new->parent = edmail;
+
+ return edmail_new;
+}
+
+void edit_mail_reset(struct edit_mail *edmail)
+{
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+
+ i_stream_unref(&edmail->stream);
+
+ field_idx = edmail->header_fields_head;
+ while (field_idx != NULL) {
+ struct _header_field_index *next = field_idx->next;
+
+ _header_field_unref(field_idx->field);
+ i_free(field_idx);
+
+ field_idx = next;
+ }
+
+ header_idx = edmail->headers_head;
+ while (header_idx != NULL) {
+ struct _header_index *next = header_idx->next;
+
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+
+ header_idx = next;
+ }
+
+ edmail->modified = FALSE;
+}
+
+void edit_mail_unwrap(struct edit_mail **edmail)
+{
+ struct edit_mail *parent;
+
+ i_assert((*edmail)->refcount > 0);
+ if (--(*edmail)->refcount != 0)
+ return;
+
+ edit_mail_reset(*edmail);
+ i_stream_unref(&(*edmail)->wrapped_stream);
+
+ parent = (*edmail)->parent;
+
+ if (parent == NULL) {
+ mailbox_transaction_rollback(&(*edmail)->mail.mail.transaction);
+ mailbox_free(&(*edmail)->mail.mail.box);
+ edit_mail_raw_storage_drop();
+ }
+
+ pool_unref(&(*edmail)->mail.pool);
+ *edmail = NULL;
+
+ if (parent != NULL)
+ edit_mail_unwrap(&parent);
+}
+
+struct mail *edit_mail_get_mail(struct edit_mail *edmail)
+{
+ /* Return wrapped mail when nothing is modified yet */
+ if (!edmail->modified)
+ return &edmail->wrapped->mail;
+
+ return &edmail->mail.mail;
+}
+
+/*
+ * Editing
+ */
+
+static inline void edit_mail_modify(struct edit_mail *edmail)
+{
+ edmail->mail.mail.seq++;
+ edmail->modified = TRUE;
+ edmail->snapshot_modified = TRUE;
+}
+
+/* Header modification */
+
+static inline char *_header_value_unfold(const char *value)
+{
+ string_t *out;
+ unsigned int i;
+
+ for (i = 0; value[i] != '\0'; i++) {
+ if (value[i] == '\r' || value[i] == '\n')
+ break;
+ }
+ if (value[i] == '\0')
+ return i_strdup(value);
+
+ out = t_str_new(i + strlen(value+i) + 10);
+ str_append_data(out, value, i);
+ for (; value[i] != '\0'; i++) {
+ if (value[i] == '\n') {
+ i++;
+ if (value[i] == '\0')
+ break;
+
+ switch (value[i]) {
+ case ' ':
+ str_append_c(out, ' ');
+ break;
+ case '\t':
+ default:
+ str_append_c(out, '\t');
+ }
+ } else {
+ if (value[i] != '\r')
+ str_append_c(out, value[i]);
+ }
+ }
+
+ return i_strndup(str_c(out), str_len(out));
+}
+
+static struct _header_index *
+edit_mail_header_find(struct edit_mail *edmail, const char *field_name)
+{
+ struct _header_index *header_idx;
+
+ header_idx = edmail->headers_head;
+ while (header_idx != NULL) {
+ if (strcasecmp(header_idx->header->name, field_name) == 0)
+ return header_idx;
+
+ header_idx = header_idx->next;
+ }
+
+ return NULL;
+}
+
+static struct _header_index *
+edit_mail_header_create(struct edit_mail *edmail, const char *field_name)
+{
+ struct _header_index *header_idx;
+
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL) {
+ header_idx = i_new(struct _header_index, 1);
+ header_idx->header = _header_create(field_name);
+
+ DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail,
+ header_idx);
+ }
+
+ return header_idx;
+}
+
+static struct _header_index *
+edit_mail_header_clone(struct edit_mail *edmail, struct _header *header)
+{
+ struct _header_index *header_idx;
+
+ header_idx = edmail->headers_head;
+ while (header_idx != NULL) {
+ if (header_idx->header == header)
+ return header_idx;
+
+ header_idx = header_idx->next;
+ }
+
+ header_idx = i_new(struct _header_index, 1);
+ header_idx->header = header;
+ _header_ref(header);
+ DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail,
+ header_idx);
+
+ return header_idx;
+}
+
+static struct _header_field_index *
+edit_mail_header_field_create(struct edit_mail *edmail, const char *field_name,
+ const char *value)
+{
+ struct _header_index *header_idx;
+ struct _header *header;
+ struct _header_field_index *field_idx;
+ struct _header_field *field;
+ unsigned int lines;
+
+ /* Get/create header index item */
+ header_idx = edit_mail_header_create(edmail, field_name);
+ header = header_idx->header;
+
+ /* Create new field index item */
+ field_idx = i_new(struct _header_field_index, 1);
+ field_idx->header = header_idx;
+ field_idx->field = field = _header_field_create(header);
+
+ /* Create header field data (folded if necessary) */
+ T_BEGIN {
+ string_t *enc_value, *data;
+
+ enc_value = t_str_new(strlen(field_name) + strlen(value) + 64);
+ data = t_str_new(strlen(field_name) + strlen(value) + 128);
+
+ message_header_encode(value, enc_value);
+
+ lines = rfc2822_header_append(data, field_name,
+ str_c(enc_value), edmail->crlf,
+ &field->body_offset);
+
+ /* Copy to new field */
+ field->data = i_strndup(str_data(data), str_len(data));
+ field->size = str_len(data);
+ field->virtual_size = (edmail->crlf ?
+ field->size : field->size + lines);
+ field->lines = lines;
+ } T_END;
+
+ /* Record original (utf8) value */
+ field->utf8_value = _header_value_unfold(value);
+
+ return field_idx;
+}
+
+static void
+edit_mail_header_field_delete(struct edit_mail *edmail,
+ struct _header_field_index *field_idx,
+ bool update_index)
+{
+ struct _header_index *header_idx = field_idx->header;
+ struct _header_field *field = field_idx->field;
+
+ i_assert(header_idx != NULL);
+
+ edmail->hdr_size.physical_size -= field->size;
+ edmail->hdr_size.virtual_size -= field->virtual_size;
+ edmail->hdr_size.lines -= field->lines;
+
+ header_idx->count--;
+ if (update_index) {
+ if (header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head,
+ &edmail->headers_tail, header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->first->next;
+
+ while (hfield != NULL && hfield->header != header_idx)
+ hfield = hfield->next;
+
+ i_assert(hfield != NULL);
+ header_idx->first = hfield;
+ } else if (header_idx->last == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->last->prev;
+
+ while (hfield != NULL && hfield->header != header_idx)
+ hfield = hfield->prev;
+
+ i_assert(hfield != NULL);
+ header_idx->last = hfield;
+ }
+ }
+
+ DLLIST2_REMOVE(&edmail->header_fields_head, &edmail->header_fields_tail,
+ field_idx);
+ _header_field_unref(field_idx->field);
+ i_free(field_idx);
+}
+
+static struct _header_field_index *
+edit_mail_header_field_replace(struct edit_mail *edmail,
+ struct _header_field_index *field_idx,
+ const char *newname, const char *newvalue,
+ bool update_index)
+{
+ struct _header_field_index *field_idx_new;
+ struct _header_index *header_idx = field_idx->header, *header_idx_new;
+ struct _header_field *field = field_idx->field, *field_new;
+
+ i_assert(header_idx != NULL);
+ i_assert(newname != NULL || newvalue != NULL);
+
+ if (newname == NULL)
+ newname = header_idx->header->name;
+ if (newvalue == NULL)
+ newvalue = field_idx->field->utf8_value;
+ field_idx_new = edit_mail_header_field_create(
+ edmail, newname, newvalue);
+ field_new = field_idx_new->field;
+ header_idx_new = field_idx_new->header;
+
+ edmail->hdr_size.physical_size -= field->size;
+ edmail->hdr_size.virtual_size -= field->virtual_size;
+ edmail->hdr_size.lines -= field->lines;
+
+ edmail->hdr_size.physical_size += field_new->size;
+ edmail->hdr_size.virtual_size += field_new->virtual_size;
+ edmail->hdr_size.lines += field_new->lines;
+
+ /* Replace header field index */
+ field_idx_new->prev = field_idx->prev;
+ field_idx_new->next = field_idx->next;
+ if (field_idx->prev != NULL)
+ field_idx->prev->next = field_idx_new;
+ if (field_idx->next != NULL)
+ field_idx->next->prev = field_idx_new;
+ if (edmail->header_fields_head == field_idx)
+ edmail->header_fields_head = field_idx_new;
+ if (edmail->header_fields_tail == field_idx)
+ edmail->header_fields_tail = field_idx_new;
+
+ if (header_idx_new == header_idx) {
+ if (header_idx->first == field_idx)
+ header_idx->first = field_idx_new;
+ if (header_idx->last == field_idx)
+ header_idx->last = field_idx_new;
+ } else {
+ header_idx->count--;
+ header_idx_new->count++;
+
+ if (update_index) {
+ if (header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head,
+ &edmail->headers_tail,
+ header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->first->next;
+
+ while (hfield != NULL &&
+ hfield->header != header_idx)
+ hfield = hfield->next;
+
+ i_assert(hfield != NULL);
+ header_idx->first = hfield;
+ } else if (header_idx->last == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->last->prev;
+
+ while (hfield != NULL &&
+ hfield->header != header_idx)
+ hfield = hfield->prev;
+
+ i_assert(hfield != NULL);
+ header_idx->last = hfield;
+ }
+ if (header_idx_new->count > 0) {
+ struct _header_field_index *hfield;
+
+ hfield = edmail->header_fields_head;
+ while (hfield != NULL &&
+ hfield->header != header_idx_new)
+ hfield = hfield->next;
+
+ i_assert(hfield != NULL);
+ header_idx_new->first = hfield;
+
+ hfield = edmail->header_fields_tail;
+ while (hfield != NULL &&
+ hfield->header != header_idx_new)
+ hfield = hfield->prev;
+
+ i_assert(hfield != NULL);
+ header_idx_new->last = hfield;
+ }
+ }
+ }
+
+ _header_field_unref(field_idx->field);
+ i_free(field_idx);
+ return field_idx_new;
+}
+
+static inline char *
+_header_decode(const unsigned char *hdr_data, size_t hdr_data_len)
+{
+ string_t *str = t_str_new(512);
+
+ /* hdr_data is already unfolded */
+
+ /* Decode MIME encoded-words. */
+ message_header_decode_utf8((const unsigned char *)hdr_data,
+ hdr_data_len, str, NULL);
+ return i_strdup(str_c(str));
+}
+
+static int edit_mail_headers_parse(struct edit_mail *edmail)
+{
+ struct message_header_parser_ctx *hparser;
+ enum message_header_parser_flags hparser_flags =
+ MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+ MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE;
+ struct message_header_line *hdr;
+ struct _header_index *header_idx;
+ struct _header_field_index *head = NULL, *tail = NULL, *current;
+ string_t *hdr_data;
+ uoff_t offset = 0, body_offset = 0, vsize_diff = 0;
+ unsigned int lines = 0;
+ int ret;
+
+ if (edmail->headers_parsed)
+ return 1;
+
+ i_stream_seek(edmail->wrapped_stream, 0);
+ hparser = message_parse_header_init(edmail->wrapped_stream, NULL,
+ hparser_flags);
+
+ T_BEGIN {
+ hdr_data = t_str_new(1024);
+ while ((ret = message_parse_header_next(hparser, &hdr)) > 0) {
+ struct _header_field_index *field_idx_new;
+ struct _header_field *field;
+
+ if (hdr->eoh) {
+ /* Record whether header ends in CRLF or LF */
+ edmail->eoh_crlf = hdr->crlf_newline;
+ }
+
+ if (hdr == NULL || hdr->eoh)
+ break;
+
+ /* We deny the existence of any `Content-Length:'
+ header. This header is non-standard and it can wreak
+ havok when the message is modified.
+ */
+ if (strcasecmp(hdr->name, "Content-Length" ) == 0)
+ continue;
+
+ if (hdr->continued) {
+ /* Continued line of folded header */
+ buffer_append(hdr_data, hdr->value,
+ hdr->value_len);
+ } else {
+ /* First line of header */
+ offset = hdr->name_offset;
+ body_offset = hdr->name_len + hdr->middle_len;
+ str_truncate(hdr_data, 0);
+ buffer_append(hdr_data, hdr->name,
+ hdr->name_len);
+ buffer_append(hdr_data, hdr->middle,
+ hdr->middle_len);
+ buffer_append(hdr_data, hdr->value,
+ hdr->value_len);
+ lines = 0;
+ vsize_diff = 0;
+ }
+
+ if (!hdr->no_newline) {
+ lines++;
+
+ if (hdr->crlf_newline) {
+ buffer_append(hdr_data, "\r\n", 2);
+ } else {
+ buffer_append(hdr_data, "\n", 1);
+ vsize_diff++;
+ }
+ }
+
+ if (hdr->continues) {
+ hdr->use_full_value = TRUE;
+ continue;
+ }
+
+ /* Create new header field index entry */
+
+ field_idx_new = i_new(struct _header_field_index, 1);
+
+ header_idx = edit_mail_header_create(edmail, hdr->name);
+ header_idx->count++;
+ field_idx_new->header = header_idx;
+ field_idx_new->field = field =
+ _header_field_create(header_idx->header);
+
+ i_assert(body_offset > 0);
+ field->body_offset = body_offset;
+
+ field->utf8_value = _header_decode(hdr->full_value,
+ hdr->full_value_len);
+
+ field->size = str_len(hdr_data);
+ field->virtual_size = field->size + vsize_diff;
+ field->data = i_strndup(str_data(hdr_data),
+ field->size);
+ field->offset = offset;
+ field->lines = lines;
+
+ DLLIST2_APPEND(&head, &tail, field_idx_new);
+
+ edmail->hdr_size.physical_size += field->size;
+ edmail->hdr_size.virtual_size += field->virtual_size;
+ edmail->hdr_size.lines += lines;
+ }
+ } T_END;
+
+ message_parse_header_deinit(&hparser);
+
+ /* Blocking i/o required */
+ i_assert(ret != 0);
+
+ if (ret < 0 && edmail->wrapped_stream->stream_errno != 0) {
+ /* Error; clean up */
+ i_error("read(%s) failed: %s",
+ i_stream_get_name(edmail->wrapped_stream),
+ i_stream_get_error(edmail->wrapped_stream));
+ current = head;
+ while (current != NULL) {
+ struct _header_field_index *next = current->next;
+
+ _header_field_unref(current->field);
+ i_free(current);
+
+ current = next;
+ }
+
+ return ret;
+ }
+
+ /* Insert header field index items in main list */
+ if (head != NULL && tail != NULL) {
+ if (edmail->header_fields_appended != NULL) {
+ if (edmail->header_fields_head !=
+ edmail->header_fields_appended) {
+ edmail->header_fields_appended->prev->next = head;
+ head->prev = edmail->header_fields_appended->prev;
+ } else {
+ edmail->header_fields_head = head;
+ }
+
+ tail->next = edmail->header_fields_appended;
+ edmail->header_fields_appended->prev = tail;
+ } else if (edmail->header_fields_tail != NULL) {
+ edmail->header_fields_tail->next = head;
+ head->prev = edmail->header_fields_tail;
+ edmail->header_fields_tail = tail;
+ } else {
+ edmail->header_fields_head = head;
+ edmail->header_fields_tail = tail;
+ }
+ }
+
+ /* Rebuild header index */
+ current = edmail->header_fields_head;
+ while (current != NULL) {
+ if (current->header->first == NULL)
+ current->header->first = current;
+ current->header->last = current;
+
+ current = current->next;
+ }
+
+ /* Clear appended headers */
+ edmail->header_fields_appended = NULL;
+ edmail->appended_hdr_size.physical_size = 0;
+ edmail->appended_hdr_size.virtual_size = 0;
+ edmail->appended_hdr_size.lines = 0;
+
+ /* Do not parse headers again */
+ edmail->headers_parsed = TRUE;
+
+ return 1;
+}
+
+void edit_mail_header_add(struct edit_mail *edmail, const char *field_name,
+ const char *value, bool last)
+{
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+ struct _header_field *field;
+
+ edit_mail_modify(edmail);
+
+ field_idx = edit_mail_header_field_create(edmail, field_name, value);
+ header_idx = field_idx->header;
+ field = field_idx->field;
+
+ /* Add it to the header field index */
+ if (last) {
+ DLLIST2_APPEND(&edmail->header_fields_head,
+ &edmail->header_fields_tail, field_idx);
+
+ header_idx->last = field_idx;
+ if (header_idx->first == NULL)
+ header_idx->first = field_idx;
+
+ if (!edmail->headers_parsed) {
+ if (edmail->header_fields_appended == NULL) {
+ /* Record beginning of appended headers */
+ edmail->header_fields_appended = field_idx;
+ }
+
+ edmail->appended_hdr_size.physical_size += field->size;
+ edmail->appended_hdr_size.virtual_size += field->virtual_size;
+ edmail->appended_hdr_size.lines += field->lines;
+ }
+ } else {
+ DLLIST2_PREPEND(&edmail->header_fields_head,
+ &edmail->header_fields_tail, field_idx);
+
+ header_idx->first = field_idx;
+ if (header_idx->last == NULL)
+ header_idx->last = field_idx;
+ }
+
+ header_idx->count++;
+
+ edmail->hdr_size.physical_size += field->size;
+ edmail->hdr_size.virtual_size += field->virtual_size;
+ edmail->hdr_size.lines += field->lines;
+}
+
+int edit_mail_header_delete(struct edit_mail *edmail, const char *field_name,
+ int index)
+{
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+ int pos = 0;
+ int ret = 0;
+
+ /* Make sure headers are parsed */
+ if (edit_mail_headers_parse(edmail) <= 0)
+ return -1;
+
+ /* Find the header entry */
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL) {
+ /* Not found */
+ return 0;
+ }
+
+ /* Signal modification */
+ edit_mail_modify(edmail);
+
+ /* Iterate through all header fields and remove those that match */
+ field_idx = (index >= 0 ? header_idx->first : header_idx->last);
+ while (field_idx != NULL) {
+ struct _header_field_index *next =
+ (index >= 0 ? field_idx->next : field_idx->prev);
+
+ if (field_idx->field->header == header_idx->header) {
+ bool final;
+
+ if (index >= 0) {
+ pos++;
+ final = (header_idx->last == field_idx);
+ } else {
+ pos--;
+ final = (header_idx->first == field_idx);
+ }
+
+ if (index == 0 || index == pos) {
+ if (header_idx->first == field_idx)
+ header_idx->first = NULL;
+ if (header_idx->last == field_idx)
+ header_idx->last = NULL;
+ edit_mail_header_field_delete(
+ edmail, field_idx, FALSE);
+ ret++;
+ }
+
+ if (final || (index != 0 && index == pos))
+ break;
+ }
+
+ field_idx = next;
+ }
+
+ if (index == 0 || header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head,
+ &edmail->headers_tail, header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == NULL || header_idx->last == NULL) {
+ struct _header_field_index *current =
+ edmail->header_fields_head;
+
+ while (current != NULL) {
+ if (current->header == header_idx) {
+ if (header_idx->first == NULL)
+ header_idx->first = current;
+ header_idx->last = current;
+ }
+ current = current->next;
+ }
+ }
+
+ return ret;
+}
+
+int edit_mail_header_replace(struct edit_mail *edmail,
+ const char *field_name, int index,
+ const char *newname, const char *newvalue)
+{
+ struct _header_index *header_idx, *header_idx_new;
+ struct _header_field_index *field_idx, *field_idx_new;
+ int pos = 0;
+ int ret = 0;
+
+ /* Make sure headers are parsed */
+ if (edit_mail_headers_parse(edmail) <= 0)
+ return -1;
+
+ /* Find the header entry */
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL) {
+ /* Not found */
+ return 0;
+ }
+
+ /* Signal modification */
+ edit_mail_modify(edmail);
+
+ /* Iterate through all header fields and replace those that match */
+ field_idx = (index >= 0 ? header_idx->first : header_idx->last);
+ field_idx_new = NULL;
+ while (field_idx != NULL) {
+ struct _header_field_index *next =
+ (index >= 0 ? field_idx->next : field_idx->prev);
+
+ if (field_idx->field->header == header_idx->header) {
+ bool final;
+
+ if (index >= 0) {
+ pos++;
+ final = (header_idx->last == field_idx);
+ } else {
+ pos--;
+ final = (header_idx->first == field_idx);
+ }
+
+ if (index == 0 || index == pos) {
+ if (header_idx->first == field_idx)
+ header_idx->first = NULL;
+ if (header_idx->last == field_idx)
+ header_idx->last = NULL;
+ field_idx_new = edit_mail_header_field_replace(
+ edmail, field_idx, newname, newvalue,
+ FALSE);
+ ret++;
+ }
+
+ if (final || (index != 0 && index == pos))
+ break;
+ }
+
+ field_idx = next;
+ }
+
+ /* Update old header index */
+ if (header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail,
+ header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == NULL || header_idx->last == NULL) {
+ struct _header_field_index *current =
+ edmail->header_fields_head;
+
+ while (current != NULL) {
+ if (current->header == header_idx) {
+ if (header_idx->first == NULL)
+ header_idx->first = current;
+ header_idx->last = current;
+ }
+ current = current->next;
+ }
+ }
+
+ /* Update new header index */
+ if (field_idx_new != NULL) {
+ struct _header_field_index *current =
+ edmail->header_fields_head;
+
+ header_idx_new = field_idx_new->header;
+ while (current != NULL) {
+ if (current->header == header_idx_new) {
+ if (header_idx_new->first == NULL)
+ header_idx_new->first = current;
+ header_idx_new->last = current;
+ }
+ current = current->next;
+ }
+ }
+
+ return ret;
+}
+
+struct edit_mail_header_iter
+{
+ struct edit_mail *mail;
+ struct _header_index *header;
+ struct _header_field_index *current;
+
+ bool reverse:1;
+};
+
+int edit_mail_headers_iterate_init(struct edit_mail *edmail,
+ const char *field_name, bool reverse,
+ struct edit_mail_header_iter **edhiter_r)
+{
+ struct edit_mail_header_iter *edhiter;
+ struct _header_index *header_idx = NULL;
+ struct _header_field_index *current = NULL;
+
+ /* Make sure headers are parsed */
+ if (edit_mail_headers_parse(edmail) <= 0) {
+ /* Failure */
+ return -1;
+ }
+
+ header_idx = edit_mail_header_find(edmail, field_name);
+
+ if (field_name != NULL && header_idx == NULL) {
+ current = NULL;
+ } else if (!reverse) {
+ current = (header_idx != NULL ?
+ header_idx->first : edmail->header_fields_head);
+ } else {
+ current = (header_idx != NULL ?
+ header_idx->last : edmail->header_fields_tail);
+ if (current->header == NULL)
+ current = current->prev;
+ }
+
+ if (current == NULL)
+ return 0;
+
+ edhiter = i_new(struct edit_mail_header_iter, 1);
+ edhiter->mail = edmail;
+ edhiter->header = header_idx;
+ edhiter->reverse = reverse;
+ edhiter->current = current;
+
+ *edhiter_r = edhiter;
+ return 1;
+}
+
+void edit_mail_headers_iterate_deinit(struct edit_mail_header_iter **edhiter)
+{
+ i_free(*edhiter);
+ *edhiter = NULL;
+}
+
+void edit_mail_headers_iterate_get(struct edit_mail_header_iter *edhiter,
+ const char **value_r)
+{
+ const char *raw;
+ int i;
+
+ i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
+
+ raw = edhiter->current->field->utf8_value;
+ for (i = strlen(raw)-1; i >= 0; i--) {
+ if (raw[i] != ' ' && raw[i] != '\t')
+ break;
+ }
+
+ *value_r = t_strndup(raw, i+1);
+}
+
+bool edit_mail_headers_iterate_next(struct edit_mail_header_iter *edhiter)
+{
+ if (edhiter->current == NULL)
+ return FALSE;
+
+ do {
+ edhiter->current = (!edhiter->reverse ?
+ edhiter->current->next :
+ edhiter->current->prev );
+ } while (edhiter->current != NULL && edhiter->current->header != NULL &&
+ edhiter->header != NULL &&
+ edhiter->current->header != edhiter->header);
+
+ return (edhiter->current != NULL && edhiter->current->header != NULL);
+}
+
+bool edit_mail_headers_iterate_remove(struct edit_mail_header_iter *edhiter)
+{
+ struct _header_field_index *field_idx;
+ bool next;
+
+ i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
+
+ edit_mail_modify(edhiter->mail);
+
+ field_idx = edhiter->current;
+ next = edit_mail_headers_iterate_next(edhiter);
+ edit_mail_header_field_delete(edhiter->mail, field_idx, TRUE);
+
+ return next;
+}
+
+bool edit_mail_headers_iterate_replace(struct edit_mail_header_iter *edhiter,
+ const char *newname,
+ const char *newvalue)
+{
+ struct _header_field_index *field_idx;
+ bool next;
+
+ i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
+
+ edit_mail_modify(edhiter->mail);
+
+ field_idx = edhiter->current;
+ next = edit_mail_headers_iterate_next(edhiter);
+ edit_mail_header_field_replace(edhiter->mail, field_idx,
+ newname, newvalue, TRUE);
+
+ return next;
+}
+
+/* Body modification */
+
+// FIXME: implement
+
+/*
+ * Mail API
+ */
+
+static void edit_mail_close(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.close(&edmail->wrapped->mail);
+}
+
+static void edit_mail_free(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.free(&edmail->wrapped->mail);
+
+ edit_mail_unwrap(&edmail);
+}
+
+static void
+edit_mail_set_seq(struct mail *mail ATTR_UNUSED, uint32_t seq ATTR_UNUSED,
+ bool saving ATTR_UNUSED)
+{
+ i_panic("edit_mail_set_seq() not implemented");
+}
+
+static bool ATTR_NORETURN
+edit_mail_set_uid(struct mail *mail ATTR_UNUSED, uint32_t uid ATTR_UNUSED)
+{
+ i_panic("edit_mail_set_uid() not implemented");
+}
+
+static void edit_mail_set_uid_cache_updates(struct mail *mail, bool set)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.set_uid_cache_updates(&edmail->wrapped->mail, set);
+}
+
+static void
+edit_mail_add_temp_wanted_fields(
+ struct mail *mail ATTR_UNUSED, enum mail_fetch_field fields ATTR_UNUSED,
+ struct mailbox_header_lookup_ctx *headers ATTR_UNUSED)
+{
+ /* Nothing */
+}
+
+static enum mail_flags edit_mail_get_flags(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_flags(&edmail->wrapped->mail);
+}
+
+static const char *const *edit_mail_get_keywords(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_keywords(&edmail->wrapped->mail);
+}
+
+static const ARRAY_TYPE(keyword_indexes) *
+edit_mail_get_keyword_indexes(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_keyword_indexes(&edmail->wrapped->mail);
+}
+
+static uint64_t edit_mail_get_modseq(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_modseq(&edmail->wrapped->mail);
+}
+
+static uint64_t edit_mail_get_pvt_modseq(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_pvt_modseq(&edmail->wrapped->mail);
+}
+
+static int edit_mail_get_parts(struct mail *mail, struct message_part **parts_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_parts(&edmail->wrapped->mail, parts_r);
+}
+
+static int
+edit_mail_get_date(struct mail *mail, time_t *date_r, int *timezone_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_date(&edmail->wrapped->mail,
+ date_r, timezone_r);
+}
+
+static int edit_mail_get_received_date(struct mail *mail, time_t *date_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_received_date(&edmail->wrapped->mail,
+ date_r);
+}
+
+static int edit_mail_get_save_date(struct mail *mail, time_t *date_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_save_date(&edmail->wrapped->mail, date_r);
+}
+
+static int edit_mail_get_virtual_size(struct mail *mail, uoff_t *size_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (!edmail->headers_parsed) {
+ *size_r = (edmail->wrapped_hdr_size.virtual_size +
+ edmail->wrapped_body_size.virtual_size);
+
+ if (!edmail->modified)
+ return 0;
+ } else {
+ *size_r = edmail->wrapped_body_size.virtual_size + 2;
+ }
+
+ *size_r += (edmail->hdr_size.virtual_size +
+ edmail->body_size.virtual_size);
+ return 0;
+}
+
+static int edit_mail_get_physical_size(struct mail *mail, uoff_t *size_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ *size_r = 0;
+ if (!edmail->headers_parsed) {
+ *size_r = (edmail->wrapped_hdr_size.physical_size +
+ edmail->wrapped_body_size.physical_size);
+
+ if (!edmail->modified)
+ return 0;
+ } else {
+ *size_r = (edmail->wrapped_body_size.physical_size +
+ (edmail->eoh_crlf ? 2 : 1));
+ }
+
+ *size_r += (edmail->hdr_size.physical_size +
+ edmail->body_size.physical_size);
+ return 0;
+}
+
+static int
+edit_mail_get_first_header(struct mail *mail, const char *field_name,
+ bool decode_to_utf8, const char **value_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+ struct _header_index *header_idx;
+ struct _header_field *field;
+ int ret;
+
+ /* Check whether mail headers were modified at all */
+ if (!edmail->modified || edmail->headers_head == NULL) {
+ /* Unmodified */
+ return edmail->wrapped->v.get_first_header(
+ &edmail->wrapped->mail, field_name, decode_to_utf8,
+ value_r);
+ }
+
+ /* Try to find modified header */
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL || header_idx->count == 0 ) {
+ if (!edmail->headers_parsed) {
+ /* No new header */
+ return edmail->wrapped->v.get_first_header(
+ &edmail->wrapped->mail, field_name,
+ decode_to_utf8, value_r);
+ }
+
+ *value_r = NULL;
+ return 0;
+ }
+
+ /* Get the first occurrence */
+ if (edmail->header_fields_appended == NULL) {
+ /* There are no appended headers, so first is found directly */
+ field = header_idx->first->field;
+ } else {
+ struct _header_field_index *field_idx;
+
+ /* Scan prepended headers */
+ field_idx = edmail->header_fields_head;
+ while (field_idx != NULL) {
+ if (field_idx->header == header_idx)
+ break;
+
+ if (field_idx == edmail->header_fields_appended) {
+ field_idx = NULL;
+ break;
+ }
+ field_idx = field_idx->next;
+ }
+
+ if (field_idx == NULL) {
+ /* Check original message */
+ ret = edmail->wrapped->v.get_first_header(
+ &edmail->wrapped->mail, field_name,
+ decode_to_utf8, value_r);
+ if (ret != 0)
+ return ret;
+
+ /* Use first (apparently appended) header */
+ field = header_idx->first->field;
+ } else {
+ field = field_idx->field;
+ }
+ }
+
+ if (decode_to_utf8)
+ *value_r = field->utf8_value;
+ else
+ *value_r = (const char *)(field->data + field->body_offset);
+ return 1;
+}
+
+static int
+edit_mail_get_headers(struct mail *mail, const char *field_name,
+ bool decode_to_utf8, const char *const **value_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+ const char *const *headers;
+ ARRAY(const char *) header_values;
+
+ if (!edmail->modified || edmail->headers_head == NULL) {
+ /* Unmodified */
+ return edmail->wrapped->v.get_headers(
+ &edmail->wrapped->mail, field_name, decode_to_utf8,
+ value_r);
+ }
+
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL || header_idx->count == 0 ) {
+ if (!edmail->headers_parsed) {
+ /* No new header */
+ return edmail->wrapped->v.get_headers(
+ &edmail->wrapped->mail, field_name,
+ decode_to_utf8, value_r);
+ }
+
+ p_array_init(&header_values, edmail->mail.pool, 1);
+ (void)array_append_space(&header_values);
+ *value_r = array_idx(&header_values, 0);
+ return 0;
+ }
+
+ /* Merge */
+
+ /* Read original headers too if message headers are not parsed */
+ headers = NULL;
+ if (!edmail->headers_parsed &&
+ edmail->wrapped->v.get_headers(&edmail->wrapped->mail, field_name,
+ decode_to_utf8, &headers) < 0)
+ return -1;
+
+ /* Fill result array */
+ p_array_init(&header_values, edmail->mail.pool, 32);
+ field_idx = header_idx->first;
+ while (field_idx != NULL) {
+ /* If current field is the first appended one, we need to add
+ original headers first.
+ */
+ if (field_idx == edmail->header_fields_appended &&
+ headers != NULL) {
+ while (*headers != NULL) {
+ array_append(&header_values, headers, 1);
+ headers++;
+ }
+ }
+
+ /* Add modified header to the list */
+ if (field_idx->field->header == header_idx->header) {
+ struct _header_field *field = field_idx->field;
+
+ const char *value;
+ if (decode_to_utf8)
+ value = field->utf8_value;
+ else {
+ value = (const char *)(field->data +
+ field->body_offset);
+ }
+
+ array_append(&header_values, &value, 1);
+
+ if (field_idx == header_idx->last)
+ break;
+ }
+
+ field_idx = field_idx->next;
+ }
+
+ /* Add original headers if necessary */
+ if (headers != NULL) {
+ while (*headers != NULL) {
+ array_append(&header_values, headers, 1);
+ headers++;
+ }
+ }
+
+ (void)array_append_space(&header_values);
+ *value_r = array_idx(&header_values, 0);
+ return 1;
+}
+
+static int ATTR_NORETURN
+edit_mail_get_header_stream(
+ struct mail *mail ATTR_UNUSED,
+ struct mailbox_header_lookup_ctx *headers ATTR_UNUSED,
+ struct istream **stream_r ATTR_UNUSED)
+{
+ // FIXME: implement!
+ i_panic("edit_mail_get_header_stream() not implemented");
+}
+
+static int
+edit_mail_get_stream(struct mail *mail, bool get_body ATTR_UNUSED,
+ struct message_size *hdr_size,
+ struct message_size *body_size, struct istream **stream_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (edmail->stream == NULL)
+ edmail->stream = edit_mail_istream_create(edmail);
+
+ if (hdr_size != NULL) {
+ *hdr_size = edmail->wrapped_hdr_size;
+ hdr_size->physical_size += edmail->hdr_size.physical_size;
+ hdr_size->virtual_size += edmail->hdr_size.virtual_size;
+ hdr_size->lines += edmail->hdr_size.lines;
+ }
+
+ if (body_size != NULL)
+ *body_size = edmail->wrapped_body_size;
+
+ *stream_r = edmail->stream;
+ i_stream_seek(edmail->stream, 0);
+
+ return 0;
+}
+
+static int
+edit_mail_get_special(struct mail *mail, enum mail_fetch_field field,
+ const char **value_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (edmail->modified) {
+ /* Block certain fields when modified */
+
+ switch (field) {
+ case MAIL_FETCH_GUID:
+ /* This is in essence a new message */
+ *value_r = "";
+ return 0;
+ case MAIL_FETCH_STORAGE_ID:
+ /* Prevent hardlink copying */
+ *value_r = "";
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ return edmail->wrapped->v.get_special(&edmail->wrapped->mail,
+ field, value_r);
+}
+
+static int
+edit_mail_get_backend_mail(struct mail *mail, struct mail **real_mail_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ *real_mail_r = edit_mail_get_mail(edmail);
+ return 0;
+}
+
+static void
+edit_mail_update_flags(struct mail *mail, enum modify_type modify_type,
+ enum mail_flags flags)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_flags(&edmail->wrapped->mail,
+ modify_type, flags);
+}
+
+static void
+edit_mail_update_keywords(struct mail *mail, enum modify_type modify_type,
+ struct mail_keywords *keywords)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_keywords(&edmail->wrapped->mail,
+ modify_type, keywords);
+}
+
+static void edit_mail_update_modseq(struct mail *mail, uint64_t min_modseq)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_modseq(&edmail->wrapped->mail, min_modseq);
+}
+
+static void
+edit_mail_update_pvt_modseq(struct mail *mail, uint64_t min_pvt_modseq)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_pvt_modseq(&edmail->wrapped->mail,
+ min_pvt_modseq);
+}
+
+static void edit_mail_update_pop3_uidl(struct mail *mail, const char *uidl)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (edmail->wrapped->v.update_pop3_uidl != NULL) {
+ edmail->wrapped->v.update_pop3_uidl(
+ &edmail->wrapped->mail, uidl);
+ }
+}
+
+static void edit_mail_expunge(struct mail *mail ATTR_UNUSED)
+{
+ /* NOOP */
+}
+
+static void
+edit_mail_set_cache_corrupted(struct mail *mail, enum mail_fetch_field field,
+ const char *reason)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.set_cache_corrupted(&edmail->wrapped->mail,
+ field, reason);
+}
+
+static struct mail_vfuncs edit_mail_vfuncs = {
+ edit_mail_close,
+ edit_mail_free,
+ edit_mail_set_seq,
+ edit_mail_set_uid,
+ edit_mail_set_uid_cache_updates,
+ NULL,
+ NULL,
+ edit_mail_add_temp_wanted_fields,
+ edit_mail_get_flags,
+ edit_mail_get_keywords,
+ edit_mail_get_keyword_indexes,
+ edit_mail_get_modseq,
+ edit_mail_get_pvt_modseq,
+ edit_mail_get_parts,
+ edit_mail_get_date,
+ edit_mail_get_received_date,
+ edit_mail_get_save_date,
+ edit_mail_get_virtual_size,
+ edit_mail_get_physical_size,
+ edit_mail_get_first_header,
+ edit_mail_get_headers,
+ edit_mail_get_header_stream,
+ edit_mail_get_stream,
+ index_mail_get_binary_stream,
+ edit_mail_get_special,
+ edit_mail_get_backend_mail,
+ edit_mail_update_flags,
+ edit_mail_update_keywords,
+ edit_mail_update_modseq,
+ edit_mail_update_pvt_modseq,
+ edit_mail_update_pop3_uidl,
+ edit_mail_expunge,
+ edit_mail_set_cache_corrupted,
+ NULL,
+};
+
+/*
+ * Edit Mail Stream
+ */
+
+struct edit_mail_istream {
+ struct istream_private istream;
+ pool_t pool;
+
+ struct edit_mail *mail;
+
+ struct _header_field_index *cur_header;
+ uoff_t cur_header_v_offset;
+
+ bool parent_buffer:1;
+ bool header_read:1;
+ bool eof:1;
+};
+
+static void edit_mail_istream_destroy(struct iostream_private *stream)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+
+ i_stream_unref(&edstream->istream.parent);
+ i_stream_free_buffer(&edstream->istream);
+ pool_unref(&edstream->pool);
+}
+
+static ssize_t
+merge_from_parent(struct edit_mail_istream *edstream, uoff_t parent_v_offset,
+ uoff_t parent_end_v_offset, uoff_t copy_v_offset)
+{
+ struct istream_private *stream = &edstream->istream;
+ uoff_t v_offset, append_v_offset;
+ const unsigned char *data;
+ size_t pos, cur_pos, parent_bytes_left;
+ bool parent_buffer = edstream->parent_buffer;
+ ssize_t ret;
+
+ i_assert(parent_v_offset <= parent_end_v_offset);
+ edstream->parent_buffer = FALSE;
+
+ v_offset = stream->istream.v_offset;
+ if (v_offset >= copy_v_offset) {
+ i_assert((v_offset - copy_v_offset) <= parent_end_v_offset);
+ if ((v_offset - copy_v_offset) == parent_end_v_offset) {
+ /* Parent data is all read */
+ return 0;
+ }
+ }
+
+ /* Determine where we are appending more data to the stream */
+ append_v_offset = v_offset + (stream->pos - stream->skip);
+
+ if (v_offset >= copy_v_offset) {
+ /* Parent buffer used */
+ cur_pos = (stream->pos - stream->skip);
+ parent_v_offset += (v_offset - copy_v_offset);
+ } else {
+ cur_pos = 0;
+ i_assert(append_v_offset >= copy_v_offset);
+ parent_v_offset += (append_v_offset - copy_v_offset);
+ }
+
+ /* Seek parent to required position */
+ i_stream_seek(stream->parent, parent_v_offset);
+
+ /* Read from parent */
+ data = i_stream_get_data(stream->parent, &pos);
+ if (pos > cur_pos)
+ ret = 0;
+ else do {
+ /* Use normal read here, since parent data can be returned
+ directly to caller. */
+ ret = i_stream_read(stream->parent);
+
+ stream->istream.stream_errno = stream->parent->stream_errno;
+ stream->istream.eof = stream->parent->eof;
+ edstream->eof = stream->parent->eof;
+ data = i_stream_get_data(stream->parent, &pos);
+ /* Check again, in case the parent stream had been seeked
+ backwards and the previous read() didn't get us far
+ enough. */
+ } while (pos <= cur_pos && ret > 0);
+
+ /* Don't read beyond parent end offset */
+ if (parent_end_v_offset != (uoff_t)-1) {
+ parent_bytes_left = (size_t)(parent_end_v_offset -
+ parent_v_offset);
+ if (pos >= parent_bytes_left) {
+ pos = parent_bytes_left;
+ }
+ }
+
+ if (v_offset < copy_v_offset || ret == -2 ||
+ (parent_buffer && (append_v_offset + 1) >= parent_end_v_offset)) {
+ /* Merging with our local buffer; copying data from parent */
+ if (pos > 0) {
+ size_t avail;
+
+ if (parent_buffer) {
+ stream->pos = stream->skip = 0;
+ stream->buffer = NULL;
+ }
+ if (!i_stream_try_alloc(stream, pos, &avail))
+ return -2;
+ pos = (pos > avail ? avail : pos);
+
+ memcpy(stream->w_buffer + stream->pos, data, pos);
+ stream->pos += pos;
+ stream->buffer = stream->w_buffer;
+
+ if (cur_pos >= pos)
+ ret = 0;
+ else
+ ret = (ssize_t)(pos - cur_pos);
+ } else {
+ ret = (ret == 0 ? 0 : -1);
+ }
+ } else {
+ /* Just passing buffers from parent; no copying */
+ ret = (pos > cur_pos ?
+ (ssize_t)(pos - cur_pos) : (ret == 0 ? 0 : -1));
+ stream->buffer = data;
+ stream->pos = pos;
+ stream->skip = 0;
+ edstream->parent_buffer = TRUE;
+ }
+
+ i_assert(ret != -1 || stream->istream.eof ||
+ stream->istream.stream_errno != 0);
+ return ret;
+}
+
+static ssize_t merge_modified_headers(struct edit_mail_istream *edstream)
+{
+ struct istream_private *stream = &edstream->istream;
+ struct edit_mail *edmail = edstream->mail;
+ uoff_t v_offset = stream->istream.v_offset, append_v_offset;
+ size_t appended, written, avail, size;
+
+ if (edstream->cur_header == NULL) {
+ /* No (more) headers */
+ return 0;
+ }
+
+ /* Caller must already have committed remaining parent data to
+ our stream buffer. */
+ i_assert(!edstream->parent_buffer);
+
+ /* Add modified headers to buffer */
+ written = 0;
+ while (edstream->cur_header != NULL) {
+ size_t wsize;
+
+ /* Determine what part of the header was already buffered */
+ append_v_offset = v_offset + (stream->pos - stream->skip);
+ i_assert(append_v_offset >= edstream->cur_header_v_offset);
+ if (append_v_offset >= edstream->cur_header_v_offset)
+ appended = (size_t)(append_v_offset -
+ edstream->cur_header_v_offset);
+ else
+ appended = 0;
+ i_assert(appended <= edstream->cur_header->field->size);
+
+ /* Determine how much we want to write */
+ size = edstream->cur_header->field->size - appended;
+ if (size > 0) {
+ /* Determine how much we can write */
+ if (!i_stream_try_alloc(stream, size, &avail)) {
+ if (written == 0)
+ return -2;
+ break;
+ }
+ wsize = (size >= avail ? avail : size);
+
+ /* Write (part of) the header to buffer */
+ memcpy(stream->w_buffer + stream->pos,
+ edstream->cur_header->field->data + appended,
+ wsize);
+ stream->pos += wsize;
+ stream->buffer = stream->w_buffer;
+ written += wsize;
+
+ if (wsize < size) {
+ /* Could not write whole header; finish here */
+ break;
+ }
+ }
+
+ /* Skip to next header */
+ edstream->cur_header_v_offset +=
+ edstream->cur_header->field->size;
+ edstream->cur_header = edstream->cur_header->next;
+
+ /* Stop at end of prepended headers if original header is left
+ unparsed */
+ if (!edmail->headers_parsed &&
+ edstream->cur_header == edmail->header_fields_appended)
+ edstream->cur_header = NULL;
+ }
+
+ if (edstream->cur_header == NULL) {
+ /* Clear offset too, just to be tidy */
+ edstream->cur_header_v_offset = 0;
+ }
+
+ i_assert(written > 0);
+ return (ssize_t)written;
+}
+
+static ssize_t edit_mail_istream_read(struct istream_private *stream)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+ struct edit_mail *edmail = edstream->mail;
+ uoff_t v_offset, append_v_offset;
+ uoff_t parent_v_offset, parent_end_v_offset, copy_v_offset;
+ uoff_t prep_hdr_size, hdr_size;
+ ssize_t ret = 0;
+
+ if (edstream->eof) {
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+
+ if (edstream->parent_buffer && stream->skip == stream->pos) {
+ edstream->parent_buffer = FALSE;
+ stream->pos = stream->skip = 0;
+ stream->buffer = NULL;
+ }
+
+ /* Merge prepended headers */
+ if (!edstream->parent_buffer) {
+ ret = merge_modified_headers(edstream);
+ if (ret != 0)
+ return ret;
+ }
+ v_offset = stream->istream.v_offset;
+ append_v_offset = v_offset + (stream->pos - stream->skip);
+
+ if (!edmail->headers_parsed && edmail->header_fields_appended != NULL &&
+ !edstream->header_read) {
+ /* Output headers from original stream */
+
+ /* Size of the prepended header */
+ i_assert(edmail->hdr_size.physical_size >=
+ edmail->appended_hdr_size.physical_size);
+ prep_hdr_size = (edmail->hdr_size.physical_size -
+ edmail->appended_hdr_size.physical_size);
+
+ /* Calculate offset of header end or appended header. Any final
+ CR is dealt with later.
+ */
+ hdr_size = (prep_hdr_size +
+ edmail->wrapped_hdr_size.physical_size);
+ i_assert(hdr_size > 0);
+ if (append_v_offset <= (hdr_size - 1) &&
+ edmail->wrapped_hdr_size.physical_size > 0) {
+ parent_v_offset = stream->parent_start_offset;
+ parent_end_v_offset =
+ (stream->parent_start_offset +
+ edmail->wrapped_hdr_size.physical_size - 1);
+ copy_v_offset = prep_hdr_size;
+
+ ret = merge_from_parent(edstream, parent_v_offset,
+ parent_end_v_offset,
+ copy_v_offset);
+ if (ret < 0)
+ return ret;
+ append_v_offset = (v_offset +
+ (stream->pos - stream->skip));
+ i_assert(append_v_offset <= hdr_size - 1);
+
+ if (append_v_offset == hdr_size - 1) {
+ /* Strip final CR too when it is present */
+ if (stream->buffer != NULL &&
+ stream->buffer[stream->pos-1] == '\r') {
+ stream->pos--;
+ append_v_offset--;
+ ret--;
+ }
+
+ i_assert(ret >= 0);
+ edstream->cur_header =
+ edmail->header_fields_appended;
+ edstream->cur_header_v_offset = append_v_offset;
+ if (!edstream->parent_buffer)
+ edstream->header_read = TRUE;
+ }
+
+ if (ret != 0)
+ return ret;
+ } else {
+ edstream->header_read = TRUE;
+ }
+
+ /* Merge appended headers */
+ ret = merge_modified_headers(edstream);
+ if (ret != 0)
+ return ret;
+ }
+
+ /* Header does not come from original mail at all */
+ if (edmail->headers_parsed) {
+ parent_v_offset = (stream->parent_start_offset +
+ edmail->wrapped_hdr_size.physical_size -
+ (edmail->eoh_crlf ? 2 : 1));
+ copy_v_offset = edmail->hdr_size.physical_size;
+ /* Header comes partially from original mail and headers are added
+ between header and body. */
+ } else if (edmail->header_fields_appended != NULL) {
+ parent_v_offset = (stream->parent_start_offset +
+ edmail->wrapped_hdr_size.physical_size -
+ (edmail->eoh_crlf ? 2 : 1));
+ copy_v_offset = (edmail->hdr_size.physical_size +
+ edmail->wrapped_hdr_size.physical_size -
+ (edmail->eoh_crlf ? 2 : 1));
+ /* Header comes partially from original mail, but headers are only
+ prepended. */
+ } else {
+ parent_v_offset = stream->parent_start_offset;
+ copy_v_offset = edmail->hdr_size.physical_size;
+ }
+
+ return merge_from_parent(edstream, parent_v_offset, (uoff_t)-1,
+ copy_v_offset);
+}
+
+static void
+stream_reset_to(struct edit_mail_istream *edstream, uoff_t v_offset)
+{
+ edstream->istream.istream.v_offset = v_offset;
+ edstream->istream.skip = 0;
+ edstream->istream.pos = 0;
+ edstream->istream.buffer = NULL;
+ edstream->parent_buffer = FALSE;
+ edstream->eof = FALSE;
+ i_stream_seek(edstream->istream.parent, 0);
+}
+
+static void
+edit_mail_istream_seek(struct istream_private *stream, uoff_t v_offset,
+ bool mark ATTR_UNUSED)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+ struct _header_field_index *cur_header;
+ struct edit_mail *edmail = edstream->mail;
+ uoff_t offset;
+
+ edstream->header_read = FALSE;
+ edstream->cur_header = NULL;
+ edstream->cur_header_v_offset = 0;
+
+ /* The beginning */
+ if (v_offset == 0) {
+ stream_reset_to(edstream, 0);
+
+ if (edmail->header_fields_head !=
+ edmail->header_fields_appended)
+ edstream->cur_header = edmail->header_fields_head;
+ return;
+ }
+
+ /* Inside (prepended) headers */
+ if (edmail->headers_parsed) {
+ offset = edmail->hdr_size.physical_size;
+ } else {
+ offset = (edmail->hdr_size.physical_size -
+ edmail->appended_hdr_size.physical_size);
+ }
+
+ if (v_offset < offset) {
+ stream_reset_to(edstream, v_offset);
+
+ /* Find the header */
+ cur_header = edmail->header_fields_head;
+ i_assert(cur_header != NULL &&
+ cur_header != edmail->header_fields_appended);
+ edstream->cur_header_v_offset = 0;
+ offset = cur_header->field->size;
+ while (v_offset > offset) {
+ cur_header = cur_header->next;
+ i_assert(cur_header != NULL &&
+ cur_header != edmail->header_fields_appended);
+
+ edstream->cur_header_v_offset = offset;
+ offset += cur_header->field->size;
+ }
+
+ edstream->cur_header = cur_header;
+ return;
+ }
+
+ if (!edmail->headers_parsed) {
+ /* Inside original header */
+ offset = (edmail->hdr_size.physical_size -
+ edmail->appended_hdr_size.physical_size +
+ edmail->wrapped_hdr_size.physical_size);
+ if (v_offset < offset) {
+ stream_reset_to(edstream, v_offset);
+ return;
+ }
+
+ edstream->header_read = TRUE;
+
+ /* Inside appended header */
+ offset = (edmail->hdr_size.physical_size +
+ edmail->wrapped_hdr_size.physical_size);
+ if (v_offset < offset) {
+ stream_reset_to(edstream, v_offset);
+
+ offset -= edmail->appended_hdr_size.physical_size;
+
+ cur_header = edmail->header_fields_appended;
+ i_assert(cur_header != NULL);
+ edstream->cur_header_v_offset = offset;
+ offset += cur_header->field->size;
+
+ while (v_offset > offset) {
+ cur_header = cur_header->next;
+ i_assert(cur_header != NULL);
+
+ edstream->cur_header_v_offset = offset;
+ offset += cur_header->field->size;
+ }
+
+ edstream->cur_header = cur_header;
+ return;
+ }
+ }
+
+ stream_reset_to(edstream, v_offset);
+ edstream->cur_header = NULL;
+}
+
+static void ATTR_NORETURN
+edit_mail_istream_sync(struct istream_private *stream ATTR_UNUSED)
+{
+ i_panic("edit-mail istream sync() not implemented");
+}
+
+static int
+edit_mail_istream_stat(struct istream_private *stream, bool exact)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+ struct edit_mail *edmail = edstream->mail;
+ const struct stat *st;
+
+ /* Stat the original stream */
+ if (i_stream_stat(stream->parent, exact, &st) < 0)
+ return -1;
+
+ stream->statbuf = *st;
+ if (st->st_size == -1 || !exact)
+ return 0;
+
+ if (!edmail->headers_parsed) {
+ if (!edmail->modified)
+ return 0;
+ } else {
+ stream->statbuf.st_size =
+ (edmail->wrapped_body_size.physical_size +
+ (edmail->eoh_crlf ? 2 : 1));
+ }
+
+ stream->statbuf.st_size += (edmail->hdr_size.physical_size +
+ edmail->body_size.physical_size);
+ return 0;
+}
+
+struct istream *edit_mail_istream_create(struct edit_mail *edmail)
+{
+ struct edit_mail_istream *edstream;
+ struct istream *wrapped = edmail->wrapped_stream;
+
+ edstream = i_new(struct edit_mail_istream, 1);
+ edstream->pool = pool_alloconly_create(MEMPOOL_GROWING
+ "edit mail stream", 4096);
+ edstream->mail = edmail;
+
+ edstream->istream.max_buffer_size =
+ wrapped->real_stream->max_buffer_size;
+
+ edstream->istream.iostream.destroy = edit_mail_istream_destroy;
+ edstream->istream.read = edit_mail_istream_read;
+ edstream->istream.seek = edit_mail_istream_seek;
+ edstream->istream.sync = edit_mail_istream_sync;
+ edstream->istream.stat = edit_mail_istream_stat;
+
+ edstream->istream.istream.readable_fd = FALSE;
+ edstream->istream.istream.blocking = wrapped->blocking;
+ edstream->istream.istream.seekable = wrapped->seekable;
+
+ if (edmail->header_fields_head != edmail->header_fields_appended)
+ edstream->cur_header = edmail->header_fields_head;
+
+ i_stream_seek(wrapped, 0);
+
+ return i_stream_create(&edstream->istream, wrapped, -1, 0);
+}
diff --git a/pigeonhole/src/lib-sieve/util/edit-mail.h b/pigeonhole/src/lib-sieve/util/edit-mail.h
new file mode 100644
index 0000000..14d2eaa
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/edit-mail.h
@@ -0,0 +1,47 @@
+#ifndef EDIT_MAIL_H
+#define EDIT_MAIL_H
+
+struct edit_mail;
+
+struct edit_mail *edit_mail_wrap(struct mail *mail);
+void edit_mail_unwrap(struct edit_mail **edmail);
+struct edit_mail *edit_mail_snapshot(struct edit_mail *edmail);
+
+void edit_mail_reset(struct edit_mail *edmail);
+
+struct mail *edit_mail_get_mail(struct edit_mail *edmail);
+
+/*
+ * Header modification
+ */
+
+/* Simple API */
+
+void edit_mail_header_add(struct edit_mail *edmail, const char *field_name,
+ const char *value, bool last);
+int edit_mail_header_delete(struct edit_mail *edmail,
+ const char *field_name, int index);
+int edit_mail_header_replace(struct edit_mail *edmail,
+ const char *field_name, int index,
+ const char *newname, const char *newvalue);
+
+/* Iterator */
+
+struct edit_mail_header_iter;
+
+int edit_mail_headers_iterate_init(struct edit_mail *edmail,
+ const char *field_name, bool reverse,
+ struct edit_mail_header_iter **edhiter_r);
+void edit_mail_headers_iterate_deinit(struct edit_mail_header_iter **edhiter);
+
+void edit_mail_headers_iterate_get(struct edit_mail_header_iter *edhiter,
+ const char **value_r);
+
+bool edit_mail_headers_iterate_next(struct edit_mail_header_iter *edhiter);
+
+bool edit_mail_headers_iterate_remove(struct edit_mail_header_iter *edhiter);
+bool edit_mail_headers_iterate_replace(struct edit_mail_header_iter *edhiter,
+ const char *newname,
+ const char *newvalue);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/util/mail-raw.c b/pigeonhole/src/lib-sieve/util/mail-raw.c
new file mode 100644
index 0000000..b357fe1
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/mail-raw.c
@@ -0,0 +1,247 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "istream.h"
+#include "istream-seekable.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "strescape.h"
+#include "safe-mkstemp.h"
+#include "path-util.h"
+#include "message-address.h"
+#include "mbox-from.h"
+#include "raw-storage.h"
+#include "mail-namespace.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "settings-parser.h"
+#include "mail-raw.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pwd.h>
+
+/*
+ * Configuration
+ */
+
+#define DEFAULT_ENVELOPE_SENDER "MAILER-DAEMON"
+
+/* After buffer grows larger than this, create a temporary file to /tmp
+ where to read the mail. */
+#define MAIL_MAX_MEMORY_BUFFER (1024*128)
+
+static const char *wanted_headers[] = {
+ "From", "Message-ID", "Subject", "Return-Path",
+ NULL
+};
+
+/*
+ * Global data
+ */
+
+struct mail_raw_user {
+ struct mail_namespace *ns;
+ struct mail_user *mail_user;
+};
+
+/*
+ * Raw mail implementation
+ */
+
+static int seekable_fd_callback
+(const char **path_r, void *context)
+{
+ struct mail_user *ruser = (struct mail_user *)context;
+ string_t *path;
+ int fd;
+
+ path = t_str_new(128);
+ mail_user_set_get_temp_prefix(path, ruser->set);
+ fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1);
+ if (fd == -1) {
+ i_error("safe_mkstemp(%s) failed: %m", str_c(path));
+ return -1;
+ }
+
+ /* we just want the fd, unlink it */
+ if (i_unlink(str_c(path)) < 0) {
+ /* shouldn't happen.. */
+ i_close_fd(&fd);
+ return -1;
+ }
+
+ *path_r = str_c(path);
+ return fd;
+}
+
+static struct istream *mail_raw_create_stream
+(struct mail_user *ruser, int fd, time_t *mtime_r, const char **sender)
+{
+ struct istream *input, *input2, *input_list[2];
+ const unsigned char *data;
+ size_t i, size;
+ int ret, tz;
+ char *env_sender = NULL;
+
+ *mtime_r = (time_t)-1;
+ fd_set_nonblock(fd, FALSE);
+
+ input = i_stream_create_fd(fd, 4096);
+ input->blocking = TRUE;
+ /* If input begins with a From-line, drop it */
+ ret = i_stream_read_bytes(input, &data, &size, 5);
+ if (ret > 0 && memcmp(data, "From ", 5) == 0) {
+ /* skip until the first LF */
+ i_stream_skip(input, 5);
+ while ( i_stream_read_more(input, &data, &size) > 0 ) {
+ for (i = 0; i < size; i++) {
+ if (data[i] == '\n')
+ break;
+ }
+ if (i != size) {
+ (void)mbox_from_parse(data, i, mtime_r, &tz, &env_sender);
+ i_stream_skip(input, i + 1);
+ break;
+ }
+ i_stream_skip(input, size);
+ }
+ }
+
+ if (env_sender != NULL && sender != NULL) {
+ *sender = t_strdup(env_sender);
+ }
+ i_free(env_sender);
+
+ if (input->v_offset == 0) {
+ input2 = input;
+ i_stream_ref(input2);
+ } else {
+ input2 = i_stream_create_limit(input, (uoff_t)-1);
+ }
+ i_stream_unref(&input);
+
+ input_list[0] = input2; input_list[1] = NULL;
+ input = i_stream_create_seekable(input_list, MAIL_MAX_MEMORY_BUFFER,
+ seekable_fd_callback, (void*)ruser);
+ i_stream_unref(&input2);
+ return input;
+}
+
+/*
+ * Init/Deinit
+ */
+
+struct mail_user *mail_raw_user_create
+(struct master_service *service, struct mail_user *mail_user)
+{
+ void **sets = master_service_settings_get_others(service);
+
+ return raw_storage_create_from_set(mail_user->set_info, sets[0]);
+}
+
+/*
+ * Open raw mail data
+ */
+
+static struct mail_raw *mail_raw_create
+(struct mail_user *ruser, struct istream *input,
+ const char *mailfile, const char *sender, time_t mtime)
+{
+ struct mail_raw *mailr;
+ struct mailbox_header_lookup_ctx *headers_ctx;
+ const char *envelope_sender, *error;
+ int ret;
+
+ if ( mailfile != NULL && *mailfile != '/' )
+ if (t_abspath(mailfile, &mailfile, &error) < 0)
+ i_fatal("t_abspath(%s) failed: %s",
+ mailfile, error);
+
+ mailr = i_new(struct mail_raw, 1);
+
+ envelope_sender = sender != NULL ? sender : DEFAULT_ENVELOPE_SENDER;
+ if ( mailfile == NULL ) {
+ ret = raw_mailbox_alloc_stream(ruser, input, mtime,
+ envelope_sender, &mailr->box);
+ } else {
+ ret = raw_mailbox_alloc_path(ruser, mailfile, (time_t)-1,
+ envelope_sender, &mailr->box);
+ }
+
+ if ( ret < 0 ) {
+ if ( mailfile == NULL ) {
+ i_fatal("Can't open delivery mail as raw: %s",
+ mailbox_get_last_internal_error(mailr->box, NULL));
+ } else {
+ i_fatal("Can't open delivery mail as raw (file=%s): %s",
+ mailfile, mailbox_get_last_internal_error(mailr->box, NULL));
+ }
+ }
+
+ mailr->trans = mailbox_transaction_begin(mailr->box, 0, __func__);
+ headers_ctx = mailbox_header_lookup_init(mailr->box, wanted_headers);
+ mailr->mail = mail_alloc(mailr->trans, 0, headers_ctx);
+ mailbox_header_lookup_unref(&headers_ctx);
+ mail_set_seq(mailr->mail, 1);
+
+ return mailr;
+}
+
+struct mail_raw *mail_raw_open_stream
+(struct mail_user *ruser, struct istream *input)
+{
+ struct mail_raw *mailr;
+
+ i_assert(input->seekable);
+ i_stream_set_name(input, "data");
+ mailr = mail_raw_create(ruser, input, NULL, NULL, (time_t)-1);
+
+ return mailr;
+}
+
+struct mail_raw *mail_raw_open_data
+(struct mail_user *ruser, string_t *mail_data)
+{
+ struct mail_raw *mailr;
+ struct istream *input;
+
+ input = i_stream_create_from_data(str_data(mail_data), str_len(mail_data));
+
+ mailr = mail_raw_open_stream(ruser, input);
+
+ i_stream_unref(&input);
+ return mailr;
+}
+
+struct mail_raw *mail_raw_open_file
+(struct mail_user *ruser, const char *path)
+{
+ struct mail_raw *mailr;
+ struct istream *input = NULL;
+ time_t mtime = (time_t)-1;
+ const char *sender = NULL;
+
+ if ( path == NULL || strcmp(path, "-") == 0 ) {
+ path = NULL;
+ input = mail_raw_create_stream(ruser, 0, &mtime, &sender);
+ }
+
+ mailr = mail_raw_create(ruser, input, path, sender, mtime);
+ i_stream_unref(&input);
+
+ return mailr;
+}
+
+void mail_raw_close(struct mail_raw **mailr)
+{
+ mail_free(&(*mailr)->mail);
+ mailbox_transaction_rollback(&(*mailr)->trans);
+ mailbox_free(&(*mailr)->box);
+
+ i_free(*mailr);
+ *mailr = NULL;
+}
+
diff --git a/pigeonhole/src/lib-sieve/util/mail-raw.h b/pigeonhole/src/lib-sieve/util/mail-raw.h
new file mode 100644
index 0000000..a942d06
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/mail-raw.h
@@ -0,0 +1,27 @@
+#ifndef MAIL_RAW_H
+#define MAIL_RAW_H
+
+#include "lib.h"
+#include "master-service.h"
+
+struct mail_raw {
+ pool_t pool;
+ struct mail *mail;
+
+ struct mailbox *box;
+ struct mailbox_transaction_context *trans;
+};
+
+struct mail_user *mail_raw_user_create
+ (struct master_service *service, struct mail_user *mail_user);
+
+struct mail_raw *mail_raw_open_stream
+ (struct mail_user *ruser, struct istream *input);
+struct mail_raw *mail_raw_open_file
+ (struct mail_user *ruser, const char *path);
+struct mail_raw *mail_raw_open_data
+ (struct mail_user *ruser, string_t *mail_data);
+void mail_raw_close(struct mail_raw **mailr);
+
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/util/rfc2822.c b/pigeonhole/src/lib-sieve/util/rfc2822.c
new file mode 100644
index 0000000..ff3a9ad
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/rfc2822.c
@@ -0,0 +1,277 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* NOTE: much of the functionality implemented here should eventually appear
+ * somewhere in Dovecot itself.
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "unichar.h"
+
+#include "rfc2822.h"
+
+#include "message-header-encode.h"
+
+#include <stdio.h>
+#include <ctype.h>
+
+bool rfc2822_header_field_name_verify
+(const char *field_name, unsigned int len)
+{
+ const char *p = field_name;
+ const char *pend = p + len;
+
+ /* field-name = 1*ftext
+ * ftext = %d33-57 / ; Any character except
+ * %d59-126 ; controls, SP, and
+ * ; ":".
+ */
+
+ while ( p < pend ) {
+ if ( *p < 33 || *p == ':' )
+ return FALSE;
+
+ p++;
+ }
+
+ return TRUE;
+}
+
+bool rfc2822_header_field_body_verify
+(const char *field_body, unsigned int len, bool allow_crlf, bool allow_utf8)
+{
+ const unsigned char *p = (const unsigned char *)field_body;
+ const unsigned char *pend = p + len;
+ bool is8bit = FALSE;
+
+ /* RFC5322:
+ *
+ * unstructured = (*([FWS] VCHAR) *WSP)
+ * VCHAR = %x21-7E
+ * FWS = ([*WSP CRLF] 1*WSP) / ; Folding white space
+ * WSP = SP / HTAB ; White space
+ */
+
+ while ( p < pend ) {
+ if ( *p < 0x20 ) {
+ if ( (*p == '\r' || *p == '\n') ) {
+ if ( !allow_crlf )
+ return FALSE;
+ } else if ( *p != '\t' ) {
+ return FALSE;
+ }
+ }
+
+ if ( !is8bit && *p > 127 ) {
+ if ( !allow_utf8 )
+ return FALSE;
+
+ is8bit = TRUE;
+ }
+
+ p++;
+ }
+
+ if ( is8bit && !uni_utf8_str_is_valid(field_body) ) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ *
+ */
+
+const char *rfc2822_header_field_name_sanitize(const char *name)
+{
+ char *result = t_strdup_noconst(name);
+ char *p;
+
+ /* Make the whole name lower case ... */
+ result = str_lcase(result);
+
+ /* ... except for the first letter and those that follow '-' */
+ p = result;
+ *p = i_toupper(*p);
+ while ( *p != '\0' ) {
+ if ( *p == '-' ) {
+ p++;
+
+ if ( *p != '\0' )
+ *p = i_toupper(*p);
+
+ continue;
+ }
+
+ p++;
+ }
+
+ return result;
+}
+
+/*
+ * Message construction
+ */
+
+/* FIXME: This should be collected into a Dovecot API for composing internet
+ * mail messages.
+ */
+
+unsigned int rfc2822_header_append
+(string_t *header, const char *name, const char *body, bool crlf,
+ uoff_t *body_offset_r)
+{
+ static const unsigned int max_line = 80;
+
+ const char *bp = body; /* Pointer */
+ const char *sp = body; /* Start pointer */
+ const char *wp = NULL; /* Whitespace pointer */
+ const char *nlp = NULL; /* New-line pointer */
+ unsigned int line_len = strlen(name);
+ unsigned int lines = 0;
+
+ /* Write header field name first */
+ str_append(header, name);
+ str_append(header, ": ");
+
+ if ( body_offset_r != NULL )
+ *body_offset_r = str_len(header);
+
+ line_len += 2;
+
+ /* Add field body; fold it if necessary and account for existing folding */
+ while ( *bp != '\0' ) {
+ bool ws_first = TRUE;
+
+ while ( *bp != '\0' && nlp == NULL &&
+ (wp == NULL || line_len < max_line) ) {
+ if ( *bp == ' ' || *bp == '\t' ) {
+ if (ws_first)
+ wp = bp;
+ ws_first = FALSE;
+ } else if ( *bp == '\r' || *bp == '\n' ) {
+ if (ws_first)
+ nlp = bp;
+ else
+ nlp = wp;
+ break;
+ } else {
+ ws_first = TRUE;
+ }
+
+ bp++; line_len++;
+ }
+
+ if ( *bp == '\0' ) break;
+
+ /* Existing newline ? */
+ if ( nlp != NULL ) {
+ /* Replace any consecutive newline and whitespace for
+ consistency */
+ while ( *bp == ' ' || *bp == '\t' || *bp == '\r' || *bp == '\n' )
+ bp++;
+
+ str_append_data(header, sp, nlp-sp);
+
+ if ( crlf )
+ str_append(header, "\r\n");
+ else
+ str_append(header, "\n");
+
+ while ( *bp == ' ' || *bp == '\t' )
+ bp++;
+ if ( *bp != '\0' ) {
+ /* Continued line; replace leading whitespace with single TAB */
+ str_append_c(header, '\t');
+ }
+
+ sp = bp;
+ } else {
+ /* Insert newline at last whitespace within the max_line limit */
+ i_assert(wp >= sp);
+ str_append_data(header, sp, wp-sp);
+
+ /* Force continued line; drop any existing whitespace */
+ while ( *wp == ' ' || *wp == '\t' )
+ wp++;
+
+ if ( crlf )
+ str_append(header, "\r\n");
+ else
+ str_append(header, "\n");
+
+ /* Insert single TAB instead of the original whitespace */
+ str_append_c(header, '\t');
+
+ sp = wp;
+ if (sp > bp)
+ bp = sp;
+ }
+
+ lines++;
+
+ line_len = bp - sp;
+ wp = NULL;
+ nlp = NULL;
+ }
+
+ if ( bp != sp || lines == 0 ) {
+ str_append_data(header, sp, bp-sp);
+ if ( crlf )
+ str_append(header, "\r\n");
+ else
+ str_append(header, "\n");
+ lines++;
+ }
+
+ return lines;
+}
+
+void rfc2822_header_printf
+(string_t *header, const char *name, const char *fmt, ...)
+{
+ const char *body;
+ va_list args;
+
+ va_start(args, fmt);
+ body = t_strdup_vprintf(fmt, args);
+ va_end(args);
+
+ rfc2822_header_write(header, name, body);
+}
+
+void rfc2822_header_utf8_printf
+(string_t *header, const char *name, const char *fmt, ...)
+{
+ string_t *body = t_str_new(256);
+ va_list args;
+
+ va_start(args, fmt);
+ message_header_encode(t_strdup_vprintf(fmt, args), body);
+ va_end(args);
+
+ rfc2822_header_write(header, name, str_c(body));
+}
+
+
+void rfc2822_header_write_address(string_t *header,
+ const char *name, const char *address)
+{
+ bool has_8bit = FALSE;
+ const char *p;
+
+ for (p = address; *p != '\0'; p++) {
+ if ((*p & 0x80) != 0)
+ has_8bit = TRUE;
+ }
+
+ if (!has_8bit) {
+ rfc2822_header_write(header, name, address);
+ } else {
+ string_t *body = t_str_new(256);
+ message_header_encode(address, body);
+ rfc2822_header_write(header, name, str_c(body));
+ }
+}
diff --git a/pigeonhole/src/lib-sieve/util/rfc2822.h b/pigeonhole/src/lib-sieve/util/rfc2822.h
new file mode 100644
index 0000000..02266a9
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/rfc2822.h
@@ -0,0 +1,46 @@
+#ifndef RFC2822_H
+#define RFC2822_H
+
+#include "lib.h"
+
+#include <stdio.h>
+
+/*
+ * Verification
+ */
+
+bool rfc2822_header_field_name_verify
+ (const char *field_name, unsigned int len);
+bool rfc2822_header_field_body_verify
+ (const char *field_body, unsigned int len, bool allow_crlf, bool allow_utf8);
+
+/*
+ *
+ */
+
+const char *rfc2822_header_field_name_sanitize(const char *name);
+
+/*
+ * Message composition
+ */
+
+unsigned int rfc2822_header_append
+ (string_t *header, const char *name, const char *body, bool crlf,
+ uoff_t *body_offset_r);
+
+static inline void rfc2822_header_write
+(string_t *header, const char *name, const char *body)
+{
+ (void)rfc2822_header_append(header, name, body, TRUE, NULL);
+}
+
+void rfc2822_header_printf
+ (string_t *header, const char *name, const char *fmt, ...) ATTR_FORMAT(3, 4);
+void rfc2822_header_utf8_printf
+ (string_t *header, const char *name, const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+void rfc2822_header_write_address(string_t *header,
+ const char *name, const char *address);
+
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/util/test-edit-mail.c b/pigeonhole/src/lib-sieve/util/test-edit-mail.c
new file mode 100644
index 0000000..0e263a2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/test-edit-mail.c
@@ -0,0 +1,842 @@
+/* Copyright (c) 2018 Pigeonhole authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "path-util.h"
+#include "buffer.h"
+#include "str.h"
+#include "istream.h"
+#include "istream-concat.h"
+#include "istream-crlf.h"
+#include "unlink-directory.h"
+#include "master-service.h"
+#include "istream-header-filter.h"
+#include "mail-storage.h"
+#include "mail-storage-service.h"
+#include "mail-user.h"
+
+#include "mail-raw.h"
+#include "edit-mail.h"
+
+#include <time.h>
+
+static pool_t test_pool;
+
+static struct mail_storage_service_ctx *mail_storage_service = NULL;
+static struct mail_user *test_mail_user = NULL;
+static struct mail_storage_service_user *test_service_user = NULL;
+static const char *mail_home;
+static char *test_dir;
+
+static struct mail_user *test_raw_mail_user = NULL;
+
+static void str_append_no_cr(string_t *str, const char *cstr)
+{
+ const char *p, *poff;
+
+ poff = p = cstr;
+ while (*p != '\0') {
+ if (*p == '\r') {
+ str_append_data(str, poff, (p - poff));
+ poff = p+1;
+ }
+ p++;
+ }
+ str_append_data(str, poff, (p - poff));
+}
+
+static int test_init_mail_user(void)
+{
+ const char *error;
+
+ mail_home = p_strdup_printf(test_pool, "%s/test_user.%ld.%ld",
+ test_dir, (long)time(NULL), (long)getpid());
+
+ struct mail_storage_service_input input = {
+ .userdb_fields = (const char*const[]){
+ t_strdup_printf("mail=maildir:~/"),
+ t_strdup_printf("home=%s", mail_home),
+ NULL
+ },
+ .username = "test@example.com",
+ .no_userdb_lookup = TRUE,
+ .debug = TRUE,
+ };
+
+ mail_storage_service = mail_storage_service_init(
+ master_service, NULL,
+ (MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS |
+ MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
+ MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS));
+
+ if (mail_storage_service_lookup(mail_storage_service, &input,
+ &test_service_user, &error) < 0)
+ {
+ i_error("Cannot lookup test user: %s", error);
+ return -1;
+ }
+
+ if (mail_storage_service_next(mail_storage_service, test_service_user,
+ &test_mail_user, &error) < 0)
+ {
+ i_error("Cannot lookup test user: %s", error);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void test_deinit_mail_user()
+{
+ const char *error;
+ mail_user_unref(&test_mail_user);
+ mail_storage_service_user_unref(&test_service_user);
+ mail_storage_service_deinit(&mail_storage_service);
+ if (unlink_directory(mail_home, UNLINK_DIRECTORY_FLAG_RMDIR,
+ &error) < 0)
+ i_error("unlink_directory(%s) failed: %s", mail_home, error);
+}
+
+static void test_init(void)
+{
+ test_pool = pool_alloconly_create(MEMPOOL_GROWING"test pool", 128);
+
+ test_init_mail_user();
+ test_raw_mail_user =
+ mail_raw_user_create(master_service, test_mail_user);
+}
+
+static void test_deinit(void)
+{
+ mail_user_unref(&test_raw_mail_user);
+ test_deinit_mail_user();
+ pool_unref(&test_pool);
+}
+
+static void test_stream_data(struct istream *input, buffer_t *buffer)
+{
+ const unsigned char *data;
+ size_t size;
+
+ while (i_stream_read_more(input, &data, &size) > 0) {
+ buffer_append(buffer, data, size);
+ i_stream_skip(input, size);
+ }
+
+ test_assert(!i_stream_have_bytes_left(input));
+ test_assert(input->stream_errno == 0);
+}
+
+static void test_stream_data_slow(struct istream *input, buffer_t *buffer)
+{
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ ret = i_stream_read(input);
+ while (ret > 0 || i_stream_have_bytes_left(input) || ret == -2) {
+ data = i_stream_get_data(input, &size);
+ buffer_append(buffer, data, 1);
+ i_stream_skip(input, 1);
+
+ ret = i_stream_read(input);
+ }
+
+ test_assert(!i_stream_have_bytes_left(input));
+ test_assert(input->stream_errno == 0);
+}
+
+static void test_edit_mail_concatenated(void)
+{
+ static const char *hide_headers[] =
+ { "Return-Path", "X-Sieve", "X-Sieve-Redirected-From" };
+ static const char *msg_part1 =
+ "Received: from example.com ([127.0.0.1] helo=example.com)\r\n"
+ " by example.org with LMTP (Dovecot)\r\n"
+ " (envelope-from <frop-bounces@example.com>)\r\n"
+ " id 1er3e8-0015df-QO\r\n"
+ " for timo@example.org;\r\n"
+ " Sat, 03 Mar 2018 10:40:05 +0100\r\n";
+ static const char *msg_part2 =
+ "Return-Path: <stephan@example.com>\r\n";
+ static const char *msg_part3 =
+ "Delivered-To: <timo@example.org>\r\n";
+ static const char *msg_part4 =
+ "From: <stephan@example.com>\r\n"
+ "To: <timo@example.org>\r\n"
+ "Subject: Sieve editheader breaks with LMTP\r\n"
+ "\r\n"
+ "Hi,\r\n"
+ "\r\n"
+ "Sieve editheader seems to be broken when used from LMTP\r\n"
+ "\r\n"
+ "Regards,\r\n"
+ "\r\n"
+ "Stephan.\r\n";
+ static const char *msg_added =
+ "X-Filter-Junk-Type: NONE\r\n"
+ "X-Filter-Junk-Flag: NO\r\n";
+ struct istream *inputs[5], *input_msg, *input_filt, *input_mail, *input;
+ buffer_t *buffer;
+ struct mail_raw *rawmail;
+ struct edit_mail *edmail;
+ struct mail *mail;
+ string_t *expected;
+ const char *value;
+
+ test_begin("edit-mail - concatenated");
+ test_init();
+
+ /* Compose the message */
+
+ inputs[0] = i_stream_create_from_data(msg_part1, strlen(msg_part1));
+ inputs[1] = i_stream_create_from_data(msg_part2, strlen(msg_part2));
+ inputs[2] = i_stream_create_from_data(msg_part3, strlen(msg_part3));
+ inputs[3] = i_stream_create_from_data(msg_part4, strlen(msg_part4));
+ inputs[4] = NULL;
+
+ input_msg = i_stream_create_concat(inputs);
+
+ i_stream_unref(&inputs[0]);
+ i_stream_unref(&inputs[1]);
+ i_stream_unref(&inputs[2]);
+ i_stream_unref(&inputs[3]);
+
+ rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
+
+ /* Add headers */
+
+ edmail = edit_mail_wrap(rawmail->mail);
+
+ edit_mail_header_add(edmail, "X-Filter-Junk-Flag", "NO", FALSE);
+ edit_mail_header_add(edmail, "X-Filter-Junk-Type", "NONE", FALSE);
+
+ mail = edit_mail_get_mail(edmail);
+
+ /* Evaluate modified header */
+
+ test_assert(mail_get_first_header_utf8(mail, "Subject", &value) > 0);
+ test_assert(strcmp(value, "Sieve editheader breaks with LMTP") == 0);
+
+ test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Flag",
+ &value) > 0);
+ test_assert(strcmp(value, "NO") == 0);
+ test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Type",
+ &value) > 0);
+ test_assert(strcmp(value, "NONE") == 0);
+
+ test_assert(mail_get_first_header_utf8(mail, "Delivered-To",
+ &value) > 0);
+
+ /* Prepare tests */
+
+ if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
+ i_fatal("Failed to open mail stream: %s",
+ mailbox_get_last_internal_error(mail->box, NULL));
+ }
+
+ buffer = buffer_create_dynamic(default_pool, 1024);
+ expected = t_str_new(1024);
+
+ /* Added */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+ input = input_mail;
+
+ test_stream_data(input_mail, buffer);
+
+ str_truncate(expected, 0);
+ str_append(expected, msg_added);
+ str_append(expected, msg_part1);
+ str_append(expected, msg_part2);
+ str_append(expected, msg_part3);
+ str_append(expected, msg_part4);
+
+ test_out("added", strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ /* Added, slow */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ test_stream_data_slow(input_mail, buffer);
+
+ str_truncate(expected, 0);
+ str_append(expected, msg_added);
+ str_append(expected, msg_part1);
+ str_append(expected, msg_part2);
+ str_append(expected, msg_part3);
+ str_append(expected, msg_part4);
+
+ test_out("added, slow", strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ /* Added, filtered */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ input_filt = i_stream_create_header_filter(
+ input_mail, (HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR),
+ hide_headers, N_ELEMENTS(hide_headers),
+ *null_header_filter_callback, (void *)NULL);
+ input = i_stream_create_lf(input_filt);
+ i_stream_unref(&input_filt);
+
+ test_stream_data(input, buffer);
+ test_assert(!i_stream_have_bytes_left(input_mail));
+ test_assert(input_mail->stream_errno == 0);
+
+ str_truncate(expected, 0);
+ str_append_no_cr(expected, msg_added);
+ str_append_no_cr(expected, msg_part1);
+ str_append_no_cr(expected, msg_part3);
+ str_append_no_cr(expected, msg_part4);
+
+ test_out("added, filtered",
+ strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ i_stream_unref(&input);
+
+ /* Added, filtered, slow */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ input_filt = i_stream_create_header_filter(
+ input_mail, (HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR),
+ hide_headers, N_ELEMENTS(hide_headers),
+ *null_header_filter_callback, (void *)NULL);
+ input = i_stream_create_lf(input_filt);
+ i_stream_unref(&input_filt);
+
+ test_stream_data_slow(input, buffer);
+ test_assert(!i_stream_have_bytes_left(input_mail));
+ test_assert(input_mail->stream_errno == 0);
+
+ str_truncate(expected, 0);
+ str_append_no_cr(expected, msg_added);
+ str_append_no_cr(expected, msg_part1);
+ str_append_no_cr(expected, msg_part3);
+ str_append_no_cr(expected, msg_part4);
+
+ test_out("added, filtered, slow",
+ strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ i_stream_unref(&input);
+
+ /* Delete header */
+
+ edit_mail_header_delete(edmail, "Delivered-To", 0);
+
+ /* Evaluate modified header */
+
+ test_assert(mail_get_first_header_utf8(mail, "Subject", &value) > 0);
+ test_assert(strcmp(value, "Sieve editheader breaks with LMTP") == 0);
+
+ test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Flag",
+ &value) > 0);
+ test_assert(strcmp(value, "NO") == 0);
+ test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Type",
+ &value) > 0);
+ test_assert(strcmp(value, "NONE") == 0);
+
+ test_assert(mail_get_first_header_utf8(mail, "Delivered-To",
+ &value) == 0);
+
+ /* Deleted */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+ input = input_mail;
+
+ test_stream_data(input_mail, buffer);
+
+ str_truncate(expected, 0);
+ str_append(expected, msg_added);
+ str_append(expected, msg_part1);
+ str_append(expected, msg_part2);
+ str_append(expected, msg_part4);
+
+ test_out("deleted", strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ /* Deleted, slow */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ test_stream_data_slow(input_mail, buffer);
+
+ str_truncate(expected, 0);
+ str_append(expected, msg_added);
+ str_append(expected, msg_part1);
+ str_append(expected, msg_part2);
+ str_append(expected, msg_part4);
+
+ test_out("deleted, slow", strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ /* Deleted, filtered */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ input_filt = i_stream_create_header_filter(
+ input_mail, (HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR),
+ hide_headers, N_ELEMENTS(hide_headers),
+ *null_header_filter_callback, (void *)NULL);
+ input = i_stream_create_lf(input_filt);
+ i_stream_unref(&input_filt);
+
+ test_stream_data(input, buffer);
+ test_assert(!i_stream_have_bytes_left(input_mail));
+ test_assert(input_mail->stream_errno == 0);
+
+ str_truncate(expected, 0);
+ str_append_no_cr(expected, msg_added);
+ str_append_no_cr(expected, msg_part1);
+ str_append_no_cr(expected, msg_part4);
+
+ test_out("deleted, filtered",
+ strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ i_stream_unref(&input);
+
+ /* Deleted, filtered, slow */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ input_filt = i_stream_create_header_filter(input_mail,
+ HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, hide_headers,
+ N_ELEMENTS(hide_headers), *null_header_filter_callback,
+ (void *)NULL);
+ input = i_stream_create_lf(input_filt);
+ i_stream_unref(&input_filt);
+
+ test_stream_data_slow(input, buffer);
+ test_assert(!i_stream_have_bytes_left(input_mail));
+ test_assert(input_mail->stream_errno == 0);
+
+ str_truncate(expected, 0);
+ str_append_no_cr(expected, msg_added);
+ str_append_no_cr(expected, msg_part1);
+ str_append_no_cr(expected, msg_part4);
+
+ test_out("deleted, filtered, slow",
+ strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ i_stream_unref(&input);
+
+ /* clean up */
+
+ buffer_free(&buffer);
+ edit_mail_unwrap(&edmail);
+ mail_raw_close(&rawmail);
+ i_stream_unref(&input_msg);
+ test_deinit();
+ test_end();
+}
+
+static const char *big_header =
+ "X-A: AAAA\n"
+ "X-Big-One: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ "X-B: BBBB\n"
+ "\n"
+ "Frop!\n";
+
+static void test_edit_mail_big_header(void)
+{
+ struct istream *input_msg, *input_mail;
+ buffer_t *buffer;
+ struct mail_raw *rawmail;
+ struct edit_mail *edmail;
+ struct mail *mail;
+ const char *value;
+
+ test_begin("edit-mail - big header");
+ test_init();
+
+ /* compose the message */
+
+ input_msg = i_stream_create_from_data(big_header, strlen(big_header));
+
+ rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
+
+ edmail = edit_mail_wrap(rawmail->mail);
+
+ /* delete header */
+
+ edit_mail_header_delete(edmail, "X-B", 0);
+ mail = edit_mail_get_mail(edmail);
+
+ /* prepare tests */
+
+ if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
+ i_fatal("Failed to open mail stream: %s",
+ mailbox_get_last_internal_error(mail->box, NULL));
+ }
+
+ buffer = buffer_create_dynamic(default_pool, 1024);
+
+ /* evaluate modified header */
+
+ test_assert(mail_get_first_header_utf8(mail, "X-B", &value) == 0);
+
+ /* deleted */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ test_stream_data(input_mail, buffer);
+
+ /* clean up */
+
+ buffer_free(&buffer);
+ edit_mail_unwrap(&edmail);
+ mail_raw_close(&rawmail);
+ i_stream_unref(&input_msg);
+ test_deinit();
+ test_end();
+}
+
+static void test_edit_mail_small_buffer(void)
+{
+ static const char *message =
+ "X-A: AAAA\n"
+ "X-B: BBBB\n"
+ "\n"
+ "Frop!\n";
+ struct istream *input_msg, *input_mail;
+ buffer_t *buffer;
+ struct mail_raw *rawmail;
+ struct edit_mail *edmail;
+ struct mail *mail;
+ const char *value;
+ unsigned int i;
+
+ test_begin("edit-mail - small buffer");
+ test_init();
+
+ /* compose the message */
+
+ input_msg = i_stream_create_from_data(message, strlen(message));
+ i_stream_set_max_buffer_size(input_msg, 16);
+
+ rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
+
+ edmail = edit_mail_wrap(rawmail->mail);
+
+ /* add headers */
+
+ for (i = 0; i < 16; i++) {
+ edit_mail_header_add(edmail, "X-F", "FF", FALSE);
+ edit_mail_header_add(edmail, "X-L", "LL", TRUE);
+ }
+
+ mail = edit_mail_get_mail(edmail);
+
+ /* prepare tests */
+
+ if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
+ i_fatal("Failed to open mail stream: %s",
+ mailbox_get_last_internal_error(mail->box, NULL));
+ }
+
+ buffer = buffer_create_dynamic(default_pool, 1024);
+
+ /* evaluate modified header */
+
+ test_assert(mail_get_first_header_utf8(mail, "X-F", &value) > 0);
+ test_assert(mail_get_first_header_utf8(mail, "X-A", &value) > 0);
+ test_assert(mail_get_first_header_utf8(mail, "X-B", &value) > 0);
+ test_assert(mail_get_first_header_utf8(mail, "X-L", &value) > 0);
+
+ /* check stream read */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ test_stream_data(input_mail, buffer);
+
+ /* clean up */
+
+ buffer_free(&buffer);
+ edit_mail_unwrap(&edmail);
+ mail_raw_close(&rawmail);
+ i_stream_unref(&input_msg);
+ test_deinit();
+ test_end();
+}
+
+int main(int argc, char *argv[])
+{
+ static void (*test_functions[])(void) = {
+ test_edit_mail_concatenated,
+ test_edit_mail_big_header,
+ test_edit_mail_small_buffer,
+ NULL
+ };
+ const enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_STANDALONE |
+ MASTER_SERVICE_FLAG_DONT_SEND_STATS |
+ MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS;
+ const char *cwd, *error;
+ int ret;
+
+ master_service = master_service_init("test-edit-header", service_flags,
+ &argc, &argv, "");
+ master_service_init_finish(master_service);
+
+ if (t_get_working_dir(&cwd, &error) < 0)
+ i_fatal("getcwd() failed: %s", error);
+ test_dir = i_strdup(cwd);
+
+ ret = test_run(test_functions);
+
+ i_free(test_dir);
+ master_service_deinit(&master_service);
+
+ return ret;
+}
+
diff --git a/pigeonhole/src/lib-sieve/util/test-rfc2822.c b/pigeonhole/src/lib-sieve/util/test-rfc2822.c
new file mode 100644
index 0000000..66e8ee5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/test-rfc2822.c
@@ -0,0 +1,197 @@
+/* Copyright (c) 2018 Pigeonhole authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "str.h"
+
+#include "rfc2822.h"
+
+struct test_header_write {
+ const char *name;
+ const char *body;
+ const char *output;
+};
+
+static const struct test_header_write header_write_tests[] = {
+ {
+ .name = "Frop",
+ .body = "Bladiebla",
+ .output = "Frop: Bladiebla\r\n"
+ },{
+ .name = "Subject",
+ .body = "This is a very long subject that well exceeds the "
+ "boundary of 80 characters. It should therefore "
+ "trigger the header folding algorithm.",
+ .output =
+ "Subject: This is a very long subject that well "
+ "exceeds the boundary of 80\r\n"
+ "\tcharacters. It should therefore trigger the header "
+ "folding algorithm.\r\n"
+ },{
+ .name = "Subject",
+ .body = "This\tis\ta\tvery\tlong\tsubject\tthat\twell\texceeds"
+ "\tthe\tboundary\tof\t80\tcharacters.\tIt\tshould\t"
+ "therefore\ttrigger\tthe\theader\tfolding\talgorithm.",
+ .output =
+ "Subject: This\tis\ta\tvery\tlong\tsubject\tthat\twell"
+ "\texceeds\tthe\tboundary\tof\t80\r\n"
+ "\tcharacters.\tIt\tshould\ttherefore\ttrigger\tthe\t"
+ "header\tfolding\talgorithm.\r\n"
+ },{
+ .name = "Comment",
+ .body = "This header already contains newlines.\n"
+ "The header folding algorithm should respect these.\n"
+ "It also should convert between CRLF and LF when "
+ "needed.",
+ .output = "Comment: This header already contains newlines.\r\n"
+ "\tThe header folding algorithm should respect "
+ "these.\r\n"
+ "\tIt also should convert between CRLF and LF when "
+ "needed.\r\n"
+ },{
+ .name = "References",
+ .body = "<messageid1@example.com> <messageid2@example.com> "
+ "<extremelylonglonglonglonglonglonglonglonglonglong"
+ "longlongmessageid3@example.com> "
+ "<messageid4@example.com>",
+ .output = "References: <messageid1@example.com> "
+ "<messageid2@example.com>\r\n"
+ "\t<extremelylonglonglonglonglonglonglonglonglonglong"
+ "longlongmessageid3@example.com>\r\n"
+ "\t<messageid4@example.com>\r\n",
+ },{
+ .name = "Cc",
+ .body = "\"Richard Edgar Cipient\" "
+ "<r.e.cipient@example.com>, \"Albert Buser\" "
+ "<a.buser@example.com>, \"Steven Pammer\" "
+ "<s.pammer@example.com>",
+ .output = "Cc: \"Richard Edgar Cipient\" "
+ "<r.e.cipient@example.com>, \"Albert Buser\"\r\n"
+ "\t<a.buser@example.com>, \"Steven Pammer\" "
+ "<s.pammer@example.com>\r\n"
+ },{
+ .name = "References",
+ .body = "<00fd01d31b6c$33d98e30$9b8caa90$@karel@aa.example.org"
+ "> <00f201d31c36$afbfa320$0f3ee960$@karel@aa.example.o"
+ "rg> <015c01d32023$fe3840c0$faa8c240$@karel@aa.examp"
+ "le.org> <014601d325a4$ece1ed90$c6a5c8b0$@karel@aa."
+ "example.org> <012801d32b24$7734c380$659e4a80$@karel"
+ "@aa.example.org> <00da01d32be9$2d9944b0$88cbce10$@kar"
+ "el@aa.example.org> <006a01d336ef$6825d5b0$387181"
+ "10$@karel@aa.example.org> <018501d33880$58b654f0$0a2"
+ "2fed0$@frederik@aa.example.org> <00e601d33ba3$be50f10"
+ "0$3af2d300$@frederik@aa.example.org> <016501d341ee$e"
+ "678e1a0$b36aa4e0$@frederik@aa.example.org> <00ab01"
+ "d348f9$ae2e1ab0$0a8a5010$@karel@aa.example.org> <0086"
+ "01d349c1$98ff4ba0$cafde2e0$@frederik@aa.example.org> "
+ " <019301d357e6$a2190680$e64b1380$@frederik@aa.example"
+ ".org> <025f01d384b0$24d2c"
+ "660$6e785320$@karel@aa.example.org> <01cf01d3889e$7"
+ "280cb90$578262b0$@karel@aa.example.org> <013701d38"
+ "bc2$9164b950$b42e2bf0$@karel@aa.example.org> "
+ " <014f01d3a5b1$a51afc80$ef\n"
+ " \n"
+ "\t \t \t \t \t \t \t \t5\t0\tf\t5\t8\t0\t$\t@\tk\ta\t"
+ "r\te\tl\t@\taa.example.org> <01cb01d3af29$dd7d"
+ "1b40$987751c0$@karel@aa.example.org> "
+ " <00b401d3f2bc$6ad8c180$408a4480"
+ "$@karel@aa.example.org> <011a01d3f6ab$0eeb0480$2cc1"
+ "0d80$@frederik@aa.example.org> <005c01d3f774$37f1b210"
+ "$a7d51630$@richard@aa.example.org> <01a801d3fc2d$59"
+ "0f7730$0b2e6590$@frederik@aa.example.org> <007501d3fc"
+ "f5$23d75ce0$6b8616a0$@frederik@aa.example.org> <015d0"
+ "1d3fdbf$136da510$3a48ef30$@frederik@aa.example.org> <"
+ "021a01d3fe87$556d68b0$00483a10$@frederik@aa.example.o"
+ "rg> <013f01d3ff4e$a2d13d30$e873b790$@frederik@aa.exam"
+ "ple.org> <001f01d401ab$31e7b090$95b711b0$@frederik@aa"
+ ".example.org> <017201d40273$a118d200$e34a7600$@freder"
+ "ik@aa.example.org> <017401d4033e$ca3602e0$5ea208a0$@f"
+ "rederik@aa.example.org> <02a601d40404$608b9e10$21a2da"
+ "30$@frederik@aa.example.org> <014301d404d0$b65269b0$2"
+ "2f73d10$@frederik@aa.example.org> <015901d4072b$b5a1b"
+ "950$20e52bf0$@frederik@aa.example.org> <01b401d407f3$"
+ "bef52050$3cdf\n"
+ " 60 \n"
+ "\tf0 \t$@ \tfr \ted \teri\tk@aa.example.org> <012801d"
+ "408bd$6602fce0$3208f6a0$@frederik@aa.example.org> <01"
+ "c801d40984$ae4b23c0$0ae16b40$@frederik@aa.example.org"
+ "> <00ec01d40a4d$12859190$3790b4b0$@frederik@aa.exampl"
+ "e.org> <02af01d40d74$589c9050$09d5b0f0$@frederik@aa.e"
+ "xample.org> <000d01d40ec8$d3d337b0$7b79a710$@richard@"
+ "aa.example.org>\n",
+ .output = "References: <00fd01d31b6c$33d98e30$9b8caa90$@karel@aa.example.org>\r\n"
+ "\t<00f201d31c36$afbfa320$0f3ee960$@karel@aa.example.org>\r\n"
+ "\t<015c01d32023$fe3840c0$faa8c240$@karel@aa.example.org>\r\n"
+ "\t<014601d325a4$ece1ed90$c6a5c8b0$@karel@aa.example.org>\r\n"
+ "\t<012801d32b24$7734c380$659e4a80$@karel@aa.example.org>\r\n"
+ "\t<00da01d32be9$2d9944b0$88cbce10$@karel@aa.example.org>\r\n"
+ "\t<006a01d336ef$6825d5b0$38718110$@karel@aa.example.org>\r\n"
+ "\t<018501d33880$58b654f0$0a22fed0$@frederik@aa.example.org>\r\n"
+ "\t<00e601d33ba3$be50f100$3af2d300$@frederik@aa.example.org>\r\n"
+ "\t<016501d341ee$e678e1a0$b36aa4e0$@frederik@aa.example.org>\r\n"
+ "\t<00ab01d348f9$ae2e1ab0$0a8a5010$@karel@aa.example.org>\r\n"
+ "\t<008601d349c1$98ff4ba0$cafde2e0$@frederik@aa.example.org>\r\n"
+ "\t<019301d357e6$a2190680$e64b1380$@frederik@aa.example.org>\r\n"
+ "\t<025f01d384b0$24d2c660$6e785320$@karel@aa.example.org>\r\n"
+ "\t<01cf01d3889e$7280cb90$578262b0$@karel@aa.example.org>\r\n"
+ "\t<013701d38bc2$9164b950$b42e2bf0$@karel@aa.example.org>\r\n"
+ "\t<014f01d3a5b1$a51afc80$ef\r\n"
+ "\t5\t0\tf\t5\t8\t0\t$\t@\tk\ta\tr\te\tl\t@\taa.example.org>\r\n"
+ "\t<01cb01d3af29$dd7d1b40$987751c0$@karel@aa.example.org>\r\n"
+ "\t<00b401d3f2bc$6ad8c180$408a4480$@karel@aa.example.org>\r\n"
+ "\t<011a01d3f6ab$0eeb0480$2cc10d80$@frederik@aa.example.org>\r\n"
+ "\t<005c01d3f774$37f1b210$a7d51630$@richard@aa.example.org>\r\n"
+ "\t<01a801d3fc2d$590f7730$0b2e6590$@frederik@aa.example.org>\r\n"
+ "\t<007501d3fcf5$23d75ce0$6b8616a0$@frederik@aa.example.org>\r\n"
+ "\t<015d01d3fdbf$136da510$3a48ef30$@frederik@aa.example.org>\r\n"
+ "\t<021a01d3fe87$556d68b0$00483a10$@frederik@aa.example.org>\r\n"
+ "\t<013f01d3ff4e$a2d13d30$e873b790$@frederik@aa.example.org>\r\n"
+ "\t<001f01d401ab$31e7b090$95b711b0$@frederik@aa.example.org>\r\n"
+ "\t<017201d40273$a118d200$e34a7600$@frederik@aa.example.org>\r\n"
+ "\t<017401d4033e$ca3602e0$5ea208a0$@frederik@aa.example.org>\r\n"
+ "\t<02a601d40404$608b9e10$21a2da30$@frederik@aa.example.org>\r\n"
+ "\t<014301d404d0$b65269b0$22f73d10$@frederik@aa.example.org>\r\n"
+ "\t<015901d4072b$b5a1b950$20e52bf0$@frederik@aa.example.org>\r\n"
+ "\t<01b401d407f3$bef52050$3cdf\r\n"
+ "\t60\r\n"
+ "\tf0 \t$@ \tfr \ted \teri\tk@aa.example.org>\r\n"
+ "\t<012801d408bd$6602fce0$3208f6a0$@frederik@aa.example.org>\r\n"
+ "\t<01c801d40984$ae4b23c0$0ae16b40$@frederik@aa.example.org>\r\n"
+ "\t<00ec01d40a4d$12859190$3790b4b0$@frederik@aa.example.org>\r\n"
+ "\t<02af01d40d74$589c9050$09d5b0f0$@frederik@aa.example.org>\r\n"
+ "\t<000d01d40ec8$d3d337b0$7b79a710$@richard@aa.example.org>\r\n"
+ }
+};
+
+static const unsigned int header_write_tests_count =
+ N_ELEMENTS(header_write_tests);
+
+static void test_rfc2822_header_write(void)
+{
+ string_t *header;
+ unsigned int i;
+
+ test_begin("rfc2822 - header write");
+
+ header = t_str_new(1024);
+ for (i = 0; i < header_write_tests_count; i++) {
+ const struct test_header_write *test = &header_write_tests[i];
+
+ str_truncate(header, 0);
+ rfc2822_header_write(header, test->name, test->body);
+
+ test_assert_idx(strcmp(str_c(header), test->output) == 0, i);
+ }
+
+ test_end();
+}
+
+int main(void)
+{
+ static void (*test_functions[])(void) = {
+ test_rfc2822_header_write,
+ NULL
+ };
+ return test_run(test_functions);
+}
+