From f7548d6d28c313cf80e6f3ef89aed16a19815df1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 11:51:24 +0200 Subject: Adding upstream version 1:2.3.19.1+dfsg1. Signed-off-by: Daniel Baumann --- pigeonhole/src/lib-sieve/util/Makefile.am | 51 + pigeonhole/src/lib-sieve/util/Makefile.in | 810 +++++++++ pigeonhole/src/lib-sieve/util/edit-mail.c | 2251 ++++++++++++++++++++++++ pigeonhole/src/lib-sieve/util/edit-mail.h | 47 + pigeonhole/src/lib-sieve/util/mail-raw.c | 247 +++ pigeonhole/src/lib-sieve/util/mail-raw.h | 27 + pigeonhole/src/lib-sieve/util/rfc2822.c | 277 +++ pigeonhole/src/lib-sieve/util/rfc2822.h | 46 + pigeonhole/src/lib-sieve/util/test-edit-mail.c | 842 +++++++++ pigeonhole/src/lib-sieve/util/test-rfc2822.c | 197 +++ 10 files changed, 4795 insertions(+) create mode 100644 pigeonhole/src/lib-sieve/util/Makefile.am create mode 100644 pigeonhole/src/lib-sieve/util/Makefile.in create mode 100644 pigeonhole/src/lib-sieve/util/edit-mail.c create mode 100644 pigeonhole/src/lib-sieve/util/edit-mail.h create mode 100644 pigeonhole/src/lib-sieve/util/mail-raw.c create mode 100644 pigeonhole/src/lib-sieve/util/mail-raw.h create mode 100644 pigeonhole/src/lib-sieve/util/rfc2822.c create mode 100644 pigeonhole/src/lib-sieve/util/rfc2822.h create mode 100644 pigeonhole/src/lib-sieve/util/test-edit-mail.c create mode 100644 pigeonhole/src/lib-sieve/util/test-rfc2822.c (limited to 'pigeonhole/src/lib-sieve/util') 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 +#include +#include +#include + +/* + * 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 +#include + +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 + +/* + * 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 + +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 )\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: \r\n"; + static const char *msg_part3 = + "Delivered-To: \r\n"; + static const char *msg_part4 = + "From: \r\n" + "To: \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 = " " + " " + "", + .output = "References: " + "\r\n" + "\t\r\n" + "\t\r\n", + },{ + .name = "Cc", + .body = "\"Richard Edgar Cipient\" " + ", \"Albert Buser\" " + ", \"Steven Pammer\" " + "", + .output = "Cc: \"Richard Edgar Cipient\" " + ", \"Albert Buser\"\r\n" + "\t, \"Steven Pammer\" " + "\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); +} + -- cgit v1.2.3