summaryrefslogtreecommitdiffstats
path: root/src/lib-imap
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-imap-client/Makefile.am53
-rw-r--r--src/lib-imap-client/Makefile.in880
-rw-r--r--src/lib-imap-client/imapc-client-private.h63
-rw-r--r--src/lib-imap-client/imapc-client.c584
-rw-r--r--src/lib-imap-client/imapc-client.h250
-rw-r--r--src/lib-imap-client/imapc-connection.c2513
-rw-r--r--src/lib-imap-client/imapc-connection.h63
-rw-r--r--src/lib-imap-client/imapc-msgmap.c89
-rw-r--r--src/lib-imap-client/imapc-msgmap.h18
-rw-r--r--src/lib-imap-client/test-imapc-client.c901
-rw-r--r--src/lib-imap-storage/Makefile.am24
-rw-r--r--src/lib-imap-storage/Makefile.in825
-rw-r--r--src/lib-imap-storage/imap-metadata.c314
-rw-r--r--src/lib-imap-storage/imap-metadata.h61
-rw-r--r--src/lib-imap-storage/imap-msgpart-url.c287
-rw-r--r--src/lib-imap-storage/imap-msgpart-url.h50
-rw-r--r--src/lib-imap-storage/imap-msgpart.c860
-rw-r--r--src/lib-imap-storage/imap-msgpart.h68
-rw-r--r--src/lib-imap-urlauth/Makefile.am29
-rw-r--r--src/lib-imap-urlauth/Makefile.in835
-rw-r--r--src/lib-imap-urlauth/imap-urlauth-backend.c174
-rw-r--r--src/lib-imap-urlauth/imap-urlauth-backend.h16
-rw-r--r--src/lib-imap-urlauth/imap-urlauth-connection.c1027
-rw-r--r--src/lib-imap-urlauth/imap-urlauth-connection.h42
-rw-r--r--src/lib-imap-urlauth/imap-urlauth-fetch.c530
-rw-r--r--src/lib-imap-urlauth/imap-urlauth-fetch.h56
-rw-r--r--src/lib-imap-urlauth/imap-urlauth-private.h20
-rw-r--r--src/lib-imap-urlauth/imap-urlauth.c486
-rw-r--r--src/lib-imap-urlauth/imap-urlauth.h55
-rw-r--r--src/lib-imap/Makefile.am120
-rw-r--r--src/lib-imap/Makefile.in1196
-rw-r--r--src/lib-imap/fuzz-imap-bodystructure.c52
-rw-r--r--src/lib-imap/fuzz-imap-utf7.c15
-rw-r--r--src/lib-imap/imap-arg.c137
-rw-r--r--src/lib-imap/imap-arg.h108
-rw-r--r--src/lib-imap/imap-base-subject.c248
-rw-r--r--src/lib-imap/imap-base-subject.h13
-rw-r--r--src/lib-imap/imap-bodystructure.c956
-rw-r--r--src/lib-imap/imap-bodystructure.h39
-rw-r--r--src/lib-imap/imap-date.c247
-rw-r--r--src/lib-imap/imap-date.h25
-rw-r--r--src/lib-imap/imap-envelope.c248
-rw-r--r--src/lib-imap/imap-envelope.h20
-rw-r--r--src/lib-imap/imap-id.c173
-rw-r--r--src/lib-imap/imap-id.h19
-rw-r--r--src/lib-imap/imap-keepalive.c47
-rw-r--r--src/lib-imap/imap-keepalive.h24
-rw-r--r--src/lib-imap/imap-match.c382
-rw-r--r--src/lib-imap/imap-match.h42
-rw-r--r--src/lib-imap/imap-parser.c1023
-rw-r--r--src/lib-imap/imap-parser.h117
-rw-r--r--src/lib-imap/imap-quote.c239
-rw-r--r--src/lib-imap/imap-quote.h21
-rw-r--r--src/lib-imap/imap-resp-code.h28
-rw-r--r--src/lib-imap/imap-seqset.c105
-rw-r--r--src/lib-imap/imap-seqset.h15
-rw-r--r--src/lib-imap/imap-url.c1009
-rw-r--r--src/lib-imap/imap-url.h71
-rw-r--r--src/lib-imap/imap-utf7.c380
-rw-r--r--src/lib-imap/imap-utf7.h28
-rw-r--r--src/lib-imap/imap-util.c202
-rw-r--r--src/lib-imap/imap-util.h29
-rw-r--r--src/lib-imap/test-imap-bodystructure.c733
-rw-r--r--src/lib-imap/test-imap-envelope.c205
-rw-r--r--src/lib-imap/test-imap-match.c127
-rw-r--r--src/lib-imap/test-imap-parser.c157
-rw-r--r--src/lib-imap/test-imap-quote.c171
-rw-r--r--src/lib-imap/test-imap-url.c1029
-rw-r--r--src/lib-imap/test-imap-utf7.c216
-rw-r--r--src/lib-imap/test-imap-util.c79
70 files changed, 21268 insertions, 0 deletions
diff --git a/src/lib-imap-client/Makefile.am b/src/lib-imap-client/Makefile.am
new file mode 100644
index 0000000..8fe8a8d
--- /dev/null
+++ b/src/lib-imap-client/Makefile.am
@@ -0,0 +1,53 @@
+noinst_LTLIBRARIES = libimap_client.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dns \
+ -I$(top_srcdir)/src/lib-sasl \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap
+
+libimap_client_la_SOURCES = \
+ imapc-client.c \
+ imapc-connection.c \
+ imapc-msgmap.c
+
+headers = \
+ imapc-client.h \
+ imapc-client-private.h \
+ imapc-connection.h \
+ imapc-msgmap.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+test_programs = \
+ test-imapc-client
+
+noinst_PROGRAMS = $(test_programs)
+
+test_deps = \
+ $(noinst_LTLIBRARIES) \
+ ../lib-ssl-iostream/libssl_iostream.la \
+ ../lib-sasl/libsasl.la \
+ ../lib-imap/libimap.la \
+ ../lib-mail/libmail.la \
+ ../lib-charset/libcharset.la \
+ ../lib-dns/libdns.la \
+ ../lib-test/libtest.la \
+ ../lib/liblib.la
+
+test_libs = \
+ $(test_deps) \
+ $(MODULE_LIBS)
+
+test_imapc_client_SOURCES = test-imapc-client.c
+test_imapc_client_LDADD = $(test_libs)
+test_imapc_client_DEPENDENCIES = $(test_deps)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
diff --git a/src/lib-imap-client/Makefile.in b/src/lib-imap-client/Makefile.in
new file mode 100644
index 0000000..a878047
--- /dev/null
+++ b/src/lib-imap-client/Makefile.in
@@ -0,0 +1,880 @@
+# 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-imap-client
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-imapc-client$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libimap_client_la_LIBADD =
+am_libimap_client_la_OBJECTS = imapc-client.lo imapc-connection.lo \
+ imapc-msgmap.lo
+libimap_client_la_OBJECTS = $(am_libimap_client_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_imapc_client_OBJECTS = test-imapc-client.$(OBJEXT)
+test_imapc_client_OBJECTS = $(am_test_imapc_client_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = $(test_deps) $(am__DEPENDENCIES_1)
+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)/imapc-client.Plo \
+ ./$(DEPDIR)/imapc-connection.Plo ./$(DEPDIR)/imapc-msgmap.Plo \
+ ./$(DEPDIR)/test-imapc-client.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 = $(libimap_client_la_SOURCES) $(test_imapc_client_SOURCES)
+DIST_SOURCES = $(libimap_client_la_SOURCES) \
+ $(test_imapc_client_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@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libimap_client.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-dns \
+ -I$(top_srcdir)/src/lib-sasl \
+ -I$(top_srcdir)/src/lib-ssl-iostream \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-imap
+
+libimap_client_la_SOURCES = \
+ imapc-client.c \
+ imapc-connection.c \
+ imapc-msgmap.c
+
+headers = \
+ imapc-client.h \
+ imapc-client-private.h \
+ imapc-connection.h \
+ imapc-msgmap.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+test_programs = \
+ test-imapc-client
+
+test_deps = \
+ $(noinst_LTLIBRARIES) \
+ ../lib-ssl-iostream/libssl_iostream.la \
+ ../lib-sasl/libsasl.la \
+ ../lib-imap/libimap.la \
+ ../lib-mail/libmail.la \
+ ../lib-charset/libcharset.la \
+ ../lib-dns/libdns.la \
+ ../lib-test/libtest.la \
+ ../lib/liblib.la
+
+test_libs = \
+ $(test_deps) \
+ $(MODULE_LIBS)
+
+test_imapc_client_SOURCES = test-imapc-client.c
+test_imapc_client_LDADD = $(test_libs)
+test_imapc_client_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-imap-client/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-imap-client/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}; \
+ }
+
+libimap_client.la: $(libimap_client_la_OBJECTS) $(libimap_client_la_DEPENDENCIES) $(EXTRA_libimap_client_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libimap_client_la_OBJECTS) $(libimap_client_la_LIBADD) $(LIBS)
+
+test-imapc-client$(EXEEXT): $(test_imapc_client_OBJECTS) $(test_imapc_client_DEPENDENCIES) $(EXTRA_test_imapc_client_DEPENDENCIES)
+ @rm -f test-imapc-client$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_imapc_client_OBJECTS) $(test_imapc_client_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imapc-msgmap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imapc-client.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(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)/imapc-client.Plo
+ -rm -f ./$(DEPDIR)/imapc-connection.Plo
+ -rm -f ./$(DEPDIR)/imapc-msgmap.Plo
+ -rm -f ./$(DEPDIR)/test-imapc-client.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)/imapc-client.Plo
+ -rm -f ./$(DEPDIR)/imapc-connection.Plo
+ -rm -f ./$(DEPDIR)/imapc-msgmap.Plo
+ -rm -f ./$(DEPDIR)/test-imapc-client.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: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES clean-noinstPROGRAMS 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-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib-imap-client/imapc-client-private.h b/src/lib-imap-client/imapc-client-private.h
new file mode 100644
index 0000000..98c4e8e
--- /dev/null
+++ b/src/lib-imap-client/imapc-client-private.h
@@ -0,0 +1,63 @@
+#ifndef IMAPC_CLIENT_PRIVATE_H
+#define IMAPC_CLIENT_PRIVATE_H
+
+#include "imapc-client.h"
+
+#define IMAPC_CLIENT_IDLE_SEND_DELAY_MSECS 100
+
+struct imapc_client_connection {
+ struct imapc_connection *conn;
+ struct imapc_client *client;
+ struct imapc_client_mailbox *box;
+};
+
+struct imapc_client {
+ pool_t pool;
+ int refcount;
+
+ struct event *event;
+ struct imapc_client_settings set;
+ struct ssl_iostream_context *ssl_ctx;
+
+ imapc_untagged_callback_t *untagged_callback;
+ void *untagged_context;
+
+ imapc_state_change_callback_t *state_change_callback;
+ void *state_change_context;
+
+ imapc_command_callback_t *login_callback;
+ void *login_context;
+
+ ARRAY(struct imapc_client_connection *) conns;
+ bool logging_out;
+
+ struct ioloop *ioloop;
+ bool stop_on_state_finish;
+};
+
+struct imapc_client_mailbox {
+ struct imapc_client *client;
+ struct imapc_connection *conn;
+ struct imapc_msgmap *msgmap;
+ struct timeout *to_send_idle;
+
+ void (*reopen_callback)(void *context);
+ void *reopen_context;
+
+ void *untagged_box_context;
+
+ bool reconnect_ok;
+ bool reconnecting;
+ bool closing;
+};
+
+extern unsigned int imapc_client_cmd_tag_counter;
+
+void imapc_client_ref(struct imapc_client *client);
+void imapc_client_unref(struct imapc_client **client);
+
+void imapc_command_set_mailbox(struct imapc_command *cmd,
+ struct imapc_client_mailbox *box);
+void imapc_client_try_stop(struct imapc_client *client);
+
+#endif
diff --git a/src/lib-imap-client/imapc-client.c b/src/lib-imap-client/imapc-client.c
new file mode 100644
index 0000000..adb4dcb
--- /dev/null
+++ b/src/lib-imap-client/imapc-client.c
@@ -0,0 +1,584 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "ioloop.h"
+#include "safe-mkstemp.h"
+#include "iostream-ssl.h"
+#include "imapc-msgmap.h"
+#include "imapc-connection.h"
+#include "imapc-client-private.h"
+
+#include <unistd.h>
+
+const char *imapc_command_state_names[] = {
+ "OK", "NO", "BAD", "(auth failed)", "(disconnected)"
+
+};
+
+const struct imapc_capability_name imapc_capability_names[] = {
+ { "SASL-IR", IMAPC_CAPABILITY_SASL_IR },
+ { "LITERAL+", IMAPC_CAPABILITY_LITERALPLUS },
+ { "QRESYNC", IMAPC_CAPABILITY_QRESYNC },
+ { "IDLE", IMAPC_CAPABILITY_IDLE },
+ { "UIDPLUS", IMAPC_CAPABILITY_UIDPLUS },
+ { "AUTH=PLAIN", IMAPC_CAPABILITY_AUTH_PLAIN },
+ { "STARTTLS", IMAPC_CAPABILITY_STARTTLS },
+ { "X-GM-EXT-1", IMAPC_CAPABILITY_X_GM_EXT_1 },
+ { "CONDSTORE", IMAPC_CAPABILITY_CONDSTORE },
+ { "NAMESPACE", IMAPC_CAPABILITY_NAMESPACE },
+ { "UNSELECT", IMAPC_CAPABILITY_UNSELECT },
+ { "ESEARCH", IMAPC_CAPABILITY_ESEARCH },
+ { "WITHIN", IMAPC_CAPABILITY_WITHIN },
+ { "QUOTA", IMAPC_CAPABILITY_QUOTA },
+ { "ID", IMAPC_CAPABILITY_ID },
+ { "SAVEDATE", IMAPC_CAPABILITY_SAVEDATE },
+
+ { "IMAP4REV1", IMAPC_CAPABILITY_IMAP4REV1 },
+ { NULL, 0 }
+};
+
+unsigned int imapc_client_cmd_tag_counter = 0;
+
+static void
+default_untagged_callback(const struct imapc_untagged_reply *reply ATTR_UNUSED,
+ void *context ATTR_UNUSED)
+{
+}
+
+struct imapc_client *
+imapc_client_init(const struct imapc_client_settings *set,
+ struct event *event_parent)
+{
+ struct imapc_client *client;
+ const char *error;
+ pool_t pool;
+
+ i_assert(set->connect_retry_count == 0 ||
+ set->connect_retry_interval_msecs > 0);
+
+ pool = pool_alloconly_create("imapc client", 1024);
+ client = p_new(pool, struct imapc_client, 1);
+ client->pool = pool;
+ client->refcount = 1;
+ client->event = event_create(event_parent);
+
+ client->set.debug = set->debug;
+ client->set.host = p_strdup(pool, set->host);
+ client->set.port = set->port;
+ client->set.master_user = p_strdup_empty(pool, set->master_user);
+ client->set.username = p_strdup(pool, set->username);
+ client->set.password = p_strdup(pool, set->password);
+ client->set.sasl_mechanisms = p_strdup(pool, set->sasl_mechanisms);
+ client->set.session_id_prefix = p_strdup(pool, set->session_id_prefix);
+ client->set.use_proxyauth = set->use_proxyauth;
+ client->set.dns_client_socket_path =
+ p_strdup(pool, set->dns_client_socket_path);
+ client->set.temp_path_prefix =
+ p_strdup(pool, set->temp_path_prefix);
+ client->set.rawlog_dir = p_strdup(pool, set->rawlog_dir);
+ client->set.max_idle_time = set->max_idle_time;
+ client->set.connect_timeout_msecs = set->connect_timeout_msecs != 0 ?
+ set->connect_timeout_msecs :
+ IMAPC_DEFAULT_CONNECT_TIMEOUT_MSECS;
+ client->set.connect_retry_count = set->connect_retry_count;
+ client->set.connect_retry_interval_msecs = set->connect_retry_interval_msecs;
+ client->set.cmd_timeout_msecs = set->cmd_timeout_msecs != 0 ?
+ set->cmd_timeout_msecs : IMAPC_DEFAULT_COMMAND_TIMEOUT_MSECS;
+ client->set.max_line_length = set->max_line_length != 0 ?
+ set->max_line_length : IMAPC_DEFAULT_MAX_LINE_LENGTH;
+ client->set.throttle_set = set->throttle_set;
+
+ if (client->set.throttle_set.init_msecs == 0)
+ client->set.throttle_set.init_msecs = IMAPC_THROTTLE_DEFAULT_INIT_MSECS;
+ if (client->set.throttle_set.max_msecs == 0)
+ client->set.throttle_set.max_msecs = IMAPC_THROTTLE_DEFAULT_MAX_MSECS;
+ if (client->set.throttle_set.shrink_min_msecs == 0)
+ client->set.throttle_set.shrink_min_msecs = IMAPC_THROTTLE_DEFAULT_SHRINK_MIN_MSECS;
+
+ if (set->ssl_mode != IMAPC_CLIENT_SSL_MODE_NONE) {
+ client->set.ssl_mode = set->ssl_mode;
+ ssl_iostream_settings_init_from(pool, &client->set.ssl_set, &set->ssl_set);
+ client->set.ssl_set.verbose_invalid_cert = !client->set.ssl_set.allow_invalid_cert;
+ if (ssl_iostream_client_context_cache_get(&client->set.ssl_set,
+ &client->ssl_ctx,
+ &error) < 0) {
+ i_error("imapc(%s:%u): Couldn't initialize SSL context: %s",
+ set->host, set->port, error);
+ }
+ }
+ client->untagged_callback = default_untagged_callback;
+
+ p_array_init(&client->conns, pool, 8);
+ return client;
+}
+
+void imapc_client_ref(struct imapc_client *client)
+{
+ i_assert(client->refcount > 0);
+
+ client->refcount++;
+}
+
+void imapc_client_unref(struct imapc_client **_client)
+{
+ struct imapc_client *client = *_client;
+
+ *_client = NULL;
+
+ i_assert(client->refcount > 0);
+ if (--client->refcount > 0)
+ return;
+
+ if (client->ssl_ctx != NULL)
+ ssl_iostream_context_unref(&client->ssl_ctx);
+ event_unref(&client->event);
+ pool_unref(&client->pool);
+}
+
+void imapc_client_disconnect(struct imapc_client *client)
+{
+ struct imapc_client_connection *const *conns, *conn;
+ unsigned int i, count;
+
+ conns = array_get(&client->conns, &count);
+ for (i = count; i > 0; i--) {
+ conn = conns[i-1];
+ array_delete(&client->conns, i-1, 1);
+
+ i_assert(imapc_connection_get_mailbox(conn->conn) == NULL);
+ imapc_connection_deinit(&conn->conn);
+ i_free(conn);
+ }
+}
+
+void imapc_client_deinit(struct imapc_client **_client)
+{
+ struct imapc_client *client = *_client;
+
+ imapc_client_disconnect(client);
+ imapc_client_unref(_client);
+}
+
+void imapc_client_register_untagged(struct imapc_client *client,
+ imapc_untagged_callback_t *callback,
+ void *context)
+{
+ client->untagged_callback = callback;
+ client->untagged_context = context;
+}
+
+static void imapc_client_run_pre(struct imapc_client *client)
+{
+ struct imapc_client_connection *conn;
+ struct ioloop *prev_ioloop = current_ioloop;
+
+ i_assert(client->ioloop == NULL);
+
+ client->ioloop = io_loop_create();
+ io_loop_set_running(client->ioloop);
+
+ array_foreach_elem(&client->conns, conn) {
+ imapc_connection_ioloop_changed(conn->conn);
+ if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DISCONNECTED)
+ imapc_connection_connect(conn->conn);
+ }
+
+ if (io_loop_is_running(client->ioloop))
+ io_loop_run(client->ioloop);
+ io_loop_set_current(prev_ioloop);
+}
+
+static void imapc_client_run_post(struct imapc_client *client)
+{
+ struct imapc_client_connection *conn;
+ struct ioloop *ioloop = client->ioloop;
+
+ client->ioloop = NULL;
+ array_foreach_elem(&client->conns, conn)
+ imapc_connection_ioloop_changed(conn->conn);
+
+ io_loop_set_current(ioloop);
+ io_loop_destroy(&ioloop);
+}
+
+void imapc_client_run(struct imapc_client *client)
+{
+ imapc_client_run_pre(client);
+ imapc_client_run_post(client);
+}
+
+void imapc_client_stop(struct imapc_client *client)
+{
+ if (client->ioloop != NULL)
+ io_loop_stop(client->ioloop);
+}
+
+void imapc_client_try_stop(struct imapc_client *client)
+{
+ struct imapc_client_connection *conn;
+ array_foreach_elem(&client->conns, conn)
+ if (imapc_connection_get_state(conn->conn) != IMAPC_CONNECTION_STATE_DISCONNECTED)
+ return;
+ imapc_client_stop(client);
+}
+
+bool imapc_client_is_running(struct imapc_client *client)
+{
+ return client->ioloop != NULL;
+}
+
+static void imapc_client_login_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_client_connection *conn = context;
+ struct imapc_client *client = conn->client;
+ struct imapc_client_mailbox *box = conn->box;
+
+ if (box != NULL && box->reconnecting) {
+ box->reconnecting = FALSE;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK) {
+ /* reopen the mailbox */
+ box->reopen_callback(box->reopen_context);
+ } else {
+ imapc_connection_abort_commands(box->conn, NULL, FALSE);
+ }
+ }
+
+ /* call the login callback only once */
+ if (client->login_callback != NULL) {
+ imapc_command_callback_t *callback = client->login_callback;
+ void *context = client->login_context;
+
+ client->login_callback = NULL;
+ client->login_context = NULL;
+ callback(reply, context);
+ }
+}
+
+static struct imapc_client_connection *
+imapc_client_add_connection(struct imapc_client *client)
+{
+ struct imapc_client_connection *conn;
+
+ conn = i_new(struct imapc_client_connection, 1);
+ conn->client = client;
+ conn->conn = imapc_connection_init(client, imapc_client_login_callback,
+ conn);
+ array_push_back(&client->conns, &conn);
+ return conn;
+}
+
+static struct imapc_connection *
+imapc_client_find_connection(struct imapc_client *client)
+{
+ struct imapc_client_connection *const *connp;
+
+ /* FIXME: stupid algorithm */
+ if (array_count(&client->conns) == 0)
+ return imapc_client_add_connection(client)->conn;
+ connp = array_front(&client->conns);
+ return (*connp)->conn;
+}
+
+struct imapc_command *
+imapc_client_cmd(struct imapc_client *client,
+ imapc_command_callback_t *callback, void *context)
+{
+ struct imapc_connection *conn;
+
+ conn = imapc_client_find_connection(client);
+ return imapc_connection_cmd(conn, callback, context);
+}
+
+static struct imapc_client_connection *
+imapc_client_get_unboxed_connection(struct imapc_client *client)
+{
+ struct imapc_client_connection *const *conns;
+ unsigned int i, count;
+
+ conns = array_get(&client->conns, &count);
+ for (i = 0; i < count; i++) {
+ if (conns[i]->box == NULL)
+ return conns[i];
+ }
+ return imapc_client_add_connection(client);
+}
+
+
+void imapc_client_login(struct imapc_client *client)
+{
+ struct imapc_client_connection *conn;
+
+ i_assert(client->login_callback != NULL);
+ i_assert(array_count(&client->conns) == 0);
+
+ conn = imapc_client_add_connection(client);
+ imapc_connection_connect(conn->conn);
+}
+
+struct imapc_logout_ctx {
+ struct imapc_client *client;
+ unsigned int logout_count;
+};
+
+static void
+imapc_client_logout_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
+ void *context)
+{
+ struct imapc_logout_ctx *ctx = context;
+
+ i_assert(ctx->logout_count > 0);
+
+ if (--ctx->logout_count == 0)
+ imapc_client_stop(ctx->client);
+}
+
+void imapc_client_logout(struct imapc_client *client)
+{
+ struct imapc_logout_ctx ctx = { .client = client };
+ struct imapc_client_connection *conn;
+ struct imapc_command *cmd;
+
+ client->logging_out = TRUE;
+
+ /* send LOGOUT to all connections */
+ array_foreach_elem(&client->conns, conn) {
+ if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DISCONNECTED)
+ continue;
+ imapc_connection_set_no_reconnect(conn->conn);
+ ctx.logout_count++;
+ cmd = imapc_connection_cmd(conn->conn,
+ imapc_client_logout_callback, &ctx);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN |
+ IMAPC_COMMAND_FLAG_LOGOUT);
+ imapc_command_send(cmd, "LOGOUT");
+ }
+
+ /* wait for LOGOUT to finish */
+ while (ctx.logout_count > 0)
+ imapc_client_run(client);
+
+ /* we should have disconnected all clients already, but if there were
+ any timeouts there may be some clients left. */
+ imapc_client_disconnect(client);
+}
+
+struct imapc_client_mailbox *
+imapc_client_mailbox_open(struct imapc_client *client,
+ void *untagged_box_context)
+{
+ struct imapc_client_mailbox *box;
+ struct imapc_client_connection *conn;
+
+ box = i_new(struct imapc_client_mailbox, 1);
+ box->client = client;
+ box->untagged_box_context = untagged_box_context;
+ conn = imapc_client_get_unboxed_connection(client);
+ conn->box = box;
+ box->conn = conn->conn;
+ box->msgmap = imapc_msgmap_init();
+ /* if we get disconnected before the SELECT is finished, allow
+ one reconnect retry. */
+ box->reconnect_ok = TRUE;
+ return box;
+}
+
+void imapc_client_mailbox_set_reopen_cb(struct imapc_client_mailbox *box,
+ void (*callback)(void *context),
+ void *context)
+{
+ box->reopen_callback = callback;
+ box->reopen_context = context;
+}
+
+bool imapc_client_mailbox_can_reconnect(struct imapc_client_mailbox *box)
+{
+ /* the reconnect_ok flag attempts to avoid infinite reconnection loops
+ to a server that keeps disconnecting us (e.g. some of the commands
+ we send keeps crashing it always) */
+ return box->reopen_callback != NULL && box->reconnect_ok;
+}
+
+void imapc_client_mailbox_reconnect(struct imapc_client_mailbox *box,
+ const char *errmsg)
+{
+ imapc_connection_try_reconnect(box->conn, errmsg, 0, FALSE);
+}
+
+void imapc_client_mailbox_close(struct imapc_client_mailbox **_box)
+{
+ struct imapc_client_mailbox *box = *_box;
+ struct imapc_client_connection *conn;
+
+ box->closing = TRUE;
+
+ /* cancel any pending commands */
+ imapc_connection_unselect(box);
+
+ if (box->reconnecting) {
+ /* need to abort the reconnection so it won't try to access
+ the box */
+ imapc_connection_disconnect(box->conn);
+ }
+
+ /* set this only after unselect, which may cancel some commands that
+ reference this box */
+ *_box = NULL;
+
+ array_foreach_elem(&box->client->conns, conn) {
+ if (conn->box == box) {
+ conn->box = NULL;
+ break;
+ }
+ }
+
+ imapc_msgmap_deinit(&box->msgmap);
+ timeout_remove(&box->to_send_idle);
+ i_free(box);
+}
+
+struct imapc_command *
+imapc_client_mailbox_cmd(struct imapc_client_mailbox *box,
+ imapc_command_callback_t *callback, void *context)
+{
+ struct imapc_command *cmd;
+
+ i_assert(!box->closing);
+
+ cmd = imapc_connection_cmd(box->conn, callback, context);
+ imapc_command_set_mailbox(cmd, box);
+ return cmd;
+}
+
+struct imapc_msgmap *
+imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box)
+{
+ return box->msgmap;
+}
+
+static void imapc_client_mailbox_idle_send(struct imapc_client_mailbox *box)
+{
+ timeout_remove(&box->to_send_idle);
+ if (imapc_client_mailbox_is_opened(box))
+ imapc_connection_idle(box->conn);
+}
+
+void imapc_client_mailbox_idle(struct imapc_client_mailbox *box)
+{
+ /* send the IDLE with a delay to avoid unnecessary IDLEs that are
+ immediately aborted */
+ if (box->to_send_idle == NULL && imapc_client_mailbox_is_opened(box)) {
+ box->to_send_idle =
+ timeout_add_short(IMAPC_CLIENT_IDLE_SEND_DELAY_MSECS,
+ imapc_client_mailbox_idle_send, box);
+ }
+ /* we're done with all work at this point. */
+ box->reconnect_ok = TRUE;
+}
+
+bool imapc_client_mailbox_is_opened(struct imapc_client_mailbox *box)
+{
+ struct imapc_client_mailbox *selected_box;
+
+ if (box->closing ||
+ imapc_connection_get_state(box->conn) != IMAPC_CONNECTION_STATE_DONE)
+ return FALSE;
+
+ selected_box = imapc_connection_get_mailbox(box->conn);
+ if (selected_box != box) {
+ if (selected_box != NULL)
+ i_error("imapc: Selected mailbox changed unexpectedly");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+imapc_client_get_any_capabilities(struct imapc_client *client,
+ enum imapc_capability *capabilities_r)
+{
+ struct imapc_client_connection *conn;
+
+ array_foreach_elem(&client->conns, conn) {
+ if (imapc_connection_get_state(conn->conn) == IMAPC_CONNECTION_STATE_DONE) {
+ *capabilities_r = imapc_connection_get_capabilities(conn->conn);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+int imapc_client_get_capabilities(struct imapc_client *client,
+ enum imapc_capability *capabilities_r)
+{
+ /* try to find a connection that is already logged in */
+ if (imapc_client_get_any_capabilities(client, capabilities_r))
+ return 0;
+
+ /* if there are no connections yet, create one */
+ if (array_count(&client->conns) == 0)
+ (void)imapc_client_add_connection(client);
+
+ /* wait for any of the connections to login */
+ client->stop_on_state_finish = TRUE;
+ imapc_client_run(client);
+ client->stop_on_state_finish = FALSE;
+ if (imapc_client_get_any_capabilities(client, capabilities_r))
+ return 0;
+
+ /* failed */
+ return -1;
+}
+
+int imapc_client_create_temp_fd(struct imapc_client *client,
+ const char **path_r)
+{
+ string_t *path;
+ int fd;
+
+ if (client->set.temp_path_prefix == NULL) {
+ i_error("imapc: temp_path_prefix not set, "
+ "can't create temp file");
+ return -1;
+ }
+
+ path = t_str_new(128);
+ str_append(path, client->set.temp_path_prefix);
+ 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;
+}
+
+void imapc_client_register_state_change_callback(struct imapc_client *client,
+ imapc_state_change_callback_t *cb,
+ void *context)
+{
+ i_assert(client->state_change_callback == NULL);
+ i_assert(client->state_change_context == NULL);
+
+ client->state_change_callback = cb;
+ client->state_change_context = context;
+}
+
+void
+imapc_client_set_login_callback(struct imapc_client *client,
+ imapc_command_callback_t *callback, void *context)
+{
+ client->login_callback = callback;
+ client->login_context = context;
+}
+
diff --git a/src/lib-imap-client/imapc-client.h b/src/lib-imap-client/imapc-client.h
new file mode 100644
index 0000000..5c81be6
--- /dev/null
+++ b/src/lib-imap-client/imapc-client.h
@@ -0,0 +1,250 @@
+#ifndef IMAPC_CLIENT_H
+#define IMAPC_CLIENT_H
+
+#include "net.h"
+#include "iostream-ssl.h"
+
+/* IMAP RFC defines this to be at least 30 minutes. */
+#define IMAPC_DEFAULT_MAX_IDLE_TIME (60*29)
+
+enum imapc_command_state {
+ IMAPC_COMMAND_STATE_OK = 0,
+ IMAPC_COMMAND_STATE_NO,
+ IMAPC_COMMAND_STATE_BAD,
+ /* Authentication to IMAP server failed (NO or BAD) */
+ IMAPC_COMMAND_STATE_AUTH_FAILED,
+ /* Client was unexpectedly disconnected. */
+ IMAPC_COMMAND_STATE_DISCONNECTED
+};
+extern const char *imapc_command_state_names[];
+
+enum imapc_capability {
+ IMAPC_CAPABILITY_SASL_IR = 0x01,
+ IMAPC_CAPABILITY_LITERALPLUS = 0x02,
+ IMAPC_CAPABILITY_QRESYNC = 0x04,
+ IMAPC_CAPABILITY_IDLE = 0x08,
+ IMAPC_CAPABILITY_UIDPLUS = 0x10,
+ IMAPC_CAPABILITY_AUTH_PLAIN = 0x20,
+ IMAPC_CAPABILITY_STARTTLS = 0x40,
+ IMAPC_CAPABILITY_X_GM_EXT_1 = 0x80,
+ IMAPC_CAPABILITY_CONDSTORE = 0x100,
+ IMAPC_CAPABILITY_NAMESPACE = 0x200,
+ IMAPC_CAPABILITY_UNSELECT = 0x400,
+ IMAPC_CAPABILITY_ESEARCH = 0x800,
+ IMAPC_CAPABILITY_WITHIN = 0x1000,
+ IMAPC_CAPABILITY_QUOTA = 0x2000,
+ IMAPC_CAPABILITY_ID = 0x4000,
+ IMAPC_CAPABILITY_SAVEDATE = 0x8000,
+
+ IMAPC_CAPABILITY_IMAP4REV1 = 0x40000000
+};
+struct imapc_capability_name {
+ const char *name;
+ enum imapc_capability capability;
+};
+extern const struct imapc_capability_name imapc_capability_names[];
+
+enum imapc_command_flags {
+ /* The command changes the selected mailbox (SELECT, EXAMINE) */
+ IMAPC_COMMAND_FLAG_SELECT = 0x01,
+ /* The command is sent to server before login (or is the login
+ command itself). Non-prelogin commands will be queued until login
+ is successful. */
+ IMAPC_COMMAND_FLAG_PRELOGIN = 0x02,
+ /* Allow command to be automatically retried if disconnected before it
+ finishes. */
+ IMAPC_COMMAND_FLAG_RETRIABLE = 0x04,
+ /* This is the LOGOUT command. Use a small timeout for it. */
+ IMAPC_COMMAND_FLAG_LOGOUT = 0x08,
+ /* Command is being resent after a reconnection. */
+ IMAPC_COMMAND_FLAG_RECONNECTED = 0x10
+};
+
+enum imapc_client_ssl_mode {
+ IMAPC_CLIENT_SSL_MODE_NONE,
+ IMAPC_CLIENT_SSL_MODE_IMMEDIATE,
+ IMAPC_CLIENT_SSL_MODE_STARTTLS
+};
+
+#define IMAPC_DEFAULT_CONNECT_TIMEOUT_MSECS (1000*30)
+#define IMAPC_DEFAULT_COMMAND_TIMEOUT_MSECS (1000*60*5)
+#define IMAPC_DEFAULT_MAX_LINE_LENGTH (SIZE_MAX)
+
+struct imapc_throttling_settings {
+ unsigned int init_msecs;
+ unsigned int max_msecs;
+ unsigned int shrink_min_msecs;
+};
+
+struct imapc_client_settings {
+ const char *host;
+ in_port_t port;
+
+ const char *master_user;
+ const char *username;
+ const char *password;
+ /* Space-separated list of SASL mechanisms to try (in the specified
+ order). The default is to use only LOGIN command or SASL PLAIN. */
+ const char *sasl_mechanisms;
+ bool use_proxyauth; /* Use Sun/Oracle PROXYAUTH command */
+ unsigned int max_idle_time;
+ /* If ID capability is advertised, send a unique "x-session-ext-id",
+ which begins with this prefix. */
+ const char *session_id_prefix;
+
+ const char *dns_client_socket_path;
+ const char *temp_path_prefix;
+ struct ssl_iostream_settings ssl_set;
+
+ enum imapc_client_ssl_mode ssl_mode;
+
+ const char *rawlog_dir;
+ bool debug;
+
+ /* Timeout for logging in. 0 = default. */
+ unsigned int connect_timeout_msecs;
+ /* Number of retries, -1 = infinity */
+ unsigned int connect_retry_count;
+ /* Interval between retries, must be > 0 if retries > 0 */
+ unsigned int connect_retry_interval_msecs;
+
+ /* Timeout for IMAP commands. Reset every time more data is being
+ sent or received. 0 = default. */
+ unsigned int cmd_timeout_msecs;
+
+ /* Maximum allowed line length (not including literals read as
+ streams). 0 = unlimited. */
+ size_t max_line_length;
+
+ struct imapc_throttling_settings throttle_set;
+};
+
+struct imapc_command_reply {
+ enum imapc_command_state state;
+ /* "[RESP TEXT]" produces key=RESP, value=TEXT.
+ "[RESP]" produces key=RESP, value=NULL
+ otherwise both are NULL */
+ const char *resp_text_key, *resp_text_value;
+ /* The full tagged reply, including [RESP TEXT]. */
+ const char *text_full;
+ /* Tagged reply text without [RESP TEXT] */
+ const char *text_without_resp;
+};
+
+struct imapc_arg_file {
+ /* file descriptor containing the value */
+ int fd;
+
+ /* parent_arg.list[list_idx] points to the IMAP_ARG_LITERAL_SIZE
+ argument */
+ const struct imap_arg *parent_arg;
+ unsigned int list_idx;
+};
+
+struct imapc_untagged_reply {
+ /* name of the untagged reply, e.g. EXISTS */
+ const char *name;
+ /* number at the beginning of the reply, or 0 if there wasn't any.
+ Set for EXISTS, EXPUNGE, etc. */
+ uint32_t num;
+ /* the rest of the reply can be read from these args. */
+ const struct imap_arg *args;
+ /* arguments whose contents are stored into files. only
+ "FETCH (BODY[" arguments can be here. */
+ const struct imapc_arg_file *file_args;
+ unsigned int file_args_count;
+
+ /* "* OK [RESP TEXT]" produces key=RESP, value=TEXT.
+ "* OK [RESP]" produces key=RESP, value=NULL
+ otherwise both are NULL */
+ const char *resp_text_key, *resp_text_value;
+
+ /* If this reply occurred while a mailbox was selected, this contains
+ the mailbox's untagged_context. */
+ void *untagged_box_context;
+};
+
+enum imapc_state_change_event {
+ IMAPC_STATE_CHANGE_AUTH_OK,
+ IMAPC_STATE_CHANGE_AUTH_FAILED,
+};
+
+/* Called when tagged reply is received for command. */
+typedef void imapc_command_callback_t(const struct imapc_command_reply *reply,
+ void *context);
+/* Called each time untagged input is received. */
+typedef void imapc_untagged_callback_t(const struct imapc_untagged_reply *reply,
+ void *context);
+typedef void imapc_state_change_callback_t(void *context,
+ enum imapc_state_change_event event,
+ const char *error);
+
+struct imapc_client *
+imapc_client_init(const struct imapc_client_settings *set,
+ struct event *event_parent);
+void imapc_client_disconnect(struct imapc_client *client);
+void imapc_client_deinit(struct imapc_client **client);
+
+/* Set login callback, must be set before calling other commands.
+ This is called only for the first login, not for any reconnects or if there
+ are multiple connections created. */
+void
+imapc_client_set_login_callback(struct imapc_client *client,
+ imapc_command_callback_t *callback, void *context);
+/* Explicitly login to server (also done automatically). */
+void imapc_client_login(struct imapc_client *client);
+/* Send a LOGOUT and wait for disconnection. */
+void imapc_client_logout(struct imapc_client *client);
+
+struct imapc_command *
+imapc_client_cmd(struct imapc_client *client,
+ imapc_command_callback_t *callback, void *context);
+void imapc_command_set_flags(struct imapc_command *cmd,
+ enum imapc_command_flags flags);
+bool imapc_command_connection_is_selected(struct imapc_command *cmd);
+void imapc_command_send(struct imapc_command *cmd, const char *cmd_str);
+void imapc_command_sendf(struct imapc_command *cmd, const char *cmd_fmt, ...)
+ ATTR_FORMAT(2, 3);
+void imapc_command_sendvf(struct imapc_command *cmd,
+ const char *cmd_fmt, va_list args) ATTR_FORMAT(2, 0);
+const char *imapc_command_get_tag(struct imapc_command *cmd);
+void imapc_command_abort(struct imapc_command **cmd);
+
+void imapc_client_register_untagged(struct imapc_client *client,
+ imapc_untagged_callback_t *callback,
+ void *context);
+
+void imapc_client_run(struct imapc_client *client);
+void imapc_client_stop(struct imapc_client *client);
+bool imapc_client_is_running(struct imapc_client *client);
+
+struct imapc_client_mailbox *
+imapc_client_mailbox_open(struct imapc_client *client,
+ void *untagged_box_context);
+void imapc_client_mailbox_set_reopen_cb(struct imapc_client_mailbox *box,
+ void (*callback)(void *context),
+ void *context);
+void imapc_client_mailbox_close(struct imapc_client_mailbox **box);
+bool imapc_client_mailbox_can_reconnect(struct imapc_client_mailbox *box);
+void imapc_client_mailbox_reconnect(struct imapc_client_mailbox *box,
+ const char *errmsg);
+struct imapc_command *
+imapc_client_mailbox_cmd(struct imapc_client_mailbox *box,
+ imapc_command_callback_t *callback, void *context);
+struct imapc_msgmap *
+imapc_client_mailbox_get_msgmap(struct imapc_client_mailbox *box);
+
+void imapc_client_mailbox_idle(struct imapc_client_mailbox *box);
+bool imapc_client_mailbox_is_opened(struct imapc_client_mailbox *box);
+
+int imapc_client_get_capabilities(struct imapc_client *client,
+ enum imapc_capability *capabilities_r);
+
+int imapc_client_create_temp_fd(struct imapc_client *client,
+ const char **path_r);
+
+void imapc_client_register_state_change_callback(struct imapc_client *client,
+ imapc_state_change_callback_t *cb,
+ void *context);
+
+#endif
diff --git a/src/lib-imap-client/imapc-connection.c b/src/lib-imap-client/imapc-connection.c
new file mode 100644
index 0000000..5730f94
--- /dev/null
+++ b/src/lib-imap-client/imapc-connection.c
@@ -0,0 +1,2513 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "net.h"
+#include "istream.h"
+#include "ostream.h"
+#include "base64.h"
+#include "write-full.h"
+#include "str.h"
+#include "time-util.h"
+#include "dns-lookup.h"
+#include "dsasl-client.h"
+#include "iostream-rawlog.h"
+#include "iostream-ssl.h"
+#include "imap-quote.h"
+#include "imap-util.h"
+#include "imap-parser.h"
+#include "imapc-client-private.h"
+#include "imapc-connection.h"
+
+#include <unistd.h>
+#include <ctype.h>
+
+#define IMAPC_COMMAND_STATE_AUTHENTICATE_CONTINUE 10000
+#define IMAPC_MAX_INLINE_LITERAL_SIZE (1024*32)
+/* If LOGOUT reply takes longer than this, disconnect. */
+#define IMAPC_LOGOUT_TIMEOUT_MSECS 5000
+
+enum imapc_input_state {
+ IMAPC_INPUT_STATE_NONE = 0,
+ IMAPC_INPUT_STATE_PLUS,
+ IMAPC_INPUT_STATE_UNTAGGED,
+ IMAPC_INPUT_STATE_UNTAGGED_NUM,
+ IMAPC_INPUT_STATE_TAGGED
+};
+
+struct imapc_command_stream {
+ unsigned int pos;
+ uoff_t size;
+ struct istream *input;
+};
+
+struct imapc_command {
+ pool_t pool;
+ buffer_t *data;
+ unsigned int send_pos;
+ unsigned int tag;
+
+ enum imapc_command_flags flags;
+ struct imapc_connection *conn;
+ /* If non-NULL, points to the mailbox where this command should be
+ executed */
+ struct imapc_client_mailbox *box;
+
+ ARRAY(struct imapc_command_stream) streams;
+
+ imapc_command_callback_t *callback;
+ void *context;
+
+ /* This is the AUTHENTICATE command */
+ bool authenticate:1;
+ /* This is the IDLE command */
+ bool idle:1;
+ /* Waiting for '+' literal reply before we can continue */
+ bool wait_for_literal:1;
+ /* Command is fully sent to server */
+ bool sent:1;
+};
+ARRAY_DEFINE_TYPE(imapc_command, struct imapc_command *);
+
+struct imapc_connection_literal {
+ char *temp_path;
+ int fd;
+ uoff_t bytes_left;
+
+ const struct imap_arg *parent_arg;
+ unsigned int list_idx;
+};
+
+struct imapc_connection {
+ struct imapc_client *client;
+ char *name;
+ int refcount;
+
+ int fd;
+ struct io *io;
+ struct istream *input, *raw_input;
+ struct ostream *output, *raw_output;
+ struct imap_parser *parser;
+ struct timeout *to;
+ struct timeout *to_output;
+ struct dns_lookup *dns_lookup;
+ struct dsasl_client *sasl_client;
+
+ struct ssl_iostream *ssl_iostream;
+
+ int (*input_callback)(struct imapc_connection *conn);
+ enum imapc_input_state input_state;
+ unsigned int cur_tag;
+ uint32_t cur_num;
+ struct timeval last_connect;
+ unsigned int reconnect_count;
+
+ struct imapc_client_mailbox *selecting_box, *selected_box;
+ enum imapc_connection_state state;
+ char *disconnect_reason;
+
+ enum imapc_capability capabilities;
+ char **capabilities_list;
+
+ imapc_command_callback_t *login_callback;
+ void *login_context;
+
+ /* commands pending in queue to be sent */
+ ARRAY_TYPE(imapc_command) cmd_send_queue;
+ /* commands that have been sent, waiting for their tagged reply */
+ ARRAY_TYPE(imapc_command) cmd_wait_list;
+ /* commands that were already sent, but were aborted since (due to
+ unselecting mailbox). */
+ ARRAY_TYPE(seq_range) aborted_cmd_tags;
+ unsigned int reconnect_command_count;
+
+ unsigned int ips_count, prev_connect_idx;
+ struct ip_addr *ips;
+
+ struct imapc_connection_literal literal;
+ ARRAY(struct imapc_arg_file) literal_files;
+
+ unsigned int throttle_msecs;
+ unsigned int throttle_shrink_msecs;
+ unsigned int last_successful_throttle_msecs;
+ bool throttle_pending;
+ struct timeval throttle_end_timeval;
+ struct timeout *to_throttle, *to_throttle_shrink;
+
+ bool reconnecting:1;
+ bool reconnect_waiting:1;
+ bool reconnect_ok:1;
+ bool idling:1;
+ bool idle_stopping:1;
+ bool idle_plus_waiting:1;
+ bool select_waiting_reply:1;
+};
+
+static void imapc_connection_capability_cb(const struct imapc_command_reply *reply,
+ void *context);
+static int imapc_connection_output(struct imapc_connection *conn);
+static int imapc_connection_ssl_init(struct imapc_connection *conn);
+static void imapc_command_free(struct imapc_command *cmd);
+static void imapc_command_send_more(struct imapc_connection *conn);
+static void
+imapc_login_callback(struct imapc_connection *conn,
+ const struct imapc_command_reply *reply);
+
+static void
+imapc_auth_ok(struct imapc_connection *conn)
+{
+ if (conn->client->set.debug)
+ i_debug("imapc(%s): Authenticated successfully", conn->name);
+
+ if (conn->client->state_change_callback == NULL)
+ return;
+
+ conn->client->state_change_callback(conn->client->state_change_context,
+ IMAPC_STATE_CHANGE_AUTH_OK, NULL);
+}
+
+static void
+imapc_auth_failed(struct imapc_connection *conn, const struct imapc_command_reply *_reply,
+ const char *error)
+{
+ struct imapc_command_reply reply = *_reply;
+
+ reply.text_without_resp = reply.text_full =
+ t_strdup_printf("Authentication failed: %s", error);
+ if (reply.state != IMAPC_COMMAND_STATE_DISCONNECTED) {
+ reply.state = IMAPC_COMMAND_STATE_AUTH_FAILED;
+ i_error("imapc(%s): %s", conn->name, reply.text_full);
+ }
+ imapc_login_callback(conn, &reply);
+
+ if (conn->client->state_change_callback == NULL)
+ return;
+
+ conn->client->state_change_callback(conn->client->state_change_context,
+ IMAPC_STATE_CHANGE_AUTH_FAILED,
+ error);
+}
+
+struct imapc_connection *
+imapc_connection_init(struct imapc_client *client,
+ imapc_command_callback_t *login_callback,
+ void *login_context)
+{
+ struct imapc_connection *conn;
+
+ conn = i_new(struct imapc_connection, 1);
+ conn->refcount = 1;
+ conn->client = client;
+ conn->login_callback = login_callback;
+ conn->login_context = login_context;
+ conn->fd = -1;
+ conn->name = i_strdup_printf("%s:%u", client->set.host,
+ client->set.port);
+ conn->literal.fd = -1;
+ conn->reconnect_ok = (client->set.connect_retry_count>0);
+ i_array_init(&conn->cmd_send_queue, 8);
+ i_array_init(&conn->cmd_wait_list, 32);
+ i_array_init(&conn->literal_files, 4);
+ i_array_init(&conn->aborted_cmd_tags, 8);
+
+ if (client->set.debug)
+ i_debug("imapc(%s): Created new connection", conn->name);
+
+ imapc_client_ref(client);
+ return conn;
+}
+
+static void imapc_connection_ref(struct imapc_connection *conn)
+{
+ i_assert(conn->refcount > 0);
+
+ conn->refcount++;
+}
+
+static void imapc_connection_unref(struct imapc_connection **_conn)
+{
+ struct imapc_connection *conn = *_conn;
+
+ i_assert(conn->refcount > 0);
+
+ *_conn = NULL;
+ if (--conn->refcount > 0)
+ return;
+
+ i_assert(conn->disconnect_reason == NULL);
+
+ if (conn->capabilities_list != NULL)
+ p_strsplit_free(default_pool, conn->capabilities_list);
+ array_free(&conn->cmd_send_queue);
+ array_free(&conn->cmd_wait_list);
+ array_free(&conn->literal_files);
+ array_free(&conn->aborted_cmd_tags);
+ imapc_client_unref(&conn->client);
+ i_free(conn->ips);
+ i_free(conn->name);
+ i_free(conn);
+}
+
+void imapc_connection_deinit(struct imapc_connection **_conn)
+{
+ imapc_connection_disconnect(*_conn);
+ imapc_connection_unref(_conn);
+}
+
+void imapc_connection_ioloop_changed(struct imapc_connection *conn)
+{
+ if (conn->io != NULL)
+ conn->io = io_loop_move_io(&conn->io);
+ if (conn->to != NULL)
+ conn->to = io_loop_move_timeout(&conn->to);
+ if (conn->to_throttle != NULL)
+ conn->to_throttle = io_loop_move_timeout(&conn->to_throttle);
+ if (conn->to_throttle_shrink != NULL)
+ conn->to_throttle_shrink = io_loop_move_timeout(&conn->to_throttle_shrink);
+ if (conn->output != NULL)
+ o_stream_switch_ioloop(conn->output);
+ if (conn->dns_lookup != NULL)
+ dns_lookup_switch_ioloop(conn->dns_lookup);
+
+ if (conn->client->ioloop == NULL && conn->to_output != NULL) {
+ /* we're only once moving the to_output to the main ioloop,
+ since timeout moves currently also reset the timeout.
+ (the rest of the times this is a no-op) */
+ conn->to_output = io_loop_move_timeout(&conn->to_output);
+ }
+}
+
+static const char *imapc_command_get_readable(struct imapc_command *cmd)
+{
+ string_t *str = t_str_new(256);
+ const unsigned char *data = cmd->data->data;
+ unsigned int i;
+
+ for (i = 0; i < cmd->data->used; i++) {
+ if (data[i] != '\r' && data[i] != '\n')
+ str_append_c(str, data[i]);
+ }
+ return str_c(str);
+}
+
+static void
+imapc_connection_abort_commands_array(ARRAY_TYPE(imapc_command) *cmd_array,
+ ARRAY_TYPE(imapc_command) *dest_array,
+ struct imapc_client_mailbox *only_box,
+ bool keep_retriable)
+{
+ struct imapc_command *cmd;
+ unsigned int i;
+
+ for (i = 0; i < array_count(cmd_array); ) {
+ cmd = array_idx_elem(cmd_array, i);
+
+ if (cmd->box != only_box && only_box != NULL)
+ i++;
+ else if (keep_retriable &&
+ (cmd->flags & IMAPC_COMMAND_FLAG_RETRIABLE) != 0) {
+ cmd->send_pos = 0;
+ cmd->wait_for_literal = 0;
+ cmd->flags |= IMAPC_COMMAND_FLAG_RECONNECTED;
+ i++;
+ } else {
+ array_delete(cmd_array, i, 1);
+ array_push_back(dest_array, &cmd);
+ }
+ }
+}
+
+void imapc_connection_abort_commands(struct imapc_connection *conn,
+ struct imapc_client_mailbox *only_box,
+ bool keep_retriable)
+{
+ struct imapc_command *cmd;
+ ARRAY_TYPE(imapc_command) tmp_array;
+ struct imapc_command_reply reply;
+
+ t_array_init(&tmp_array, 8);
+ imapc_connection_abort_commands_array(&conn->cmd_wait_list, &tmp_array,
+ only_box, keep_retriable);
+ imapc_connection_abort_commands_array(&conn->cmd_send_queue, &tmp_array,
+ only_box, keep_retriable);
+
+ if (array_count(&conn->cmd_wait_list) > 0 && only_box == NULL) {
+ /* need to move all the waiting commands to send queue */
+ array_append_array(&conn->cmd_wait_list,
+ &conn->cmd_send_queue);
+ array_clear(&conn->cmd_send_queue);
+ array_append_array(&conn->cmd_send_queue,
+ &conn->cmd_wait_list);
+ array_clear(&conn->cmd_wait_list);
+ }
+
+ /* abort the commands. we'll do it here later so that if the
+ callback recurses us back here we don't crash */
+ i_zero(&reply);
+ reply.state = IMAPC_COMMAND_STATE_DISCONNECTED;
+ if (only_box != NULL) {
+ reply.text_without_resp = reply.text_full =
+ "Unselecting mailbox";
+ } else {
+ reply.text_without_resp = reply.text_full =
+ "Disconnected from server";
+ }
+ array_foreach_elem(&tmp_array, cmd) {
+ if (cmd->sent && conn->state == IMAPC_CONNECTION_STATE_DONE) {
+ /* We're not disconnected, so the reply will still
+ come. Remember that it needs to be ignored. */
+ seq_range_array_add(&conn->aborted_cmd_tags, cmd->tag);
+ }
+ cmd->callback(&reply, cmd->context);
+ imapc_command_free(cmd);
+ }
+ if (array_count(&conn->cmd_wait_list) == 0)
+ timeout_remove(&conn->to);
+}
+
+static void
+imapc_login_callback(struct imapc_connection *conn,
+ const struct imapc_command_reply *reply)
+{
+ if (conn->login_callback != NULL)
+ conn->login_callback(reply, conn->login_context);
+}
+
+static void imapc_connection_set_state(struct imapc_connection *conn,
+ enum imapc_connection_state state)
+{
+ struct imapc_command_reply reply;
+
+ conn->state = state;
+
+ switch (state) {
+ case IMAPC_CONNECTION_STATE_DISCONNECTED:
+ i_zero(&reply);
+ reply.state = IMAPC_COMMAND_STATE_DISCONNECTED;
+ reply.text_full = "Disconnected from server";
+ if (conn->disconnect_reason != NULL) {
+ reply.text_full = t_strdup_printf("%s: %s",
+ reply.text_full, conn->disconnect_reason);
+ i_free_and_null(conn->disconnect_reason);
+ }
+ reply.text_without_resp = reply.text_full;
+ if (!conn->reconnecting) {
+ imapc_login_callback(conn, &reply);
+ i_free(conn->ips);
+ conn->ips_count = 0;
+ }
+ array_clear(&conn->aborted_cmd_tags);
+ conn->idling = FALSE;
+ conn->idle_plus_waiting = FALSE;
+ conn->idle_stopping = FALSE;
+
+ conn->select_waiting_reply = FALSE;
+ conn->selecting_box = NULL;
+ conn->selected_box = NULL;
+ /* fall through */
+ case IMAPC_CONNECTION_STATE_DONE:
+ /* if we came from imapc_client_get_capabilities(), stop so
+ it can finish up and not just hang indefinitely. */
+ if (conn->client->stop_on_state_finish && !conn->reconnecting)
+ imapc_client_stop(conn->client);
+ break;
+ default:
+ break;
+ }
+}
+
+static void imapc_connection_lfiles_free(struct imapc_connection *conn)
+{
+ struct imapc_arg_file *lfile;
+
+ array_foreach_modifiable(&conn->literal_files, lfile) {
+ if (close(lfile->fd) < 0)
+ i_error("imapc: close(literal file) failed: %m");
+ }
+ array_clear(&conn->literal_files);
+}
+
+static void
+imapc_connection_literal_reset(struct imapc_connection_literal *literal)
+{
+ i_close_fd_path(&literal->fd, literal->temp_path);
+ i_free_and_null(literal->temp_path);
+
+ i_zero(literal);
+ literal->fd = -1;
+}
+
+void imapc_connection_disconnect_full(struct imapc_connection *conn,
+ bool reconnecting)
+{
+ /* timeout may be set also in disconnected state */
+ timeout_remove(&conn->to);
+ conn->reconnecting = reconnecting;
+
+ if (conn->state == IMAPC_CONNECTION_STATE_DISCONNECTED) {
+ i_assert(array_count(&conn->cmd_wait_list) == 0);
+ return;
+ }
+
+ if (conn->client->set.debug)
+ i_debug("imapc(%s): Disconnected", conn->name);
+
+ if (conn->dns_lookup != NULL)
+ dns_lookup_abort(&conn->dns_lookup);
+ imapc_connection_lfiles_free(conn);
+ imapc_connection_literal_reset(&conn->literal);
+ timeout_remove(&conn->to_output);
+ timeout_remove(&conn->to_throttle);
+ timeout_remove(&conn->to_throttle_shrink);
+ if (conn->parser != NULL)
+ imap_parser_unref(&conn->parser);
+ io_remove(&conn->io);
+ ssl_iostream_destroy(&conn->ssl_iostream);
+ if (conn->fd != -1) {
+ i_stream_destroy(&conn->input);
+ o_stream_destroy(&conn->output);
+ net_disconnect(conn->fd);
+ conn->fd = -1;
+ }
+
+ /* get capabilities again after reconnection. this is especially
+ important because post-login capabilities often do not contain AUTH=
+ capabilities. */
+ conn->capabilities = 0;
+ if (conn->capabilities_list != NULL) {
+ p_strsplit_free(default_pool, conn->capabilities_list);
+ conn->capabilities_list = NULL;
+ }
+
+ imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED);
+ imapc_connection_abort_commands(conn, NULL, reconnecting);
+
+ if (!reconnecting) {
+ imapc_client_try_stop(conn->client);
+ }
+}
+
+void imapc_connection_set_no_reconnect(struct imapc_connection *conn)
+{
+ conn->reconnect_ok = FALSE;
+}
+
+void imapc_connection_disconnect(struct imapc_connection *conn)
+{
+ imapc_connection_disconnect_full(conn, FALSE);
+}
+
+static void imapc_connection_set_disconnected(struct imapc_connection *conn)
+{
+ imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DISCONNECTED);
+ imapc_connection_abort_commands(conn, NULL, FALSE);
+}
+
+static bool imapc_connection_can_reconnect(struct imapc_connection *conn)
+{
+ if (conn->client->logging_out)
+ return FALSE;
+ if (conn->client->set.connect_retry_count == 0 ||
+ (conn->client->set.connect_retry_count < UINT_MAX &&
+ conn->reconnect_count >= conn->client->set.connect_retry_count))
+ return FALSE;
+
+ if (conn->selected_box != NULL)
+ return imapc_client_mailbox_can_reconnect(conn->selected_box);
+ else {
+ return conn->reconnect_command_count == 0 &&
+ conn->reconnect_ok;
+ }
+}
+
+static void imapc_connection_reconnect(struct imapc_connection *conn)
+{
+ conn->reconnect_ok = FALSE;
+ conn->reconnect_waiting = FALSE;
+
+ if (conn->selected_box != NULL) {
+ i_assert(!conn->selected_box->reconnecting);
+ conn->selected_box->reconnecting = TRUE;
+ /* if we fail again, avoid reconnecting immediately. if the
+ server is broken we could just get into an infinitely
+ failing reconnection loop. */
+ conn->selected_box->reconnect_ok = FALSE;
+ }
+ imapc_connection_disconnect_full(conn, TRUE);
+ imapc_connection_connect(conn);
+}
+
+void imapc_connection_try_reconnect(struct imapc_connection *conn,
+ const char *errstr,
+ unsigned int delay_msecs,
+ bool connect_error)
+{
+ /* Try the next IP address only for connect() problems. */
+ if (conn->prev_connect_idx + 1 < conn->ips_count && connect_error) {
+ i_warning("imapc(%s): %s - trying the next IP", conn->name, errstr);
+ conn->reconnect_ok = TRUE;
+ imapc_connection_disconnect_full(conn, TRUE);
+ imapc_connection_connect(conn);
+ return;
+ }
+
+ if (!imapc_connection_can_reconnect(conn)) {
+ i_error("imapc(%s): %s - disconnecting", conn->name, errstr);
+ imapc_connection_disconnect(conn);
+ } else {
+ conn->reconnecting = TRUE;
+ i_warning("imapc(%s): %s - reconnecting (delay %u ms)", conn->name, errstr, delay_msecs);
+ if (delay_msecs == 0)
+ imapc_connection_reconnect(conn);
+ else {
+ imapc_connection_disconnect_full(conn, TRUE);
+ conn->to = timeout_add(delay_msecs, imapc_connection_reconnect, conn);
+ conn->reconnect_count++;
+ conn->reconnect_waiting = TRUE;
+ }
+ }
+}
+
+static void ATTR_FORMAT(2, 3)
+imapc_connection_input_error(struct imapc_connection *conn,
+ const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ i_error("imapc(%s): Server sent invalid input: %s",
+ conn->name, t_strdup_vprintf(fmt, va));
+ imapc_connection_disconnect(conn);
+ va_end(va);
+}
+
+static bool last_arg_is_fetch_body(const struct imap_arg *args,
+ const struct imap_arg **parent_arg_r,
+ unsigned int *idx_r)
+{
+ const struct imap_arg *list;
+ const char *name;
+ unsigned int count;
+
+ if (args[0].type == IMAP_ARG_ATOM &&
+ imap_arg_atom_equals(&args[1], "FETCH") &&
+ imap_arg_get_list_full(&args[2], &list, &count) && count >= 2 &&
+ list[count].type == IMAP_ARG_LITERAL_SIZE &&
+ imap_arg_get_atom(&list[count-1], &name) &&
+ strncasecmp(name, "BODY[", 5) == 0) {
+ *parent_arg_r = &args[2];
+ *idx_r = count;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int
+imapc_connection_read_literal_init(struct imapc_connection *conn, uoff_t size,
+ const struct imap_arg *args)
+{
+ const char *path;
+ const struct imap_arg *parent_arg;
+ unsigned int idx;
+
+ i_assert(conn->literal.fd == -1);
+
+ if (size <= IMAPC_MAX_INLINE_LITERAL_SIZE ||
+ !last_arg_is_fetch_body(args, &parent_arg, &idx)) {
+ /* read the literal directly into parser */
+ return 0;
+ }
+
+ conn->literal.fd = imapc_client_create_temp_fd(conn->client, &path);
+ if (conn->literal.fd == -1)
+ return -1;
+ conn->literal.temp_path = i_strdup(path);
+ conn->literal.bytes_left = size;
+ conn->literal.parent_arg = parent_arg;
+ conn->literal.list_idx = idx;
+ return 1;
+}
+
+static int imapc_connection_read_literal(struct imapc_connection *conn)
+{
+ struct imapc_arg_file *lfile;
+ const unsigned char *data;
+ size_t size;
+
+ if (conn->literal.bytes_left == 0)
+ return 1;
+
+ data = i_stream_get_data(conn->input, &size);
+ if (size > conn->literal.bytes_left)
+ size = conn->literal.bytes_left;
+ if (size > 0) {
+ if (write_full(conn->literal.fd, data, size) < 0) {
+ i_error("imapc(%s): write(%s) failed: %m",
+ conn->name, conn->literal.temp_path);
+ imapc_connection_disconnect(conn);
+ return -1;
+ }
+ i_stream_skip(conn->input, size);
+ conn->literal.bytes_left -= size;
+ }
+ if (conn->literal.bytes_left > 0)
+ return 0;
+
+ /* finished */
+ lfile = array_append_space(&conn->literal_files);
+ lfile->fd = conn->literal.fd;
+ lfile->parent_arg = conn->literal.parent_arg;
+ lfile->list_idx = conn->literal.list_idx;
+
+ conn->literal.fd = -1;
+ imapc_connection_literal_reset(&conn->literal);
+ return 1;
+}
+
+static int
+imapc_connection_read_line_more(struct imapc_connection *conn,
+ const struct imap_arg **imap_args_r)
+{
+ uoff_t literal_size;
+ int ret;
+
+ if ((ret = imapc_connection_read_literal(conn)) <= 0)
+ return ret;
+
+ ret = imap_parser_read_args(conn->parser, 0,
+ IMAP_PARSE_FLAG_LITERAL_SIZE |
+ IMAP_PARSE_FLAG_ATOM_ALLCHARS |
+ IMAP_PARSE_FLAG_LITERAL8 |
+ IMAP_PARSE_FLAG_SERVER_TEXT, imap_args_r);
+ if (ret == -2) {
+ /* need more data */
+ return 0;
+ }
+ if (ret < 0) {
+ enum imap_parser_error parser_error;
+ const char *err_msg = imap_parser_get_error(conn->parser, &parser_error);
+ if (parser_error != IMAP_PARSE_ERROR_BAD_SYNTAX)
+ imapc_connection_input_error(conn, "Error parsing input: %s", err_msg);
+ else
+ i_error("Error parsing input: %s", err_msg);
+ return -1;
+ }
+
+ if (imap_parser_get_literal_size(conn->parser, &literal_size)) {
+ if (imapc_connection_read_literal_init(conn, literal_size,
+ *imap_args_r) <= 0) {
+ imap_parser_read_last_literal(conn->parser);
+ return 2;
+ }
+ return imapc_connection_read_line_more(conn, imap_args_r);
+ }
+ return 1;
+}
+
+static int
+imapc_connection_read_line(struct imapc_connection *conn,
+ const struct imap_arg **imap_args_r)
+{
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ while ((ret = imapc_connection_read_line_more(conn, imap_args_r)) == 2)
+ ;
+
+ if (ret > 0) {
+ data = i_stream_get_data(conn->input, &size);
+ if (size >= 2 && data[0] == '\r' && data[1] == '\n')
+ i_stream_skip(conn->input, 2);
+ else if (size >= 1 && data[0] == '\n')
+ i_stream_skip(conn->input, 1);
+ else
+ i_panic("imapc: Missing LF from input line");
+ } else if (ret < 0) {
+ data = i_stream_get_data(conn->input, &size);
+ unsigned char *lf = memchr(data, '\n', size);
+ if (lf != NULL)
+ i_stream_skip(conn->input, (lf - data) + 1);
+ }
+ return ret;
+}
+
+static int
+imapc_connection_parse_capability(struct imapc_connection *conn,
+ const char *value)
+{
+ const char *const *tmp;
+ unsigned int i;
+
+ if (conn->client->set.debug) {
+ i_debug("imapc(%s): Server capabilities: %s",
+ conn->name, value);
+ }
+
+ conn->capabilities = 0;
+ if (conn->capabilities_list != NULL)
+ p_strsplit_free(default_pool, conn->capabilities_list);
+ conn->capabilities_list = p_strsplit(default_pool, value, " ");
+
+ for (tmp = t_strsplit(value, " "); *tmp != NULL; tmp++) {
+ for (i = 0; imapc_capability_names[i].name != NULL; i++) {
+ const struct imapc_capability_name *cap =
+ &imapc_capability_names[i];
+
+ if (strcasecmp(*tmp, cap->name) == 0) {
+ conn->capabilities |= cap->capability;
+ break;
+ }
+ }
+ }
+
+ if ((conn->capabilities & IMAPC_CAPABILITY_IMAP4REV1) == 0) {
+ imapc_connection_input_error(conn,
+ "CAPABILITY list is missing IMAP4REV1");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+imapc_connection_handle_resp_text_code(struct imapc_connection *conn,
+ const char *key, const char *value)
+{
+ if (strcasecmp(key, "CAPABILITY") == 0) {
+ if (imapc_connection_parse_capability(conn, value) < 0)
+ return -1;
+ }
+ if (strcasecmp(key, "CLOSED") == 0) {
+ /* QRESYNC: SELECTing another mailbox */
+ if (conn->selecting_box != NULL) {
+ conn->selected_box = conn->selecting_box;
+ conn->selecting_box = NULL;
+ }
+ }
+ return 0;
+}
+
+static int
+imapc_connection_handle_resp_text(struct imapc_connection *conn,
+ const char *text,
+ const char **key_r, const char **value_r)
+{
+ const char *p, *value;
+
+ i_assert(text[0] == '[');
+
+ p = strchr(text, ']');
+ if (p == NULL) {
+ imapc_connection_input_error(conn, "Missing ']' in resp-text");
+ return -1;
+ }
+ text = t_strdup_until(text + 1, p);
+ value = strchr(text, ' ');
+ if (value != NULL) {
+ *key_r = t_strdup_until(text, value);
+ *value_r = value + 1;
+ } else {
+ *key_r = text;
+ *value_r = "";
+ }
+ return imapc_connection_handle_resp_text_code(conn, *key_r, *value_r);
+}
+
+static int
+imapc_connection_handle_imap_resp_text(struct imapc_connection *conn,
+ const struct imap_arg *args,
+ const char **key_r, const char **value_r)
+{
+ const char *text;
+
+ if (args->type != IMAP_ARG_ATOM)
+ return 0;
+
+ text = imap_args_to_str(args);
+ if (*text != '[') {
+ if (*text == '\0') {
+ imapc_connection_input_error(conn,
+ "Missing text in resp-text");
+ return -1;
+ }
+ return 0;
+ }
+ return imapc_connection_handle_resp_text(conn, text, key_r, value_r);
+}
+
+static bool need_literal(const char *str)
+{
+ unsigned int i;
+
+ for (i = 0; str[i] != '\0'; i++) {
+ unsigned char c = str[i];
+
+ if ((c & 0x80) != 0 || c == '\r' || c == '\n')
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void imapc_connection_input_reset(struct imapc_connection *conn)
+{
+ conn->input_state = IMAPC_INPUT_STATE_NONE;
+ conn->cur_tag = 0;
+ conn->cur_num = 0;
+ if (conn->parser != NULL)
+ imap_parser_reset(conn->parser);
+ imapc_connection_lfiles_free(conn);
+}
+
+static void
+imapc_connection_auth_finish(struct imapc_connection *conn,
+ const struct imapc_command_reply *reply)
+{
+ if (reply->state != IMAPC_COMMAND_STATE_OK) {
+ imapc_auth_failed(conn, reply, reply->text_full);
+ imapc_connection_disconnect(conn);
+ return;
+ }
+
+ imapc_auth_ok(conn);
+
+ i_assert(array_count(&conn->cmd_wait_list) == 0);
+ timeout_remove(&conn->to);
+ imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_DONE);
+ imapc_login_callback(conn, reply);
+
+ imapc_command_send_more(conn);
+}
+
+static void imapc_connection_login_cb(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_connection *conn = context;
+
+ imapc_connection_auth_finish(conn, reply);
+}
+
+static void
+imapc_connection_proxyauth_login_cb(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_connection *conn = context;
+ const struct imapc_client_settings *set = &conn->client->set;
+ struct imapc_command *cmd;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK) {
+ cmd = imapc_connection_cmd(conn, imapc_connection_login_cb,
+ conn);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN);
+ imapc_command_sendf(cmd, "PROXYAUTH %s", set->username);
+ imapc_command_send_more(conn);
+ } else {
+ imapc_connection_auth_finish(conn, reply);
+ }
+}
+
+static void
+imapc_connection_authenticate_cb(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_connection *conn = context;
+ const unsigned char *sasl_output;
+ size_t input_len, sasl_output_len;
+ buffer_t *buf;
+ const char *error;
+
+ if ((int)reply->state != IMAPC_COMMAND_STATE_AUTHENTICATE_CONTINUE) {
+ dsasl_client_free(&conn->sasl_client);
+ imapc_connection_auth_finish(conn, reply);
+ return;
+ }
+
+ input_len = strlen(reply->text_full);
+ buf = t_buffer_create(MAX_BASE64_DECODED_SIZE(input_len));
+ if (base64_decode(reply->text_full, input_len, NULL, buf) < 0) {
+ imapc_auth_failed(conn, reply,
+ t_strdup_printf("Server sent non-base64 input for AUTHENTICATE: %s",
+ reply->text_full));
+ } else if (dsasl_client_input(conn->sasl_client, buf->data, buf->used, &error) < 0) {
+ imapc_auth_failed(conn, reply, error);
+ } else if (dsasl_client_output(conn->sasl_client, &sasl_output,
+ &sasl_output_len, &error) < 0) {
+ imapc_auth_failed(conn, reply, error);
+ } else {
+ string_t *imap_output =
+ t_str_new(MAX_BASE64_ENCODED_SIZE(sasl_output_len)+2);
+ base64_encode(sasl_output, sasl_output_len, imap_output);
+ str_append(imap_output, "\r\n");
+ o_stream_nsend(conn->output, str_data(imap_output),
+ str_len(imap_output));
+ return;
+ }
+ imapc_connection_disconnect(conn);
+}
+
+static bool imapc_connection_have_auth(struct imapc_connection *conn,
+ const char *mech_name)
+{
+ char *const *capa;
+
+ for (capa = conn->capabilities_list; *capa != NULL; capa++) {
+ if (strncasecmp(*capa, "AUTH=", 5) == 0 &&
+ strcasecmp((*capa)+5, mech_name) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int
+imapc_connection_get_sasl_mech(struct imapc_connection *conn,
+ const struct dsasl_client_mech **mech_r,
+ const char **error_r)
+{
+ const struct imapc_client_settings *set = &conn->client->set;
+ const char *const *mechanisms =
+ t_strsplit_spaces(set->sasl_mechanisms, ", ");
+
+ /* find one of the specified SASL mechanisms */
+ for (; *mechanisms != NULL; mechanisms++) {
+ if (imapc_connection_have_auth(conn, *mechanisms)) {
+ *mech_r = dsasl_client_mech_find(*mechanisms);
+ if (*mech_r != NULL)
+ return 0;
+
+ *error_r = t_strdup_printf(
+ "Support for SASL method '%s' is missing", *mechanisms);
+ return -1;
+ }
+ }
+ *error_r = t_strdup_printf("IMAP server doesn't support any of the requested SASL mechanisms: %s",
+ set->sasl_mechanisms);
+ return -1;
+}
+
+static void imapc_connection_authenticate(struct imapc_connection *conn)
+{
+ const struct imapc_client_settings *set = &conn->client->set;
+ struct imapc_command *cmd;
+ struct dsasl_client_settings sasl_set;
+ const struct dsasl_client_mech *sasl_mech = NULL;
+ const char *error;
+
+ if (conn->client->set.debug) {
+ if (set->master_user == NULL) {
+ i_debug("imapc(%s): Authenticating as %s",
+ conn->name, set->username);
+ } else {
+ i_debug("imapc(%s): Authenticating as %s for user %s",
+ conn->name, set->master_user, set->username);
+ }
+ }
+
+ if (set->sasl_mechanisms != NULL && set->sasl_mechanisms[0] != '\0') {
+ if (imapc_connection_get_sasl_mech(conn, &sasl_mech, &error) < 0) {
+ struct imapc_command_reply reply;
+ i_zero(&reply);
+ reply.state = IMAPC_COMMAND_STATE_DISCONNECTED;
+ reply.text_full = "";
+ imapc_auth_failed(conn, &reply, error);
+ imapc_connection_disconnect(conn);
+ return;
+ }
+ }
+
+ if (set->use_proxyauth && set->master_user != NULL) {
+ /* We can use LOGIN command */
+ cmd = imapc_connection_cmd(conn, imapc_connection_proxyauth_login_cb,
+ conn);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN);
+ imapc_command_sendf(cmd, "LOGIN %s %s",
+ set->master_user, set->password);
+ return;
+ }
+ if (sasl_mech == NULL &&
+ ((set->master_user == NULL &&
+ !need_literal(set->username) && !need_literal(set->password)) ||
+ (conn->capabilities & IMAPC_CAPABILITY_AUTH_PLAIN) == 0)) {
+ /* We can use LOGIN command */
+ cmd = imapc_connection_cmd(conn, imapc_connection_login_cb,
+ conn);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN);
+ imapc_command_sendf(cmd, "LOGIN %s %s",
+ set->username, set->password);
+ return;
+ }
+
+ i_zero(&sasl_set);
+ if (set->master_user == NULL)
+ sasl_set.authid = set->username;
+ else {
+ sasl_set.authid = set->master_user;
+ sasl_set.authzid = set->username;
+ }
+ sasl_set.password = set->password;
+
+ if (sasl_mech == NULL)
+ sasl_mech = &dsasl_client_mech_plain;
+ conn->sasl_client = dsasl_client_new(sasl_mech, &sasl_set);
+
+ cmd = imapc_connection_cmd(conn, imapc_connection_authenticate_cb, conn);
+ cmd->authenticate = TRUE;
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN);
+
+ if ((conn->capabilities & IMAPC_CAPABILITY_SASL_IR) != 0) {
+ const unsigned char *sasl_output;
+ size_t sasl_output_len;
+ string_t *sasl_output_base64;
+ const char *error;
+
+ if (dsasl_client_output(conn->sasl_client, &sasl_output,
+ &sasl_output_len, &error) < 0) {
+ i_error("imapc(%s): Failed to create initial SASL reply: %s",
+ conn->name, error);
+ imapc_connection_disconnect(conn);
+ return;
+ }
+ sasl_output_base64 = t_str_new(MAX_BASE64_ENCODED_SIZE(sasl_output_len));
+ base64_encode(sasl_output, sasl_output_len, sasl_output_base64);
+
+ imapc_command_sendf(cmd, "AUTHENTICATE %1s %1s",
+ dsasl_client_mech_get_name(sasl_mech),
+ str_c(sasl_output_base64));
+ } else {
+ imapc_command_sendf(cmd, "AUTHENTICATE %1s",
+ dsasl_client_mech_get_name(sasl_mech));
+ }
+}
+
+static void
+imapc_connection_starttls_cb(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_connection *conn = context;
+ struct imapc_command *cmd;
+
+ if (reply->state != IMAPC_COMMAND_STATE_OK) {
+ imapc_connection_input_error(conn, "STARTTLS failed: %s",
+ reply->text_full);
+ return;
+ }
+
+ if (imapc_connection_ssl_init(conn) < 0)
+ imapc_connection_disconnect(conn);
+ else {
+ /* get updated capabilities */
+ cmd = imapc_connection_cmd(conn, imapc_connection_capability_cb,
+ conn);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN);
+ imapc_command_send(cmd, "CAPABILITY");
+ }
+}
+
+static void
+imapc_connection_id_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
+ void *context ATTR_UNUSED)
+{
+}
+
+static void imapc_connection_send_id(struct imapc_connection *conn)
+{
+ static unsigned int global_id_counter = 0;
+ struct imapc_command *cmd;
+
+ if ((conn->capabilities & IMAPC_CAPABILITY_ID) == 0 ||
+ conn->client->set.session_id_prefix == NULL)
+ return;
+
+ cmd = imapc_connection_cmd(conn, imapc_connection_id_callback, conn);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN);
+ imapc_command_send(cmd, t_strdup_printf(
+ "ID (\"name\" \"Dovecot\" \"x-session-ext-id\" \"%s-%u\")",
+ conn->client->set.session_id_prefix, ++global_id_counter));
+}
+
+static void imapc_connection_starttls(struct imapc_connection *conn)
+{
+ struct imapc_command *cmd;
+
+ if (conn->client->set.ssl_mode == IMAPC_CLIENT_SSL_MODE_STARTTLS &&
+ conn->ssl_iostream == NULL) {
+ if ((conn->capabilities & IMAPC_CAPABILITY_STARTTLS) == 0) {
+ i_error("imapc(%s): Requested STARTTLS, "
+ "but server doesn't support it",
+ conn->name);
+ imapc_connection_disconnect(conn);
+ return;
+ }
+ cmd = imapc_connection_cmd(conn, imapc_connection_starttls_cb,
+ conn);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN);
+ imapc_command_send(cmd, "STARTTLS");
+ return;
+ }
+ imapc_connection_send_id(conn);
+ imapc_connection_authenticate(conn);
+}
+
+static void
+imapc_connection_capability_cb(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_connection *conn = context;
+
+ if (reply->state != IMAPC_COMMAND_STATE_OK) {
+ imapc_connection_input_error(conn,
+ "Failed to get capabilities: %s", reply->text_full);
+ } else if (conn->capabilities == 0) {
+ imapc_connection_input_error(conn,
+ "Capabilities not returned by server");
+ } else {
+ imapc_connection_starttls(conn);
+ }
+}
+
+static int imapc_connection_input_banner(struct imapc_connection *conn)
+{
+ const struct imap_arg *imap_args;
+ const char *key, *value;
+ struct imapc_command *cmd;
+ int ret;
+
+ if ((ret = imapc_connection_read_line(conn, &imap_args)) <= 0)
+ return ret;
+ /* we already verified that the banner beigns with OK */
+ i_assert(imap_arg_atom_equals(imap_args, "OK"));
+ imap_args++;
+
+ if (imapc_connection_handle_imap_resp_text(conn, imap_args,
+ &key, &value) < 0)
+ return -1;
+ imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_AUTHENTICATING);
+
+ if (conn->capabilities == 0) {
+ /* capabilities weren't sent in the banner. ask for them. */
+ cmd = imapc_connection_cmd(conn, imapc_connection_capability_cb,
+ conn);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN);
+ imapc_command_send(cmd, "CAPABILITY");
+ } else {
+ imapc_connection_starttls(conn);
+ }
+ conn->input_callback = NULL;
+ imapc_connection_input_reset(conn);
+ return 1;
+}
+
+static int imapc_connection_input_untagged(struct imapc_connection *conn)
+{
+ const struct imap_arg *imap_args;
+ const unsigned char *data;
+ size_t size;
+ const char *name, *value;
+ struct imap_parser *parser;
+ struct imapc_untagged_reply reply;
+ int ret;
+
+ if (conn->state == IMAPC_CONNECTION_STATE_CONNECTING) {
+ /* input banner */
+ data = i_stream_get_data(conn->input, &size);
+ if (size < 3 && memchr(data, '\n', size) == NULL)
+ return 0;
+ if (i_memcasecmp(data, "OK ", 3) != 0) {
+ imapc_connection_input_error(conn,
+ "Banner doesn't begin with OK: %s",
+ t_strcut(t_strndup(data, size), '\n'));
+ return -1;
+ }
+ conn->input_callback = imapc_connection_input_banner;
+ return 1;
+ }
+
+ if ((ret = imapc_connection_read_line(conn, &imap_args)) == 0)
+ return 0;
+ else if (ret < 0) {
+ imapc_connection_input_reset(conn);
+ return 1;
+ }
+ if (!imap_arg_get_atom(&imap_args[0], &name)) {
+ imapc_connection_input_error(conn, "Invalid untagged reply");
+ return -1;
+ }
+ imap_args++;
+
+ if (conn->input_state == IMAPC_INPUT_STATE_UNTAGGED &&
+ str_to_uint32(name, &conn->cur_num) == 0) {
+ /* <seq> <event> */
+ conn->input_state = IMAPC_INPUT_STATE_UNTAGGED_NUM;
+ if (!imap_arg_get_atom(&imap_args[0], &name)) {
+ imapc_connection_input_error(conn,
+ "Invalid untagged reply");
+ return -1;
+ }
+ imap_args++;
+ }
+ i_zero(&reply);
+
+ if (strcasecmp(name, "OK") == 0) {
+ if (imapc_connection_handle_imap_resp_text(conn, imap_args,
+ &reply.resp_text_key,
+ &reply.resp_text_value) < 0)
+ return -1;
+ } else if (strcasecmp(name, "CAPABILITY") == 0) {
+ value = imap_args_to_str(imap_args);
+ if (imapc_connection_parse_capability(conn, value) < 0)
+ return -1;
+ } else if (strcasecmp(name, "BYE") == 0) {
+ i_free(conn->disconnect_reason);
+ conn->disconnect_reason = i_strdup(imap_args_to_str(imap_args));
+ }
+
+ reply.name = name;
+ reply.num = conn->cur_num;
+ reply.args = imap_args;
+ reply.file_args = array_get(&conn->literal_files,
+ &reply.file_args_count);
+
+ if (conn->selected_box != NULL) {
+ reply.untagged_box_context =
+ conn->selected_box->untagged_box_context;
+ }
+
+ /* the callback may disconnect and destroy the parser */
+ parser = conn->parser;
+ imap_parser_ref(parser);
+ conn->client->untagged_callback(&reply, conn->client->untagged_context);
+ imap_parser_unref(&parser);
+ imapc_connection_input_reset(conn);
+ return 1;
+}
+
+static int imapc_connection_input_plus(struct imapc_connection *conn)
+{
+ struct imapc_command *const *cmds;
+ unsigned int cmds_count;
+ const char *line;
+
+ if ((line = i_stream_next_line(conn->input)) == NULL)
+ return 0;
+
+ cmds = array_get(&conn->cmd_send_queue, &cmds_count);
+ if (conn->idle_plus_waiting) {
+ /* "+ idling" reply for IDLE command */
+ conn->idle_plus_waiting = FALSE;
+ conn->idling = TRUE;
+ /* no timing out while IDLEing */
+ if (conn->to != NULL && !conn->idle_stopping)
+ timeout_remove(&conn->to);
+ } else if (cmds_count > 0 && cmds[0]->wait_for_literal) {
+ /* reply for literal */
+ cmds[0]->wait_for_literal = FALSE;
+ imapc_command_send_more(conn);
+ } else {
+ cmds = array_get(&conn->cmd_wait_list, &cmds_count);
+ if (cmds_count > 0 && cmds[0]->authenticate) {
+ /* continue AUTHENTICATE */
+ struct imapc_command_reply reply;
+
+ i_zero(&reply);
+ reply.state = (enum imapc_command_state)IMAPC_COMMAND_STATE_AUTHENTICATE_CONTINUE;
+ reply.text_full = line;
+ cmds[0]->callback(&reply, cmds[0]->context);
+ } else {
+ imapc_connection_input_error(conn, "Unexpected '+': %s", line);
+ return -1;
+ }
+ }
+
+ imapc_connection_input_reset(conn);
+ return 1;
+}
+
+static void
+imapc_connection_throttle_shrink_timeout(struct imapc_connection *conn)
+{
+ if (conn->throttle_msecs <= 1)
+ conn->throttle_msecs = 0;
+ else
+ conn->throttle_msecs = conn->throttle_msecs*3 / 4;
+
+ if (conn->throttle_shrink_msecs <= conn->client->set.throttle_set.shrink_min_msecs)
+ conn->throttle_shrink_msecs = 0;
+ else
+ conn->throttle_shrink_msecs = conn->throttle_shrink_msecs*3 / 4;
+
+ timeout_remove(&conn->to_throttle_shrink);
+ if (conn->throttle_shrink_msecs > 0) {
+ conn->to_throttle_shrink =
+ timeout_add(conn->throttle_shrink_msecs,
+ imapc_connection_throttle_shrink_timeout, conn);
+ }
+}
+
+static void
+imapc_connection_throttle(struct imapc_connection *conn,
+ const struct imapc_command_reply *reply)
+{
+ timeout_remove(&conn->to_throttle);
+
+ /* If GMail returns [THROTTLED], start slowing down commands.
+ Unfortunately this isn't a nice resp-text-code, but just
+ appended at the end of the line (although we kind of support
+ it as resp-text-code also in here if it's uppercased). */
+ if (strstr(reply->text_full, "[THROTTLED]") != NULL) {
+ if (conn->throttle_msecs == 0)
+ conn->throttle_msecs = conn->client->set.throttle_set.init_msecs;
+ else if (conn->throttle_msecs < conn->last_successful_throttle_msecs)
+ conn->throttle_msecs = conn->last_successful_throttle_msecs;
+ else {
+ conn->throttle_msecs *= 2;
+ if (conn->throttle_msecs > conn->client->set.throttle_set.max_msecs)
+ conn->throttle_msecs = conn->client->set.throttle_set.max_msecs;
+ }
+ if (conn->throttle_shrink_msecs == 0)
+ conn->throttle_shrink_msecs = conn->client->set.throttle_set.shrink_min_msecs;
+ else
+ conn->throttle_shrink_msecs *= 2;
+ if (conn->to_throttle_shrink != NULL)
+ timeout_reset(conn->to_throttle_shrink);
+ } else {
+ if (conn->throttle_shrink_msecs > 0 &&
+ conn->to_throttle_shrink == NULL) {
+ conn->to_throttle_shrink =
+ timeout_add(conn->throttle_shrink_msecs,
+ imapc_connection_throttle_shrink_timeout, conn);
+ }
+ conn->last_successful_throttle_msecs = conn->throttle_msecs;
+ }
+
+ if (conn->throttle_msecs > 0) {
+ conn->throttle_end_timeval = ioloop_timeval;
+ timeval_add_msecs(&conn->throttle_end_timeval,
+ conn->throttle_msecs);
+ conn->throttle_pending = TRUE;
+ }
+}
+
+static void
+imapc_command_reply_free(struct imapc_command *cmd,
+ const struct imapc_command_reply *reply)
+{
+ cmd->callback(reply, cmd->context);
+ imapc_command_free(cmd);
+}
+
+static int imapc_connection_input_tagged(struct imapc_connection *conn)
+{
+ struct imapc_command *const *cmds, *cmd = NULL;
+ unsigned int i, count;
+ char *line, *linep;
+ const char *p;
+ struct imapc_command_reply reply;
+
+ line = i_stream_next_line(conn->input);
+ if (line == NULL)
+ return 0;
+ /* make sure reply texts stays valid if input stream gets freed */
+ line = t_strdup_noconst(line);
+
+ i_zero(&reply);
+
+ linep = strchr(line, ' ');
+ if (linep == NULL)
+ reply.text_full = "";
+ else {
+ *linep = '\0';
+ reply.text_full = linep + 1;
+ }
+
+ if (strcasecmp(line, "ok") == 0)
+ reply.state = IMAPC_COMMAND_STATE_OK;
+ else if (strcasecmp(line, "no") == 0)
+ reply.state = IMAPC_COMMAND_STATE_NO;
+ else if (strcasecmp(line, "bad") == 0)
+ reply.state = IMAPC_COMMAND_STATE_BAD;
+ else {
+ imapc_connection_input_error(conn,
+ "Invalid state in tagged reply: %u %s %s",
+ conn->cur_tag, line, reply.text_full);
+ return -1;
+ }
+
+ if (reply.text_full[0] == '[') {
+ /* get resp-text */
+ if (imapc_connection_handle_resp_text(conn, reply.text_full,
+ &reply.resp_text_key,
+ &reply.resp_text_value) < 0)
+ return -1;
+
+ p = i_strchr_to_next(reply.text_full, ']');
+ i_assert(p != NULL);
+ reply.text_without_resp = p;
+ if (reply.text_without_resp[0] == ' ')
+ reply.text_without_resp++;
+ } else {
+ reply.text_without_resp = reply.text_full;
+ }
+ /* if we've pipelined multiple commands, handle [THROTTLED] reply
+ from only one of them */
+ if (!conn->throttle_pending)
+ imapc_connection_throttle(conn, &reply);
+
+ /* find the command. it's either the first command in send queue
+ (literal failed) or somewhere in wait list. */
+ cmds = array_get(&conn->cmd_send_queue, &count);
+ if (count > 0 && cmds[0]->tag == conn->cur_tag) {
+ cmd = cmds[0];
+ array_pop_front(&conn->cmd_send_queue);
+ } else {
+ cmds = array_get(&conn->cmd_wait_list, &count);
+ for (i = 0; i < count; i++) {
+ if (cmds[i]->tag == conn->cur_tag) {
+ cmd = cmds[i];
+ array_delete(&conn->cmd_wait_list, i, 1);
+ break;
+ }
+ }
+ }
+ if (array_count(&conn->cmd_wait_list) == 0 &&
+ array_count(&conn->cmd_send_queue) == 0 &&
+ conn->state == IMAPC_CONNECTION_STATE_DONE && conn->to != NULL)
+ timeout_remove(&conn->to);
+
+ if (cmd == NULL) {
+ if (seq_range_exists(&conn->aborted_cmd_tags, conn->cur_tag)) {
+ /* sent command was already aborted - ignore it */
+ seq_range_array_remove(&conn->aborted_cmd_tags,
+ conn->cur_tag);
+ imapc_connection_input_reset(conn);
+ return 1;
+ }
+ imapc_connection_input_error(conn,
+ "Unknown tag in a reply: %u %s %s",
+ conn->cur_tag, line, reply.text_full);
+ return -1;
+ }
+ if ((cmd->flags & IMAPC_COMMAND_FLAG_SELECT) != 0)
+ conn->select_waiting_reply = FALSE;
+
+ if (reply.state == IMAPC_COMMAND_STATE_BAD) {
+ i_error("imapc(%s): Command '%s' failed with BAD: %u %s",
+ conn->name, imapc_command_get_readable(cmd),
+ conn->cur_tag, reply.text_full);
+ imapc_connection_disconnect(conn);
+ }
+
+ if (reply.state == IMAPC_COMMAND_STATE_NO &&
+ (cmd->flags & IMAPC_COMMAND_FLAG_SELECT) != 0 &&
+ conn->selected_box != NULL) {
+ /* EXAMINE/SELECT failed: mailbox is no longer selected */
+ imapc_connection_unselect(conn->selected_box);
+ }
+
+ if (conn->reconnect_command_count > 0 &&
+ (cmd->flags & IMAPC_COMMAND_FLAG_RECONNECTED) != 0) {
+ i_assert(conn->reconnect_command_count > 0);
+ if (--conn->reconnect_command_count == 0) {
+ /* we've received replies for all the commands started
+ before reconnection. if we get disconnected now, we
+ can safely reconnect without worrying about infinite
+ reconnect loops. */
+ if (conn->selected_box != NULL)
+ conn->selected_box->reconnect_ok = TRUE;
+ }
+ }
+ if (conn->reconnect_command_count == 0) {
+ /* we've successfully received replies to some commands. */
+ conn->reconnect_ok = TRUE;
+ }
+ imapc_connection_input_reset(conn);
+ imapc_command_reply_free(cmd, &reply);
+ imapc_command_send_more(conn);
+ return 1;
+}
+
+static int imapc_connection_input_one(struct imapc_connection *conn)
+{
+ const char *tag;
+ int ret = -1;
+
+ if (conn->input_callback != NULL)
+ return conn->input_callback(conn);
+
+ switch (conn->input_state) {
+ case IMAPC_INPUT_STATE_NONE:
+ tag = imap_parser_read_word(conn->parser);
+ if (tag == NULL)
+ return 0;
+
+ if (strcmp(tag, "*") == 0) {
+ conn->input_state = IMAPC_INPUT_STATE_UNTAGGED;
+ conn->cur_num = 0;
+ ret = imapc_connection_input_untagged(conn);
+ } else if (strcmp(tag, "+") == 0) {
+ conn->input_state = IMAPC_INPUT_STATE_PLUS;
+ ret = imapc_connection_input_plus(conn);
+ } else {
+ conn->input_state = IMAPC_INPUT_STATE_TAGGED;
+ if (str_to_uint(tag, &conn->cur_tag) < 0 ||
+ conn->cur_tag == 0) {
+ imapc_connection_input_error(conn,
+ "Invalid command tag: %s", tag);
+ ret = -1;
+ } else {
+ ret = imapc_connection_input_tagged(conn);
+ }
+ }
+ break;
+ case IMAPC_INPUT_STATE_PLUS:
+ ret = imapc_connection_input_plus(conn);
+ break;
+ case IMAPC_INPUT_STATE_UNTAGGED:
+ case IMAPC_INPUT_STATE_UNTAGGED_NUM:
+ ret = imapc_connection_input_untagged(conn);
+ break;
+ case IMAPC_INPUT_STATE_TAGGED:
+ ret = imapc_connection_input_tagged(conn);
+ break;
+ }
+ return ret;
+}
+
+static void imapc_connection_input(struct imapc_connection *conn)
+{
+ const char *errstr;
+ string_t *str;
+ ssize_t ret = 0;
+
+ /* we need to read as much as we can with SSL streams to avoid
+ hanging */
+ imapc_connection_ref(conn);
+ while (conn->input != NULL && (ret = i_stream_read(conn->input)) > 0)
+ imapc_connection_input_pending(conn);
+
+ if (ret < 0 && conn->client->logging_out &&
+ conn->disconnect_reason != NULL) {
+ /* expected disconnection */
+ imapc_connection_disconnect(conn);
+ } else if (ret < 0) {
+ /* disconnected or buffer full */
+ str = t_str_new(128);
+ if (conn->disconnect_reason != NULL) {
+ str_printfa(str, "Server disconnected with message: %s",
+ conn->disconnect_reason);
+ } else if (ret == -2) {
+ str_printfa(str, "Server sent too large input "
+ "(buffer full at %zu)",
+ i_stream_get_data_size(conn->input));
+ } else if (conn->ssl_iostream == NULL) {
+ errstr = conn->input->stream_errno == 0 ? "EOF" :
+ i_stream_get_error(conn->input);
+ str_printfa(str, "Server disconnected unexpectedly: %s",
+ errstr);
+ } else {
+ errstr = ssl_iostream_get_last_error(conn->ssl_iostream);
+ if (errstr == NULL) {
+ errstr = conn->input->stream_errno == 0 ? "EOF" :
+ i_stream_get_error(conn->input);
+ }
+ str_printfa(str, "Server disconnected unexpectedly: %s",
+ errstr);
+ }
+ imapc_connection_try_reconnect(conn, str_c(str), 0, FALSE);
+ }
+ imapc_connection_unref(&conn);
+}
+
+static int imapc_connection_ssl_handshaked(const char **error_r, void *context)
+{
+ struct imapc_connection *conn = context;
+ const char *error;
+
+ if (ssl_iostream_check_cert_validity(conn->ssl_iostream,
+ conn->client->set.host, &error) == 0) {
+ if (conn->client->set.debug) {
+ i_debug("imapc(%s): SSL handshake successful",
+ conn->name);
+ }
+ return 0;
+ } else if (conn->client->set.ssl_set.allow_invalid_cert) {
+ if (conn->client->set.debug) {
+ i_debug("imapc(%s): SSL handshake successful, "
+ "ignoring invalid certificate: %s",
+ conn->name, error);
+ }
+ return 0;
+ } else {
+ *error_r = error;
+ return -1;
+ }
+}
+
+static int imapc_connection_ssl_init(struct imapc_connection *conn)
+{
+ const char *error;
+
+ if (conn->client->ssl_ctx == NULL) {
+ i_error("imapc(%s): No SSL context", conn->name);
+ return -1;
+ }
+
+ if (conn->client->set.debug)
+ i_debug("imapc(%s): Starting SSL handshake", conn->name);
+
+ if (conn->raw_input != conn->input) {
+ /* recreate rawlog after STARTTLS */
+ i_stream_ref(conn->raw_input);
+ o_stream_ref(conn->raw_output);
+ i_stream_destroy(&conn->input);
+ o_stream_destroy(&conn->output);
+ conn->input = conn->raw_input;
+ conn->output = conn->raw_output;
+ }
+
+ io_remove(&conn->io);
+ if (io_stream_create_ssl_client(conn->client->ssl_ctx,
+ conn->client->set.host,
+ &conn->client->set.ssl_set,
+ &conn->input, &conn->output,
+ &conn->ssl_iostream, &error) < 0) {
+ i_error("imapc(%s): Couldn't initialize SSL client: %s",
+ conn->name, error);
+ return -1;
+ }
+ conn->io = io_add_istream(conn->input, imapc_connection_input, conn);
+ ssl_iostream_set_handshake_callback(conn->ssl_iostream,
+ imapc_connection_ssl_handshaked,
+ conn);
+ if (ssl_iostream_handshake(conn->ssl_iostream) < 0) {
+ i_error("imapc(%s): SSL handshake failed: %s", conn->name,
+ ssl_iostream_get_last_error(conn->ssl_iostream));
+ return -1;
+ }
+
+ if (*conn->client->set.rawlog_dir != '\0') {
+ iostream_rawlog_create(conn->client->set.rawlog_dir,
+ &conn->input, &conn->output);
+ }
+
+ imap_parser_set_streams(conn->parser, conn->input, NULL);
+ return 0;
+}
+
+static int imapc_connection_connected(struct imapc_connection *conn)
+{
+ const struct ip_addr *ip = &conn->ips[conn->prev_connect_idx];
+ struct ip_addr local_ip;
+ in_port_t local_port;
+ int err;
+
+ i_assert(conn->io == NULL);
+
+ err = net_geterror(conn->fd);
+ if (err != 0) {
+ imapc_connection_try_reconnect(conn, t_strdup_printf(
+ "connect(%s, %u) failed: %s",
+ net_ip2addr(ip), conn->client->set.port,
+ strerror(err)), conn->client->set.connect_retry_interval_msecs, TRUE);
+ return -1;
+ }
+ if (net_getsockname(conn->fd, &local_ip, &local_port) < 0)
+ local_port = 0;
+ i_info("imapc(%s): Connected to %s:%u (local %s:%u)", conn->name,
+ net_ip2addr(ip), conn->client->set.port,
+ net_ip2addr(&local_ip), local_port);
+ conn->io = io_add(conn->fd, IO_READ, imapc_connection_input, conn);
+ o_stream_set_flush_callback(conn->output, imapc_connection_output,
+ conn);
+
+ if (conn->client->set.ssl_mode == IMAPC_CLIENT_SSL_MODE_IMMEDIATE) {
+ if (imapc_connection_ssl_init(conn) < 0)
+ imapc_connection_disconnect(conn);
+ }
+ return imapc_connection_output(conn);
+}
+
+static void imapc_connection_timeout(struct imapc_connection *conn)
+{
+ const struct ip_addr *ip = &conn->ips[conn->prev_connect_idx];
+ const char *errstr;
+ bool connect_error = FALSE;
+
+ switch (conn->state) {
+ case IMAPC_CONNECTION_STATE_CONNECTING:
+ errstr = t_strdup_printf("connect(%s, %u) timed out after %u seconds",
+ net_ip2addr(ip), conn->client->set.port,
+ conn->client->set.connect_timeout_msecs/1000);
+ connect_error = TRUE;
+ break;
+ case IMAPC_CONNECTION_STATE_AUTHENTICATING:
+ errstr = t_strdup_printf("Authentication timed out after %u seconds",
+ conn->client->set.connect_timeout_msecs/1000);
+ break;
+ default:
+ i_unreached();
+ }
+ imapc_connection_try_reconnect(conn, errstr, 0, connect_error);
+}
+
+static void
+imapc_noop_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
+ void *context ATTR_UNUSED)
+{
+}
+
+static void
+imapc_reidle_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
+ void *context)
+{
+ struct imapc_connection *conn = context;
+
+ imapc_connection_idle(conn);
+}
+
+static void imapc_connection_reset_idle(struct imapc_connection *conn)
+{
+ struct imapc_command *cmd;
+
+ if (conn->idling)
+ cmd = imapc_connection_cmd(conn, imapc_reidle_callback, conn);
+ else if (array_count(&conn->cmd_wait_list) == 0)
+ cmd = imapc_connection_cmd(conn, imapc_noop_callback, NULL);
+ else {
+ /* IMAP command reply is taking a long time */
+ return;
+ }
+ imapc_command_send(cmd, "NOOP");
+}
+
+static void imapc_connection_connect_next_ip(struct imapc_connection *conn)
+{
+ const struct ip_addr *ip = NULL;
+ unsigned int i;
+ int fd;
+
+ i_assert(conn->client->set.max_idle_time > 0);
+
+ for (i = 0; i<conn->ips_count;) {
+ conn->prev_connect_idx = (conn->prev_connect_idx+1) % conn->ips_count;
+ ip = &conn->ips[conn->prev_connect_idx];
+ fd = net_connect_ip(ip, conn->client->set.port, NULL);
+ if (fd != -1)
+ break;
+
+ /* failed to connect to one of the IPs immediately
+ (e.g. IPv6 address without connectivity). try all IPs
+ before failing completely. */
+ i_error("net_connect_ip(%s:%u) failed: %m",
+ net_ip2addr(ip), conn->client->set.port);
+ if (conn->prev_connect_idx+1 == conn->ips_count) {
+ imapc_connection_try_reconnect(conn, "No more IP address(es) to try",
+ conn->client->set.connect_retry_interval_msecs, TRUE);
+ return;
+ }
+ }
+
+ i_assert(ip != NULL);
+
+ conn->fd = fd;
+ conn->input = conn->raw_input =
+ i_stream_create_fd(fd, conn->client->set.max_line_length);
+ conn->output = conn->raw_output = o_stream_create_fd(fd, SIZE_MAX);
+ o_stream_set_no_error_handling(conn->output, TRUE);
+
+ if (*conn->client->set.rawlog_dir != '\0' &&
+ conn->client->set.ssl_mode != IMAPC_CLIENT_SSL_MODE_IMMEDIATE) {
+ iostream_rawlog_create(conn->client->set.rawlog_dir,
+ &conn->input, &conn->output);
+ }
+
+ o_stream_set_flush_pending(conn->output, TRUE);
+ o_stream_set_flush_callback(conn->output, imapc_connection_connected,
+ conn);
+ conn->parser = imap_parser_create(conn->input, NULL,
+ conn->client->set.max_line_length);
+ conn->to = timeout_add(conn->client->set.connect_timeout_msecs,
+ imapc_connection_timeout, conn);
+ conn->to_output = timeout_add(conn->client->set.max_idle_time*1000,
+ imapc_connection_reset_idle, conn);
+ if (conn->client->set.debug) {
+ i_debug("imapc(%s): Connecting to %s:%u", conn->name,
+ net_ip2addr(ip), conn->client->set.port);
+ }
+}
+
+static void
+imapc_connection_dns_callback(const struct dns_lookup_result *result,
+ struct imapc_connection *conn)
+{
+ conn->dns_lookup = NULL;
+
+ if (result->ret != 0) {
+ i_error("imapc(%s): dns_lookup(%s) failed: %s",
+ conn->name, conn->client->set.host, result->error);
+ imapc_connection_set_disconnected(conn);
+ return;
+ }
+
+ i_assert(result->ips_count > 0);
+ conn->ips_count = result->ips_count;
+ conn->ips = i_new(struct ip_addr, conn->ips_count);
+ memcpy(conn->ips, result->ips, sizeof(*conn->ips) * conn->ips_count);
+ conn->prev_connect_idx = conn->ips_count - 1;
+
+ imapc_connection_connect_next_ip(conn);
+}
+
+void imapc_connection_connect(struct imapc_connection *conn)
+{
+ struct dns_lookup_settings dns_set;
+ struct ip_addr ip, *ips;
+ unsigned int ips_count;
+ int ret;
+
+ if (conn->fd != -1 || conn->dns_lookup != NULL)
+ return;
+ if (conn->reconnect_waiting) {
+ /* wait for the reconnection delay to finish before
+ doing anything. */
+ return;
+ }
+
+ conn->reconnecting = FALSE;
+ /* if we get disconnected before we've finished all the pending
+ commands, don't reconnect */
+ conn->reconnect_command_count = array_count(&conn->cmd_wait_list) +
+ array_count(&conn->cmd_send_queue);
+
+ imapc_connection_input_reset(conn);
+ conn->last_connect = ioloop_timeval;
+
+ if (conn->client->set.debug) {
+ i_debug("imapc(%s): Looking up IP address "
+ "(reconnect_ok=%s, last_connect=%ld)", conn->name,
+ (conn->reconnect_ok ? "true" : "false"),
+ (long)conn->last_connect.tv_sec);
+ }
+
+ i_zero(&dns_set);
+ dns_set.dns_client_socket_path =
+ conn->client->set.dns_client_socket_path;
+ dns_set.timeout_msecs = conn->client->set.connect_timeout_msecs;
+ dns_set.event_parent = conn->client->event;
+
+ imapc_connection_set_state(conn, IMAPC_CONNECTION_STATE_CONNECTING);
+ if (conn->ips_count > 0) {
+ /* do nothing */
+ } else if (net_addr2ip(conn->client->set.host, &ip) == 0) {
+ conn->ips_count = 1;
+ conn->ips = i_new(struct ip_addr, conn->ips_count);
+ conn->ips[0] = ip;
+ } else if (*dns_set.dns_client_socket_path == '\0') {
+ ret = net_gethostbyname(conn->client->set.host,
+ &ips, &ips_count);
+ if (ret != 0) {
+ i_error("imapc(%s): net_gethostbyname(%s) failed: %s",
+ conn->name, conn->client->set.host,
+ net_gethosterror(ret));
+ imapc_connection_set_disconnected(conn);
+ return;
+ }
+ conn->ips_count = ips_count;
+ conn->ips = i_new(struct ip_addr, ips_count);
+ memcpy(conn->ips, ips, ips_count * sizeof(*ips));
+ } else {
+ (void)dns_lookup(conn->client->set.host, &dns_set,
+ imapc_connection_dns_callback, conn,
+ &conn->dns_lookup);
+ return;
+ }
+ imapc_connection_connect_next_ip(conn);
+}
+
+void imapc_connection_input_pending(struct imapc_connection *conn)
+{
+ int ret = 1;
+
+ if (conn->input == NULL)
+ return;
+
+ if (conn->to != NULL && !conn->idle_stopping)
+ timeout_reset(conn->to);
+
+ o_stream_cork(conn->output);
+ while (ret > 0 && conn->input != NULL) {
+ T_BEGIN {
+ ret = imapc_connection_input_one(conn);
+ } T_END;
+ }
+
+ if (conn->output != NULL)
+ o_stream_uncork(conn->output);
+}
+
+static struct imapc_command *
+imapc_command_begin(imapc_command_callback_t *callback, void *context)
+{
+ struct imapc_command *cmd;
+ pool_t pool;
+
+ i_assert(callback != NULL);
+
+ pool = pool_alloconly_create("imapc command", 2048);
+ cmd = p_new(pool, struct imapc_command, 1);
+ cmd->pool = pool;
+ cmd->callback = callback;
+ cmd->context = context;
+
+ /* use a globally unique tag counter so looking at rawlogs is
+ somewhat easier */
+ if (++imapc_client_cmd_tag_counter == 0)
+ imapc_client_cmd_tag_counter++;
+ cmd->tag = imapc_client_cmd_tag_counter;
+ return cmd;
+}
+
+static void imapc_command_free(struct imapc_command *cmd)
+{
+ struct imapc_command_stream *stream;
+
+ if (array_is_created(&cmd->streams)) {
+ array_foreach_modifiable(&cmd->streams, stream)
+ i_stream_unref(&stream->input);
+ }
+ pool_unref(&cmd->pool);
+}
+
+const char *imapc_command_get_tag(struct imapc_command *cmd)
+{
+ return t_strdup_printf("%u", cmd->tag);
+}
+
+void imapc_command_abort(struct imapc_command **_cmd)
+{
+ struct imapc_command *cmd = *_cmd;
+
+ *_cmd = NULL;
+ imapc_command_free(cmd);
+}
+
+static void imapc_command_timeout(struct imapc_connection *conn)
+{
+ struct imapc_command *const *cmds;
+ unsigned int count;
+
+ cmds = array_get(&conn->cmd_wait_list, &count);
+ i_assert(count > 0);
+
+ imapc_connection_try_reconnect(conn, t_strdup_printf(
+ "Command '%s' timed out", imapc_command_get_readable(cmds[0])), 0, FALSE);
+}
+
+static bool
+parse_sync_literal(const unsigned char *data, unsigned int pos,
+ unsigned int *value_r)
+{
+ unsigned int value = 0, mul = 1;
+
+ /* data should contain "{size}\r\n" and pos points after \n */
+ if (pos <= 4 || data[pos-1] != '\n' || data[pos-2] != '\r' ||
+ data[pos-3] != '}' || !i_isdigit(data[pos-4]))
+ return FALSE;
+ pos -= 4;
+
+ do {
+ value += (data[pos] - '0') * mul;
+ mul = mul*10;
+ pos--;
+ } while (pos > 0 && i_isdigit(data[pos]));
+
+ if (pos == 0 || data[pos] != '{')
+ return FALSE;
+
+ *value_r = value;
+ return TRUE;
+}
+
+static void imapc_command_send_finished(struct imapc_connection *conn,
+ struct imapc_command *cmd)
+{
+ struct imapc_command *const *cmdp;
+
+ i_assert(conn->to != NULL);
+
+ if (cmd->idle)
+ conn->idle_plus_waiting = TRUE;
+ cmd->sent = TRUE;
+
+ /* everything sent. move command to wait list. */
+ cmdp = array_front(&conn->cmd_send_queue);
+ i_assert(*cmdp == cmd);
+ array_pop_front(&conn->cmd_send_queue);
+ array_push_back(&conn->cmd_wait_list, &cmd);
+
+ /* send the next command in queue */
+ imapc_command_send_more(conn);
+}
+
+static struct imapc_command_stream *
+imapc_command_get_sending_stream(struct imapc_command *cmd)
+{
+ struct imapc_command_stream *stream;
+
+ if (!array_is_created(&cmd->streams) || array_count(&cmd->streams) == 0)
+ return NULL;
+
+ stream = array_front_modifiable(&cmd->streams);
+ if (stream->pos != cmd->send_pos)
+ return NULL;
+ return stream;
+}
+
+static int imapc_command_try_send_stream(struct imapc_connection *conn,
+ struct imapc_command *cmd)
+{
+ struct imapc_command_stream *stream;
+ enum ostream_send_istream_result res;
+
+ stream = imapc_command_get_sending_stream(cmd);
+ if (stream == NULL)
+ return -2;
+
+ /* we're sending the stream now */
+ o_stream_set_max_buffer_size(conn->output, 0);
+ res = o_stream_send_istream(conn->output, stream->input);
+ o_stream_set_max_buffer_size(conn->output, SIZE_MAX);
+
+ switch (res) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ i_assert(stream->input->v_offset < stream->size);
+ return 0;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ i_error("imapc: read(%s) failed: %s",
+ i_stream_get_name(stream->input),
+ i_stream_get_error(stream->input));
+ return -1;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ /* disconnected */
+ return -1;
+ }
+ i_assert(stream->input->v_offset == stream->size);
+
+ /* finished with the stream */
+ i_stream_unref(&stream->input);
+ array_pop_front(&cmd->streams);
+
+ i_assert(cmd->send_pos != cmd->data->used);
+ return 1;
+}
+
+static void imapc_connection_set_selecting(struct imapc_client_mailbox *box)
+{
+ struct imapc_connection *conn = box->conn;
+
+ i_assert(conn->selecting_box == NULL);
+
+ if (conn->selected_box != NULL &&
+ (conn->capabilities & IMAPC_CAPABILITY_QRESYNC) != 0) {
+ /* server will send a [CLOSED] once selected mailbox is
+ closed */
+ conn->selecting_box = box;
+ } else {
+ /* we'll have to assume that all the future untagged messages
+ are for the mailbox we're selecting */
+ conn->selected_box = box;
+ }
+ conn->select_waiting_reply = TRUE;
+}
+
+static bool imapc_connection_is_throttled(struct imapc_connection *conn)
+{
+ timeout_remove(&conn->to_throttle);
+
+ if (conn->throttle_msecs == 0) {
+ /* we haven't received [THROTTLED] recently */
+ return FALSE;
+ }
+ if (array_count(&conn->cmd_wait_list) > 0) {
+ /* wait until we have received the existing commands' tagged
+ replies to see if we're still throttled */
+ return TRUE;
+ }
+ if (timeval_cmp(&ioloop_timeval, &conn->throttle_end_timeval) >= 0) {
+ /* we reached the throttle timeout - send the next command */
+ conn->throttle_pending = FALSE;
+ return FALSE;
+ }
+
+ /* we're still being throttled - wait for it to end */
+ conn->to_throttle = timeout_add_absolute(&conn->throttle_end_timeval,
+ imapc_command_send_more, conn);
+ return TRUE;
+}
+
+static void imapc_command_send_more(struct imapc_connection *conn)
+{
+ struct imapc_command *const *cmds, *cmd;
+ struct imapc_command_reply reply;
+ const unsigned char *p, *data;
+ unsigned int count, size;
+ size_t seek_pos, start_pos, end_pos;
+ int ret;
+
+ if (imapc_connection_is_throttled(conn))
+ return;
+
+ cmds = array_get(&conn->cmd_send_queue, &count);
+ if (count == 0)
+ return;
+ cmd = cmds[0];
+
+ if ((cmd->flags & IMAPC_COMMAND_FLAG_PRELOGIN) == 0 &&
+ conn->state != IMAPC_CONNECTION_STATE_DONE) {
+ /* wait until we're fully connected */
+ return;
+ }
+ if ((cmd->flags & IMAPC_COMMAND_FLAG_LOGOUT) != 0 &&
+ array_count(&conn->cmd_wait_list) > 0) {
+ /* wait until existing commands have finished */
+ return;
+ }
+ if (conn->select_waiting_reply) {
+ /* wait for SELECT to finish */
+ return;
+ }
+ if (cmd->wait_for_literal) {
+ /* wait until we received '+' */
+ return;
+ }
+
+ i_assert(cmd->send_pos < cmd->data->used);
+
+ if (cmd->box == NULL) {
+ /* non-mailbox command */
+ } else if (cmd->send_pos == 0 &&
+ (cmd->flags & IMAPC_COMMAND_FLAG_SELECT) != 0) {
+ /* SELECT/EXAMINE command */
+ imapc_connection_set_selecting(cmd->box);
+ } else if (!imapc_client_mailbox_is_opened(cmd->box)) {
+ if (cmd->box->reconnecting) {
+ /* wait for SELECT/EXAMINE */
+ return;
+ }
+ /* shouldn't normally happen */
+ i_zero(&reply);
+ reply.text_without_resp = reply.text_full = "Mailbox not open";
+ reply.state = IMAPC_COMMAND_STATE_DISCONNECTED;
+
+ array_pop_front(&conn->cmd_send_queue);
+ imapc_command_reply_free(cmd, &reply);
+ imapc_command_send_more(conn);
+ return;
+ }
+
+ /* add timeout for commands if there's not one yet
+ (pre-login has its own timeout) */
+ if ((cmd->flags & IMAPC_COMMAND_FLAG_LOGOUT) != 0) {
+ /* LOGOUT has a shorter timeout */
+ timeout_remove(&conn->to);
+ conn->to = timeout_add(IMAPC_LOGOUT_TIMEOUT_MSECS,
+ imapc_command_timeout, conn);
+ } else if (conn->to == NULL) {
+ conn->to = timeout_add(conn->client->set.cmd_timeout_msecs,
+ imapc_command_timeout, conn);
+ }
+
+ timeout_reset(conn->to_output);
+ if ((ret = imapc_command_try_send_stream(conn, cmd)) == 0)
+ return;
+ if (ret == -1) {
+ i_zero(&reply);
+ reply.text_without_resp = reply.text_full = "Mailbox not open";
+ reply.state = IMAPC_COMMAND_STATE_DISCONNECTED;
+
+ array_pop_front(&conn->cmd_send_queue);
+ imapc_command_reply_free(cmd, &reply);
+ imapc_command_send_more(conn);
+ return;
+ }
+
+ seek_pos = cmd->send_pos;
+ if (seek_pos != 0 && ret == -2) {
+ /* skip over the literal. we can also get here from
+ AUTHENTICATE command, which doesn't use a literal */
+ if (parse_sync_literal(cmd->data->data, seek_pos, &size)) {
+ seek_pos += size;
+ i_assert(seek_pos <= cmd->data->used);
+ }
+ }
+
+ do {
+ start_pos = seek_pos;
+ p = memchr(CONST_PTR_OFFSET(cmd->data->data, seek_pos), '\n',
+ cmd->data->used - seek_pos);
+ i_assert(p != NULL);
+
+ seek_pos = p - (const unsigned char *)cmd->data->data + 1;
+ /* keep going for LITERAL+ command */
+ } while (start_pos + 3 < seek_pos &&
+ p[-1] == '\r' && p[-2] == '}' && p[-3] == '+');
+ end_pos = seek_pos;
+
+ data = CONST_PTR_OFFSET(cmd->data->data, cmd->send_pos);
+ size = end_pos - cmd->send_pos;
+ o_stream_nsend(conn->output, data, size);
+ cmd->send_pos = end_pos;
+
+ if (cmd->send_pos == cmd->data->used) {
+ i_assert(!array_is_created(&cmd->streams) ||
+ array_count(&cmd->streams) == 0);
+ imapc_command_send_finished(conn, cmd);
+ } else {
+ cmd->wait_for_literal = TRUE;
+ }
+}
+
+static void imapc_connection_send_idle_done(struct imapc_connection *conn)
+{
+ if ((conn->idling || conn->idle_plus_waiting) && !conn->idle_stopping) {
+ conn->idle_stopping = TRUE;
+ o_stream_nsend_str(conn->output, "DONE\r\n");
+ if (conn->to == NULL) {
+ conn->to = timeout_add(conn->client->set.cmd_timeout_msecs,
+ imapc_command_timeout, conn);
+ }
+ }
+}
+
+static void imapc_connection_cmd_send(struct imapc_command *cmd)
+{
+ struct imapc_connection *conn = cmd->conn;
+ struct imapc_command *const *cmds;
+ unsigned int i, count;
+
+ imapc_connection_send_idle_done(conn);
+
+ i_assert((cmd->flags & IMAPC_COMMAND_FLAG_RECONNECTED) == 0);
+
+ if ((cmd->flags & IMAPC_COMMAND_FLAG_PRELOGIN) != 0 &&
+ conn->state == IMAPC_CONNECTION_STATE_AUTHENTICATING) {
+ /* pre-login commands get inserted before everything else */
+ array_push_front(&conn->cmd_send_queue, &cmd);
+ imapc_command_send_more(conn);
+ return;
+ }
+
+ /* add the command just before retried commands */
+ cmds = array_get(&conn->cmd_send_queue, &count);
+ for (i = count; i > 0; i--) {
+ if ((cmds[i-1]->flags & IMAPC_COMMAND_FLAG_RECONNECTED) == 0)
+ break;
+ }
+ array_insert(&conn->cmd_send_queue, i, &cmd, 1);
+ imapc_command_send_more(conn);
+}
+
+static int imapc_connection_output(struct imapc_connection *conn)
+{
+ struct imapc_command *const *cmds;
+ unsigned int count;
+ int ret;
+
+ if (conn->to != NULL)
+ timeout_reset(conn->to);
+
+ if ((ret = o_stream_flush(conn->output)) < 0)
+ return 1;
+
+ imapc_connection_ref(conn);
+ cmds = array_get(&conn->cmd_send_queue, &count);
+ if (count > 0) {
+ if (imapc_command_get_sending_stream(cmds[0]) != NULL &&
+ !cmds[0]->wait_for_literal) {
+ /* we're sending a stream. send more. */
+ imapc_command_send_more(conn);
+ }
+ }
+ imapc_connection_unref(&conn);
+ return ret;
+}
+
+struct imapc_command *
+imapc_connection_cmd(struct imapc_connection *conn,
+ imapc_command_callback_t *callback, void *context)
+{
+ struct imapc_command *cmd;
+
+ cmd = imapc_command_begin(callback, context);
+ cmd->conn = conn;
+ return cmd;
+}
+
+void imapc_command_set_flags(struct imapc_command *cmd,
+ enum imapc_command_flags flags)
+{
+ cmd->flags = flags;
+}
+
+void imapc_command_set_mailbox(struct imapc_command *cmd,
+ struct imapc_client_mailbox *box)
+{
+ cmd->box = box;
+}
+
+bool imapc_command_connection_is_selected(struct imapc_command *cmd)
+{
+ return cmd->conn->selected_box != NULL ||
+ cmd->conn->selecting_box != NULL;
+}
+
+void imapc_command_send(struct imapc_command *cmd, const char *cmd_str)
+{
+ size_t len = strlen(cmd_str);
+
+ cmd->data = str_new(cmd->pool, 6 + len + 2);
+ str_printfa(cmd->data, "%u %s\r\n", cmd->tag, cmd_str);
+ imapc_connection_cmd_send(cmd);
+}
+
+void imapc_command_sendf(struct imapc_command *cmd, const char *cmd_fmt, ...)
+{
+ va_list args;
+
+ va_start(args, cmd_fmt);
+ imapc_command_sendvf(cmd, cmd_fmt, args);
+ va_end(args);
+}
+
+void imapc_command_sendvf(struct imapc_command *cmd,
+ const char *cmd_fmt, va_list args)
+{
+ unsigned int i;
+
+ cmd->data = str_new(cmd->pool, 128);
+ str_printfa(cmd->data, "%u ", cmd->tag);
+
+ for (i = 0; cmd_fmt[i] != '\0'; i++) {
+ if (cmd_fmt[i] != '%') {
+ str_append_c(cmd->data, cmd_fmt[i]);
+ continue;
+ }
+
+ switch (cmd_fmt[++i]) {
+ case '\0':
+ i_unreached();
+ case 'u': {
+ unsigned int arg = va_arg(args, unsigned int);
+
+ str_printfa(cmd->data, "%u", arg);
+ break;
+ }
+ case 'p': {
+ struct istream *input = va_arg(args, struct istream *);
+ struct imapc_command_stream *s;
+ uoff_t size;
+
+ if (!array_is_created(&cmd->streams))
+ p_array_init(&cmd->streams, cmd->pool, 2);
+ if (i_stream_get_size(input, TRUE, &size) < 0)
+ size = 0;
+ str_printfa(cmd->data, "{%"PRIuUOFF_T"}\r\n", size);
+ s = array_append_space(&cmd->streams);
+ s->pos = str_len(cmd->data);
+ s->size = size;
+ s->input = input;
+ i_stream_ref(input);
+ break;
+ }
+ case 's': {
+ const char *arg = va_arg(args, const char *);
+
+ if (!need_literal(arg))
+ imap_append_quoted(cmd->data, arg);
+ else if ((cmd->conn->capabilities &
+ IMAPC_CAPABILITY_LITERALPLUS) != 0) {
+ str_printfa(cmd->data, "{%zu+}\r\n%s",
+ strlen(arg), arg);
+ } else {
+ str_printfa(cmd->data, "{%zu}\r\n%s",
+ strlen(arg), arg);
+ }
+ break;
+ }
+ case '1': {
+ /* %1s - no quoting */
+ const char *arg = va_arg(args, const char *);
+
+ i++;
+ i_assert(cmd_fmt[i] == 's');
+ str_append(cmd->data, arg);
+ break;
+ }
+ }
+ }
+ str_append(cmd->data, "\r\n");
+
+ imapc_connection_cmd_send(cmd);
+}
+
+enum imapc_connection_state
+imapc_connection_get_state(struct imapc_connection *conn)
+{
+ return conn->state;
+}
+
+enum imapc_capability
+imapc_connection_get_capabilities(struct imapc_connection *conn)
+{
+ return conn->capabilities;
+}
+
+void imapc_connection_unselect(struct imapc_client_mailbox *box)
+{
+ struct imapc_connection *conn = box->conn;
+
+ if (conn->selected_box != NULL || conn->selecting_box != NULL) {
+ i_assert(conn->selected_box == box ||
+ conn->selecting_box == box);
+
+ conn->selected_box = NULL;
+ conn->selecting_box = NULL;
+ }
+ imapc_connection_send_idle_done(conn);
+ imapc_connection_abort_commands(conn, box, FALSE);
+}
+
+struct imapc_client_mailbox *
+imapc_connection_get_mailbox(struct imapc_connection *conn)
+{
+ if (conn->selecting_box != NULL)
+ return conn->selecting_box;
+ return conn->selected_box;
+}
+
+static void
+imapc_connection_idle_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
+ void *context)
+{
+ struct imapc_connection *conn = context;
+
+ conn->idling = FALSE;
+ conn->idle_plus_waiting = FALSE;
+ conn->idle_stopping = FALSE;
+}
+
+void imapc_connection_idle(struct imapc_connection *conn)
+{
+ struct imapc_command *cmd;
+
+ if (array_count(&conn->cmd_send_queue) != 0 ||
+ array_count(&conn->cmd_wait_list) != 0 ||
+ conn->idling || conn->idle_plus_waiting ||
+ (conn->capabilities & IMAPC_CAPABILITY_IDLE) == 0)
+ return;
+
+ cmd = imapc_connection_cmd(conn, imapc_connection_idle_callback, conn);
+ cmd->idle = TRUE;
+ imapc_command_send(cmd, "IDLE");
+}
diff --git a/src/lib-imap-client/imapc-connection.h b/src/lib-imap-client/imapc-connection.h
new file mode 100644
index 0000000..8fda692
--- /dev/null
+++ b/src/lib-imap-client/imapc-connection.h
@@ -0,0 +1,63 @@
+#ifndef IMAPC_CONNECTION_H
+#define IMAPC_CONNECTION_H
+
+#include "imapc-client.h"
+
+/* [THROTTLED] handling behavior */
+#define IMAPC_THROTTLE_DEFAULT_INIT_MSECS 50
+#define IMAPC_THROTTLE_DEFAULT_MAX_MSECS (16*1000)
+#define IMAPC_THROTTLE_DEFAULT_SHRINK_MIN_MSECS 500
+
+struct imapc_client;
+struct imapc_connection;
+
+enum imapc_connection_state {
+ /* No connection */
+ IMAPC_CONNECTION_STATE_DISCONNECTED = 0,
+ /* Trying to connect */
+ IMAPC_CONNECTION_STATE_CONNECTING,
+ /* Connected, trying to authenticate */
+ IMAPC_CONNECTION_STATE_AUTHENTICATING,
+ /* Authenticated, ready to accept commands */
+ IMAPC_CONNECTION_STATE_DONE
+};
+
+struct imapc_connection *
+imapc_connection_init(struct imapc_client *client,
+ imapc_command_callback_t *login_callback,
+ void *login_context);
+void imapc_connection_deinit(struct imapc_connection **conn);
+
+void imapc_connection_connect(struct imapc_connection *conn);
+void imapc_connection_set_no_reconnect(struct imapc_connection *conn);
+void imapc_connection_disconnect(struct imapc_connection *conn);
+void imapc_connection_disconnect_full(struct imapc_connection *conn,
+ bool reconnecting);
+void imapc_connection_try_reconnect(struct imapc_connection *conn,
+ const char *errstr,
+ unsigned int delay_msecs,
+ bool connect_error);
+void imapc_connection_abort_commands(struct imapc_connection *conn,
+ struct imapc_client_mailbox *only_box,
+ bool keep_retriable) ATTR_NULL(2);
+void imapc_connection_ioloop_changed(struct imapc_connection *conn);
+void imapc_connection_input_pending(struct imapc_connection *conn);
+
+struct imapc_command *
+imapc_connection_cmd(struct imapc_connection *conn,
+ imapc_command_callback_t *callback, void *context)
+ ATTR_NULL(3);
+
+void imapc_connection_unselect(struct imapc_client_mailbox *box);
+
+enum imapc_connection_state
+imapc_connection_get_state(struct imapc_connection *conn);
+enum imapc_capability
+imapc_connection_get_capabilities(struct imapc_connection *conn);
+
+struct imapc_client_mailbox *
+imapc_connection_get_mailbox(struct imapc_connection *conn);
+
+void imapc_connection_idle(struct imapc_connection *conn);
+
+#endif
diff --git a/src/lib-imap-client/imapc-msgmap.c b/src/lib-imap-client/imapc-msgmap.c
new file mode 100644
index 0000000..6280a24
--- /dev/null
+++ b/src/lib-imap-client/imapc-msgmap.c
@@ -0,0 +1,89 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "imapc-msgmap.h"
+#include "sort.h"
+
+struct imapc_msgmap {
+ ARRAY_TYPE(uint32_t) uids;
+ uint32_t uid_next;
+};
+
+struct imapc_msgmap *imapc_msgmap_init(void)
+{
+ struct imapc_msgmap *msgmap;
+
+ msgmap = i_new(struct imapc_msgmap, 1);
+ i_array_init(&msgmap->uids, 128);
+ msgmap->uid_next = 1;
+ return msgmap;
+}
+
+void imapc_msgmap_deinit(struct imapc_msgmap **_msgmap)
+{
+ struct imapc_msgmap *msgmap = *_msgmap;
+
+ *_msgmap = NULL;
+
+ array_free(&msgmap->uids);
+ i_free(msgmap);
+}
+
+uint32_t imapc_msgmap_count(struct imapc_msgmap *msgmap)
+{
+ return array_count(&msgmap->uids);
+}
+
+uint32_t imapc_msgmap_uidnext(struct imapc_msgmap *msgmap)
+{
+ return msgmap->uid_next;
+}
+
+uint32_t imapc_msgmap_rseq_to_uid(struct imapc_msgmap *msgmap, uint32_t rseq)
+{
+ const uint32_t *uidp;
+
+ uidp = array_idx(&msgmap->uids, rseq-1);
+ return *uidp;
+}
+
+bool imapc_msgmap_uid_to_rseq(struct imapc_msgmap *msgmap,
+ uint32_t uid, uint32_t *rseq_r)
+{
+ const uint32_t *p, *first;
+
+ p = array_bsearch(&msgmap->uids, &uid, uint32_cmp);
+ if (p == NULL) {
+ *rseq_r = 0;
+ return FALSE;
+ }
+
+ first = array_front(&msgmap->uids);
+ *rseq_r = (p - first) + 1;
+ return TRUE;
+}
+
+void imapc_msgmap_append(struct imapc_msgmap *msgmap,
+ uint32_t rseq, uint32_t uid)
+{
+ i_assert(rseq == imapc_msgmap_count(msgmap) + 1);
+ i_assert(uid >= msgmap->uid_next);
+
+ msgmap->uid_next = uid + 1;
+ array_push_back(&msgmap->uids, &uid);
+}
+
+void imapc_msgmap_expunge(struct imapc_msgmap *msgmap, uint32_t rseq)
+{
+ i_assert(rseq > 0);
+ i_assert(rseq <= imapc_msgmap_count(msgmap));
+
+ array_delete(&msgmap->uids, rseq-1, 1);
+}
+
+void imapc_msgmap_reset(struct imapc_msgmap *msgmap)
+{
+ array_clear(&msgmap->uids);
+ msgmap->uid_next = 1;
+}
diff --git a/src/lib-imap-client/imapc-msgmap.h b/src/lib-imap-client/imapc-msgmap.h
new file mode 100644
index 0000000..934bf97
--- /dev/null
+++ b/src/lib-imap-client/imapc-msgmap.h
@@ -0,0 +1,18 @@
+#ifndef IMAPC_MSGMAP_H
+#define IMAPC_MSGMAP_H
+
+struct imapc_msgmap *imapc_msgmap_init(void);
+void imapc_msgmap_deinit(struct imapc_msgmap **msgmap);
+
+uint32_t imapc_msgmap_count(struct imapc_msgmap *msgmap);
+uint32_t imapc_msgmap_uidnext(struct imapc_msgmap *msgmap);
+uint32_t imapc_msgmap_rseq_to_uid(struct imapc_msgmap *msgmap, uint32_t rseq);
+bool imapc_msgmap_uid_to_rseq(struct imapc_msgmap *msgmap,
+ uint32_t uid, uint32_t *rseq_r);
+
+void imapc_msgmap_append(struct imapc_msgmap *msgmap,
+ uint32_t rseq, uint32_t uid);
+void imapc_msgmap_expunge(struct imapc_msgmap *msgmap, uint32_t rseq);
+void imapc_msgmap_reset(struct imapc_msgmap *msgmap);
+
+#endif
diff --git a/src/lib-imap-client/test-imapc-client.c b/src/lib-imap-client/test-imapc-client.c
new file mode 100644
index 0000000..c8953f0
--- /dev/null
+++ b/src/lib-imap-client/test-imapc-client.c
@@ -0,0 +1,901 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hostpid.h"
+#include "net.h"
+#include "istream.h"
+#include "ostream.h"
+#include "ioloop.h"
+#include "unlink-directory.h"
+#include "sleep.h"
+#include "test-common.h"
+#include "test-subprocess.h"
+#include "imapc-client-private.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#define SERVER_KILL_TIMEOUT_SECS 20
+
+#define IMAPC_COMMAND_STATE_INVALID (enum imapc_command_state)-1
+
+typedef void test_server_init_t(void);
+typedef void test_client_init_t(void);
+
+struct test_server {
+ in_port_t port;
+ pid_t pid;
+
+ int fd_listen, fd;
+ struct istream *input;
+ struct ostream *output;
+};
+
+static struct ip_addr bind_ip;
+static struct test_server server;
+static struct imapc_client *imapc_client;
+static enum imapc_command_state imapc_login_last_reply;
+static ARRAY(enum imapc_command_state) imapc_cmd_last_replies;
+static bool debug = FALSE;
+
+static void main_deinit(void);
+
+/*
+ * Test client
+ */
+
+static struct imapc_client_settings test_imapc_default_settings = {
+ .host = "127.0.0.1",
+ .username = "testuser",
+ .password = "testpass",
+
+ .dns_client_socket_path = "",
+ .temp_path_prefix = ".test-tmp/",
+ .rawlog_dir = "",
+
+ .connect_timeout_msecs = 5000,
+ .connect_retry_count = 3,
+ .connect_retry_interval_msecs = 10,
+
+ .max_idle_time = 10000,
+};
+
+static enum imapc_command_state test_imapc_cmd_last_reply_pop(void)
+{
+ const enum imapc_command_state *replies;
+ enum imapc_command_state reply;
+ unsigned int count;
+
+ replies = array_get(&imapc_cmd_last_replies, &count);
+ if (count == 0)
+ return IMAPC_COMMAND_STATE_INVALID;
+ reply = replies[0];
+ array_pop_front(&imapc_cmd_last_replies);
+ return reply;
+}
+
+static bool test_imapc_cmd_last_reply_expect(enum imapc_command_state state)
+{
+ if (array_count(&imapc_cmd_last_replies) == 0)
+ imapc_client_run(imapc_client);
+ return test_imapc_cmd_last_reply_pop() == state;
+}
+
+static void imapc_login_callback(const struct imapc_command_reply *reply,
+ void *context ATTR_UNUSED)
+{
+ if (debug) {
+ i_debug("Login reply: %s %s",
+ imapc_command_state_names[reply->state],
+ reply->text_full);
+ }
+ imapc_login_last_reply = reply->state;
+ imapc_client_stop(imapc_client);
+}
+
+static void imapc_command_callback(const struct imapc_command_reply *reply,
+ void *context ATTR_UNUSED)
+{
+ if (debug) {
+ i_debug("Command reply: %s %s",
+ imapc_command_state_names[reply->state],
+ reply->text_full);
+ }
+ array_push_back(&imapc_cmd_last_replies, &reply->state);
+ imapc_client_stop(imapc_client);
+}
+
+static void imapc_reopen_callback(void *context)
+{
+ struct imapc_client_mailbox *box = context;
+ struct imapc_command *cmd;
+
+ cmd = imapc_client_mailbox_cmd(box, imapc_command_callback, NULL);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_SELECT);
+ imapc_command_send(cmd, "SELECT");
+}
+
+/*
+ * Test server
+ */
+
+static bool
+test_imapc_server_expect_full(struct test_server *server,
+ const char *expected_line)
+{
+ const char *line = i_stream_read_next_line(server->input);
+
+ if (debug)
+ i_debug("Received: %s", (line == NULL ? "<EOF>" : line));
+
+ if (line == NULL) {
+ printf("imapc client disconnected unexpectedly: %s\n",
+ i_stream_get_error(server->input));
+ return FALSE;
+ } else if (strcmp(line, expected_line) != 0) {
+ printf("imapc client sent '%s' when expecting '%s'\n",
+ line, expected_line);
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+static bool test_imapc_server_expect(const char *expected_line)
+{
+ return test_imapc_server_expect_full(&server, expected_line);
+}
+
+static void
+test_server_wait_connection(struct test_server *server, bool send_banner)
+{
+ if (debug)
+ i_debug("Waiting for connection");
+
+ server->fd = net_accept(server->fd_listen, NULL, NULL);
+ i_assert(server->fd >= 0);
+
+ if (debug)
+ i_debug("Client connected");
+
+ fd_set_nonblock(server->fd, FALSE);
+ server->input = i_stream_create_fd(server->fd, SIZE_MAX);
+ server->output = o_stream_create_fd(server->fd, SIZE_MAX);
+ o_stream_set_no_error_handling(server->output, TRUE);
+
+ if (send_banner) {
+ o_stream_nsend_str(server->output,
+ "* OK [CAPABILITY IMAP4rev1 UNSELECT QUOTA] ready\r\n");
+ }
+}
+
+static void test_server_disconnect(struct test_server *server)
+{
+ if (debug)
+ i_debug("Disconnecting client");
+
+ i_stream_unref(&server->input);
+ o_stream_unref(&server->output);
+ i_close_fd(&server->fd);
+}
+
+static void test_server_disconnect_and_wait(bool send_banner)
+{
+ test_server_disconnect(&server);
+ test_server_wait_connection(&server, send_banner);
+}
+
+/*
+ * Test processes
+ */
+
+static int test_open_server_fd(in_port_t *bind_port)
+{
+ int fd = net_listen(&bind_ip, bind_port, 128);
+ if (debug)
+ i_debug("server listening on %u", *bind_port);
+ if (fd == -1) {
+ i_fatal("listen(%s:%u) failed: %m",
+ net_ip2addr(&bind_ip), *bind_port);
+ }
+ fd_set_nonblock(fd, FALSE);
+ return fd;
+}
+
+static int test_run_server(test_server_init_t *server_test)
+{
+ struct ioloop *ioloop;
+
+ i_set_failure_prefix("SERVER: ");
+
+ if (debug)
+ i_debug("PID=%s", my_pid);
+
+ ioloop = io_loop_create();
+ if (server_test != NULL)
+ server_test();
+ test_server_disconnect(&server);
+ io_loop_destroy(&ioloop);
+
+ if (debug)
+ i_debug("Terminated");
+
+ i_close_fd(&server.fd_listen);
+ main_deinit();
+ return 0;
+}
+
+static void
+test_run_client(const struct imapc_client_settings *client_set,
+ test_client_init_t *client_test)
+{
+ struct ioloop *ioloop;
+
+ i_set_failure_prefix("CLIENT: ");
+
+ if (debug)
+ i_debug("PID=%s", my_pid);
+
+ i_sleep_msecs(100); /* wait a little for server setup */
+
+ ioloop = io_loop_create();
+ imapc_client = imapc_client_init(client_set, NULL);
+ client_test();
+ imapc_client_logout(imapc_client);
+ test_assert(array_count(&imapc_cmd_last_replies) == 0);
+ if (imapc_client != NULL)
+ imapc_client_deinit(&imapc_client);
+ io_loop_destroy(&ioloop);
+
+ if (debug)
+ i_debug("Terminated");
+}
+
+static void
+test_run_client_server(const struct imapc_client_settings *client_set,
+ test_client_init_t *client_test,
+ test_server_init_t *server_test)
+{
+ struct imapc_client_settings client_set_copy = *client_set;
+ const char *error;
+
+ imapc_client_cmd_tag_counter = 0;
+ imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID;
+ t_array_init(&imapc_cmd_last_replies, 4);
+
+ i_zero(&server);
+ server.pid = (pid_t)-1;
+ server.fd = -1;
+ server.fd_listen = test_open_server_fd(&server.port);
+ client_set_copy.port = server.port;
+
+ if (mkdir(client_set->temp_path_prefix, 0700) < 0 && errno != EEXIST)
+ i_fatal("mkdir(%s) failed: %m", client_set->temp_path_prefix);
+
+ if (server_test != NULL) {
+ /* Fork server */
+ test_subprocess_fork(test_run_server, server_test, TRUE);
+ }
+ i_close_fd(&server.fd_listen);
+
+ /* Run client */
+ test_run_client(&client_set_copy, client_test);
+
+ i_unset_failure_prefix();
+ test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS);
+ if (unlink_directory(client_set->temp_path_prefix,
+ UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0)
+ i_fatal("%s", error);
+}
+
+/*
+ * imapc connect failed
+ */
+
+static void test_imapc_connect_failed_client(void)
+{
+ imapc_client_set_login_callback(imapc_client,
+ imapc_login_callback, NULL);
+ imapc_client_login(imapc_client);
+ /* connection refused & one reconnect */
+ test_expect_errors(2);
+ imapc_client_run(imapc_client);
+ test_expect_no_more_errors();
+ test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_DISCONNECTED);
+}
+
+static void test_imapc_connect_failed(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+
+ test_begin("imapc connect failed");
+ test_run_client_server(&set, test_imapc_connect_failed_client, NULL);
+ test_end();
+}
+
+/*
+ * imapc banner hang
+ */
+
+static void test_imapc_banner_hangs_client(void)
+{
+ imapc_client_set_login_callback(imapc_client,
+ imapc_login_callback, NULL);
+ imapc_client_login(imapc_client);
+ test_expect_errors(2);
+ imapc_client_run(imapc_client);
+ test_expect_no_more_errors();
+ test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_DISCONNECTED);
+}
+
+static void test_imapc_banner_hangs_server(void)
+{
+ struct test_server server2 = { .fd_listen = server.fd_listen };
+
+ test_server_wait_connection(&server, FALSE);
+ test_server_wait_connection(&server2, FALSE);
+ test_assert(i_stream_read_next_line(server2.input) == NULL);
+ test_server_disconnect(&server2);
+}
+
+static void test_imapc_banner_hangs(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+ set.connect_timeout_msecs = 500;
+
+ test_begin("imapc banner hangs");
+ test_run_client_server(&set, test_imapc_banner_hangs_client,
+ test_imapc_banner_hangs_server);
+ test_end();
+}
+
+/*
+ * imapc login hangs
+ */
+
+static void test_imapc_login_hangs_client(void)
+{
+ imapc_client_set_login_callback(imapc_client,
+ imapc_login_callback, NULL);
+ imapc_client_login(imapc_client);
+ /* run the first login */
+ test_expect_error_string("Authentication timed out");
+ imapc_client_run(imapc_client);
+ test_expect_no_more_errors();
+ /* imapc_login_callback() has stopped us. run the second reconnect
+ login. */
+ test_expect_error_string("Authentication timed out");
+ imapc_client_run(imapc_client);
+ test_expect_no_more_errors();
+ test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_DISCONNECTED);
+}
+
+static void test_imapc_login_hangs_server(void)
+{
+ struct test_server server2 = { .fd_listen = server.fd_listen };
+
+ test_server_wait_connection(&server, TRUE);
+ test_assert(test_imapc_server_expect(
+ "1 LOGIN \"testuser\" \"testpass\""));
+
+ test_server_wait_connection(&server2, TRUE);
+ test_assert(test_imapc_server_expect_full(
+ &server2, "2 LOGIN \"testuser\" \"testpass\""));
+
+ test_assert(i_stream_read_next_line(server2.input) == NULL);
+ test_server_disconnect(&server2);
+}
+
+static void test_imapc_login_hangs(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+ set.connect_timeout_msecs = 500;
+
+ test_begin("imapc login hangs");
+ test_run_client_server(&set, test_imapc_login_hangs_client,
+ test_imapc_login_hangs_server);
+ test_end();
+}
+
+/*
+ * imapc login fails
+ */
+
+static void test_imapc_login_fails_client(void)
+{
+ imapc_client_set_login_callback(imapc_client,
+ imapc_login_callback, NULL);
+ imapc_client_login(imapc_client);
+ test_expect_error_string("Authentication failed: Test login failed");
+ imapc_client_run(imapc_client);
+ test_expect_no_more_errors();
+ test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_AUTH_FAILED);
+}
+
+static void test_imapc_login_fails_server(void)
+{
+ test_server_wait_connection(&server, TRUE);
+ test_assert(test_imapc_server_expect(
+ "1 LOGIN \"testuser\" \"testpass\""));
+ o_stream_nsend_str(server.output, "1 NO Test login failed\r\n");
+}
+
+static void test_imapc_login_fails(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+
+ test_begin("imapc login fails");
+ test_run_client_server(&set, test_imapc_login_fails_client,
+ test_imapc_login_fails_server);
+ test_end();
+}
+
+/*
+ * imapc reconnect
+ */
+
+static void test_imapc_reconnect_client(void)
+{
+ struct imapc_command *cmd;
+
+ /* login to server */
+ imapc_client_set_login_callback(imapc_client,
+ imapc_login_callback, NULL);
+ imapc_client_login(imapc_client);
+ imapc_client_run(imapc_client);
+ test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK);
+ imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID;
+
+ /* disconnect */
+ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL);
+ imapc_command_send(cmd, "DISCONNECT");
+ test_expect_error_string("reconnecting");
+ imapc_client_run(imapc_client);
+ test_expect_no_more_errors();
+ test_assert(test_imapc_cmd_last_reply_pop() ==
+ IMAPC_COMMAND_STATE_DISCONNECTED);
+
+ /* we should be reconnected now. try a command. */
+ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL);
+ imapc_command_send(cmd, "NOOP");
+ imapc_client_run(imapc_client);
+ test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_INVALID);
+ test_assert(test_imapc_cmd_last_reply_pop() == IMAPC_COMMAND_STATE_OK);
+}
+
+static void test_imapc_reconnect_server(void)
+{
+ test_server_wait_connection(&server, TRUE);
+ test_assert(test_imapc_server_expect(
+ "1 LOGIN \"testuser\" \"testpass\""));
+ o_stream_nsend_str(server.output, "1 OK \r\n");
+
+ test_assert(test_imapc_server_expect("2 DISCONNECT"));
+ test_server_disconnect_and_wait(TRUE);
+
+ test_assert(test_imapc_server_expect(
+ "4 LOGIN \"testuser\" \"testpass\""));
+ o_stream_nsend_str(server.output, "4 OK \r\n");
+ test_assert(test_imapc_server_expect("3 NOOP"));
+ o_stream_nsend_str(server.output, "3 OK \r\n");
+
+ test_assert(test_imapc_server_expect("5 LOGOUT"));
+ o_stream_nsend_str(server.output, "5 OK \r\n");
+
+ test_assert(i_stream_read_next_line(server.input) == NULL);
+}
+
+static void test_imapc_reconnect(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+
+ test_begin("imapc reconnect");
+ test_run_client_server(&set, test_imapc_reconnect_client,
+ test_imapc_reconnect_server);
+ test_end();
+}
+
+/*
+ * imapc reconnect resend commands
+ */
+
+static void test_imapc_reconnect_resend_cmds_client(void)
+{
+ struct imapc_command *cmd;
+
+ /* login to server */
+ imapc_client_set_login_callback(imapc_client,
+ imapc_login_callback, NULL);
+ imapc_client_login(imapc_client);
+ imapc_client_run(imapc_client);
+ test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK);
+ imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID;
+
+ /* send two commands */
+ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_send(cmd, "RETRY1");
+ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_send(cmd, "RETRY2");
+
+ /* disconnect & reconnect automatically */
+ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL);
+ imapc_command_send(cmd, "DISCONNECT");
+ test_expect_error_string("reconnecting");
+ imapc_client_run(imapc_client);
+ test_expect_no_more_errors();
+ test_assert(test_imapc_cmd_last_reply_expect(
+ IMAPC_COMMAND_STATE_DISCONNECTED));
+
+ /* continue reconnection */
+ test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK));
+ test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK));
+}
+
+static void test_imapc_reconnect_resend_cmds_server(void)
+{
+ test_server_wait_connection(&server, TRUE);
+ test_assert(test_imapc_server_expect(
+ "1 LOGIN \"testuser\" \"testpass\""));
+ o_stream_nsend_str(server.output, "1 OK \r\n");
+
+ test_assert(test_imapc_server_expect("2 RETRY1"));
+ test_assert(test_imapc_server_expect("3 RETRY2"));
+ test_assert(test_imapc_server_expect("4 DISCONNECT"));
+ test_server_disconnect_and_wait(TRUE);
+
+ test_assert(test_imapc_server_expect(
+ "5 LOGIN \"testuser\" \"testpass\""));
+ o_stream_nsend_str(server.output, "5 OK \r\n");
+ test_assert(test_imapc_server_expect("2 RETRY1"));
+ o_stream_nsend_str(server.output, "2 OK \r\n");
+ test_assert(test_imapc_server_expect("3 RETRY2"));
+ o_stream_nsend_str(server.output, "3 OK \r\n");
+
+ test_assert(test_imapc_server_expect("6 LOGOUT"));
+ o_stream_nsend_str(server.output, "6 OK \r\n");
+
+ test_assert(i_stream_read_next_line(server.input) == NULL);
+}
+
+static void test_imapc_reconnect_resend_commands(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+
+ test_begin("imapc reconnect resend commands");
+ test_run_client_server(&set, test_imapc_reconnect_resend_cmds_client,
+ test_imapc_reconnect_resend_cmds_server);
+ test_end();
+}
+
+/*
+ * imapc reconnect resend commands failed
+ */
+
+static void test_imapc_reconnect_resend_cmds_failed_client(void)
+{
+ struct imapc_command *cmd;
+
+ /* login to server */
+ imapc_client_set_login_callback(imapc_client,
+ imapc_login_callback, NULL);
+ imapc_client_login(imapc_client);
+ imapc_client_run(imapc_client);
+ test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK);
+ imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID;
+
+ /* send two commands */
+ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_send(cmd, "RETRY1");
+ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_send(cmd, "RETRY2");
+
+ /* disconnect & try to reconnect automatically */
+ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL);
+ imapc_command_send(cmd, "DISCONNECT");
+ test_expect_error_string("reconnecting");
+ imapc_client_run(imapc_client);
+ test_expect_no_more_errors();
+ test_assert(test_imapc_cmd_last_reply_expect(
+ IMAPC_COMMAND_STATE_DISCONNECTED));
+ test_expect_error_string("timed out");
+ test_assert(test_imapc_cmd_last_reply_expect(
+ IMAPC_COMMAND_STATE_DISCONNECTED));
+ test_expect_no_more_errors();
+ test_assert(test_imapc_cmd_last_reply_expect(
+ IMAPC_COMMAND_STATE_DISCONNECTED));
+}
+
+static void test_imapc_reconnect_resend_cmds_failed_server(void)
+{
+ test_server_wait_connection(&server, TRUE);
+ test_assert(test_imapc_server_expect(
+ "1 LOGIN \"testuser\" \"testpass\""));
+ o_stream_nsend_str(server.output, "1 OK \r\n");
+
+ test_assert(test_imapc_server_expect("2 RETRY1"));
+ test_assert(test_imapc_server_expect("3 RETRY2"));
+ test_assert(test_imapc_server_expect("4 DISCONNECT"));
+ test_server_disconnect(&server);
+
+ i_sleep_intr_secs(60);
+}
+
+static void test_imapc_reconnect_resend_commands_failed(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+ set.connect_timeout_msecs = 500;
+
+ test_begin("imapc reconnect resend commands failed");
+ test_run_client_server(&set,
+ test_imapc_reconnect_resend_cmds_failed_client,
+ test_imapc_reconnect_resend_cmds_failed_server);
+ test_end();
+}
+
+/*
+ * imapc reconnect mailbox
+ */
+
+static void test_imapc_reconnect_mailbox_client(void)
+{
+ struct imapc_command *cmd;
+ struct imapc_client_mailbox *box;
+
+ /* login to server */
+ imapc_client_set_login_callback(imapc_client,
+ imapc_login_callback, NULL);
+ imapc_client_login(imapc_client);
+ imapc_client_run(imapc_client);
+ test_assert(imapc_login_last_reply == IMAPC_COMMAND_STATE_OK);
+ imapc_login_last_reply = IMAPC_COMMAND_STATE_INVALID;
+
+ /* select a mailbox */
+ box = imapc_client_mailbox_open(imapc_client, NULL);
+ imapc_client_mailbox_set_reopen_cb(box, imapc_reopen_callback, box);
+
+ cmd = imapc_client_mailbox_cmd(box, imapc_command_callback, NULL);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_SELECT);
+ imapc_command_send(cmd, "SELECT");
+ imapc_client_run(imapc_client);
+ test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK));
+
+ /* send a command */
+ cmd = imapc_client_mailbox_cmd(box, imapc_command_callback, NULL);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ imapc_command_send(cmd, "RETRY");
+
+ /* disconnect & reconnect automatically */
+ cmd = imapc_client_cmd(imapc_client, imapc_command_callback, NULL);
+ imapc_command_send(cmd, "DISCONNECT");
+ test_expect_error_string("reconnecting");
+ imapc_client_run(imapc_client);
+ test_expect_no_more_errors();
+ test_assert(test_imapc_cmd_last_reply_expect(
+ IMAPC_COMMAND_STATE_DISCONNECTED));
+
+ /* continue reconnection */
+ test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK));
+ test_assert(test_imapc_cmd_last_reply_expect(IMAPC_COMMAND_STATE_OK));
+
+ imapc_client_mailbox_close(&box);
+}
+
+static void test_imapc_reconnect_mailbox_server(void)
+{
+ test_server_wait_connection(&server, TRUE);
+ test_assert(test_imapc_server_expect(
+ "1 LOGIN \"testuser\" \"testpass\""));
+ o_stream_nsend_str(server.output, "1 OK \r\n");
+
+ test_assert(test_imapc_server_expect("2 SELECT"));
+ o_stream_nsend_str(server.output, "2 OK \r\n");
+
+ test_assert(test_imapc_server_expect("3 RETRY"));
+ test_assert(test_imapc_server_expect("4 DISCONNECT"));
+ test_server_disconnect_and_wait(TRUE);
+
+ test_assert(test_imapc_server_expect(
+ "5 LOGIN \"testuser\" \"testpass\""));
+ o_stream_nsend_str(server.output, "5 OK \r\n");
+ test_assert(test_imapc_server_expect("6 SELECT"));
+ o_stream_nsend_str(server.output, "6 OK \r\n");
+ test_assert(test_imapc_server_expect("3 RETRY"));
+ o_stream_nsend_str(server.output, "3 OK \r\n");
+
+ test_assert(test_imapc_server_expect("7 LOGOUT"));
+ o_stream_nsend_str(server.output, "7 OK \r\n");
+
+ test_assert(i_stream_read_next_line(server.input) == NULL);
+}
+
+static void test_imapc_reconnect_mailbox(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+
+ test_begin("imapc reconnect mailbox");
+ test_run_client_server(&set, test_imapc_reconnect_mailbox_client,
+ test_imapc_reconnect_mailbox_server);
+ test_end();
+}
+
+/*
+ * imapc_client_get_capabilities()
+ */
+
+static void test_imapc_client_get_capabilities_client(void)
+{
+ enum imapc_capability capabilities;
+
+ test_assert(imapc_client_get_capabilities(imapc_client, &capabilities) == 0);
+ test_assert(capabilities == (IMAPC_CAPABILITY_IMAP4REV1 |
+ IMAPC_CAPABILITY_UNSELECT |
+ IMAPC_CAPABILITY_QUOTA));
+}
+
+static void test_imapc_client_get_capabilities_server(void)
+{
+ test_server_wait_connection(&server, TRUE);
+ test_assert(test_imapc_server_expect(
+ "1 LOGIN \"testuser\" \"testpass\""));
+ o_stream_nsend_str(server.output, "1 OK \r\n");
+
+ test_assert(test_imapc_server_expect("2 LOGOUT"));
+ o_stream_nsend_str(server.output, "2 OK \r\n");
+
+ test_assert(i_stream_read_next_line(server.input) == NULL);
+}
+
+static void test_imapc_client_get_capabilities(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+
+ test_begin("imapc_client_get_capabilities()");
+ test_run_client_server(&set, test_imapc_client_get_capabilities_client,
+ test_imapc_client_get_capabilities_server);
+ test_end();
+}
+
+/*
+ * imapc_client_get_capabilities() reconnected
+ */
+
+static void test_imapc_client_get_capabilities_reconnected_client(void)
+{
+ enum imapc_capability capabilities;
+
+ test_expect_error_string("Server disconnected unexpectedly");
+ test_assert(imapc_client_get_capabilities(imapc_client,
+ &capabilities) == 0);
+ test_assert(capabilities == (IMAPC_CAPABILITY_IMAP4REV1 |
+ IMAPC_CAPABILITY_UNSELECT |
+ IMAPC_CAPABILITY_QUOTA));
+ test_expect_no_more_errors();
+}
+
+static void test_imapc_client_get_capabilities_reconnected_server(void)
+{
+ test_server_wait_connection(&server, TRUE);
+ test_server_disconnect_and_wait(TRUE);
+
+ test_assert(test_imapc_server_expect(
+ "2 LOGIN \"testuser\" \"testpass\""));
+ o_stream_nsend_str(server.output, "2 OK \r\n");
+
+ test_assert(test_imapc_server_expect("3 LOGOUT"));
+ o_stream_nsend_str(server.output, "3 OK \r\n");
+
+ test_assert(i_stream_read_next_line(server.input) == NULL);
+}
+
+static void test_imapc_client_get_capabilities_reconnected(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+
+ test_begin("imapc_client_get_capabilities() reconnected");
+
+ test_run_client_server(
+ &set, test_imapc_client_get_capabilities_reconnected_client,
+ test_imapc_client_get_capabilities_reconnected_server);
+ test_end();
+}
+
+/*
+ * imapc_client_get_capabilities() disconnected
+ */
+
+static void test_imapc_client_get_capabilities_disconnected_client(void)
+{
+ enum imapc_capability capabilities;
+
+ test_expect_errors(2);
+ test_assert(imapc_client_get_capabilities(imapc_client,
+ &capabilities) < 0);
+ test_expect_no_more_errors();
+}
+
+static void test_imapc_client_get_capabilities_disconnected_server(void)
+{
+ test_server_wait_connection(&server, TRUE);
+ test_server_disconnect_and_wait(TRUE);
+}
+
+static void test_imapc_client_get_capabilities_disconnected(void)
+{
+ struct imapc_client_settings set = test_imapc_default_settings;
+
+ test_begin("imapc_client_get_capabilities() disconnected");
+
+ test_run_client_server(
+ &set, test_imapc_client_get_capabilities_disconnected_client,
+ test_imapc_client_get_capabilities_disconnected_server);
+ test_end();
+}
+
+/*
+ * Main
+ */
+
+static void main_init(void)
+{
+ /* nothing yet */
+}
+
+static void main_deinit(void)
+{
+ /* nothing yet; also called from sub-processes */
+}
+
+int main(int argc ATTR_UNUSED, char *argv[])
+{
+ int c;
+ int ret;
+
+ static void (*const test_functions[])(void) = {
+ test_imapc_connect_failed,
+ test_imapc_banner_hangs,
+ test_imapc_login_hangs,
+ test_imapc_login_fails,
+ test_imapc_reconnect,
+ test_imapc_reconnect_resend_commands,
+ test_imapc_reconnect_resend_commands_failed,
+ test_imapc_reconnect_mailbox,
+ test_imapc_client_get_capabilities,
+ test_imapc_client_get_capabilities_reconnected,
+ test_imapc_client_get_capabilities_disconnected,
+ NULL
+ };
+
+ lib_init();
+ main_init();
+
+ while ((c = getopt(argc, argv, "D")) > 0) {
+ switch (c) {
+ case 'D':
+ debug = TRUE;
+ break;
+ default:
+ i_fatal("Usage: %s [-D]", argv[0]);
+ }
+ }
+
+ test_subprocesses_init(debug);
+ test_imapc_default_settings.debug = debug;
+
+ /* listen on localhost */
+ i_zero(&bind_ip);
+ bind_ip.family = AF_INET;
+ bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK);
+
+ ret = test_run(test_functions);
+
+ test_subprocesses_deinit();
+ main_deinit();
+ lib_deinit();
+
+ return ret;
+}
diff --git a/src/lib-imap-storage/Makefile.am b/src/lib-imap-storage/Makefile.am
new file mode 100644
index 0000000..b084653
--- /dev/null
+++ b/src/lib-imap-storage/Makefile.am
@@ -0,0 +1,24 @@
+noinst_LTLIBRARIES = libimap-storage.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-charset \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-imap
+
+libimap_storage_la_SOURCES = \
+ imap-msgpart.c \
+ imap-msgpart-url.c \
+ imap-metadata.c
+
+headers = \
+ imap-msgpart.h \
+ imap-msgpart-url.h \
+ imap-metadata.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
diff --git a/src/lib-imap-storage/Makefile.in b/src/lib-imap-storage/Makefile.in
new file mode 100644
index 0000000..c0750d9
--- /dev/null
+++ b/src/lib-imap-storage/Makefile.in
@@ -0,0 +1,825 @@
+# 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@
+subdir = src/lib-imap-storage
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libimap_storage_la_LIBADD =
+am_libimap_storage_la_OBJECTS = imap-msgpart.lo imap-msgpart-url.lo \
+ imap-metadata.lo
+libimap_storage_la_OBJECTS = $(am_libimap_storage_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_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)/imap-metadata.Plo \
+ ./$(DEPDIR)/imap-msgpart-url.Plo ./$(DEPDIR)/imap-msgpart.Plo
+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 = $(libimap_storage_la_SOURCES)
+DIST_SOURCES = $(libimap_storage_la_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@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libimap-storage.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-charset \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-imap
+
+libimap_storage_la_SOURCES = \
+ imap-msgpart.c \
+ imap-msgpart-url.c \
+ imap-metadata.c
+
+headers = \
+ imap-msgpart.h \
+ imap-msgpart-url.h \
+ imap-metadata.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+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-imap-storage/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-imap-storage/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-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}; \
+ }
+
+libimap-storage.la: $(libimap_storage_la_OBJECTS) $(libimap_storage_la_DEPENDENCIES) $(EXTRA_libimap_storage_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libimap_storage_la_OBJECTS) $(libimap_storage_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-metadata.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-msgpart-url.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-msgpart.Plo@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 $(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 \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/imap-metadata.Plo
+ -rm -f ./$(DEPDIR)/imap-msgpart-url.Plo
+ -rm -f ./$(DEPDIR)/imap-msgpart.Plo
+ -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)/imap-metadata.Plo
+ -rm -f ./$(DEPDIR)/imap-msgpart-url.Plo
+ -rm -f ./$(DEPDIR)/imap-msgpart.Plo
+ -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 \
+ 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
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib-imap-storage/imap-metadata.c b/src/lib-imap-storage/imap-metadata.c
new file mode 100644
index 0000000..eb791a4
--- /dev/null
+++ b/src/lib-imap-storage/imap-metadata.c
@@ -0,0 +1,314 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "imap-metadata.h"
+
+struct imap_metadata_transaction {
+ struct mailbox *box;
+ struct mailbox_transaction_context *trans;
+
+ enum mail_error error;
+ char *error_string;
+
+ bool server:1;
+ bool validated_only:1;
+};
+
+bool imap_metadata_verify_entry_name(const char *name,
+ const char **client_error_r)
+{
+ unsigned int i;
+ bool ok;
+
+ if (name[0] != '/') {
+ *client_error_r = "Entry name must begin with '/'";
+ return FALSE;
+ }
+ for (i = 0; name[i] != '\0'; i++) {
+ switch (name[i]) {
+ case '/':
+ if (i > 0 && name[i-1] == '/') {
+ *client_error_r = "Entry name can't contain consecutive '/'";
+ return FALSE;
+ }
+ if (name[i+1] == '\0') {
+ *client_error_r = "Entry name can't end with '/'";
+ return FALSE;
+ }
+ break;
+ case '*':
+ *client_error_r = "Entry name can't contain '*'";
+ return FALSE;
+ case '%':
+ *client_error_r = "Entry name can't contain '%'";
+ return FALSE;
+ default:
+ if (name[i] <= 0x19) {
+ *client_error_r = "Entry name can't contain control chars";
+ return FALSE;
+ }
+ break;
+ }
+ }
+ T_BEGIN {
+ const char *prefix, *p = strchr(name+1, '/');
+
+ prefix = p == NULL ? name : t_strdup_until(name, p);
+ ok = strcasecmp(prefix, IMAP_METADATA_PRIVATE_PREFIX) == 0 ||
+ strcasecmp(prefix, IMAP_METADATA_SHARED_PREFIX) == 0;
+ } T_END;
+ if (!ok) {
+ *client_error_r = "Entry name must begin with /private or /shared";
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+imap_metadata_transaction_set_error(struct imap_metadata_transaction *imtrans,
+ enum mail_error error, const char *string)
+{
+ i_free(imtrans->error_string);
+ imtrans->error_string = i_strdup(string);
+ imtrans->error = error;
+}
+
+static bool
+imap_metadata_entry2key(struct imap_metadata_transaction *imtrans,
+ const char *entry, enum mail_attribute_type *type_r,
+ const char **key_r)
+{
+ const char *key_prefix = (imtrans->server ?
+ MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER : NULL);
+
+ /* names are case-insensitive so we'll always lowercase them */
+ entry = t_str_lcase(entry);
+
+ if (str_begins(entry, IMAP_METADATA_PRIVATE_PREFIX)) {
+ *key_r = entry + strlen(IMAP_METADATA_PRIVATE_PREFIX);
+ *type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE;
+ } else {
+ i_assert(str_begins(entry, IMAP_METADATA_SHARED_PREFIX));
+ *key_r = entry + strlen(IMAP_METADATA_SHARED_PREFIX);
+ *type_r = MAIL_ATTRIBUTE_TYPE_SHARED;
+ }
+ if ((*key_r)[0] == '\0') {
+ /* /private or /shared prefix has no value itself */
+ } else {
+ i_assert((*key_r)[0] == '/');
+ *key_r += 1;
+ }
+
+ if (imtrans->validated_only)
+ *type_r |= MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED;
+
+ if (str_begins(*key_r, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) {
+ /* Dovecot's internal attribute (mailbox or server).
+ don't allow accessing this. */
+ return FALSE;
+ }
+ /* Add the server-prefix (after checking for the above internal
+ attribute). */
+ if (key_prefix != NULL)
+ *key_r = t_strconcat(key_prefix, *key_r, NULL);
+ return TRUE;
+}
+
+static int
+imap_metadata_get_mailbox_transaction(struct imap_metadata_transaction *imtrans)
+{
+ if (imtrans->trans != NULL)
+ return 0;
+
+ if (imtrans->box == NULL || mailbox_open(imtrans->box) < 0)
+ return -1;
+ imtrans->trans = mailbox_transaction_begin(imtrans->box,
+ MAILBOX_TRANSACTION_FLAG_EXTERNAL, __func__);
+ return 0;
+}
+
+int imap_metadata_set(struct imap_metadata_transaction *imtrans,
+ const char *entry, const struct mail_attribute_value *value)
+{
+ enum mail_attribute_type type;
+ const char *key;
+
+ if (!imap_metadata_entry2key(imtrans, entry, &type, &key)) {
+ imap_metadata_transaction_set_error(imtrans, MAIL_ERROR_PARAMS,
+ "Internal mailbox attributes cannot be accessed");
+ return -1;
+ }
+
+ if (imap_metadata_get_mailbox_transaction(imtrans) < 0)
+ return -1;
+ return (value->value == NULL && value->value_stream == NULL ?
+ mailbox_attribute_unset(imtrans->trans, type, key) :
+ mailbox_attribute_set(imtrans->trans, type, key, value));
+}
+
+int imap_metadata_unset(struct imap_metadata_transaction *imtrans,
+ const char *entry)
+{
+ struct mail_attribute_value value;
+
+ i_zero(&value);
+ return imap_metadata_set(imtrans, entry, &value);
+}
+
+int imap_metadata_get(struct imap_metadata_transaction *imtrans,
+ const char *entry, struct mail_attribute_value *value_r)
+{
+ enum mail_attribute_type type;
+ const char *key;
+
+ i_zero(value_r);
+ if (!imap_metadata_entry2key(imtrans, entry, &type, &key))
+ return 0;
+ return mailbox_attribute_get(imtrans->box, type, key, value_r);
+}
+
+int imap_metadata_get_stream(struct imap_metadata_transaction *imtrans,
+ const char *entry, struct mail_attribute_value *value_r)
+{
+ enum mail_attribute_type type;
+ const char *key;
+
+ i_zero(value_r);
+ if (!imap_metadata_entry2key(imtrans, entry, &type, &key))
+ return 0;
+ return mailbox_attribute_get_stream(imtrans->box, type, key, value_r);
+}
+
+struct imap_metadata_iter {
+ struct mailbox_attribute_iter *iter;
+};
+
+struct imap_metadata_iter *
+imap_metadata_iter_init(struct imap_metadata_transaction *imtrans,
+ const char *entry)
+{
+ struct imap_metadata_iter *iter;
+ enum mail_attribute_type type;
+ const char *key;
+
+ iter = i_new(struct imap_metadata_iter, 1);
+ if (imap_metadata_entry2key(imtrans, entry, &type, &key)) {
+ const char *prefix =
+ key[0] == '\0' ? "" : t_strconcat(key, "/", NULL);
+ iter->iter = mailbox_attribute_iter_init(imtrans->box, type,
+ prefix);
+ }
+ return iter;
+}
+
+const char *imap_metadata_iter_next(struct imap_metadata_iter *iter)
+{
+ if (iter->iter == NULL)
+ return NULL;
+ return mailbox_attribute_iter_next(iter->iter);
+}
+
+int imap_metadata_iter_deinit(struct imap_metadata_iter **_iter)
+{
+ struct imap_metadata_iter *iter = *_iter;
+ int ret;
+
+ *_iter = NULL;
+
+ if (iter->iter == NULL)
+ ret = 0;
+ else
+ ret = mailbox_attribute_iter_deinit(&iter->iter);
+ i_free(iter);
+ return ret;
+}
+
+struct imap_metadata_transaction *
+imap_metadata_transaction_begin(struct mailbox *box)
+{
+ struct imap_metadata_transaction *imtrans;
+
+ imtrans = i_new(struct imap_metadata_transaction, 1);
+ imtrans->box = box;
+ return imtrans;
+}
+
+struct imap_metadata_transaction *
+imap_metadata_transaction_begin_server(struct mail_user *user)
+{
+ struct mail_namespace *ns;
+ struct mailbox *box;
+ struct imap_metadata_transaction *imtrans;
+
+ ns = mail_namespace_find_inbox(user->namespaces);
+ /* Server metadata shouldn't depend on INBOX's ACLs, so ignore them. */
+ box = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_IGNORE_ACLS |
+ MAILBOX_FLAG_ATTRIBUTE_SESSION);
+ imtrans = imap_metadata_transaction_begin(box);
+ imtrans->server = TRUE;
+ return imtrans;
+}
+
+void imap_metadata_transaction_validated_only(struct imap_metadata_transaction *imtrans,
+ bool set)
+{
+ imtrans->validated_only = set;
+}
+
+static void
+imap_metadata_transaction_finish(struct imap_metadata_transaction **_imtrans)
+{
+ struct imap_metadata_transaction *imtrans = *_imtrans;
+
+ if (imtrans->server)
+ mailbox_free(&imtrans->box);
+
+ i_free(imtrans->error_string);
+ i_free(imtrans);
+ *_imtrans = NULL;
+}
+
+int imap_metadata_transaction_commit(
+ struct imap_metadata_transaction **_imtrans,
+ enum mail_error *error_code_r, const char **client_error_r)
+{
+ struct imap_metadata_transaction *imtrans = *_imtrans;
+ int ret = 0;
+
+ if (imtrans->trans != NULL) {
+ const char *error = NULL;
+ ret = mailbox_transaction_commit(&imtrans->trans);
+ if (ret < 0)
+ error = mailbox_get_last_error(imtrans->box, error_code_r);
+ if (client_error_r != NULL)
+ *client_error_r = error;
+ }
+ imap_metadata_transaction_finish(_imtrans);
+ return ret;
+}
+
+void imap_metadata_transaction_rollback(
+ struct imap_metadata_transaction **_imtrans)
+{
+ struct imap_metadata_transaction *imtrans = *_imtrans;
+
+ if (imtrans->trans != NULL)
+ mailbox_transaction_rollback(&imtrans->trans);
+ imap_metadata_transaction_finish(_imtrans);
+}
+
+const char *
+imap_metadata_transaction_get_last_error(
+ struct imap_metadata_transaction *imtrans,
+ enum mail_error *error_code_r)
+{
+ if (imtrans->error != MAIL_ERROR_NONE) {
+ if (error_code_r != NULL)
+ *error_code_r = imtrans->error;
+ return imtrans->error_string;
+ }
+ i_assert(imtrans->box != NULL);
+ return mailbox_get_last_error(imtrans->box, error_code_r);
+}
diff --git a/src/lib-imap-storage/imap-metadata.h b/src/lib-imap-storage/imap-metadata.h
new file mode 100644
index 0000000..ef88e61
--- /dev/null
+++ b/src/lib-imap-storage/imap-metadata.h
@@ -0,0 +1,61 @@
+#ifndef IMAP_METADATA_H
+#define IMAP_METADATA_H
+
+#define IMAP_METADATA_PRIVATE_PREFIX "/private"
+#define IMAP_METADATA_SHARED_PREFIX "/shared"
+
+struct imap_metadata_iter;
+struct imap_metadata_transaction;
+
+/* Checks whether IMAP metadata entry name is valid */
+bool imap_metadata_verify_entry_name(
+ const char *name, const char **client_error_r);
+
+/* Set IMAP metadata entry to value. */
+int imap_metadata_set(struct imap_metadata_transaction *imtrans,
+ const char *entry, const struct mail_attribute_value *value);
+/* Delete IMAP metadata entry. This is just a wrapper to
+ imap_metadata_set() with value->value=NULL. */
+int imap_metadata_unset(struct imap_metadata_transaction *imtrans,
+ const char *entry);
+/* Returns value for IMAP metadata entry. Returns 1 if value was returned,
+ 0 if value wasn't found (set to NULL), -1 if error */
+int imap_metadata_get(struct imap_metadata_transaction *imtrans,
+ const char *entry, struct mail_attribute_value *value_r);
+/* Same as imap_metadata_get(), but the returned value may be either an
+ input stream or a string. */
+int imap_metadata_get_stream(struct imap_metadata_transaction *imtrans,
+ const char *entry, struct mail_attribute_value *value_r);
+
+/* Iterate through IMAP metadata entries names under the specified entry. */
+struct imap_metadata_iter *
+imap_metadata_iter_init(struct imap_metadata_transaction *imtrans,
+ const char *entry);
+/* Returns the next IMAP metadata entry name or NULL if there are no more
+ entries. */
+const char *imap_metadata_iter_next(struct imap_metadata_iter *iter);
+int imap_metadata_iter_deinit(struct imap_metadata_iter **_iter);
+
+struct imap_metadata_transaction *
+imap_metadata_transaction_begin(struct mailbox *box);
+struct imap_metadata_transaction *
+imap_metadata_transaction_begin_mailbox(struct mail_user *user,
+ const char *mailbox);
+struct imap_metadata_transaction *
+imap_metadata_transaction_begin_server(struct mail_user *user);
+
+/* Allow access only to validated attributes within this transaction. */
+void imap_metadata_transaction_validated_only(struct imap_metadata_transaction *imtrans,
+ bool set);
+
+int imap_metadata_transaction_commit(
+ struct imap_metadata_transaction **_imtrans,
+ enum mail_error *error_code_r, const char **client_error_r);
+void imap_metadata_transaction_rollback(
+ struct imap_metadata_transaction **_imtrans);
+const char *
+imap_metadata_transaction_get_last_error(
+ struct imap_metadata_transaction *imtrans,
+ enum mail_error *error_code_r);
+
+#endif
diff --git a/src/lib-imap-storage/imap-msgpart-url.c b/src/lib-imap-storage/imap-msgpart-url.c
new file mode 100644
index 0000000..2469dd3
--- /dev/null
+++ b/src/lib-imap-storage/imap-msgpart-url.c
@@ -0,0 +1,287 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "net.h"
+#include "istream.h"
+#include "message-parser.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+#include "imap-url.h"
+#include "imap-msgpart.h"
+#include "imap-msgpart-url.h"
+
+struct imap_msgpart_url {
+ char *mailbox;
+ uint32_t uidvalidity;
+ uint32_t uid;
+ char *section;
+ uoff_t partial_offset, partial_size;
+
+ struct imap_msgpart *part;
+
+ struct mail_user *user;
+ struct mailbox *selected_box;
+ struct mailbox *box;
+ struct mailbox_transaction_context *trans;
+ struct mail *mail;
+
+ struct imap_msgpart_open_result result;
+
+ bool decode_cte_to_binary:1;
+};
+
+int imap_msgpart_url_create(struct mail_user *user, const struct imap_url *url,
+ struct imap_msgpart_url **mpurl_r,
+ const char **client_error_r)
+{
+ const char *section = url->section == NULL ? "" : url->section;
+ struct imap_msgpart_url *mpurl;
+ struct imap_msgpart *msgpart;
+
+ if (url->mailbox == NULL || url->uid == 0 ||
+ url->search_program != NULL) {
+ *client_error_r = "Invalid messagepart IMAP URL";
+ return -1;
+ }
+ if (imap_msgpart_parse(section, &msgpart) < 0) {
+ *client_error_r = "Invalid section";
+ return -1;
+ }
+
+ mpurl = i_new(struct imap_msgpart_url, 1);
+ mpurl->part = msgpart;
+ mpurl->user = user;
+ mpurl->mailbox = i_strdup(url->mailbox);
+ mpurl->uidvalidity = url->uidvalidity;
+ mpurl->uid = url->uid;
+ if (url->section != NULL)
+ mpurl->section = i_strdup(url->section);
+ mpurl->partial_offset = url->partial_offset;
+ mpurl->partial_size = url->partial_size;
+
+ imap_msgpart_set_partial(msgpart, url->partial_offset,
+ url->partial_size == 0 ?
+ UOFF_T_MAX : url->partial_size);
+
+ *mpurl_r = mpurl;
+ return 0;
+}
+
+int imap_msgpart_url_parse(struct mail_user *user, struct mailbox *selected_box,
+ const char *urlstr, struct imap_msgpart_url **mpurl_r,
+ const char **client_error_r)
+{
+ struct mailbox_status box_status;
+ struct imap_url base_url, *url;
+ const char *error;
+
+ /* build base url */
+ i_zero(&base_url);
+ if (selected_box != NULL) {
+ mailbox_get_open_status(selected_box, STATUS_UIDVALIDITY,
+ &box_status);
+ base_url.mailbox = mailbox_get_vname(selected_box);
+ base_url.uidvalidity = box_status.uidvalidity;
+ }
+
+ /* parse url */
+ if (imap_url_parse(urlstr, &base_url,
+ IMAP_URL_PARSE_REQUIRE_RELATIVE, &url, &error) < 0) {
+ *client_error_r = t_strconcat("Invalid IMAP URL: ", error, NULL);
+ return 0;
+ }
+ if (url->mailbox == NULL) {
+ *client_error_r = "Mailbox-relative IMAP URL, but no mailbox selected";
+ return 0;
+ }
+ if (imap_msgpart_url_create(user, url, mpurl_r, client_error_r) < 0)
+ return -1;
+ (*mpurl_r)->selected_box = selected_box;
+ return 1;
+}
+
+struct mailbox *imap_msgpart_url_get_mailbox(struct imap_msgpart_url *mpurl)
+{
+ return mpurl->box;
+}
+
+int imap_msgpart_url_open_mailbox(struct imap_msgpart_url *mpurl,
+ struct mailbox **box_r, enum mail_error *error_code_r,
+ const char **client_error_r)
+{
+ struct mailbox_status box_status;
+ enum mailbox_flags flags = MAILBOX_FLAG_READONLY;
+ struct mail_namespace *ns;
+ struct mailbox *box;
+
+ if (mpurl->box != NULL) {
+ *box_r = mpurl->box;
+ *error_code_r = MAIL_ERROR_NONE;
+ return 1;
+ }
+
+ /* find mailbox namespace */
+ ns = mail_namespace_find(mpurl->user->namespaces, mpurl->mailbox);
+
+ /* open mailbox */
+ if (mpurl->selected_box != NULL &&
+ mailbox_equals(mpurl->selected_box, ns, mpurl->mailbox))
+ box = mpurl->selected_box;
+ else
+ box = mailbox_alloc(ns->list, mpurl->mailbox, flags);
+ if (mailbox_open(box) < 0) {
+ *client_error_r = mail_storage_get_last_error(mailbox_get_storage(box),
+ error_code_r);
+ if (box != mpurl->selected_box)
+ mailbox_free(&box);
+ return *error_code_r == MAIL_ERROR_TEMP ? -1 : 0;
+ }
+
+ /* verify UIDVALIDITY */
+ mailbox_get_open_status(box, STATUS_UIDVALIDITY, &box_status);
+ if (mpurl->uidvalidity > 0 &&
+ box_status.uidvalidity != mpurl->uidvalidity) {
+ *client_error_r = "Invalid UIDVALIDITY";
+ *error_code_r = MAIL_ERROR_EXPUNGED;
+ if (box != mpurl->selected_box)
+ mailbox_free(&box);
+ return 0;
+ }
+ mpurl->box = box;
+ *box_r = box;
+ return 1;
+}
+
+int imap_msgpart_url_open_mail(struct imap_msgpart_url *mpurl,
+ struct mail **mail_r,
+ const char **client_error_r)
+{
+ struct mailbox_transaction_context *t;
+ struct mailbox *box;
+ enum mail_error error_code;
+ struct mail *mail;
+ int ret;
+
+ if (mpurl->mail != NULL) {
+ *mail_r = mpurl->mail;
+ return 1;
+ }
+
+ /* open mailbox if it is not yet open */
+ if ((ret = imap_msgpart_url_open_mailbox(mpurl, &box, &error_code,
+ client_error_r)) <= 0)
+ return ret;
+
+ /* start transaction */
+ t = mailbox_transaction_begin(box, 0, __func__);
+ mail = mail_alloc(t, MAIL_FETCH_MESSAGE_PARTS |
+ MAIL_FETCH_IMAP_BODYSTRUCTURE, NULL);
+
+ /* find the message */
+ if (!mail_set_uid(mail, mpurl->uid)) {
+ *client_error_r = "Message not found";
+ mail_free(&mail);
+ mailbox_transaction_rollback(&t);
+ return 0;
+ }
+
+ mpurl->trans = t;
+ mpurl->mail = mail;
+ *mail_r = mail;
+ return 1;
+}
+
+struct imap_msgpart *
+imap_msgpart_url_get_part(struct imap_msgpart_url *mpurl)
+{
+ return mpurl->part;
+}
+
+void imap_msgpart_url_set_decode_to_binary(struct imap_msgpart_url *mpurl)
+{
+ imap_msgpart_set_decode_to_binary(mpurl->part);
+}
+
+int imap_msgpart_url_read_part(struct imap_msgpart_url *mpurl,
+ struct imap_msgpart_open_result *result_r,
+ const char **client_error_r)
+{
+ struct mail *mail;
+ int ret;
+
+ if (mpurl->result.input != NULL) {
+ i_stream_seek(mpurl->result.input, 0);
+ *result_r = mpurl->result;
+ return 1;
+ }
+
+ /* open mail if it is not yet open */
+ ret = imap_msgpart_url_open_mail(mpurl, &mail, client_error_r);
+ if (ret <= 0)
+ return ret;
+
+ /* open the referenced part as a stream */
+ ret = imap_msgpart_open(mail, mpurl->part, result_r);
+ if (ret < 0) {
+ *client_error_r = mailbox_get_last_error(mpurl->box, NULL);
+ return ret;
+ }
+
+ mpurl->result = *result_r;
+ return 1;
+}
+
+int imap_msgpart_url_verify(struct imap_msgpart_url *mpurl,
+ const char **client_error_r)
+{
+ struct mail *mail;
+ int ret;
+
+ if (mpurl->result.input != NULL)
+ return 1;
+
+ /* open mail if it is not yet open */
+ ret = imap_msgpart_url_open_mail(mpurl, &mail, client_error_r);
+ return ret;
+}
+
+int imap_msgpart_url_get_bodypartstructure(struct imap_msgpart_url *mpurl,
+ const char **bpstruct_r,
+ const char **client_error_r)
+{
+ struct mail *mail;
+ int ret;
+
+ /* open mail if it is not yet open */
+ ret = imap_msgpart_url_open_mail(mpurl, &mail, client_error_r);
+ if (ret <= 0)
+ return ret;
+
+ ret = imap_msgpart_bodypartstructure(mail, mpurl->part, bpstruct_r);
+ if (ret < 0)
+ *client_error_r = mailbox_get_last_error(mpurl->box, NULL);
+ else if (ret == 0)
+ *client_error_r = "Message part not found";
+ return ret;
+}
+
+void imap_msgpart_url_free(struct imap_msgpart_url **_mpurl)
+{
+ struct imap_msgpart_url *mpurl = *_mpurl;
+
+ *_mpurl = NULL;
+
+ i_stream_unref(&mpurl->result.input);
+ if (mpurl->part != NULL)
+ imap_msgpart_free(&mpurl->part);
+ if (mpurl->mail != NULL)
+ mail_free(&mpurl->mail);
+ if (mpurl->trans != NULL)
+ mailbox_transaction_rollback(&mpurl->trans);
+ if (mpurl->box != NULL && mpurl->box != mpurl->selected_box)
+ mailbox_free(&mpurl->box);
+ if (mpurl->section != NULL)
+ i_free(mpurl->section);
+ i_free(mpurl->mailbox);
+ i_free(mpurl);
+}
diff --git a/src/lib-imap-storage/imap-msgpart-url.h b/src/lib-imap-storage/imap-msgpart-url.h
new file mode 100644
index 0000000..5bc72c2
--- /dev/null
+++ b/src/lib-imap-storage/imap-msgpart-url.h
@@ -0,0 +1,50 @@
+#ifndef IMAP_MSGPART_URL_H
+#define IMAP_MSGPART_URL_H
+
+#include "imap-msgpart.h"
+
+struct imap_url;
+struct imap_msgpart;
+struct imap_msgpart_url;
+
+/* Functions returning int return 1 on success, 0 if URL doesn't point to
+ valid mail, -1 on storage error. */
+
+int imap_msgpart_url_create(struct mail_user *user, const struct imap_url *url,
+ struct imap_msgpart_url **url_r,
+ const char **client_error_r);
+int imap_msgpart_url_parse(struct mail_user *user, struct mailbox *selected_box,
+ const char *urlstr, struct imap_msgpart_url **url_r,
+ const char **client_error_r);
+
+int imap_msgpart_url_open_mailbox(struct imap_msgpart_url *mpurl,
+ struct mailbox **box_r, enum mail_error *error_code_r,
+ const char **client_error_r);
+struct mailbox *imap_msgpart_url_get_mailbox(struct imap_msgpart_url *mpurl);
+int imap_msgpart_url_open_mail(struct imap_msgpart_url *mpurl,
+ struct mail **mail_r,
+ const char **client_error_r);
+
+struct imap_msgpart *
+imap_msgpart_url_get_part(struct imap_msgpart_url *mpurl);
+
+/* Decode MIME parts with Content-Transfer-Encoding: base64/quoted-printable
+ to binary data (IMAP BINARY extension). If something can't be decoded, fails
+ with storage error set to MAIL_ERROR_CONVERSION. */
+void imap_msgpart_url_set_decode_to_binary(struct imap_msgpart_url *mpurl);
+
+/* stream_r is set to NULL when part has zero length, e.g. when partial offset
+ is larger than the size of the referenced part */
+int imap_msgpart_url_read_part(struct imap_msgpart_url *mpurl,
+ struct imap_msgpart_open_result *result_r,
+ const char **client_error_r);
+
+int imap_msgpart_url_get_bodypartstructure(struct imap_msgpart_url *mpurl,
+ const char **bpstruct_r,
+ const char **client_error_r);
+
+int imap_msgpart_url_verify(struct imap_msgpart_url *mpurl,
+ const char **client_error_r);
+void imap_msgpart_url_free(struct imap_msgpart_url **mpurl);
+
+#endif
diff --git a/src/lib-imap-storage/imap-msgpart.c b/src/lib-imap-storage/imap-msgpart.c
new file mode 100644
index 0000000..3bce117
--- /dev/null
+++ b/src/lib-imap-storage/imap-msgpart.c
@@ -0,0 +1,860 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+#include "istream.h"
+#include "istream-crlf.h"
+#include "istream-nonuls.h"
+#include "istream-base64.h"
+#include "istream-header-filter.h"
+#include "istream-qp.h"
+#include "ostream.h"
+#include "message-parser.h"
+#include "message-decoder.h"
+#include "mail-storage-private.h"
+#include "mail-namespace.h"
+#include "imap-bodystructure.h"
+#include "imap-parser.h"
+#include "imap-msgpart.h"
+
+enum fetch_type {
+ FETCH_FULL,
+ FETCH_MIME,
+ FETCH_MIME_BODY,
+ FETCH_HEADER,
+ FETCH_HEADER_FIELDS,
+ FETCH_HEADER_FIELDS_NOT,
+ FETCH_BODY
+};
+
+struct imap_msgpart {
+ pool_t pool;
+
+ /* "" for root, otherwise e.g. "1.2.3". the .MIME, .HEADER, etc.
+ suffix not included */
+ const char *section_number;
+ enum fetch_type fetch_type;
+ enum mail_fetch_field wanted_fields;
+
+ /* HEADER.FIELDS[.NOT] (list of headers) */
+ struct mailbox_header_lookup_ctx *header_ctx;
+ const char *const *headers;
+
+ /* which part of the message part to fetch (default: 0..UOFF_T_MAX) */
+ uoff_t partial_offset, partial_size;
+
+ bool decode_cte_to_binary:1;
+};
+
+struct imap_msgpart_open_ctx {
+ /* from matching message_part, set after opening: */
+ uoff_t physical_pos;
+ struct message_size mime_hdr_size;
+ struct message_size mime_body_size;
+};
+
+static struct imap_msgpart *imap_msgpart_type(enum fetch_type fetch_type)
+{
+ struct imap_msgpart *msgpart;
+ pool_t pool;
+
+ pool = pool_alloconly_create("imap msgpart", sizeof(*msgpart)+32);
+ msgpart = p_new(pool, struct imap_msgpart, 1);
+ msgpart->pool = pool;
+ msgpart->partial_size = UOFF_T_MAX;
+ msgpart->fetch_type = fetch_type;
+ msgpart->section_number = "";
+ if (fetch_type == FETCH_HEADER || fetch_type == FETCH_FULL)
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER;
+ if (fetch_type == FETCH_BODY || fetch_type == FETCH_FULL)
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+ return msgpart;
+}
+
+struct imap_msgpart *imap_msgpart_full(void)
+{
+ return imap_msgpart_type(FETCH_FULL);
+}
+
+struct imap_msgpart *imap_msgpart_header(void)
+{
+ return imap_msgpart_type(FETCH_HEADER);
+}
+
+struct imap_msgpart *imap_msgpart_body(void)
+{
+ return imap_msgpart_type(FETCH_BODY);
+}
+
+static struct message_part *
+imap_msgpart_find(struct message_part *parts, const char *section)
+{
+ struct message_part *part = parts;
+ const char *path;
+ unsigned int num;
+
+ path = section;
+ while (*path >= '0' && *path <= '9' && part != NULL) {
+ /* get part number, we have already verified its validity */
+ num = 0;
+ while (*path != '\0' && *path != '.') {
+ i_assert(*path >= '0' && *path <= '9');
+
+ num = num*10 + (*path - '0');
+ path++;
+ }
+
+ if (*path == '.')
+ path++;
+
+ if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
+ /* find the part */
+ part = part->children;
+ for (; num > 1 && part != NULL; num--)
+ part = part->next;
+ } else {
+ /* only 1 allowed with non-multipart messages.
+ if the child isn't message/rfc822, the path must be
+ finished after this. */
+ if (num != 1)
+ part = NULL;
+ else if (*path != '\0' &&
+ (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0)
+ part = NULL;
+ }
+
+ if (part != NULL &&
+ (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0 &&
+ (*path >= '0' && *path <= '9')) {
+ /* if we continue inside the message/rfc822, skip this
+ body part */
+ part = part->children;
+ }
+ }
+ i_assert(part == NULL || *path == '\0');
+ return part;
+}
+
+static int
+imap_msgpart_get_header_fields(pool_t pool, const char *header_list,
+ ARRAY_TYPE(const_string) *fields)
+{
+ struct istream *input;
+ struct imap_parser *parser;
+ const struct imap_arg *args, *hdr_list;
+ unsigned int list_count;
+ unsigned int i;
+ int result = 0;
+
+ input = i_stream_create_from_data(header_list, strlen(header_list));
+ parser = imap_parser_create(input, NULL, SIZE_MAX);
+
+ if (imap_parser_finish_line(parser, 0, 0, &args) > 0 &&
+ imap_arg_get_list_full(args, &hdr_list, &list_count) &&
+ args[1].type == IMAP_ARG_EOL &&
+ list_count > 0) {
+ const char *value;
+
+ p_array_init(fields, pool, list_count);
+ for (i = 0; i < list_count; i++) {
+ if (!imap_arg_get_astring(&hdr_list[i], &value)) {
+ result = -1;
+ break;
+ }
+
+ value = p_strdup(pool, t_str_ucase(value));
+ array_push_back(fields, &value);
+ }
+ /* istream-header-filter requires headers to be sorted */
+ array_sort(fields, i_strcasecmp_p);
+ } else {
+ result = -1;
+ }
+
+ imap_parser_unref(&parser);
+ i_stream_unref(&input);
+ return result;
+}
+
+static int
+imap_msgpart_parse_header_fields(struct imap_msgpart *msgpart,
+ const char *header_list)
+{
+ ARRAY_TYPE(const_string) fields;
+
+ if (header_list[0] == ' ')
+ header_list++;
+
+ /* HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
+ if (imap_msgpart_get_header_fields(msgpart->pool, header_list,
+ &fields) < 0)
+ return -1;
+
+ array_append_zero(&fields);
+ msgpart->headers = array_front(&fields);
+ return 0;
+}
+
+int imap_msgpart_parse(const char *section, struct imap_msgpart **msgpart_r)
+{
+ struct imap_msgpart *msgpart;
+ pool_t pool;
+ unsigned int i;
+ bool next_digit;
+ int ret;
+
+ pool = pool_alloconly_create("imap msgpart", 1024);
+ msgpart = *msgpart_r = p_new(pool, struct imap_msgpart, 1);
+ msgpart->pool = pool;
+ msgpart->partial_size = UOFF_T_MAX;
+
+ /* get the section number */
+ next_digit = TRUE;
+ for (i = 0; section[i] != '\0'; i++) {
+ if (section[i] >= '0' && section[i] <= '9') {
+ next_digit = FALSE;
+ } else if (!next_digit && section[i] == '.') {
+ next_digit = TRUE;
+ } else {
+ break;
+ }
+ }
+ if (i == 0) {
+ /* [], [HEADER], etc. */
+ msgpart->section_number = "";
+ } else if (section[i] == '\0') {
+ /* [1.2.3] */
+ if (i > 0 && section[i-1] == '.') {
+ pool_unref(&pool);
+ return -1;
+ }
+ msgpart->section_number = p_strdup(pool, section);
+ section = "";
+ } else {
+ /* [1.2.3.MIME], [1.2.3.HEADER], etc */
+ if (section[i-1] != '.') {
+ pool_unref(&pool);
+ return -1;
+ }
+ msgpart->section_number = p_strndup(pool, section, i-1);
+ section += i;
+ }
+
+ if (*section == '\0') {
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+ if (*msgpart->section_number == '\0') {
+ /* BODY[] - header+body */
+ msgpart->fetch_type = FETCH_FULL;
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER;
+ } else {
+ /* BODY[1] - body only */
+ msgpart->fetch_type = FETCH_MIME_BODY;
+ }
+ return 0;
+ }
+ section = t_str_ucase(section);
+
+ if (strcmp(section, "MIME") == 0) {
+ if (msgpart->section_number[0] == '\0')
+ return -1;
+ msgpart->fetch_type = FETCH_MIME;
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+ } else if (strcmp(section, "TEXT") == 0) {
+ /* body (for root or for message/rfc822) */
+ msgpart->fetch_type = FETCH_BODY;
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+ } else if (str_begins(section, "HEADER")) {
+ /* header (for root or for message/rfc822) */
+ if (section[6] == '\0') {
+ msgpart->fetch_type = FETCH_HEADER;
+ ret = 0;
+ } else if (str_begins(section, "HEADER.FIELDS.NOT")) {
+ msgpart->fetch_type = FETCH_HEADER_FIELDS_NOT;
+ ret = imap_msgpart_parse_header_fields(msgpart,
+ section+17);
+ } else if (str_begins(section, "HEADER.FIELDS")) {
+ msgpart->fetch_type = FETCH_HEADER_FIELDS;
+ ret = imap_msgpart_parse_header_fields(msgpart,
+ section+13);
+ } else {
+ ret = -1;
+ }
+ if (ret < 0) {
+ imap_msgpart_free(&msgpart);
+ return -1;
+ }
+ if (msgpart->fetch_type == FETCH_HEADER_FIELDS) {
+ /* we may be able to get this from cache, don't give a
+ wanted_fields hint */
+ } else if (*msgpart->section_number == '\0')
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER;
+ else
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+ } else {
+ imap_msgpart_free(&msgpart);
+ return -1;
+ }
+ return 0;
+}
+
+void imap_msgpart_free(struct imap_msgpart **_msgpart)
+{
+ struct imap_msgpart *msgpart = *_msgpart;
+
+ *_msgpart = NULL;
+
+ imap_msgpart_close_mailbox(msgpart);
+ pool_unref(&msgpart->pool);
+}
+
+bool imap_msgpart_contains_body(const struct imap_msgpart *msgpart)
+{
+ switch (msgpart->fetch_type) {
+ case FETCH_HEADER:
+ case FETCH_HEADER_FIELDS:
+ case FETCH_HEADER_FIELDS_NOT:
+ return FALSE;
+ case FETCH_FULL:
+ case FETCH_MIME:
+ case FETCH_MIME_BODY:
+ case FETCH_BODY:
+ break;
+ }
+ return TRUE;
+}
+
+void imap_msgpart_set_decode_to_binary(struct imap_msgpart *msgpart)
+{
+ msgpart->decode_cte_to_binary = TRUE;
+}
+
+void imap_msgpart_set_partial(struct imap_msgpart *msgpart,
+ uoff_t offset, uoff_t size)
+{
+ msgpart->partial_offset = offset;
+ msgpart->partial_size = size;
+}
+
+uoff_t imap_msgpart_get_partial_offset(struct imap_msgpart *msgpart)
+{
+ return msgpart->partial_offset;
+}
+
+uoff_t imap_msgpart_get_partial_size(struct imap_msgpart *msgpart)
+{
+ return msgpart->partial_size;
+}
+
+enum mail_fetch_field imap_msgpart_get_fetch_data(struct imap_msgpart *msgpart)
+{
+ return msgpart->wanted_fields;
+}
+
+void imap_msgpart_get_wanted_headers(struct imap_msgpart *msgpart,
+ ARRAY_TYPE(const_string) *headers)
+{
+ unsigned int i;
+
+ if (msgpart->fetch_type != FETCH_HEADER_FIELDS)
+ return;
+
+ for (i = 0; msgpart->headers[i] != NULL; i++)
+ array_push_back(headers, &msgpart->headers[i]);
+}
+
+static int
+imap_msgpart_get_partial_header(struct mail *mail, struct istream *mail_input,
+ const struct imap_msgpart *msgpart,
+ uoff_t *virtual_size_r, bool *have_crlfs_r,
+ struct imap_msgpart_open_result *result_r)
+{
+ const char *const *hdr_fields = msgpart->headers;
+ unsigned int hdr_count = str_array_length(hdr_fields);
+ struct message_size hdr_size;
+ struct istream *input;
+ bool has_nuls;
+
+ if (msgpart->fetch_type != FETCH_HEADER_FIELDS) {
+ i_assert(msgpart->fetch_type == FETCH_HEADER_FIELDS_NOT);
+ input = i_stream_create_header_filter(mail_input,
+ HEADER_FILTER_EXCLUDE |
+ HEADER_FILTER_HIDE_BODY,
+ hdr_fields, hdr_count,
+ *null_header_filter_callback,
+ NULL);
+ } else if (msgpart->section_number[0] != '\0') {
+ /* fetching partial headers for a message/rfc822 part. */
+ input = i_stream_create_header_filter(mail_input,
+ HEADER_FILTER_INCLUDE |
+ HEADER_FILTER_HIDE_BODY,
+ hdr_fields, hdr_count,
+ *null_header_filter_callback,
+ NULL);
+ } else {
+ /* mail_get_header_stream() already filtered out the
+ unwanted headers. */
+ input = mail_input;
+ i_stream_ref(input);
+ }
+
+ if (message_get_header_size(input, &hdr_size, &has_nuls) < 0) {
+ mail_set_critical(mail,
+ "read(%s) failed: %s", i_stream_get_name(input),
+ i_stream_get_error(input));
+ i_stream_unref(&input);
+ return -1;
+ }
+ i_stream_seek(input, 0);
+ result_r->input = input;
+ result_r->size = hdr_size.virtual_size;
+ result_r->size_field = 0;
+ *virtual_size_r = hdr_size.virtual_size;
+ *have_crlfs_r = hdr_size.physical_size == hdr_size.virtual_size;
+ return 0;
+}
+
+static struct istream *
+imap_msgpart_crlf_seek(struct mail *mail, struct istream *input,
+ const struct imap_msgpart *msgpart)
+{
+ struct mail_msgpart_partial_cache *cache = &mail->box->partial_cache;
+ struct istream *crlf_input, *errinput;
+ uoff_t physical_start = input->v_offset;
+ uoff_t virtual_skip = msgpart->partial_offset;
+ bool cr_skipped;
+
+ if (virtual_skip == 0) {
+ /* no need to seek */
+ } else if (mail->uid > 0 && cache->uid == mail->uid &&
+ cache->physical_start == physical_start &&
+ cache->virtual_pos < virtual_skip) {
+ /* use cache */
+ i_stream_seek(input, physical_start + cache->physical_pos);
+ virtual_skip -= cache->virtual_pos;
+ }
+ if (message_skip_virtual(input, virtual_skip, &cr_skipped) < 0) {
+ errinput = i_stream_create_error_str(errno, "%s", i_stream_get_error(input));
+ i_stream_set_name(errinput, i_stream_get_name(input));
+ i_stream_unref(&input);
+ return errinput;
+ }
+
+ if (mail->uid > 0 &&
+ (msgpart->partial_offset != 0 ||
+ msgpart->partial_size != UOFF_T_MAX) && !input->eof) {
+ /* update cache */
+ cache->uid = mail->uid;
+ cache->physical_start = physical_start;
+ cache->physical_pos = input->v_offset - physical_start;
+ cache->virtual_pos = msgpart->partial_offset;
+ if (cr_skipped) {
+ /* the physical_pos points to virtual CRLF, but
+ virtual_pos already skipped CR. that can't work,
+ so seek back the virtual CR */
+ cache->virtual_pos--;
+ }
+ }
+ crlf_input = i_stream_create_crlf(input);
+ if (cr_skipped)
+ i_stream_skip(crlf_input, 1);
+ i_stream_unref(&input);
+ return crlf_input;
+}
+
+static void
+imap_msgpart_get_partial(struct mail *mail, const struct imap_msgpart *msgpart,
+ bool convert_nuls, bool use_partial_cache,
+ uoff_t virtual_size, bool have_crlfs,
+ struct imap_msgpart_open_result *result)
+{
+ struct istream *input2;
+ uoff_t bytes_left;
+
+ /* input is already seeked to the beginning of the wanted data */
+
+ if (msgpart->partial_offset >= virtual_size) {
+ /* can't seek past the MIME part */
+ i_stream_unref(&result->input);
+ result->input = i_stream_create_from_data("", 0);
+ result->size = 0;
+ return;
+ }
+
+ if (have_crlfs) {
+ /* input has CRLF linefeeds, we can quickly seek to
+ wanted position */
+ i_stream_skip(result->input, msgpart->partial_offset);
+ } else {
+ /* input has LF linefeeds. it can be slow to seek to wanted
+ position, so try to do caching whenever possible */
+ i_assert(use_partial_cache);
+ result->input = imap_msgpart_crlf_seek(mail, result->input,
+ msgpart);
+ }
+
+ bytes_left = virtual_size - msgpart->partial_offset;
+ if (msgpart->partial_size <= bytes_left) {
+ /* limit output to specified number of bytes */
+ result->size = msgpart->partial_size;
+ } else {
+ /* send all bytes */
+ result->size = bytes_left;
+ }
+
+ if (!mail->has_no_nuls && convert_nuls) {
+ /* IMAP literals must not contain NULs. change them to
+ 0x80 characters. */
+ input2 = i_stream_create_nonuls(result->input, '\x80');
+ i_stream_unref(&result->input);
+ result->input = input2;
+ }
+ input2 = i_stream_create_limit(result->input, result->size);
+ i_stream_unref(&result->input);
+ result->input = input2;
+}
+
+static int
+imap_msgpart_find_part(struct mail *mail, const struct imap_msgpart *msgpart,
+ struct message_part **part_r)
+{
+ struct message_part *parts, *part = NULL;
+
+ if (*msgpart->section_number == '\0') {
+ *part_r = NULL;
+ return 1;
+ }
+
+ if (mail_get_parts(mail, &parts) < 0)
+ return -1;
+ part = imap_msgpart_find(parts, msgpart->section_number);
+ if (part == NULL) {
+ /* MIME part not found. */
+ *part_r = NULL;
+ return 0;
+ }
+
+ switch (msgpart->fetch_type) {
+ case FETCH_MIME:
+ /* What to do if this is a message/rfc822? Does it have
+ MIME headers or not? Possibilities are: a) no, return
+ empty string (UW-IMAP does this), b) return the same as
+ HEADER. Dovecot has done b) for a long time and it's not
+ very clear which one is correct, so we'll just continue
+ with b) */
+ case FETCH_FULL:
+ case FETCH_MIME_BODY:
+ break;
+ case FETCH_HEADER:
+ case FETCH_HEADER_FIELDS:
+ case FETCH_HEADER_FIELDS_NOT:
+ case FETCH_BODY:
+ /* fetching message/rfc822 part's header/body */
+ if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0) {
+ *part_r = NULL;
+ return 0;
+ }
+ i_assert(part->children != NULL &&
+ part->children->next == NULL);
+ part = part->children;
+ break;
+ }
+ *part_r = part;
+ return 1;
+}
+
+static int
+imap_msgpart_open_normal(struct mail *mail, struct imap_msgpart *msgpart,
+ const struct message_part *part,
+ uoff_t *virtual_size_r, bool *have_crlfs_r,
+ struct imap_msgpart_open_result *result_r)
+{
+ struct message_size hdr_size, body_size, part_size;
+ struct istream *input = NULL;
+ bool unknown_crlfs = FALSE;
+
+ i_zero(&hdr_size);
+ i_zero(&body_size);
+ i_zero(&part_size);
+
+ if (*msgpart->section_number != '\0') {
+ /* find the MIME part */
+ i_assert(part != NULL);
+
+ if (mail_get_stream_because(mail, NULL, NULL, "MIME part", &input) < 0)
+ return -1;
+
+ i_stream_seek(input, part->physical_pos);
+ hdr_size = part->header_size;
+ body_size = part->body_size;
+ } else switch (msgpart->fetch_type) {
+ case FETCH_FULL:
+ /* fetch the whole message */
+ if (mail_get_stream_because(mail, NULL, NULL, "full mail", &input) < 0 ||
+ mail_get_virtual_size(mail, &body_size.virtual_size) < 0)
+ return -1;
+ result_r->size_field = MAIL_FETCH_VIRTUAL_SIZE;
+
+ i_assert(mail->lookup_abort == MAIL_LOOKUP_ABORT_NEVER);
+ mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL;
+ if (mail_get_physical_size(mail, &body_size.physical_size) < 0)
+ unknown_crlfs = TRUE;
+ mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER;
+ break;
+ case FETCH_MIME:
+ case FETCH_MIME_BODY:
+ i_unreached();
+ case FETCH_HEADER:
+ case FETCH_HEADER_FIELDS_NOT:
+ /* fetch the message's header */
+ if (mail_get_hdr_stream(mail, &hdr_size, &input) < 0)
+ return -1;
+ result_r->size_field = MAIL_FETCH_MESSAGE_PARTS;
+ break;
+ case FETCH_HEADER_FIELDS:
+ /* try to lookup the headers from cache */
+ if (msgpart->header_ctx == NULL) {
+ msgpart->header_ctx =
+ mailbox_header_lookup_init(mail->box,
+ msgpart->headers);
+ }
+ if (mail_get_header_stream(mail, msgpart->header_ctx,
+ &input) < 0)
+ return -1;
+ result_r->size_field = 0;
+ break;
+ case FETCH_BODY:
+ /* fetch the message's body */
+ if (mail_get_stream_because(mail, &hdr_size, &body_size,
+ "mail body", &input) < 0)
+ return -1;
+ result_r->size_field = MAIL_FETCH_MESSAGE_PARTS;
+ break;
+ }
+
+ if (msgpart->headers != NULL) {
+ /* return specific headers */
+ return imap_msgpart_get_partial_header(mail, input, msgpart,
+ virtual_size_r,
+ have_crlfs_r, result_r);
+ }
+
+ switch (msgpart->fetch_type) {
+ case FETCH_FULL:
+ part_size.physical_size += body_size.physical_size;
+ part_size.virtual_size += body_size.virtual_size;
+ /* fall through */
+ case FETCH_MIME:
+ case FETCH_HEADER:
+ part_size.physical_size += hdr_size.physical_size;
+ part_size.virtual_size += hdr_size.virtual_size;
+ break;
+ case FETCH_HEADER_FIELDS:
+ case FETCH_HEADER_FIELDS_NOT:
+ i_unreached();
+ case FETCH_BODY:
+ case FETCH_MIME_BODY:
+ i_stream_skip(input, hdr_size.physical_size);
+ part_size.physical_size += body_size.physical_size;
+ part_size.virtual_size += body_size.virtual_size;
+ break;
+ }
+
+ result_r->input = input;
+ i_stream_ref(input);
+ *virtual_size_r = part_size.virtual_size;
+ *have_crlfs_r = !unknown_crlfs &&
+ part_size.virtual_size == part_size.physical_size;
+ return 0;
+}
+
+int imap_msgpart_open(struct mail *mail, struct imap_msgpart *msgpart,
+ struct imap_msgpart_open_result *result_r)
+{
+ struct message_part *part;
+ uoff_t virtual_size;
+ bool include_hdr, binary, use_partial_cache, have_crlfs;
+ int ret;
+
+ i_zero(result_r);
+
+ if ((ret = imap_msgpart_find_part(mail, msgpart, &part)) < 0)
+ return -1;
+ if (ret == 0) {
+ /* MIME part not found. return an empty part. */
+ result_r->input = i_stream_create_from_data("", 0);
+ return 0;
+ }
+
+ if (msgpart->decode_cte_to_binary &&
+ (msgpart->fetch_type == FETCH_FULL ||
+ msgpart->fetch_type == FETCH_BODY ||
+ msgpart->fetch_type == FETCH_MIME_BODY)) {
+ /* binary fetch */
+ include_hdr = msgpart->fetch_type == FETCH_FULL;
+ if (part == NULL) {
+ if (mail_get_parts(mail, &part) < 0)
+ return -1;
+ }
+ if (mail_get_binary_stream(mail, part, include_hdr,
+ &virtual_size, &binary,
+ &result_r->input) < 0)
+ return -1;
+ have_crlfs = TRUE;
+ use_partial_cache = FALSE;
+ } else {
+ if (imap_msgpart_open_normal(mail, msgpart, part, &virtual_size,
+ &have_crlfs, result_r) < 0)
+ return -1;
+ binary = FALSE;
+ use_partial_cache = TRUE;
+ }
+
+ if (binary && msgpart->decode_cte_to_binary)
+ result_r->binary_decoded_input_has_nuls = TRUE;
+
+ imap_msgpart_get_partial(mail, msgpart, !binary, use_partial_cache,
+ virtual_size, have_crlfs, result_r);
+ return 0;
+}
+
+int imap_msgpart_size(struct mail *mail, struct imap_msgpart *msgpart,
+ uoff_t *size_r)
+{
+ struct imap_msgpart_open_result result;
+ struct message_part *part;
+ bool include_hdr;
+ unsigned int lines;
+ int ret;
+
+ if (!msgpart->decode_cte_to_binary ||
+ (msgpart->fetch_type != FETCH_FULL &&
+ msgpart->fetch_type != FETCH_BODY &&
+ msgpart->fetch_type != FETCH_MIME_BODY)) {
+ /* generic implementation */
+ if (imap_msgpart_open(mail, msgpart, &result) < 0)
+ return -1;
+ i_stream_unref(&result.input);
+ *size_r = result.size;
+ return 0;
+ }
+
+ /* binary-optimized implementation: */
+ if ((ret = imap_msgpart_find_part(mail, msgpart, &part)) < 0)
+ return -1;
+ if (ret == 0) {
+ /* MIME part not found. return an empty part. */
+ *size_r = 0;
+ return 0;
+ }
+ if (part == NULL) {
+ if (mail_get_parts(mail, &part) < 0)
+ return -1;
+ }
+ include_hdr = msgpart->fetch_type == FETCH_FULL;
+ return mail_get_binary_size(mail, part, include_hdr, size_r, &lines);
+}
+
+static int
+imap_msgpart_parse_bodystructure(struct mail *mail,
+ struct message_part *all_parts)
+{
+ struct mail_private *pmail = (struct mail_private *)mail;
+ const char *bodystructure, *error;
+
+ if (mail_get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE,
+ &bodystructure) < 0)
+ return -1;
+ if (all_parts->context != NULL) {
+ /* we just parsed the bodystructure */
+ return 0;
+ }
+
+ if (imap_bodystructure_parse(bodystructure, pmail->data_pool,
+ all_parts, &error) < 0) {
+ mail_set_cache_corrupted(mail,
+ MAIL_FETCH_IMAP_BODYSTRUCTURE, t_strdup_printf(
+ "Invalid message_part/BODYSTRUCTURE %s: %s",
+ bodystructure, error));
+ return -1;
+ }
+ return 0;
+}
+
+static int
+imap_msgpart_vsizes_to_binary(struct mail *mail, const struct message_part *part,
+ struct message_part **binpart_r)
+{
+ struct message_part **pos;
+ uoff_t size;
+ unsigned int lines;
+
+ if (mail_get_binary_size(mail, part, FALSE, &size, &lines) < 0)
+ return -1;
+
+ *binpart_r = t_new(struct message_part, 1);
+ **binpart_r = *part;
+ (*binpart_r)->body_size.virtual_size = size;
+ (*binpart_r)->body_size.lines = lines;
+
+ pos = &(*binpart_r)->children;
+ for (part = part->children; part != NULL; part = part->next) {
+ if (imap_msgpart_vsizes_to_binary(mail, part, pos) < 0)
+ return -1;
+ pos = &(*pos)->next;
+ }
+ return 0;
+}
+
+int imap_msgpart_bodypartstructure(struct mail *mail,
+ struct imap_msgpart *msgpart,
+ const char **bpstruct_r)
+{
+ struct message_part *all_parts, *part;
+ string_t *bpstruct;
+ const char *error;
+ int ret;
+
+ /* if we start parsing the body in here, make sure we also parse the
+ BODYSTRUCTURE */
+ mail_add_temp_wanted_fields(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE, NULL);
+
+ if ((ret = imap_msgpart_find_part(mail, msgpart, &part)) < 0)
+ return -1;
+ if (ret == 0) {
+ /* MIME part not found. */
+ *bpstruct_r = NULL;
+ return 0;
+ }
+
+ if (mail_get_parts(mail, &all_parts) < 0)
+ return -1;
+ if (all_parts->context == NULL) {
+ if (imap_msgpart_parse_bodystructure(mail, all_parts) < 0)
+ return -1;
+ }
+ if (part == NULL)
+ part = all_parts;
+
+ if (msgpart->decode_cte_to_binary)
+ ret = imap_msgpart_vsizes_to_binary(mail, part, &part);
+
+ if (ret >= 0) {
+ bpstruct = t_str_new(256);
+ if (imap_bodystructure_write(part, bpstruct, TRUE, &error) < 0) {
+ error = t_strdup_printf(
+ "Invalid message_part/BODYSTRUCTURE: %s", error);
+ mail_set_cache_corrupted(mail, MAIL_FETCH_MESSAGE_PARTS,
+ error);
+ return -1;
+ }
+ *bpstruct_r = str_c(bpstruct);
+ }
+ return ret < 0 ? -1 : 1;
+}
+
+
+void imap_msgpart_close_mailbox(struct imap_msgpart *msgpart)
+{
+ mailbox_header_lookup_unref(&msgpart->header_ctx);
+}
diff --git a/src/lib-imap-storage/imap-msgpart.h b/src/lib-imap-storage/imap-msgpart.h
new file mode 100644
index 0000000..92b56c6
--- /dev/null
+++ b/src/lib-imap-storage/imap-msgpart.h
@@ -0,0 +1,68 @@
+#ifndef IMAP_MSGPART_H
+#define IMAP_MSGPART_H
+
+struct imap_msgpart;
+
+struct imap_msgpart_open_result {
+ /* message contents with CRLF linefeeds */
+ struct istream *input;
+ /* size of input */
+ uoff_t size;
+ /* if size was looked up using cache and it ends up being wrong,
+ this field can be used to log about cache corruption */
+ enum mail_fetch_field size_field;
+ /* TRUE if BINARY decoded content contains NUL characters */
+ bool binary_decoded_input_has_nuls;
+};
+
+struct imap_msgpart *imap_msgpart_full(void);
+struct imap_msgpart *imap_msgpart_header(void);
+struct imap_msgpart *imap_msgpart_body(void);
+/* Parse section into imap_msgpart. Returns 0 and msgpart_r on success,
+ -1 if the section isn't valid. The same imap_msgpart can be used to open
+ multiple messages. */
+int imap_msgpart_parse(const char *section, struct imap_msgpart **msgpart_r);
+void imap_msgpart_free(struct imap_msgpart **msgpart);
+
+/* Returns TRUE if the msgpart might return at least part of the message body.
+ Or alternatively: If FALSE is returned, the msgpart will never return
+ anything except (part of) the message header. MIME headers are counted
+ as part of the message body. */
+bool imap_msgpart_contains_body(const struct imap_msgpart *msgpart);
+/* Decode MIME parts with Content-Transfer-Encoding: base64/quoted-printable
+ to binary data (IMAP BINARY extension). If something can't be decoded, fails
+ with storage error set to MAIL_ERROR_CONVERSION. */
+void imap_msgpart_set_decode_to_binary(struct imap_msgpart *msgpart);
+
+/* Set the fetch to be partial. For unlimited size use UOFF_T_MAX. */
+void imap_msgpart_set_partial(struct imap_msgpart *msgpart,
+ uoff_t offset, uoff_t size);
+uoff_t imap_msgpart_get_partial_offset(struct imap_msgpart *msgpart);
+uoff_t imap_msgpart_get_partial_size(struct imap_msgpart *msgpart);
+/* Return wanted_fields mask. */
+enum mail_fetch_field imap_msgpart_get_fetch_data(struct imap_msgpart *msgpart);
+/* Append all the specifically requested headers to the headers array
+ (no deduplication is done) */
+void imap_msgpart_get_wanted_headers(struct imap_msgpart *msgpart,
+ ARRAY_TYPE(const_string) *headers);
+
+/* Open message part refenced by IMAP section as istream. Returns 0 if
+ successful, -1 if storage error. Returned istream is initially referenced,
+ so i_stream_unref() must be called for it. */
+int imap_msgpart_open(struct mail *mail, struct imap_msgpart *msgpart,
+ struct imap_msgpart_open_result *result_r);
+/* Return msgpart's size without actually opening the stream (if possible). */
+int imap_msgpart_size(struct mail *mail, struct imap_msgpart *msgpart,
+ uoff_t *size_r);
+
+/* Return msgpart's IMAP BODYPARTSTRUCTURE */
+int imap_msgpart_bodypartstructure(struct mail *mail,
+ struct imap_msgpart *msgpart,
+ const char **bpstruct_r);
+
+/* Header context is automatically created by imap_msgpart_open() and destroyed
+ by imap_msgpart_free(), but if you want to use the same imap_msgpart across
+ multiple mailboxes, you need to close the part before closing the mailbox. */
+void imap_msgpart_close_mailbox(struct imap_msgpart *msgpart);
+
+#endif
diff --git a/src/lib-imap-urlauth/Makefile.am b/src/lib-imap-urlauth/Makefile.am
new file mode 100644
index 0000000..6734b72
--- /dev/null
+++ b/src/lib-imap-urlauth/Makefile.am
@@ -0,0 +1,29 @@
+noinst_LTLIBRARIES = libimap-urlauth.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-charset \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-storage
+
+libimap_urlauth_la_SOURCES = \
+ imap-urlauth.c \
+ imap-urlauth-fetch.c \
+ imap-urlauth-backend.c \
+ imap-urlauth-connection.c
+
+headers = \
+ imap-urlauth.h \
+ imap-urlauth-private.h \
+ imap-urlauth-fetch.h \
+ imap-urlauth-backend.h \
+ imap-urlauth-connection.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
diff --git a/src/lib-imap-urlauth/Makefile.in b/src/lib-imap-urlauth/Makefile.in
new file mode 100644
index 0000000..3edb9bc
--- /dev/null
+++ b/src/lib-imap-urlauth/Makefile.in
@@ -0,0 +1,835 @@
+# 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@
+subdir = src/lib-imap-urlauth
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libimap_urlauth_la_LIBADD =
+am_libimap_urlauth_la_OBJECTS = imap-urlauth.lo imap-urlauth-fetch.lo \
+ imap-urlauth-backend.lo imap-urlauth-connection.lo
+libimap_urlauth_la_OBJECTS = $(am_libimap_urlauth_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_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)/imap-urlauth-backend.Plo \
+ ./$(DEPDIR)/imap-urlauth-connection.Plo \
+ ./$(DEPDIR)/imap-urlauth-fetch.Plo \
+ ./$(DEPDIR)/imap-urlauth.Plo
+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 = $(libimap_urlauth_la_SOURCES)
+DIST_SOURCES = $(libimap_urlauth_la_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@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libimap-urlauth.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-charset \
+ -I$(top_srcdir)/src/lib-dict \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-imap-storage
+
+libimap_urlauth_la_SOURCES = \
+ imap-urlauth.c \
+ imap-urlauth-fetch.c \
+ imap-urlauth-backend.c \
+ imap-urlauth-connection.c
+
+headers = \
+ imap-urlauth.h \
+ imap-urlauth-private.h \
+ imap-urlauth-fetch.h \
+ imap-urlauth-backend.h \
+ imap-urlauth-connection.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+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-imap-urlauth/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-imap-urlauth/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-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}; \
+ }
+
+libimap-urlauth.la: $(libimap_urlauth_la_OBJECTS) $(libimap_urlauth_la_DEPENDENCIES) $(EXTRA_libimap_urlauth_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libimap_urlauth_la_OBJECTS) $(libimap_urlauth_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-urlauth-backend.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-urlauth-connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-urlauth-fetch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-urlauth.Plo@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 $(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 \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/imap-urlauth-backend.Plo
+ -rm -f ./$(DEPDIR)/imap-urlauth-connection.Plo
+ -rm -f ./$(DEPDIR)/imap-urlauth-fetch.Plo
+ -rm -f ./$(DEPDIR)/imap-urlauth.Plo
+ -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)/imap-urlauth-backend.Plo
+ -rm -f ./$(DEPDIR)/imap-urlauth-connection.Plo
+ -rm -f ./$(DEPDIR)/imap-urlauth-fetch.Plo
+ -rm -f ./$(DEPDIR)/imap-urlauth.Plo
+ -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 \
+ 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
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib-imap-urlauth/imap-urlauth-backend.c b/src/lib-imap-urlauth/imap-urlauth-backend.c
new file mode 100644
index 0000000..de2b9c9
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth-backend.c
@@ -0,0 +1,174 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "randgen.h"
+#include "mail-user.h"
+#include "mail-storage.h"
+#include "mailbox-list-iter.h"
+#include "imap-urlauth-private.h"
+#include "imap-urlauth-backend.h"
+
+#define IMAP_URLAUTH_KEY MAILBOX_ATTRIBUTE_PREFIX_DOVECOT"imap-urlauth"
+
+static int
+imap_urlauth_backend_trans_set_mailbox_key(struct mailbox *box,
+ unsigned char mailbox_key_r[IMAP_URLAUTH_KEY_LEN],
+ const char **client_error_r,
+ enum mail_error *error_code_r)
+{
+ struct mail_attribute_value urlauth_key;
+ const char *mailbox_key_hex = NULL;
+ int ret;
+
+ if (mailbox_open(box) < 0) {
+ *client_error_r = mailbox_get_last_error(box, error_code_r);
+ return -1;
+ }
+
+ struct mailbox_transaction_context *t =
+ mailbox_transaction_begin(box,
+ MAILBOX_TRANSACTION_FLAG_EXTERNAL,
+ __func__);
+
+ /* create new key */
+ random_fill(mailbox_key_r, IMAP_URLAUTH_KEY_LEN);
+ mailbox_key_hex = binary_to_hex(mailbox_key_r,
+ IMAP_URLAUTH_KEY_LEN);
+ i_zero(&urlauth_key);
+ urlauth_key.value = mailbox_key_hex;
+ ret = mailbox_attribute_set(t, MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ IMAP_URLAUTH_KEY, &urlauth_key);
+
+ if (mailbox_transaction_commit(&t) < 0) {
+ *client_error_r = mailbox_get_last_error(box, error_code_r);
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static int
+imap_urlauth_backend_trans_get_mailbox_key(struct mailbox *box,
+ bool create,
+ unsigned char mailbox_key_r[IMAP_URLAUTH_KEY_LEN],
+ const char **client_error_r,
+ enum mail_error *error_code_r)
+{
+ struct mail_user *user = mail_storage_get_user(mailbox_get_storage(box));
+ struct mail_attribute_value urlauth_key;
+ const char *mailbox_key_hex = NULL;
+ buffer_t key_buf;
+ int ret;
+
+ *client_error_r = "Internal server error";
+ *error_code_r = MAIL_ERROR_TEMP;
+
+ ret = mailbox_attribute_get(box, MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ IMAP_URLAUTH_KEY, &urlauth_key);
+ if (ret < 0)
+ return -1;
+
+ e_debug(user->event, "imap-urlauth: %skey found for mailbox %s",
+ (ret > 0 ? "" : "no "), mailbox_get_vname(box));
+
+ if (ret == 0) {
+ if (!create)
+ return 0;
+
+ ret = imap_urlauth_backend_trans_set_mailbox_key(box,
+ mailbox_key_r,
+ client_error_r,
+ error_code_r);
+
+ if (ret < 0)
+ return -1;
+ e_debug(user->event, "imap-urlauth: created key for mailbox %s",
+ mailbox_get_vname(box));
+ } else {
+ /* read existing key */
+ buffer_create_from_data(&key_buf, mailbox_key_r,
+ IMAP_URLAUTH_KEY_LEN);
+ mailbox_key_hex = urlauth_key.value;
+ if (strlen(mailbox_key_hex) != 2*IMAP_URLAUTH_KEY_LEN ||
+ hex_to_binary(mailbox_key_hex, &key_buf) < 0 ||
+ key_buf.used != IMAP_URLAUTH_KEY_LEN) {
+ i_error("imap-urlauth: key found for mailbox %s is invalid",
+ mailbox_get_vname(box));
+ return -1;
+ }
+ }
+ return 1;
+}
+
+int imap_urlauth_backend_get_mailbox_key(struct mailbox *box, bool create,
+ unsigned char mailbox_key_r[IMAP_URLAUTH_KEY_LEN],
+ const char **client_error_r,
+ enum mail_error *error_code_r)
+{
+ int ret;
+
+ ret = imap_urlauth_backend_trans_get_mailbox_key(box, create,
+ mailbox_key_r,
+ client_error_r,
+ error_code_r);
+ return ret;
+}
+
+int imap_urlauth_backend_reset_mailbox_key(struct mailbox *box)
+{
+ struct mailbox_transaction_context *t;
+ int ret;
+
+ t = mailbox_transaction_begin(box, MAILBOX_TRANSACTION_FLAG_EXTERNAL,
+ __func__);
+ ret = mailbox_attribute_unset(t, MAIL_ATTRIBUTE_TYPE_PRIVATE,
+ IMAP_URLAUTH_KEY);
+ if (mailbox_transaction_commit(&t) < 0)
+ ret = -1;
+ return ret;
+}
+
+static int imap_urlauth_backend_mailbox_reset_key(struct mailbox *box)
+{
+ const char *errstr;
+ enum mail_error error;
+
+ if (mailbox_open(box) < 0) {
+ errstr = mailbox_get_last_internal_error(box, &error);
+ if (error == MAIL_ERROR_NOTFOUND || error == MAIL_ERROR_PERM)
+ return 0;
+ i_error("urlauth key reset: Couldn't open mailbox %s: %s",
+ mailbox_get_vname(box), errstr);
+ return -1;
+ }
+ return imap_urlauth_backend_reset_mailbox_key(box);
+}
+
+int imap_urlauth_backend_reset_all_keys(struct mail_user *user)
+{
+ const char *const patterns[] = { "*", NULL };
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ struct mailbox *box;
+ int ret = 0;
+
+ iter = mailbox_list_iter_init_namespaces(user->namespaces, patterns,
+ MAIL_NAMESPACE_TYPE_MASK_ALL,
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) {
+ box = mailbox_alloc(info->ns->list, info->vname, 0);
+ if (imap_urlauth_backend_mailbox_reset_key(box) < 0)
+ ret = -1;
+ mailbox_free(&box);
+ }
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ i_error("urlauth key reset: Couldn't iterate mailboxes: %s",
+ mailbox_list_get_last_internal_error(user->namespaces->list, NULL));
+ ret = -1;
+ }
+ return ret;
+}
diff --git a/src/lib-imap-urlauth/imap-urlauth-backend.h b/src/lib-imap-urlauth/imap-urlauth-backend.h
new file mode 100644
index 0000000..b76ecc5
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth-backend.h
@@ -0,0 +1,16 @@
+#ifndef IMAP_URLAUTH_BACKEND_H
+#define IMAP_URLAUTH_BACKEND_H
+
+#define IMAP_URLAUTH_KEY_LEN 64
+
+struct imap_urlauth_backend;
+
+int imap_urlauth_backend_get_mailbox_key(struct mailbox *box, bool create,
+ unsigned char mailbox_key_r[IMAP_URLAUTH_KEY_LEN],
+ const char **client_error_r,
+ enum mail_error *error_code_r);
+int imap_urlauth_backend_reset_mailbox_key(struct mailbox *box);
+int imap_urlauth_backend_reset_all_keys(struct mail_user *user);
+
+#endif
+
diff --git a/src/lib-imap-urlauth/imap-urlauth-connection.c b/src/lib-imap-urlauth/imap-urlauth-connection.c
new file mode 100644
index 0000000..5fce6f7
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth-connection.c
@@ -0,0 +1,1027 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "strescape.h"
+#include "ioloop.h"
+#include "safe-mkstemp.h"
+#include "hostpid.h"
+#include "net.h"
+#include "istream.h"
+#include "ostream.h"
+#include "write-full.h"
+#include "array.h"
+#include "aqueue.h"
+#include "mail-user.h"
+#include "imap-urlauth-fetch.h"
+
+#include "imap-urlauth-connection.h"
+
+enum imap_urlauth_state {
+ IMAP_URLAUTH_STATE_DISCONNECTED = 0,
+ IMAP_URLAUTH_STATE_AUTHENTICATING,
+ IMAP_URLAUTH_STATE_AUTHENTICATED,
+ IMAP_URLAUTH_STATE_SELECTING_TARGET,
+ IMAP_URLAUTH_STATE_UNSELECTING_TARGET,
+ IMAP_URLAUTH_STATE_READY,
+ IMAP_URLAUTH_STATE_REQUEST_PENDING,
+ IMAP_URLAUTH_STATE_REQUEST_WAIT,
+};
+
+struct imap_urlauth_request {
+ struct imap_urlauth_target *target;
+ struct imap_urlauth_request *prev, *next;
+
+ char *url;
+ enum imap_urlauth_fetch_flags flags;
+
+ char *bodypartstruct;
+
+ imap_urlauth_request_callback_t *callback;
+ void *context;
+
+ bool binary_has_nuls;
+};
+
+struct imap_urlauth_target {
+ struct imap_urlauth_target *prev, *next;
+
+ char *userid;
+
+ struct imap_urlauth_request *requests_head, *requests_tail;
+};
+
+struct imap_urlauth_connection {
+ int refcount;
+
+ char *path, *service, *session_id;
+ struct mail_user *user;
+
+ int fd;
+ struct istream *input;
+ struct ostream *output;
+ struct io *io;
+
+ struct timeout *to_reconnect, *to_idle, *to_response;
+ time_t last_reconnect;
+ unsigned int reconnect_attempts;
+ unsigned int idle_timeout_msecs;
+
+ char *literal_temp_path;
+ int literal_fd;
+ buffer_t *literal_buf;
+ uoff_t literal_size, literal_bytes_left;
+
+ enum imap_urlauth_state state;
+
+ /* userid => target struct */
+ struct imap_urlauth_target *targets_head, *targets_tail;
+
+ bool reading_literal:1;
+};
+
+#define IMAP_URLAUTH_RECONNECT_MIN_SECS 2
+#define IMAP_URLAUTH_RECONNECT_MAX_ATTEMPTS 3
+
+#define IMAP_URLAUTH_RESPONSE_TIMEOUT_MSECS 2*60*1000
+
+#define IMAP_URLAUTH_HANDSHAKE "VERSION\timap-urlauth\t2\t0\n"
+
+#define IMAP_URLAUTH_MAX_INLINE_LITERAL_SIZE (1024*32)
+
+static void imap_urlauth_connection_disconnect
+ (struct imap_urlauth_connection *conn, const char *reason);
+static void imap_urlauth_connection_abort
+ (struct imap_urlauth_connection *conn, const char *reason);
+static void imap_urlauth_connection_reconnect
+ (struct imap_urlauth_connection *conn);
+static void imap_urlauth_connection_idle_disconnect
+ (struct imap_urlauth_connection *conn);
+static void imap_urlauth_connection_timeout_abort
+ (struct imap_urlauth_connection *conn);
+static void imap_urlauth_connection_fail
+ (struct imap_urlauth_connection *conn);
+
+struct imap_urlauth_connection *
+imap_urlauth_connection_init(const char *path, const char *service,
+ struct mail_user *user, const char *session_id,
+ unsigned int idle_timeout_msecs)
+{
+ struct imap_urlauth_connection *conn;
+
+ conn = i_new(struct imap_urlauth_connection, 1);
+ conn->refcount = 1;
+ conn->service = i_strdup(service);
+ conn->path = i_strdup(path);
+ if (session_id != NULL)
+ conn->session_id = i_strdup(session_id);
+ conn->user = user;
+ conn->fd = -1;
+ conn->literal_fd = -1;
+ conn->idle_timeout_msecs = idle_timeout_msecs;
+ return conn;
+}
+
+void imap_urlauth_connection_deinit(struct imap_urlauth_connection **_conn)
+{
+ struct imap_urlauth_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ imap_urlauth_connection_abort(conn, NULL);
+
+ i_free(conn->path);
+ i_free(conn->service);
+ if (conn->session_id != NULL)
+ i_free(conn->session_id);
+
+ i_assert(conn->to_idle == NULL);
+ i_assert(conn->to_reconnect == NULL);
+ i_assert(conn->to_response == NULL);
+
+ i_free(conn);
+}
+
+static void
+imap_urlauth_stop_response_timeout(struct imap_urlauth_connection *conn)
+{
+ timeout_remove(&conn->to_response);
+}
+
+static void
+imap_urlauth_start_response_timeout(struct imap_urlauth_connection *conn)
+{
+ imap_urlauth_stop_response_timeout(conn);
+ conn->to_response = timeout_add(IMAP_URLAUTH_RESPONSE_TIMEOUT_MSECS,
+ imap_urlauth_connection_timeout_abort, conn);
+}
+
+static struct imap_urlauth_target *
+imap_urlauth_connection_get_target(struct imap_urlauth_connection *conn,
+ const char *target_user)
+{
+ struct imap_urlauth_target *target = conn->targets_head;
+
+ while (target != NULL) {
+ if (strcmp(target->userid, target_user) == 0)
+ return target;
+ target = target->next;
+ }
+
+ target = i_new(struct imap_urlauth_target, 1);
+ target->userid = i_strdup(target_user);
+ DLLIST2_APPEND(&conn->targets_head, &conn->targets_tail, target);
+ return target;
+}
+
+static void
+imap_urlauth_target_free(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_target *target)
+{
+ DLLIST2_REMOVE(&conn->targets_head, &conn->targets_tail, target);
+ i_free(target->userid);
+ i_free(target);
+}
+
+static void
+imap_urlauth_connection_select_target(struct imap_urlauth_connection *conn)
+{
+ struct imap_urlauth_target *target = conn->targets_head;
+ const char *cmd;
+
+ if (target == NULL || conn->state != IMAP_URLAUTH_STATE_AUTHENTICATED)
+ return;
+
+ e_debug(conn->user->event,
+ "imap-urlauth: Selecting target user `%s'", target->userid);
+
+ conn->state = IMAP_URLAUTH_STATE_SELECTING_TARGET;
+ cmd = t_strdup_printf("USER\t%s\n", str_tabescape(target->userid));
+ if (o_stream_send_str(conn->output, cmd) < 0) {
+ i_warning("Error sending USER request to imap-urlauth server: %m");
+ imap_urlauth_connection_fail(conn);
+ }
+
+ imap_urlauth_start_response_timeout(conn);
+}
+
+static void
+imap_urlauth_connection_send_request(struct imap_urlauth_connection *conn)
+{
+ struct imap_urlauth_request *urlreq;
+ string_t *cmd;
+
+ if (conn->targets_head == NULL ||
+ (conn->targets_head->requests_head == NULL &&
+ conn->targets_head->next == NULL &&
+ conn->state == IMAP_URLAUTH_STATE_READY)) {
+ e_debug(conn->user->event,
+ "imap-urlauth: No more requests pending; scheduling disconnect");
+ timeout_remove(&conn->to_idle);
+ if (conn->idle_timeout_msecs > 0) {
+ conn->to_idle = timeout_add(conn->idle_timeout_msecs,
+ imap_urlauth_connection_idle_disconnect, conn);
+ }
+ return;
+ }
+
+ if (conn->state == IMAP_URLAUTH_STATE_AUTHENTICATED) {
+ imap_urlauth_connection_select_target(conn);
+ return;
+ }
+
+ if (conn->state != IMAP_URLAUTH_STATE_READY)
+ return;
+
+ urlreq = conn->targets_head->requests_head;
+ if (urlreq == NULL) {
+ if (conn->targets_head->next == NULL)
+ return;
+
+ conn->state = IMAP_URLAUTH_STATE_UNSELECTING_TARGET;
+ imap_urlauth_target_free(conn, conn->targets_head);
+
+ if (o_stream_send_str(conn->output, "END\n") < 0) {
+ i_warning("Error sending END request to imap-urlauth server: %m");
+ imap_urlauth_connection_fail(conn);
+ }
+ imap_urlauth_start_response_timeout(conn);
+ return;
+ }
+
+ e_debug(conn->user->event,
+ "imap-urlauth: Fetching URL `%s'", urlreq->url);
+
+ cmd = t_str_new(128);
+ str_append(cmd, "URL\t");
+ str_append_tabescaped(cmd, urlreq->url);
+ if ((urlreq->flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0)
+ str_append(cmd, "\tbpstruct");
+ if ((urlreq->flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0)
+ str_append(cmd, "\tbinary");
+ else if ((urlreq->flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0)
+ str_append(cmd, "\tbody");
+ str_append_c(cmd, '\n');
+
+ conn->state = IMAP_URLAUTH_STATE_REQUEST_PENDING;
+ if (o_stream_send(conn->output, str_data(cmd), str_len(cmd)) < 0) {
+ i_warning("Error sending URL request to imap-urlauth server: %m");
+ imap_urlauth_connection_fail(conn);
+ }
+
+ imap_urlauth_start_response_timeout(conn);
+}
+
+struct imap_urlauth_request *
+imap_urlauth_request_new(struct imap_urlauth_connection *conn,
+ const char *target_user, const char *url,
+ enum imap_urlauth_fetch_flags flags,
+ imap_urlauth_request_callback_t *callback,
+ void *context)
+{
+ struct imap_urlauth_request *urlreq;
+ struct imap_urlauth_target *target;
+
+ target = imap_urlauth_connection_get_target(conn, target_user);
+
+ urlreq = i_new(struct imap_urlauth_request, 1);
+ urlreq->url = i_strdup(url);
+ urlreq->flags = flags;
+ urlreq->target = target;
+ urlreq->callback = callback;
+ urlreq->context = context;
+
+ DLLIST2_APPEND(&target->requests_head, &target->requests_tail, urlreq);
+
+ timeout_remove(&conn->to_idle);
+
+ e_debug(conn->user->event,
+ "imap-urlauth: Added request for URL `%s' from user `%s'",
+ url, target_user);
+
+ imap_urlauth_connection_send_request(conn);
+ return urlreq;
+}
+
+static void imap_urlauth_request_free(struct imap_urlauth_request *urlreq)
+{
+ struct imap_urlauth_target *target = urlreq->target;
+
+ DLLIST2_REMOVE(&target->requests_head, &target->requests_tail, urlreq);
+ i_free(urlreq->url);
+ i_free(urlreq->bodypartstruct);
+ i_free(urlreq);
+}
+
+static void imap_urlauth_request_drop(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_request *urlreq)
+{
+ if ((conn->state == IMAP_URLAUTH_STATE_REQUEST_PENDING ||
+ conn->state == IMAP_URLAUTH_STATE_REQUEST_WAIT) &&
+ conn->targets_head != NULL &&
+ conn->targets_head->requests_head == urlreq) {
+ /* cannot just drop pending request without breaking
+ protocol state */
+ return;
+ }
+ imap_urlauth_request_free(urlreq);
+
+}
+
+void imap_urlauth_request_abort(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_request *urlreq)
+{
+ imap_urlauth_request_callback_t *callback;
+
+ callback = urlreq->callback;
+ urlreq->callback = NULL;
+ if (callback != NULL) {
+ T_BEGIN {
+ callback(NULL, urlreq->context);
+ } T_END;
+ }
+
+ imap_urlauth_request_drop(conn, urlreq);
+}
+
+static void
+imap_urlauth_request_fail(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_request *urlreq,
+ const char *error)
+{
+ struct imap_urlauth_fetch_reply reply;
+ imap_urlauth_request_callback_t *callback;
+ int ret = 1;
+
+ callback = urlreq->callback;
+ urlreq->callback = NULL;
+ if (callback != NULL) {
+ i_zero(&reply);
+ reply.url = urlreq->url;
+ reply.flags = urlreq->flags;
+ reply.succeeded = FALSE;
+ reply.error = error;
+
+ T_BEGIN {
+ ret = callback(&reply, urlreq->context);
+ } T_END;
+ }
+
+ void *urlreq_context = urlreq->context;
+ imap_urlauth_request_drop(conn, urlreq);
+
+ if (ret < 0) {
+ /* Drop any related requests upon error */
+ imap_urlauth_request_abort_by_context(conn, urlreq_context);
+ }
+
+ if (ret != 0)
+ imap_urlauth_connection_continue(conn);
+}
+
+static void
+imap_urlauth_target_abort(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_target *target)
+{
+ struct imap_urlauth_request *urlreq, *next;
+
+ urlreq = target->requests_head;
+ while (urlreq != NULL) {
+ next = urlreq->next;
+ imap_urlauth_request_abort(conn, urlreq);
+ urlreq = next;
+ }
+
+ imap_urlauth_target_free(conn, target);
+}
+
+static void
+imap_urlauth_target_fail(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_target *target, const char *error)
+{
+ struct imap_urlauth_request *urlreq, *next;
+
+ urlreq = target->requests_head;
+ while (urlreq != NULL) {
+ next = urlreq->next;
+ imap_urlauth_request_fail(conn, urlreq, error);
+ urlreq = next;
+ }
+
+ imap_urlauth_target_free(conn, target);
+}
+
+static void
+imap_urlauth_target_abort_by_context(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_target *target,
+ void *context)
+{
+ struct imap_urlauth_request *urlreq, *next;
+
+ /* abort all matching requests */
+ urlreq = target->requests_head;
+ while (urlreq != NULL) {
+ next = urlreq->next;
+ if (urlreq->context == context)
+ imap_urlauth_request_abort(conn, urlreq);
+ urlreq = next;
+ }
+
+ if (target->requests_head == NULL)
+ imap_urlauth_target_free(conn, target);
+}
+
+static void
+imap_urlauth_connection_abort(struct imap_urlauth_connection *conn,
+ const char *reason)
+{
+ struct imap_urlauth_target *target, *next;
+
+ if (reason == NULL)
+ reason = "Aborting due to error";
+ imap_urlauth_connection_disconnect(conn, reason);
+
+ /* abort all requests */
+ target = conn->targets_head;
+ while (target != NULL) {
+ next = target->next;
+ imap_urlauth_target_abort(conn, target);
+ target = next;
+ }
+}
+
+void imap_urlauth_request_abort_by_context(struct imap_urlauth_connection *conn,
+ void *context)
+{
+ struct imap_urlauth_target *target, *next;
+
+ /* abort all matching requests */
+ target = conn->targets_head;
+ while (target != NULL) {
+ next = target->next;
+ imap_urlauth_target_abort_by_context(conn, target, context);
+ target = next;
+ }
+}
+
+static void imap_urlauth_connection_fail(struct imap_urlauth_connection *conn)
+{
+ if (conn->reconnect_attempts > IMAP_URLAUTH_RECONNECT_MAX_ATTEMPTS) {
+ imap_urlauth_connection_abort(conn,
+ "Connection failed and connection attempts exhausted");
+ } else {
+ imap_urlauth_connection_reconnect(conn);
+ }
+}
+
+static int
+imap_urlauth_connection_create_temp_fd(struct imap_urlauth_connection *conn,
+ const char **path_r)
+{
+ string_t *path;
+ int fd;
+
+ path = t_str_new(128);
+ mail_user_set_get_temp_prefix(path, conn->user->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 int
+imap_urlauth_connection_read_literal_init(struct imap_urlauth_connection *conn,
+ uoff_t size)
+{
+ const char *path;
+
+ i_assert(conn->literal_fd == -1 && conn->literal_buf == NULL);
+
+ if (size <= IMAP_URLAUTH_MAX_INLINE_LITERAL_SIZE) {
+ /* read the literal directly */
+ if (size > 0) {
+ conn->literal_buf =
+ buffer_create_dynamic(default_pool, size);
+ }
+ } else {
+ /* read it into a file */
+ conn->literal_fd =
+ imap_urlauth_connection_create_temp_fd(conn, &path);
+ if (conn->literal_fd == -1)
+ return -1;
+ conn->literal_temp_path = i_strdup(path);
+ }
+
+ conn->literal_size = size;
+ conn->literal_bytes_left = size;
+ conn->reading_literal = TRUE;
+ return 1;
+}
+
+void imap_urlauth_connection_continue(struct imap_urlauth_connection *conn)
+{
+ i_assert(conn->targets_head != NULL);
+ i_assert(conn->targets_head->requests_head != NULL);
+
+ if (conn->state != IMAP_URLAUTH_STATE_REQUEST_WAIT)
+ return;
+
+ conn->state = IMAP_URLAUTH_STATE_READY;
+ imap_urlauth_request_free(conn->targets_head->requests_head);
+
+ imap_urlauth_connection_send_request(conn);
+}
+
+static int
+imap_urlauth_connection_read_literal_data(struct imap_urlauth_connection *conn)
+{
+ const unsigned char *data;
+ size_t size;
+
+ /* read data */
+ data = i_stream_get_data(conn->input, &size);
+ if (size > conn->literal_bytes_left)
+ size = conn->literal_bytes_left;
+
+ /* write to buffer or file */
+ if (size > 0) {
+ if (conn->literal_fd >= 0) {
+ if (write_full(conn->literal_fd, data, size) < 0) {
+ i_error("imap-urlauth: write(%s) failed: %m",
+ conn->literal_temp_path);
+ return -1;
+ }
+ } else {
+ i_assert(conn->literal_buf != NULL);
+ buffer_append(conn->literal_buf, data, size);
+ }
+ i_stream_skip(conn->input, size);
+ conn->literal_bytes_left -= size;
+ }
+
+ /* exit if not finished */
+ if (conn->literal_bytes_left > 0)
+ return 0;
+
+ /* read LF guard */
+ data = i_stream_get_data(conn->input, &size);
+ if (size < 1)
+ return 0;
+
+ /* check LF guard */
+ if (data[0] != '\n') {
+ i_error("imap-urlauth: no LF at end of literal (found 0x%x)",
+ data[0]);
+ return -1;
+ }
+ i_stream_skip(conn->input, 1);
+ return 1;
+}
+
+static void literal_stream_destroy(buffer_t *buffer)
+{
+ buffer_free(&buffer);
+}
+
+static int
+imap_urlauth_fetch_reply_set_literal_stream(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_fetch_reply *reply)
+{
+ const unsigned char *data;
+ size_t size;
+ uoff_t fd_size;
+
+ if (conn->literal_fd != -1) {
+ reply->input = i_stream_create_fd_autoclose(&conn->literal_fd,
+ SIZE_MAX);
+ if (i_stream_get_size(reply->input, TRUE, &fd_size) < 1 ||
+ fd_size != conn->literal_size) {
+ i_stream_unref(&reply->input);
+ i_error("imap-urlauth: Failed to obtain proper size from literal stream");
+ imap_urlauth_connection_abort(conn,
+ "Failed during literal transfer");
+ return -1;
+ }
+ } else {
+ data = buffer_get_data(conn->literal_buf, &size);
+ i_assert(size == conn->literal_size);
+ reply->input = i_stream_create_from_data(data, size);
+ i_stream_add_destroy_callback(reply->input,
+ literal_stream_destroy,
+ conn->literal_buf);
+ }
+ reply->size = conn->literal_size;
+ return 0;
+}
+
+static int
+imap_urlauth_connection_read_literal(struct imap_urlauth_connection *conn)
+{
+ struct imap_urlauth_request *urlreq = conn->targets_head->requests_head;
+ struct imap_urlauth_fetch_reply reply;
+ imap_urlauth_request_callback_t *callback;
+ int ret;
+
+ i_assert(conn->reading_literal);
+ i_assert(urlreq != NULL);
+
+ if (conn->literal_size > 0) {
+ ret = imap_urlauth_connection_read_literal_data(conn);
+ if (ret <= 0)
+ return ret;
+ }
+ i_assert(conn->literal_bytes_left == 0);
+
+ /* reply */
+ i_zero(&reply);
+ reply.url = urlreq->url;
+ reply.flags = urlreq->flags;
+ reply.bodypartstruct = urlreq->bodypartstruct;
+ reply.binary_has_nuls = urlreq->binary_has_nuls ? 1 : 0;
+
+ if (conn->literal_size > 0) {
+ if (imap_urlauth_fetch_reply_set_literal_stream(conn, &reply) < 0)
+ return -1;
+ }
+ reply.succeeded = TRUE;
+
+ ret = 1;
+ callback = urlreq->callback;
+ urlreq->callback = NULL;
+ if (callback != NULL) T_BEGIN {
+ ret = callback(&reply, urlreq->context);
+ } T_END;
+
+ if (reply.input != NULL)
+ i_stream_unref(&reply.input);
+
+ if (ret < 0) {
+ /* Drop any related requests upon error */
+ imap_urlauth_request_abort_by_context(conn, urlreq->context);
+ }
+
+ conn->state = IMAP_URLAUTH_STATE_REQUEST_WAIT;
+ if (ret != 0)
+ imap_urlauth_connection_continue(conn);
+
+ /* finished */
+ i_free_and_null(conn->literal_temp_path);
+ conn->literal_fd = -1;
+ conn->literal_buf = NULL;
+ conn->reading_literal = FALSE;
+ return 1;
+}
+
+static int imap_urlauth_input_pending(struct imap_urlauth_connection *conn)
+{
+ struct imap_urlauth_request *urlreq;
+ const char *response, *const *args, *bpstruct = NULL;
+ uoff_t literal_size;
+
+ i_assert(conn->targets_head != NULL);
+ i_assert(conn->targets_head->requests_head != NULL);
+ urlreq = conn->targets_head->requests_head;
+
+ if (conn->reading_literal) {
+ /* Read pending literal; may callback */
+ return imap_urlauth_connection_read_literal(conn);
+ }
+
+ /* "OK"[<metadata-items>]"\t"<literal-size>"\n" or
+ "NO"["\terror="<error>]"\n" */
+ if ((response = i_stream_next_line(conn->input)) == NULL)
+ return 0;
+ imap_urlauth_stop_response_timeout(conn);
+
+ args = t_strsplit_tabescaped(response);
+ if (args[0] == NULL) {
+ i_error("imap-urlauth: Empty URL response: %s",
+ str_sanitize(response, 80));
+ return -1;
+ }
+
+ if (strcmp(args[0], "OK") != 0 || args[1] == NULL) {
+ if (strcmp(args[0], "NO") == 0) {
+ const char *param = args[1], *error = NULL;
+
+ if (param != NULL &&
+ strncasecmp(param, "error=", 6) == 0 &&
+ param[6] != '\0') {
+ error = param+6;
+ }
+ conn->state = IMAP_URLAUTH_STATE_REQUEST_WAIT;
+ imap_urlauth_request_fail(conn,
+ conn->targets_head->requests_head, error);
+ return 1;
+ }
+ i_error("imap-urlauth: Unexpected URL response: %s",
+ str_sanitize(response, 80));
+ return -1;
+ }
+
+ /* read metadata */
+ args++;
+ for (; args[1] != NULL; args++) {
+ const char *param = args[0];
+
+ if (strcasecmp(param, "hasnuls") == 0) {
+ urlreq->binary_has_nuls = TRUE;
+ } else if (strncasecmp(param, "bpstruct=", 9) == 0 &&
+ param[9] != '\0') {
+ bpstruct = param+9;
+ }
+ }
+
+ /* read literal size */
+ if (str_to_uoff(args[0], &literal_size) < 0) {
+ i_error("imap-urlauth: "
+ "Overflowing unsigned integer value for literal size: %s",
+ args[1]);
+ return -1;
+ }
+
+ /* read literal */
+ if (imap_urlauth_connection_read_literal_init(conn, literal_size) < 0)
+ return -1;
+
+ urlreq->bodypartstruct = i_strdup(bpstruct);
+ return imap_urlauth_connection_read_literal(conn);
+}
+
+static int imap_urlauth_input_next(struct imap_urlauth_connection *conn)
+{
+ const char *response;
+ int ret;
+
+ switch (conn->state) {
+ case IMAP_URLAUTH_STATE_AUTHENTICATING:
+ case IMAP_URLAUTH_STATE_UNSELECTING_TARGET:
+ if ((response = i_stream_next_line(conn->input)) == NULL)
+ return 0;
+ imap_urlauth_stop_response_timeout(conn);
+
+ if (strcasecmp(response, "OK") != 0) {
+ if (conn->state == IMAP_URLAUTH_STATE_AUTHENTICATING)
+ i_error("imap-urlauth: Failed to authenticate to service: "
+ "Got unexpected response: %s", str_sanitize(response, 80));
+ else
+ i_error("imap-urlauth: Failed to unselect target user: "
+ "Got unexpected response: %s", str_sanitize(response, 80));
+ imap_urlauth_connection_abort(conn, NULL);
+ return -1;
+ }
+
+ if (conn->state == IMAP_URLAUTH_STATE_AUTHENTICATING) {
+ e_debug(conn->user->event,
+ "imap-urlauth: Successfully authenticated to service");
+ } else {
+ e_debug(conn->user->event,
+ "imap-urlauth: Successfully unselected target user");
+ }
+
+ conn->state = IMAP_URLAUTH_STATE_AUTHENTICATED;
+ imap_urlauth_connection_select_target(conn);
+ return 0;
+ case IMAP_URLAUTH_STATE_SELECTING_TARGET:
+ if ((response = i_stream_next_line(conn->input)) == NULL)
+ return 0;
+ imap_urlauth_stop_response_timeout(conn);
+
+ i_assert(conn->targets_head != NULL);
+
+ if (strcasecmp(response, "NO") == 0) {
+ e_debug(conn->user->event,
+ "imap-urlauth: Failed to select target user %s",
+ conn->targets_head->userid);
+ imap_urlauth_target_fail(conn, conn->targets_head, NULL);
+
+ conn->state = IMAP_URLAUTH_STATE_AUTHENTICATED;
+ imap_urlauth_connection_select_target(conn);
+ return 0;
+ }
+ if (strcasecmp(response, "OK") != 0) {
+ i_error("imap-urlauth: Failed to select target user %s: "
+ "Got unexpected response: %s", conn->targets_head->userid,
+ str_sanitize(response, 80));
+ imap_urlauth_connection_abort(conn, NULL);
+ return -1;
+ }
+
+ e_debug(conn->user->event,
+ "imap-urlauth: Successfully selected target user %s",
+ conn->targets_head->userid);
+ conn->state = IMAP_URLAUTH_STATE_READY;
+ imap_urlauth_connection_send_request(conn);
+ return 0;
+ case IMAP_URLAUTH_STATE_AUTHENTICATED:
+ case IMAP_URLAUTH_STATE_READY:
+ case IMAP_URLAUTH_STATE_REQUEST_WAIT:
+ if ((response = i_stream_next_line(conn->input)) == NULL)
+ return 0;
+
+ i_error("imap-urlauth: Received input while no requests were pending: %s",
+ str_sanitize(response, 80));
+ imap_urlauth_connection_abort(conn, NULL);
+ return -1;
+ case IMAP_URLAUTH_STATE_REQUEST_PENDING:
+ if ((ret = imap_urlauth_input_pending(conn)) < 0)
+ imap_urlauth_connection_fail(conn);
+ return ret;
+ case IMAP_URLAUTH_STATE_DISCONNECTED:
+ break;
+ }
+ i_unreached();
+}
+
+static void imap_urlauth_input(struct imap_urlauth_connection *conn)
+{
+ i_assert(conn->state != IMAP_URLAUTH_STATE_DISCONNECTED);
+
+ if (conn->input->closed) {
+ /* disconnected */
+ i_error("imap-urlauth: Service disconnected unexpectedly");
+ imap_urlauth_connection_fail(conn);
+ return;
+ }
+
+ switch (i_stream_read(conn->input)) {
+ case -1:
+ /* disconnected */
+ i_error("imap-urlauth: Service disconnected unexpectedly");
+ imap_urlauth_connection_fail(conn);
+ return;
+ case -2:
+ /* input buffer full */
+ i_error("imap-urlauth: Service sent too large input");
+ imap_urlauth_connection_abort(conn, NULL);
+ return;
+ }
+
+ while (!conn->input->closed) {
+ if (imap_urlauth_input_next(conn) <= 0)
+ break;
+ }
+}
+
+static int
+imap_urlauth_connection_do_connect(struct imap_urlauth_connection *conn)
+{
+ string_t *str;
+ int fd;
+
+ if (conn->state != IMAP_URLAUTH_STATE_DISCONNECTED) {
+ imap_urlauth_connection_send_request(conn);
+ return 1;
+ }
+
+ if (conn->user->auth_token == NULL) {
+ i_error("imap-urlauth: cannot authenticate because no auth token "
+ "is available for this session (running standalone?).");
+ imap_urlauth_connection_abort(conn, NULL);
+ return -1;
+ }
+
+ e_debug(conn->user->event, "imap-urlauth: Connecting to service at %s", conn->path);
+
+ i_assert(conn->fd == -1);
+ fd = net_connect_unix(conn->path);
+ if (fd == -1) {
+ i_error("imap-urlauth: net_connect_unix(%s) failed: %m",
+ conn->path);
+ imap_urlauth_connection_abort(conn, NULL);
+ return -1;
+ }
+
+ timeout_remove(&conn->to_reconnect);
+
+ conn->fd = fd;
+ conn->input = i_stream_create_fd(fd, SIZE_MAX);
+ conn->output = o_stream_create_fd(fd, SIZE_MAX);
+ conn->io = io_add(fd, IO_READ, imap_urlauth_input, conn);
+ conn->state = IMAP_URLAUTH_STATE_AUTHENTICATING;
+
+ str = t_str_new(128);
+ str_printfa(str, IMAP_URLAUTH_HANDSHAKE"AUTH\t%s\t%s\t",
+ conn->service, my_pid);
+ str_append_tabescaped(str, conn->user->username);
+ str_append_c(str, '\t');
+ if (conn->session_id != NULL)
+ str_append_tabescaped(str, conn->session_id);
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, conn->user->auth_token);
+ str_append_c(str, '\n');
+ if (o_stream_send(conn->output, str_data(str), str_len(str)) < 0) {
+ i_warning("Error sending handshake to imap-urlauth server: %m");
+ imap_urlauth_connection_abort(conn, NULL);
+ return -1;
+ }
+
+ imap_urlauth_start_response_timeout(conn);
+ return 0;
+}
+
+int imap_urlauth_connection_connect(struct imap_urlauth_connection *conn)
+{
+ conn->reconnect_attempts = 0;
+
+ if (conn->to_reconnect == NULL)
+ return imap_urlauth_connection_do_connect(conn);
+ return 0;
+}
+
+static void imap_urlauth_connection_disconnect
+(struct imap_urlauth_connection *conn, const char *reason)
+{
+ conn->state = IMAP_URLAUTH_STATE_DISCONNECTED;
+
+ if (conn->fd != -1) {
+ if (reason == NULL)
+ e_debug(conn->user->event, "imap-urlauth: Disconnecting from service");
+ else
+ e_debug(conn->user->event, "imap-urlauth: Disconnected: %s", reason);
+
+ io_remove(&conn->io);
+ i_stream_destroy(&conn->input);
+ o_stream_destroy(&conn->output);
+ net_disconnect(conn->fd);
+ conn->fd = -1;
+ }
+ conn->reading_literal = FALSE;
+
+ if (conn->literal_fd != -1) {
+ if (close(conn->literal_fd) < 0)
+ i_error("imap-urlauth: close(%s) failed: %m", conn->literal_temp_path);
+
+ i_free_and_null(conn->literal_temp_path);
+ conn->literal_fd = -1;
+ }
+
+ buffer_free(&conn->literal_buf);
+ timeout_remove(&conn->to_reconnect);
+ timeout_remove(&conn->to_idle);
+ imap_urlauth_stop_response_timeout(conn);
+}
+
+static void
+imap_urlauth_connection_do_reconnect(struct imap_urlauth_connection *conn)
+{
+ if (conn->reconnect_attempts >= IMAP_URLAUTH_RECONNECT_MAX_ATTEMPTS) {
+ imap_urlauth_connection_abort(conn,
+ "Connection failed and connection attempts exhausted");
+ return;
+ }
+
+ if (ioloop_time - conn->last_reconnect < IMAP_URLAUTH_RECONNECT_MIN_SECS) {
+ e_debug(conn->user->event, "imap-urlauth: Scheduling reconnect");
+ timeout_remove(&conn->to_reconnect);
+ conn->to_reconnect =
+ timeout_add(IMAP_URLAUTH_RECONNECT_MIN_SECS*1000,
+ imap_urlauth_connection_do_reconnect, conn);
+ } else {
+ conn->reconnect_attempts++;
+ conn->last_reconnect = ioloop_time;
+ (void)imap_urlauth_connection_do_connect(conn);
+ }
+}
+
+static void
+imap_urlauth_connection_reconnect(struct imap_urlauth_connection *conn)
+{
+ imap_urlauth_connection_disconnect(conn, NULL);
+
+ /* don't reconnect if there are no requests */
+ if (conn->targets_head == NULL)
+ return;
+
+ imap_urlauth_connection_do_reconnect(conn);
+}
+
+static void
+imap_urlauth_connection_idle_disconnect(struct imap_urlauth_connection *conn)
+{
+ imap_urlauth_connection_disconnect(conn, "Idle timeout");
+}
+
+static void
+imap_urlauth_connection_timeout_abort(struct imap_urlauth_connection *conn)
+{
+ imap_urlauth_connection_abort(conn, "Service is not responding");
+}
+
+bool imap_urlauth_connection_is_connected(struct imap_urlauth_connection *conn)
+{
+ return conn->fd != -1 && conn->state != IMAP_URLAUTH_STATE_DISCONNECTED;
+}
diff --git a/src/lib-imap-urlauth/imap-urlauth-connection.h b/src/lib-imap-urlauth/imap-urlauth-connection.h
new file mode 100644
index 0000000..b60186a
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth-connection.h
@@ -0,0 +1,42 @@
+#ifndef IMAP_URLAUTH_CONNECTION_H
+#define IMAP_URLAUTH_CONNECTION_H
+
+struct imap_urlauth_request;
+struct imap_urlauth_fetch_reply;
+
+typedef int
+imap_urlauth_request_callback_t(struct imap_urlauth_fetch_reply *reply,
+ void *context);
+
+/* If reconnect_callback is specified, it's called when connection is lost.
+ If the callback returns FALSE, reconnection isn't attempted. */
+struct imap_urlauth_connection *
+imap_urlauth_connection_init(const char *path, const char *service,
+ struct mail_user *user, const char *session_id,
+ unsigned int idle_timeout_msecs);
+void imap_urlauth_connection_deinit(struct imap_urlauth_connection **conn);
+
+/* Connect to imap-urlauth (even if failed for previous requests). */
+int imap_urlauth_connection_connect(struct imap_urlauth_connection *conn);
+
+/* Continue after request callback returned 0 */
+void imap_urlauth_connection_continue(struct imap_urlauth_connection *conn);
+
+/* Create a new URL fetch request */
+struct imap_urlauth_request *
+imap_urlauth_request_new(struct imap_urlauth_connection *conn,
+ const char *target_user, const char *url,
+ enum imap_urlauth_fetch_flags flags,
+ imap_urlauth_request_callback_t *callback,
+ void *context);
+/* Abort request */
+void imap_urlauth_request_abort(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_request *urlreq);
+/* Abort all requests with matching context value */
+void imap_urlauth_request_abort_by_context(struct imap_urlauth_connection *conn,
+ void *context);
+
+/* Returns TRUE if currently connected imap-urlauth service. */
+bool imap_urlauth_connection_is_connected(struct imap_urlauth_connection *conn);
+
+#endif
diff --git a/src/lib-imap-urlauth/imap-urlauth-fetch.c b/src/lib-imap-urlauth/imap-urlauth-fetch.c
new file mode 100644
index 0000000..d5746f1
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth-fetch.c
@@ -0,0 +1,530 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "array.h"
+#include "net.h"
+#include "istream.h"
+#include "mail-user.h"
+#include "mail-error.h"
+#include "mail-storage.h"
+#include "imap-url.h"
+#include "imap-msgpart-url.h"
+#include "imap-urlauth-private.h"
+#include "imap-urlauth-fetch.h"
+#include "imap-urlauth-connection.h"
+
+struct imap_urlauth_fetch_url {
+ struct imap_urlauth_fetch_url *prev, *next;
+
+ char *url;
+ enum imap_urlauth_fetch_flags flags;
+};
+
+struct imap_urlauth_fetch {
+ unsigned int refcount;
+ struct imap_urlauth_context *uctx;
+
+ imap_urlauth_fetch_callback_t *callback;
+ void *context;
+
+ /* local urls */
+ struct imap_urlauth_fetch_url *local_urls_head, *local_urls_tail;
+ struct imap_msgpart_url *local_url;
+
+ unsigned int pending_requests;
+
+ struct {
+ char *url;
+ enum imap_urlauth_fetch_flags flags;
+
+ struct istream *input;
+ uoff_t size;
+
+ char *bodypartstruct;
+ char *error;
+
+ bool succeeded:1;
+ bool binary_has_nuls:1;
+ } pending_reply;
+
+ bool failed:1;
+ bool waiting_local:1;
+ bool waiting_service:1;
+};
+
+static void imap_urlauth_fetch_abort_local(struct imap_urlauth_fetch *ufetch)
+{
+ struct imap_urlauth_fetch_url *url, *url_next;
+
+ if (ufetch->local_url != NULL) {
+ ufetch->pending_requests--;
+ imap_msgpart_url_free(&ufetch->local_url);
+ }
+
+ i_free_and_null(ufetch->pending_reply.url);
+ i_free_and_null(ufetch->pending_reply.bodypartstruct);
+ i_free_and_null(ufetch->pending_reply.error);
+ i_stream_unref(&ufetch->pending_reply.input);
+
+ url = ufetch->local_urls_head;
+ for (; url != NULL; url = url_next) {
+ url_next = url->next;
+ i_free(url->url);
+ i_free(url);
+ ufetch->pending_requests--;
+ }
+ ufetch->local_urls_head = ufetch->local_urls_tail = NULL;
+}
+
+static void imap_urlauth_fetch_abort(struct imap_urlauth_fetch *ufetch)
+{
+ if (ufetch->pending_requests > 0)
+ imap_urlauth_request_abort_by_context(ufetch->uctx->conn, ufetch);
+
+ imap_urlauth_fetch_abort_local(ufetch);
+
+ i_assert(ufetch->pending_requests == 0);
+}
+
+static void imap_urlauth_fetch_fail(struct imap_urlauth_fetch *ufetch)
+{
+ imap_urlauth_fetch_abort(ufetch);
+ ufetch->failed = TRUE;
+}
+
+struct imap_urlauth_fetch *
+imap_urlauth_fetch_init(struct imap_urlauth_context *uctx,
+ imap_urlauth_fetch_callback_t *callback, void *context)
+{
+ struct imap_urlauth_fetch *ufetch;
+
+ ufetch = i_new(struct imap_urlauth_fetch, 1);
+ ufetch->refcount = 1;
+ ufetch->uctx = uctx;
+ ufetch->callback = callback;
+ ufetch->context = context;
+ return ufetch;
+}
+
+static void imap_urlauth_fetch_ref(struct imap_urlauth_fetch *ufetch)
+{
+ i_assert(ufetch->refcount > 0);
+ ufetch->refcount++;
+}
+
+static void imap_urlauth_fetch_unref(struct imap_urlauth_fetch **_ufetch)
+{
+ struct imap_urlauth_fetch *ufetch = *_ufetch;
+
+ i_assert(ufetch->refcount > 0);
+
+ *_ufetch = NULL;
+ if (--ufetch->refcount > 0)
+ return;
+
+ ufetch->refcount++;
+ imap_urlauth_fetch_abort(ufetch);
+ ufetch->refcount--;
+ i_assert(ufetch->refcount == 0);
+
+ /* dont leave the connection in limbo; make sure continue is called */
+ if (ufetch->waiting_service)
+ imap_urlauth_connection_continue(ufetch->uctx->conn);
+ i_free(ufetch);
+}
+
+void imap_urlauth_fetch_deinit(struct imap_urlauth_fetch **_ufetch)
+{
+ imap_urlauth_fetch_unref(_ufetch);
+}
+
+static void
+imap_urlauth_fetch_error(struct imap_urlauth_fetch *ufetch, const char *url,
+ enum imap_urlauth_fetch_flags url_flags,
+ const char *error)
+{
+ struct imap_urlauth_fetch_reply reply;
+ int ret;
+
+ ufetch->pending_requests--;
+
+ i_zero(&reply);
+ reply.url = url;
+ reply.flags = url_flags;
+ reply.succeeded = FALSE;
+ reply.error = error;
+
+ T_BEGIN {
+ ret = ufetch->callback(&reply, ufetch->pending_requests == 0,
+ ufetch->context);
+ } T_END;
+
+ if (ret == 0) {
+ ufetch->waiting_local = TRUE;
+ ufetch->pending_requests++;
+ } else if (ret < 0) {
+ imap_urlauth_fetch_fail(ufetch);
+ }
+}
+
+static void
+imap_urlauth_fetch_local(struct imap_urlauth_fetch *ufetch, const char *url,
+ enum imap_urlauth_fetch_flags url_flags,
+ struct imap_url *imap_url)
+{
+ struct imap_urlauth_fetch_reply reply;
+ struct imap_msgpart_open_result mpresult;
+ const char *error, *errormsg = NULL, *bpstruct = NULL;
+ bool debug = ufetch->uctx->user->mail_debug, success;
+ enum mail_error error_code;
+ struct imap_msgpart_url *mpurl = NULL;
+ int ret;
+
+ success = TRUE;
+
+ if (debug)
+ i_debug("Fetching local URLAUTH %s", url);
+
+ if (url_flags == 0)
+ url_flags = IMAP_URLAUTH_FETCH_FLAG_BODY;
+
+ /* fetch URL */
+ if (imap_url == NULL) {
+ ret = imap_urlauth_fetch(ufetch->uctx, url,
+ &mpurl, &error_code, &error);
+ } else {
+ ret = imap_urlauth_fetch_parsed(ufetch->uctx, imap_url,
+ &mpurl, &error_code, &error);
+ }
+ if (ret <= 0) {
+ if (ret == 0) {
+ errormsg = t_strdup_printf("Failed to fetch URLAUTH \"%s\": %s",
+ url, error);
+ if (debug)
+ i_debug("%s", errormsg);
+ }
+ success = FALSE;
+ }
+
+ /* fetch metadata */
+ if (success && (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0)
+ imap_msgpart_url_set_decode_to_binary(mpurl);
+ if (success &&
+ (url_flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0) {
+ ret = imap_msgpart_url_get_bodypartstructure(mpurl, &bpstruct, &error);
+ if (ret <= 0) {
+ if (ret == 0) {
+ errormsg = t_strdup_printf
+ ("Failed to read URLAUTH \"%s\": %s", url, error);
+ if (debug)
+ i_debug("%s", errormsg);
+ }
+ success = FALSE;
+ }
+ }
+
+ /* if requested, read the message part the URL points to */
+ i_zero(&mpresult);
+ if (success && ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 ||
+ (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0)) {
+ ret = imap_msgpart_url_read_part(mpurl, &mpresult, &error);
+ if (ret <= 0) {
+ if (ret == 0) {
+ errormsg = t_strdup_printf
+ ("Failed to read URLAUTH \"%s\": %s", url, error);
+ if (debug)
+ i_debug("%s", errormsg);
+ }
+ success = FALSE;
+ }
+ }
+
+ if (debug && success) {
+ if (bpstruct != NULL)
+ i_debug("Fetched URLAUTH yielded BODYPARTSTRUCTURE (%s)", bpstruct);
+ if (mpresult.size == 0 || mpresult.input == NULL)
+ i_debug("Fetched URLAUTH yielded empty result");
+ else {
+ i_debug("Fetched URLAUTH yielded %"PRIuUOFF_T" bytes "
+ "of %smessage data", mpresult.size,
+ (mpresult.binary_decoded_input_has_nuls ? "binary " : ""));
+ }
+ }
+
+ ufetch->pending_requests--;
+
+ if (!success && ret < 0) {
+ if (mpurl != NULL)
+ imap_msgpart_url_free(&mpurl);
+ (void)ufetch->callback(NULL, TRUE, ufetch->context);
+ imap_urlauth_fetch_fail(ufetch);
+ return;
+ }
+
+ i_zero(&reply);
+ reply.url = url;
+ reply.flags = url_flags;
+ reply.error = errormsg;
+ reply.succeeded = success;
+
+ reply.bodypartstruct = bpstruct;
+ reply.binary_has_nuls = mpresult.binary_decoded_input_has_nuls;
+ reply.size = mpresult.size;
+ reply.input = mpresult.input;
+
+ ret = ufetch->callback(&reply, ufetch->pending_requests == 0,
+ ufetch->context);
+ if (ret == 0) {
+ ufetch->local_url = mpurl;
+ ufetch->waiting_local = TRUE;
+ ufetch->pending_requests++;
+ } else {
+
+ if (mpurl != NULL)
+ imap_msgpart_url_free(&mpurl);
+ if (ret < 0)
+ imap_urlauth_fetch_fail(ufetch);
+ }
+}
+
+static int
+imap_urlauth_fetch_request_callback(struct imap_urlauth_fetch_reply *reply,
+ void *context)
+{
+ struct imap_urlauth_fetch *ufetch =
+ (struct imap_urlauth_fetch *)context;
+ int ret = 1;
+
+ if (ufetch->waiting_local && reply != NULL) {
+ i_assert(ufetch->pending_reply.url == NULL);
+ ufetch->pending_reply.url = i_strdup(reply->url);
+ ufetch->pending_reply.flags = reply->flags;
+ ufetch->pending_reply.bodypartstruct =
+ i_strdup(reply->bodypartstruct);
+ ufetch->pending_reply.error = i_strdup(reply->error);
+ if (reply->input != NULL) {
+ ufetch->pending_reply.input = reply->input;
+ i_stream_ref(ufetch->pending_reply.input);
+ }
+ ufetch->pending_reply.size = reply->size;
+ ufetch->pending_reply.succeeded = reply->succeeded;
+ ufetch->pending_reply.binary_has_nuls = reply->binary_has_nuls;
+ ufetch->waiting_service = TRUE;
+ return 0;
+ }
+
+ ufetch->waiting_local = FALSE;
+ ufetch->pending_requests--;
+
+ imap_urlauth_fetch_ref(ufetch);
+
+ if (!ufetch->failed) {
+ bool last = ufetch->pending_requests == 0 || reply == NULL;
+ ret = ufetch->callback(reply, last, ufetch->context);
+ }
+
+ /* report failure only once */
+ if (ret < 0 || reply == NULL) {
+ if (!ufetch->failed)
+ imap_urlauth_fetch_abort_local(ufetch);
+ ufetch->failed = TRUE;
+ } else if (ret == 0) {
+ ufetch->waiting_service = TRUE;
+ ufetch->pending_requests++;
+ }
+
+ imap_urlauth_fetch_unref(&ufetch);
+ return ret;
+}
+
+int imap_urlauth_fetch_url(struct imap_urlauth_fetch *ufetch, const char *url,
+ enum imap_urlauth_fetch_flags url_flags)
+{
+ struct imap_urlauth_context *uctx = ufetch->uctx;
+ enum imap_url_parse_flags url_parse_flags =
+ IMAP_URL_PARSE_ALLOW_URLAUTH;
+ struct mail_user *mail_user = uctx->user;
+ struct imap_url *imap_url;
+ const char *error, *errormsg;
+
+ /* parse the url */
+ if (imap_url_parse(url, NULL, url_parse_flags, &imap_url, &error) < 0) {
+ errormsg = t_strdup_printf(
+ "Failed to fetch URLAUTH \"%s\": %s", url, error);
+ e_debug(mail_user->event, "%s", errormsg);
+ ufetch->pending_requests++;
+ imap_urlauth_fetch_ref(ufetch);
+ imap_urlauth_fetch_error(ufetch, url, url_flags, errormsg);
+ imap_urlauth_fetch_unref(&ufetch);
+ return 1;
+ }
+
+ return imap_urlauth_fetch_url_parsed(ufetch, url, imap_url, url_flags);
+}
+
+int imap_urlauth_fetch_url_parsed(struct imap_urlauth_fetch *ufetch,
+ const char *url, struct imap_url *imap_url,
+ enum imap_urlauth_fetch_flags url_flags)
+{
+ struct imap_urlauth_context *uctx = ufetch->uctx;
+ struct mail_user *mail_user = uctx->user;
+ const char *error, *errormsg;
+ int ret = 0;
+
+ ufetch->failed = FALSE;
+ ufetch->pending_requests++;
+
+ imap_urlauth_fetch_ref(ufetch);
+
+ /* if access user and target user match, handle fetch request locally */
+ if (imap_url->userid != NULL &&
+ strcmp(mail_user->username, imap_url->userid) == 0) {
+
+ if (ufetch->waiting_local) {
+ struct imap_urlauth_fetch_url *url_local;
+
+ url_local = i_new(struct imap_urlauth_fetch_url, 1);
+ url_local->url = i_strdup(url);
+ url_local->flags = url_flags;
+
+ DLLIST2_APPEND(&ufetch->local_urls_head,
+ &ufetch->local_urls_tail, url_local);
+ } else T_BEGIN {
+ imap_urlauth_fetch_local(ufetch, url,
+ url_flags, imap_url);
+ } T_END;
+ imap_url = NULL;
+ /* don't try to fetch remote URLs that are already known to fail access */
+ } else if (!imap_urlauth_check(uctx, imap_url, TRUE, &error)) {
+ errormsg = t_strdup_printf(
+ "Failed to fetch URLAUTH \"%s\": %s", url, error);
+ e_debug(mail_user->event, "%s", errormsg);
+ imap_urlauth_fetch_error(ufetch, url, url_flags, errormsg);
+ imap_url = NULL;
+ }
+
+ /* create request for url */
+ if (imap_url != NULL && imap_url->userid != NULL) {
+ i_assert(uctx->conn != NULL);
+ (void)imap_urlauth_request_new(uctx->conn, imap_url->userid,
+ url, url_flags,
+ imap_urlauth_fetch_request_callback, ufetch);
+ i_assert(uctx->conn != NULL);
+ if (imap_urlauth_connection_connect(uctx->conn) < 0)
+ ret = -1;
+ }
+ if (ret >= 0)
+ ret = (ufetch->pending_requests > 0 ? 0 : 1);
+
+ imap_urlauth_fetch_unref(&ufetch);
+ return ret;
+}
+
+static bool imap_urlauth_fetch_do_continue(struct imap_urlauth_fetch *ufetch)
+{
+ struct imap_urlauth_fetch_url *url, *url_next;
+ int ret;
+
+ if (ufetch->failed)
+ return FALSE;
+
+ if (!ufetch->waiting_local && !ufetch->waiting_service) {
+ /* not currently waiting for anything */
+ return ufetch->pending_requests > 0;
+ }
+
+ /* we finished a request */
+ ufetch->pending_requests--;
+
+ if (!ufetch->waiting_local) {
+ /* not waiting for local request handling */
+ ufetch->waiting_service = FALSE;
+ imap_urlauth_connection_continue(ufetch->uctx->conn);
+ return ufetch->pending_requests > 0;
+ }
+
+ /* finished local request */
+ if (ufetch->local_url != NULL) {
+ imap_msgpart_url_free(&ufetch->local_url);
+ }
+ ufetch->waiting_local = FALSE;
+
+ /* handle pending remote reply */
+ if (ufetch->pending_reply.url != NULL) {
+ struct imap_urlauth_fetch_reply reply;
+
+ ufetch->pending_requests--;
+
+ i_zero(&reply);
+ reply.url = ufetch->pending_reply.url;
+ reply.flags = ufetch->pending_reply.flags;
+ reply.bodypartstruct = ufetch->pending_reply.bodypartstruct;
+ reply.error = ufetch->pending_reply.error;
+ reply.input = ufetch->pending_reply.input;
+ reply.size = ufetch->pending_reply.size;
+ reply.succeeded = ufetch->pending_reply.succeeded;
+ reply.binary_has_nuls = ufetch->pending_reply.binary_has_nuls;
+
+ ret = ufetch->callback(&reply, ufetch->pending_requests == 0,
+ ufetch->context);
+
+ if (ufetch->pending_reply.url != NULL)
+ i_free(ufetch->pending_reply.url);
+ if (ufetch->pending_reply.input != NULL)
+ i_stream_unref(&ufetch->pending_reply.input);
+ if (ufetch->pending_reply.bodypartstruct != NULL)
+ i_free(ufetch->pending_reply.bodypartstruct);
+ if (ufetch->pending_reply.error != NULL)
+ i_free(ufetch->pending_reply.error);
+
+ if (ret < 0) {
+ imap_urlauth_fetch_fail(ufetch);
+ return FALSE;
+ }
+
+ if (ret == 0) {
+ ufetch->waiting_service = TRUE;
+ ufetch->pending_requests++;
+ return TRUE;
+ }
+
+ ufetch->waiting_service = FALSE;
+ imap_urlauth_connection_continue(ufetch->uctx->conn);
+ }
+
+ /* handle pending local urls */
+ url = ufetch->local_urls_head;
+ while (url != NULL) {
+ url_next = url->next;
+ T_BEGIN {
+ imap_urlauth_fetch_local(ufetch, url->url,
+ url->flags, NULL);
+ } T_END;
+ DLLIST2_REMOVE(&ufetch->local_urls_head,
+ &ufetch->local_urls_tail, url);
+ i_free(url->url);
+ i_free(url);
+ if (ufetch->waiting_local)
+ return TRUE;
+ url = url_next;
+ }
+
+ return ufetch->pending_requests > 0;
+}
+
+bool imap_urlauth_fetch_continue(struct imap_urlauth_fetch *ufetch)
+{
+ bool pending;
+
+ imap_urlauth_fetch_ref(ufetch);
+ pending = imap_urlauth_fetch_do_continue(ufetch);
+ imap_urlauth_fetch_unref(&ufetch);
+
+ return pending;
+}
+
+bool imap_urlauth_fetch_is_pending(struct imap_urlauth_fetch *ufetch)
+{
+ return ufetch->pending_requests > 0;
+}
diff --git a/src/lib-imap-urlauth/imap-urlauth-fetch.h b/src/lib-imap-urlauth/imap-urlauth-fetch.h
new file mode 100644
index 0000000..628a95e
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth-fetch.h
@@ -0,0 +1,56 @@
+#ifndef IMAP_URLAUTH_FETCH_H
+#define IMAP_URLAUTH_FETCH_H
+
+struct imap_url;
+struct imap_urlauth_context;
+struct imap_urlauth_fetch;
+
+enum imap_urlauth_fetch_flags {
+ /* Indicates that this is an extended request */
+ IMAP_URLAUTH_FETCH_FLAG_EXTENDED = 0x01,
+ /* Fetch body part unmodified */
+ IMAP_URLAUTH_FETCH_FLAG_BODY = 0x02,
+ /* Fetch body part as binary, i.e. without content encoding */
+ IMAP_URLAUTH_FETCH_FLAG_BINARY = 0x04,
+ /* Fetch IMAP bodypartstructure */
+ IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE = 0x08,
+};
+
+struct imap_urlauth_fetch_reply {
+ const char *url;
+ enum imap_urlauth_fetch_flags flags;
+
+ struct istream *input;
+ uoff_t size;
+
+ const char *bodypartstruct;
+ const char *error;
+
+ bool succeeded:1;
+ bool binary_has_nuls:1;
+};
+
+/* Callback to handle fetch reply. Returns 1 if handled completely and ready
+ for next reply, 0 if not all data was processed, and -1 for error. If a
+ callback returns 0, imap_urlauth_fetch_continue() must be called once
+ new replies may be processed. If this is the last request to yield a reply,
+ argument last is TRUE. */
+typedef int
+imap_urlauth_fetch_callback_t(struct imap_urlauth_fetch_reply *reply,
+ bool last, void *context);
+
+struct imap_urlauth_fetch *
+imap_urlauth_fetch_init(struct imap_urlauth_context *uctx,
+ imap_urlauth_fetch_callback_t *callback, void *context);
+void imap_urlauth_fetch_deinit(struct imap_urlauth_fetch **ufetch);
+
+int imap_urlauth_fetch_url(struct imap_urlauth_fetch *ufetch, const char *url,
+ enum imap_urlauth_fetch_flags url_flags);
+int imap_urlauth_fetch_url_parsed(struct imap_urlauth_fetch *ufetch,
+ const char *url, struct imap_url *imap_url,
+ enum imap_urlauth_fetch_flags url_flags);
+
+bool imap_urlauth_fetch_continue(struct imap_urlauth_fetch *ufetch);
+bool imap_urlauth_fetch_is_pending(struct imap_urlauth_fetch *ufetch);
+
+#endif
diff --git a/src/lib-imap-urlauth/imap-urlauth-private.h b/src/lib-imap-urlauth/imap-urlauth-private.h
new file mode 100644
index 0000000..3b3622c
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth-private.h
@@ -0,0 +1,20 @@
+#ifndef IMAP_URLAUTH_PRIVATE_H
+#define IMAP_URLAUTH_PRIVATE_H
+
+#include "imap-urlauth.h"
+
+struct imap_urlauth_context {
+ struct mail_user *user;
+ struct imap_urlauth_connection *conn;
+
+ char *url_host;
+ in_port_t url_port;
+
+ char *access_user;
+ char *access_service;
+ const char **access_applications;
+
+ bool access_anonymous:1;
+};
+
+#endif
diff --git a/src/lib-imap-urlauth/imap-urlauth.c b/src/lib-imap-urlauth/imap-urlauth.c
new file mode 100644
index 0000000..a9c2673
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth.c
@@ -0,0 +1,486 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "var-expand.h"
+#include "hmac.h"
+#include "sha1.h"
+#include "randgen.h"
+#include "safe-memset.h"
+#include "mail-storage.h"
+#include "mail-storage-service.h"
+#include "mail-namespace.h"
+#include "mail-user.h"
+#include "imap-url.h"
+#include "imap-msgpart-url.h"
+#include "imap-urlauth-backend.h"
+#include "imap-urlauth-fetch.h"
+#include "imap-urlauth-connection.h"
+
+#include "imap-urlauth-private.h"
+
+#include <time.h>
+
+#define IMAP_URLAUTH_MECH_INTERNAL_VERSION 0x01
+
+#define IMAP_URLAUTH_NORMAL_TIMEOUT_MSECS 5*1000
+#define IMAP_URLAUTH_SPECIAL_TIMEOUT_MSECS 3*60*1000
+
+#define URL_HOST_ALLOW_ANY "*"
+
+struct imap_urlauth_context *
+imap_urlauth_init(struct mail_user *user,
+ const struct imap_urlauth_config *config)
+{
+ struct imap_urlauth_context *uctx;
+ unsigned int timeout;
+
+ i_assert(*config->url_host != '\0');
+
+ uctx = i_new(struct imap_urlauth_context, 1);
+ uctx->user = user;
+ uctx->url_host = i_strdup(config->url_host);
+ uctx->url_port = config->url_port;
+
+ if (config->access_anonymous)
+ uctx->access_user = i_strdup("anonymous");
+ else
+ uctx->access_user = i_strdup(config->access_user);
+ uctx->access_service = i_strdup(config->access_service);
+ uctx->access_anonymous = config->access_anonymous;
+ if (config->access_applications != NULL &&
+ *config->access_applications != NULL) {
+ uctx->access_applications =
+ p_strarray_dup(default_pool,
+ config->access_applications);
+ timeout = IMAP_URLAUTH_SPECIAL_TIMEOUT_MSECS;
+ } else {
+ timeout = IMAP_URLAUTH_NORMAL_TIMEOUT_MSECS;
+ }
+
+ if (config->socket_path != NULL) {
+ uctx->conn = imap_urlauth_connection_init(config->socket_path,
+ config->access_service, user, config->session_id, timeout);
+ }
+ return uctx;
+}
+
+void imap_urlauth_deinit(struct imap_urlauth_context **_uctx)
+{
+ struct imap_urlauth_context *uctx = *_uctx;
+
+ *_uctx = NULL;
+
+ if (uctx->conn != NULL)
+ imap_urlauth_connection_deinit(&uctx->conn);
+ i_free(uctx->url_host);
+ i_free(uctx->access_user);
+ i_free(uctx->access_service);
+ i_free(uctx->access_applications);
+ i_free(uctx);
+}
+
+static const unsigned char *
+imap_urlauth_internal_generate(const char *rumpurl,
+ const unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN],
+ size_t *token_len_r)
+{
+ struct hmac_context hmac;
+ unsigned char *token;
+
+ token = t_new(unsigned char, SHA1_RESULTLEN + 1);
+ token[0] = IMAP_URLAUTH_MECH_INTERNAL_VERSION;
+
+ hmac_init(&hmac, mailbox_key, IMAP_URLAUTH_KEY_LEN, &hash_method_sha1);
+ hmac_update(&hmac, rumpurl, strlen(rumpurl));
+ hmac_final(&hmac, token+1);
+
+ *token_len_r = SHA1_RESULTLEN + 1;
+ return token;
+}
+
+static bool
+imap_urlauth_internal_verify(const char *rumpurl,
+ const unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN],
+ const unsigned char *token, size_t token_len)
+{
+ const unsigned char *valtoken;
+ size_t valtoken_len;
+
+ if (rumpurl == NULL || token == NULL)
+ return FALSE;
+
+ valtoken = imap_urlauth_internal_generate(rumpurl, mailbox_key,
+ &valtoken_len);
+ /* Note: the token length has timing leak here in any case */
+ if (token_len != valtoken_len)
+ return FALSE;
+
+ return mem_equals_timing_safe(token, valtoken, valtoken_len);
+}
+
+static bool
+access_applications_have_access(struct imap_urlauth_context *uctx,
+ struct imap_url *url, const char *const *access_applications)
+{
+ const char *const *application;
+
+ if (access_applications == NULL)
+ return FALSE;
+
+ application = access_applications;
+ for (; *application != NULL; application++) {
+ const char *app = *application;
+ bool have_userid = FALSE;
+ size_t len = strlen(app);
+
+ if (app[len-1] == '+')
+ have_userid = TRUE;
+
+ if (strncasecmp(url->uauth_access_application, app, len-1) == 0) {
+ if (!have_userid) {
+ /* this access application must have no userid */
+ return url->uauth_access_user == NULL;
+ }
+
+ /* this access application must have a userid */
+ return (!uctx->access_anonymous && url->uauth_access_user != NULL);
+ }
+ }
+ return FALSE;
+}
+
+static bool
+imap_urlauth_check_access(struct imap_urlauth_context *uctx,
+ struct imap_url *url, bool ignore_unknown,
+ const char **error_r)
+{
+ const char *userid;
+
+ if (url->uauth_access_application == NULL) {
+ *error_r = "URL is missing URLAUTH";
+ return FALSE;
+ }
+
+ if (strcmp(uctx->access_service, "imap") == 0) {
+ /* these access types are only allowed if URL is accessed through imap */
+ if (strcasecmp(url->uauth_access_application, "user") == 0) {
+ /* user+<access_user> */
+ if (url->uauth_access_user == NULL) {
+ *error_r = "URLAUTH `user' access is missing userid";
+ return FALSE;
+ }
+ if (!uctx->access_anonymous ||
+ strcasecmp(url->uauth_access_user, uctx->access_user) == 0)
+ return TRUE;
+ } else if (strcasecmp(url->uauth_access_application, "authuser") == 0) {
+ /* authuser */
+ if (!uctx->access_anonymous)
+ return TRUE;
+ } else if (strcasecmp(url->uauth_access_application, "anonymous") == 0) {
+ /* anonymous */
+ return TRUE;
+ } else if (ignore_unknown || access_applications_have_access
+ (uctx, url, uctx->access_applications)) {
+ return TRUE;
+ }
+ } else if (strcmp(uctx->access_service, "submission") == 0) {
+ /* accessed directly through submission service */
+
+ if (strcasecmp(url->uauth_access_application, "submit") != 0) {
+ userid = url->uauth_access_user == NULL ? "" :
+ t_strdup_printf("+%s", url->uauth_access_user);
+ *error_r = t_strdup_printf(
+ "No '%s%s' access allowed for submission service",
+ url->uauth_access_application, userid);
+ return FALSE;
+ } else if (url->uauth_access_user == NULL) {
+ *error_r = "URLAUTH `submit' access is missing userid";
+ return FALSE;
+ } else if (!uctx->access_anonymous &&
+ strcasecmp(url->uauth_access_user, uctx->access_user) == 0) {
+ return TRUE;
+ }
+ }
+
+ userid = url->uauth_access_user == NULL ? "" :
+ t_strdup_printf("+%s", url->uauth_access_user);
+
+ if (uctx->access_anonymous) {
+ *error_r = t_strdup_printf(
+ "No '%s%s' access allowed for anonymous user",
+ url->uauth_access_application, userid);
+ } else {
+ *error_r = t_strdup_printf(
+ "No '%s%s' access allowed for user %s",
+ url->uauth_access_application, userid, uctx->access_user);
+ }
+ return FALSE;
+}
+
+static bool
+imap_urlauth_check_hostport(struct imap_urlauth_context *uctx,
+ struct imap_url *url, const char **client_error_r)
+{
+ /* validate host */
+ /* FIXME: allow host ip/ip6 as well? */
+ if (strcmp(uctx->url_host, URL_HOST_ALLOW_ANY) != 0 &&
+ strcmp(url->host.name, uctx->url_host) != 0) {
+ *client_error_r = "Invalid URL: Inappropriate host name";
+ return FALSE;
+ }
+
+ /* validate port */
+ if ((url->port == 0 && uctx->url_port != 143) ||
+ (url->port != 0 && uctx->url_port != url->port)) {
+ *client_error_r = "Invalid URL: Inappropriate server port";
+ return FALSE;
+ }
+ return TRUE;
+}
+
+int imap_urlauth_generate(struct imap_urlauth_context *uctx,
+ const char *mechanism, const char *rumpurl,
+ const char **urlauth_r, const char **client_error_r)
+{
+ struct mail_user *user = uctx->user;
+ enum imap_url_parse_flags url_flags =
+ IMAP_URL_PARSE_ALLOW_URLAUTH;
+ struct imap_url *url;
+ struct imap_msgpart_url *mpurl = NULL;
+ struct mailbox *box;
+ const char *error;
+ enum mail_error error_code;
+ unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN];
+ const unsigned char *token;
+ size_t token_len;
+ int ret;
+
+ /* validate mechanism */
+ if (strcasecmp(mechanism, "INTERNAL") != 0) {
+ *client_error_r = t_strdup_printf("Unsupported URLAUTH mechanism: %s", mechanism);
+ return 0;
+ }
+
+ /* validate URL */
+ if (imap_url_parse(rumpurl, NULL, url_flags, &url, &error) < 0) {
+ *client_error_r = t_strdup_printf("Invalid URL: %s", error);
+ return 0;
+ }
+
+ if (url->mailbox == NULL || url->uid == 0 || url->search_program != NULL ||
+ url->uauth_rumpurl == NULL || url->uauth_mechanism != NULL) {
+ *client_error_r = "Invalid URL: Must be an URLAUTH rump URL";
+ return 0;
+ }
+
+ /* validate expiry time */
+ if (url->uauth_expire != (time_t)-1) {
+ time_t now = time(NULL);
+
+ if (now > url->uauth_expire) {
+ *client_error_r = t_strdup_printf("URLAUTH has already expired");
+ return 0;
+ }
+ }
+
+ /* validate user */
+ if (url->userid == NULL) {
+ *client_error_r = "Invalid URL: Missing user name";
+ return 0;
+ }
+ if (user->anonymous || strcmp(url->userid, user->username) != 0) {
+ *client_error_r = t_strdup_printf(
+ "Not permitted to generate URLAUTH for user %s",
+ url->userid);
+ return 0;
+ }
+
+ /* validate host:port */
+ if (!imap_urlauth_check_hostport(uctx, url, client_error_r))
+ return 0;
+
+ /* validate mailbox */
+ if ((ret = imap_msgpart_url_create(user, url, &mpurl, &error)) < 0 ||
+ imap_msgpart_url_verify(mpurl, &error) <= 0) {
+ *client_error_r = t_strdup_printf("Invalid URL: %s", error);
+ if (mpurl != NULL)
+ imap_msgpart_url_free(&mpurl);
+ return ret;
+ }
+ box = imap_msgpart_url_get_mailbox(mpurl);
+
+ /* obtain mailbox key */
+ ret = imap_urlauth_backend_get_mailbox_key(box, TRUE, mailbox_key,
+ client_error_r, &error_code);
+ if (ret < 0) {
+ imap_msgpart_url_free(&mpurl);
+ return ret;
+ }
+
+ token = imap_urlauth_internal_generate(rumpurl, mailbox_key, &token_len);
+ imap_msgpart_url_free(&mpurl);
+
+ *urlauth_r = imap_url_add_urlauth(rumpurl, mechanism, token, token_len);
+ return 1;
+}
+
+bool imap_urlauth_check(struct imap_urlauth_context *uctx,
+ struct imap_url *url, bool ignore_unknown_access,
+ const char **error_r)
+{
+ /* validate URL fields */
+ if (url->mailbox == NULL || url->uid == 0 ||
+ url->search_program != NULL || url->uauth_rumpurl == NULL ||
+ url->uauth_mechanism == NULL) {
+ *error_r = "Invalid URL: Must be a full URLAUTH URL";
+ return FALSE;
+ }
+
+ /* check presence of userid */
+ if (url->userid == NULL) {
+ *error_r = "Invalid URLAUTH: Missing user name";
+ return FALSE;
+ }
+
+ /* validate mechanism */
+ if (strcasecmp(url->uauth_mechanism, "INTERNAL") != 0) {
+ *error_r = t_strdup_printf("Unsupported URLAUTH mechanism: %s",
+ url->uauth_mechanism);
+ return FALSE;
+ }
+
+ /* validate expiry time */
+ if (url->uauth_expire != (time_t)-1) {
+ time_t now = time(NULL);
+
+ if (now > url->uauth_expire) {
+ *error_r = t_strdup_printf("URLAUTH has expired");
+ return FALSE;
+ }
+ }
+
+ /* validate access */
+ if (!imap_urlauth_check_access(uctx, url, ignore_unknown_access,
+ error_r))
+ return FALSE;
+ /* validate host:port */
+ if (!imap_urlauth_check_hostport(uctx, url, error_r))
+ return FALSE;
+ return TRUE;
+}
+
+int imap_urlauth_fetch_parsed(struct imap_urlauth_context *uctx,
+ struct imap_url *url,
+ struct imap_msgpart_url **mpurl_r,
+ enum mail_error *error_code_r,
+ const char **error_r)
+{
+ struct mail_user *user = uctx->user;
+ struct imap_msgpart_url *mpurl;
+ struct mailbox *box;
+ const char *error;
+ unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN];
+ int ret;
+
+ *mpurl_r = NULL;
+ *error_r = NULL;
+ *error_code_r = MAIL_ERROR_NONE;
+
+ /* check urlauth mechanism, access, userid and authority */
+ if (!imap_urlauth_check(uctx, url, FALSE, error_r)) {
+ *error_code_r = MAIL_ERROR_PARAMS;
+ return 0;
+ }
+
+ /* validate target user */
+ if (user->anonymous || strcmp(url->userid, user->username) != 0) {
+ *error_r = t_strdup_printf("Not permitted to fetch URLAUTH for user %s",
+ url->userid);
+ *error_code_r = MAIL_ERROR_PARAMS;
+ return 0;
+ }
+
+ /* validate mailbox */
+ if ((ret = imap_msgpart_url_create(user, url, &mpurl, &error)) < 0) {
+ *error_r = t_strdup_printf("Invalid URLAUTH: %s", error);
+ *error_code_r = MAIL_ERROR_PARAMS;
+ return ret;
+ }
+
+ if ((ret = imap_msgpart_url_open_mailbox(mpurl, &box, error_code_r,
+ &error)) < 0) {
+ *error_r = "Internal server error";
+ imap_msgpart_url_free(&mpurl);
+ return -1;
+ }
+
+ if (ret == 0) {
+ /* RFC says: `If the mailbox cannot be identified, an
+ authorization token is calculated on the rump URL, using
+ random "plausible" keys (selected by the server) as needed,
+ before returning a validation failure. This prevents timing
+ attacks aimed at identifying mailbox names.' */
+ random_fill(mailbox_key, sizeof(mailbox_key));
+ (void)imap_urlauth_internal_verify(url->uauth_rumpurl,
+ mailbox_key, url->uauth_token, url->uauth_token_size);
+
+ *error_r = t_strdup_printf("Invalid URLAUTH: %s", error);
+ imap_msgpart_url_free(&mpurl);
+ return 0;
+ }
+
+ /* obtain mailbox key */
+ ret = imap_urlauth_backend_get_mailbox_key(box, FALSE, mailbox_key,
+ error_r, error_code_r);
+ if (ret < 0) {
+ imap_msgpart_url_free(&mpurl);
+ return -1;
+ }
+
+ if (ret == 0 ||
+ !imap_urlauth_internal_verify(url->uauth_rumpurl, mailbox_key,
+ url->uauth_token,
+ url->uauth_token_size)) {
+ *error_r = "URLAUTH verification failed";
+ *error_code_r = MAIL_ERROR_PERM;
+ imap_msgpart_url_free(&mpurl);
+ ret = 0;
+ } else {
+ ret = 1;
+ }
+
+ safe_memset(mailbox_key, 0, sizeof(mailbox_key));
+ *mpurl_r = mpurl;
+ return ret;
+}
+
+int imap_urlauth_fetch(struct imap_urlauth_context *uctx,
+ const char *urlauth, struct imap_msgpart_url **mpurl_r,
+ enum mail_error *error_code_r, const char **error_r)
+{
+ struct imap_url *url;
+ enum imap_url_parse_flags url_flags = IMAP_URL_PARSE_ALLOW_URLAUTH;
+ const char *error;
+
+ /* validate URL */
+ if (imap_url_parse(urlauth, NULL, url_flags, &url, &error) < 0) {
+ *error_r = t_strdup_printf("Invalid URLAUTH: %s", error);
+ *error_code_r = MAIL_ERROR_PARAMS;
+ return 0;
+ }
+
+ return imap_urlauth_fetch_parsed(uctx, url, mpurl_r,
+ error_code_r, error_r);
+}
+
+int imap_urlauth_reset_mailbox_key(struct imap_urlauth_context *uctx ATTR_UNUSED,
+ struct mailbox *box)
+{
+ return imap_urlauth_backend_reset_mailbox_key(box);
+}
+
+int imap_urlauth_reset_all_keys(struct imap_urlauth_context *uctx)
+{
+ return imap_urlauth_backend_reset_all_keys(uctx->user);
+}
diff --git a/src/lib-imap-urlauth/imap-urlauth.h b/src/lib-imap-urlauth/imap-urlauth.h
new file mode 100644
index 0000000..9c7c30f
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth.h
@@ -0,0 +1,55 @@
+#ifndef IMAP_URLAUTH_H
+#define IMAP_URLAUTH_H
+
+#include "net.h"
+
+#define IMAP_URLAUTH_SOCKET_NAME "imap-urlauth"
+
+struct imap_url;
+struct imap_msgpart_url;
+struct imap_urlauth_context;
+
+struct imap_urlauth_config {
+ const char *url_host;
+ in_port_t url_port;
+
+ const char *socket_path;
+ const char *session_id;
+
+ /* the user who is requesting access to URLAUTHs */
+ const char *access_user;
+ /* ... is using this service (i.e. imap or submission) */
+ const char *access_service;
+ /* ... represents these applications */
+ const char *const *access_applications;
+ /* ... is anonymous? */
+ bool access_anonymous;
+};
+
+struct imap_urlauth_context *
+imap_urlauth_init(struct mail_user *user,
+ const struct imap_urlauth_config *config);
+void imap_urlauth_deinit(struct imap_urlauth_context **_uctx);
+
+int imap_urlauth_generate(struct imap_urlauth_context *uctx,
+ const char *mechanism, const char *rumpurl,
+ const char **urlauth_r, const char **client_error_r);
+
+bool imap_urlauth_check(struct imap_urlauth_context *uctx,
+ struct imap_url *url, bool ignore_unknown_access,
+ const char **error_r);
+
+int imap_urlauth_fetch_parsed(struct imap_urlauth_context *uctx,
+ struct imap_url *url,
+ struct imap_msgpart_url **mpurl_r,
+ enum mail_error *error_code_r,
+ const char **error_r);
+int imap_urlauth_fetch(struct imap_urlauth_context *uctx,
+ const char *urlauth, struct imap_msgpart_url **mpurl_r,
+ enum mail_error *error_code_r, const char **error_r);
+
+int imap_urlauth_reset_mailbox_key(struct imap_urlauth_context *uctx,
+ struct mailbox *box);
+int imap_urlauth_reset_all_keys(struct imap_urlauth_context *uctx);
+
+#endif
diff --git a/src/lib-imap/Makefile.am b/src/lib-imap/Makefile.am
new file mode 100644
index 0000000..2f59f93
--- /dev/null
+++ b/src/lib-imap/Makefile.am
@@ -0,0 +1,120 @@
+noinst_LTLIBRARIES = libimap.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-charset \
+ -I$(top_srcdir)/src/lib-mail
+
+libimap_la_SOURCES = \
+ imap-arg.c \
+ imap-base-subject.c \
+ imap-bodystructure.c \
+ imap-date.c \
+ imap-envelope.c \
+ imap-id.c \
+ imap-keepalive.c \
+ imap-match.c \
+ imap-parser.c \
+ imap-quote.c \
+ imap-url.c \
+ imap-seqset.c \
+ imap-utf7.c \
+ imap-util.c
+
+headers = \
+ imap-arg.h \
+ imap-base-subject.h \
+ imap-bodystructure.h \
+ imap-date.h \
+ imap-envelope.h \
+ imap-id.h \
+ imap-keepalive.h \
+ imap-match.h \
+ imap-parser.h \
+ imap-resp-code.h \
+ imap-quote.h \
+ imap-url.h \
+ imap-seqset.h \
+ imap-utf7.h \
+ imap-util.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+test_programs = \
+ test-imap-bodystructure \
+ test-imap-envelope \
+ test-imap-match \
+ test-imap-parser \
+ test-imap-quote \
+ test-imap-url \
+ test-imap-utf7 \
+ test-imap-util
+
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+ ../lib-test/libtest.la \
+ ../lib/liblib.la
+
+test_deps = $(noinst_LTLIBRARIES) $(test_libs)
+
+test_imap_bodystructure_SOURCES = test-imap-bodystructure.c
+test_imap_bodystructure_LDADD = imap-bodystructure.lo imap-envelope.lo imap-quote.lo imap-parser.lo imap-arg.lo ../lib-mail/libmail.la $(test_libs)
+test_imap_bodystructure_DEPENDENCIES = $(test_deps) ../lib-mail/libmail.la
+
+test_imap_envelope_SOURCES = test-imap-envelope.c
+test_imap_envelope_LDADD = imap-envelope.lo imap-quote.lo imap-parser.lo imap-arg.lo ../lib-mail/libmail.la $(test_libs)
+test_imap_envelope_DEPENDENCIES = $(test_deps) ../lib-mail/libmail.la
+
+test_imap_match_SOURCES = test-imap-match.c
+test_imap_match_LDADD = imap-match.lo $(test_libs)
+test_imap_match_DEPENDENCIES = $(test_deps)
+
+test_imap_parser_SOURCES = test-imap-parser.c
+test_imap_parser_LDADD = imap-parser.lo imap-arg.lo $(test_libs)
+test_imap_parser_DEPENDENCIES = $(test_deps)
+
+test_imap_quote_SOURCES = test-imap-quote.c
+test_imap_quote_LDADD = imap-quote.lo $(test_libs)
+test_imap_quote_DEPENDENCIES = $(test_deps)
+
+test_imap_url_SOURCES = test-imap-url.c
+test_imap_url_LDADD = imap-url.lo $(test_libs)
+test_imap_url_DEPENDENCIES = $(test_deps)
+
+test_imap_utf7_SOURCES = test-imap-utf7.c
+test_imap_utf7_LDADD = imap-utf7.lo $(test_libs)
+test_imap_utf7_DEPENDENCIES = $(test_deps)
+
+test_imap_util_SOURCES = test-imap-util.c
+test_imap_util_LDADD = imap-util.lo imap-arg.lo $(test_libs)
+test_imap_util_DEPENDENCIES = $(test_deps)
+
+check-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+if USE_FUZZER
+noinst_PROGRAMS += \
+ fuzz-imap-utf7 \
+ fuzz-imap-bodystructure
+
+nodist_EXTRA_fuzz_imap_utf7_SOURCES = force-cxx-linking.cxx
+fuzz_imap_utf7_SOURCES = fuzz-imap-utf7.c
+fuzz_imap_utf7_CPPFLAGS = $(FUZZER_CPPFLAGS)
+fuzz_imap_utf7_LDFLAGS = $(FUZZER_LDFLAGS)
+fuzz_imap_utf7_LDADD = libimap.la $(test_libs)
+fuzz_imap_utf7_DEPENDENCIES = libimap.la $(test_deps)
+
+nodist_EXTRA_fuzz_imap_bodystructure_SOURCES = force-cxx-linking.cxx
+fuzz_imap_bodystructure_SOURCES = fuzz-imap-bodystructure.c
+fuzz_imap_bodystructure_CPPFLAGS = $(FUZZER_CPPFLAGS)
+fuzz_imap_bodystructure_LDFLAGS = $(FUZZER_LDFLAGS)
+fuzz_imap_bodystructure_LDADD = libimap.la $(test_libs)
+fuzz_imap_bodystructure_DEPENDENCIES = libimap.la $(test_deps)
+
+
+endif
diff --git a/src/lib-imap/Makefile.in b/src/lib-imap/Makefile.in
new file mode 100644
index 0000000..5fd6ea4
--- /dev/null
+++ b/src/lib-imap/Makefile.in
@@ -0,0 +1,1196 @@
+# 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) $(am__EXEEXT_2)
+@USE_FUZZER_TRUE@am__append_1 = \
+@USE_FUZZER_TRUE@ fuzz-imap-utf7 \
+@USE_FUZZER_TRUE@ fuzz-imap-bodystructure
+
+subdir = src/lib-imap
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-imap-bodystructure$(EXEEXT) \
+ test-imap-envelope$(EXEEXT) test-imap-match$(EXEEXT) \
+ test-imap-parser$(EXEEXT) test-imap-quote$(EXEEXT) \
+ test-imap-url$(EXEEXT) test-imap-utf7$(EXEEXT) \
+ test-imap-util$(EXEEXT)
+@USE_FUZZER_TRUE@am__EXEEXT_2 = fuzz-imap-utf7$(EXEEXT) \
+@USE_FUZZER_TRUE@ fuzz-imap-bodystructure$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libimap_la_LIBADD =
+am_libimap_la_OBJECTS = imap-arg.lo imap-base-subject.lo \
+ imap-bodystructure.lo imap-date.lo imap-envelope.lo imap-id.lo \
+ imap-keepalive.lo imap-match.lo imap-parser.lo imap-quote.lo \
+ imap-url.lo imap-seqset.lo imap-utf7.lo imap-util.lo
+libimap_la_OBJECTS = $(am_libimap_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__fuzz_imap_bodystructure_SOURCES_DIST = fuzz-imap-bodystructure.c
+@USE_FUZZER_TRUE@am_fuzz_imap_bodystructure_OBJECTS = fuzz_imap_bodystructure-fuzz-imap-bodystructure.$(OBJEXT)
+fuzz_imap_bodystructure_OBJECTS = \
+ $(am_fuzz_imap_bodystructure_OBJECTS)
+fuzz_imap_bodystructure_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(fuzz_imap_bodystructure_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am__fuzz_imap_utf7_SOURCES_DIST = fuzz-imap-utf7.c
+@USE_FUZZER_TRUE@am_fuzz_imap_utf7_OBJECTS = \
+@USE_FUZZER_TRUE@ fuzz_imap_utf7-fuzz-imap-utf7.$(OBJEXT)
+fuzz_imap_utf7_OBJECTS = $(am_fuzz_imap_utf7_OBJECTS)
+fuzz_imap_utf7_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(fuzz_imap_utf7_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am_test_imap_bodystructure_OBJECTS = \
+ test-imap-bodystructure.$(OBJEXT)
+test_imap_bodystructure_OBJECTS = \
+ $(am_test_imap_bodystructure_OBJECTS)
+am_test_imap_envelope_OBJECTS = test-imap-envelope.$(OBJEXT)
+test_imap_envelope_OBJECTS = $(am_test_imap_envelope_OBJECTS)
+am_test_imap_match_OBJECTS = test-imap-match.$(OBJEXT)
+test_imap_match_OBJECTS = $(am_test_imap_match_OBJECTS)
+am_test_imap_parser_OBJECTS = test-imap-parser.$(OBJEXT)
+test_imap_parser_OBJECTS = $(am_test_imap_parser_OBJECTS)
+am_test_imap_quote_OBJECTS = test-imap-quote.$(OBJEXT)
+test_imap_quote_OBJECTS = $(am_test_imap_quote_OBJECTS)
+am_test_imap_url_OBJECTS = test-imap-url.$(OBJEXT)
+test_imap_url_OBJECTS = $(am_test_imap_url_OBJECTS)
+am_test_imap_utf7_OBJECTS = test-imap-utf7.$(OBJEXT)
+test_imap_utf7_OBJECTS = $(am_test_imap_utf7_OBJECTS)
+am_test_imap_util_OBJECTS = test-imap-util.$(OBJEXT)
+test_imap_util_OBJECTS = $(am_test_imap_util_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)/fuzz_imap_bodystructure-force-cxx-linking.Po \
+ ./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po \
+ ./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po \
+ ./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po \
+ ./$(DEPDIR)/imap-arg.Plo ./$(DEPDIR)/imap-base-subject.Plo \
+ ./$(DEPDIR)/imap-bodystructure.Plo ./$(DEPDIR)/imap-date.Plo \
+ ./$(DEPDIR)/imap-envelope.Plo ./$(DEPDIR)/imap-id.Plo \
+ ./$(DEPDIR)/imap-keepalive.Plo ./$(DEPDIR)/imap-match.Plo \
+ ./$(DEPDIR)/imap-parser.Plo ./$(DEPDIR)/imap-quote.Plo \
+ ./$(DEPDIR)/imap-seqset.Plo ./$(DEPDIR)/imap-url.Plo \
+ ./$(DEPDIR)/imap-utf7.Plo ./$(DEPDIR)/imap-util.Plo \
+ ./$(DEPDIR)/test-imap-bodystructure.Po \
+ ./$(DEPDIR)/test-imap-envelope.Po \
+ ./$(DEPDIR)/test-imap-match.Po ./$(DEPDIR)/test-imap-parser.Po \
+ ./$(DEPDIR)/test-imap-quote.Po ./$(DEPDIR)/test-imap-url.Po \
+ ./$(DEPDIR)/test-imap-utf7.Po ./$(DEPDIR)/test-imap-util.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 =
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+SOURCES = $(libimap_la_SOURCES) $(fuzz_imap_bodystructure_SOURCES) \
+ $(nodist_EXTRA_fuzz_imap_bodystructure_SOURCES) \
+ $(fuzz_imap_utf7_SOURCES) \
+ $(nodist_EXTRA_fuzz_imap_utf7_SOURCES) \
+ $(test_imap_bodystructure_SOURCES) \
+ $(test_imap_envelope_SOURCES) $(test_imap_match_SOURCES) \
+ $(test_imap_parser_SOURCES) $(test_imap_quote_SOURCES) \
+ $(test_imap_url_SOURCES) $(test_imap_utf7_SOURCES) \
+ $(test_imap_util_SOURCES)
+DIST_SOURCES = $(libimap_la_SOURCES) \
+ $(am__fuzz_imap_bodystructure_SOURCES_DIST) \
+ $(am__fuzz_imap_utf7_SOURCES_DIST) \
+ $(test_imap_bodystructure_SOURCES) \
+ $(test_imap_envelope_SOURCES) $(test_imap_match_SOURCES) \
+ $(test_imap_parser_SOURCES) $(test_imap_quote_SOURCES) \
+ $(test_imap_url_SOURCES) $(test_imap_utf7_SOURCES) \
+ $(test_imap_util_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@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libimap.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-test \
+ -I$(top_srcdir)/src/lib-charset \
+ -I$(top_srcdir)/src/lib-mail
+
+libimap_la_SOURCES = \
+ imap-arg.c \
+ imap-base-subject.c \
+ imap-bodystructure.c \
+ imap-date.c \
+ imap-envelope.c \
+ imap-id.c \
+ imap-keepalive.c \
+ imap-match.c \
+ imap-parser.c \
+ imap-quote.c \
+ imap-url.c \
+ imap-seqset.c \
+ imap-utf7.c \
+ imap-util.c
+
+headers = \
+ imap-arg.h \
+ imap-base-subject.h \
+ imap-bodystructure.h \
+ imap-date.h \
+ imap-envelope.h \
+ imap-id.h \
+ imap-keepalive.h \
+ imap-match.h \
+ imap-parser.h \
+ imap-resp-code.h \
+ imap-quote.h \
+ imap-url.h \
+ imap-seqset.h \
+ imap-utf7.h \
+ imap-util.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+test_programs = \
+ test-imap-bodystructure \
+ test-imap-envelope \
+ test-imap-match \
+ test-imap-parser \
+ test-imap-quote \
+ test-imap-url \
+ test-imap-utf7 \
+ test-imap-util
+
+test_libs = \
+ ../lib-test/libtest.la \
+ ../lib/liblib.la
+
+test_deps = $(noinst_LTLIBRARIES) $(test_libs)
+test_imap_bodystructure_SOURCES = test-imap-bodystructure.c
+test_imap_bodystructure_LDADD = imap-bodystructure.lo imap-envelope.lo imap-quote.lo imap-parser.lo imap-arg.lo ../lib-mail/libmail.la $(test_libs)
+test_imap_bodystructure_DEPENDENCIES = $(test_deps) ../lib-mail/libmail.la
+test_imap_envelope_SOURCES = test-imap-envelope.c
+test_imap_envelope_LDADD = imap-envelope.lo imap-quote.lo imap-parser.lo imap-arg.lo ../lib-mail/libmail.la $(test_libs)
+test_imap_envelope_DEPENDENCIES = $(test_deps) ../lib-mail/libmail.la
+test_imap_match_SOURCES = test-imap-match.c
+test_imap_match_LDADD = imap-match.lo $(test_libs)
+test_imap_match_DEPENDENCIES = $(test_deps)
+test_imap_parser_SOURCES = test-imap-parser.c
+test_imap_parser_LDADD = imap-parser.lo imap-arg.lo $(test_libs)
+test_imap_parser_DEPENDENCIES = $(test_deps)
+test_imap_quote_SOURCES = test-imap-quote.c
+test_imap_quote_LDADD = imap-quote.lo $(test_libs)
+test_imap_quote_DEPENDENCIES = $(test_deps)
+test_imap_url_SOURCES = test-imap-url.c
+test_imap_url_LDADD = imap-url.lo $(test_libs)
+test_imap_url_DEPENDENCIES = $(test_deps)
+test_imap_utf7_SOURCES = test-imap-utf7.c
+test_imap_utf7_LDADD = imap-utf7.lo $(test_libs)
+test_imap_utf7_DEPENDENCIES = $(test_deps)
+test_imap_util_SOURCES = test-imap-util.c
+test_imap_util_LDADD = imap-util.lo imap-arg.lo $(test_libs)
+test_imap_util_DEPENDENCIES = $(test_deps)
+@USE_FUZZER_TRUE@nodist_EXTRA_fuzz_imap_utf7_SOURCES = force-cxx-linking.cxx
+@USE_FUZZER_TRUE@fuzz_imap_utf7_SOURCES = fuzz-imap-utf7.c
+@USE_FUZZER_TRUE@fuzz_imap_utf7_CPPFLAGS = $(FUZZER_CPPFLAGS)
+@USE_FUZZER_TRUE@fuzz_imap_utf7_LDFLAGS = $(FUZZER_LDFLAGS)
+@USE_FUZZER_TRUE@fuzz_imap_utf7_LDADD = libimap.la $(test_libs)
+@USE_FUZZER_TRUE@fuzz_imap_utf7_DEPENDENCIES = libimap.la $(test_deps)
+@USE_FUZZER_TRUE@nodist_EXTRA_fuzz_imap_bodystructure_SOURCES = force-cxx-linking.cxx
+@USE_FUZZER_TRUE@fuzz_imap_bodystructure_SOURCES = fuzz-imap-bodystructure.c
+@USE_FUZZER_TRUE@fuzz_imap_bodystructure_CPPFLAGS = $(FUZZER_CPPFLAGS)
+@USE_FUZZER_TRUE@fuzz_imap_bodystructure_LDFLAGS = $(FUZZER_LDFLAGS)
+@USE_FUZZER_TRUE@fuzz_imap_bodystructure_LDADD = libimap.la $(test_libs)
+@USE_FUZZER_TRUE@fuzz_imap_bodystructure_DEPENDENCIES = libimap.la $(test_deps)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .cxx .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-imap/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-imap/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}; \
+ }
+
+libimap.la: $(libimap_la_OBJECTS) $(libimap_la_DEPENDENCIES) $(EXTRA_libimap_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libimap_la_OBJECTS) $(libimap_la_LIBADD) $(LIBS)
+
+fuzz-imap-bodystructure$(EXEEXT): $(fuzz_imap_bodystructure_OBJECTS) $(fuzz_imap_bodystructure_DEPENDENCIES) $(EXTRA_fuzz_imap_bodystructure_DEPENDENCIES)
+ @rm -f fuzz-imap-bodystructure$(EXEEXT)
+ $(AM_V_CXXLD)$(fuzz_imap_bodystructure_LINK) $(fuzz_imap_bodystructure_OBJECTS) $(fuzz_imap_bodystructure_LDADD) $(LIBS)
+
+fuzz-imap-utf7$(EXEEXT): $(fuzz_imap_utf7_OBJECTS) $(fuzz_imap_utf7_DEPENDENCIES) $(EXTRA_fuzz_imap_utf7_DEPENDENCIES)
+ @rm -f fuzz-imap-utf7$(EXEEXT)
+ $(AM_V_CXXLD)$(fuzz_imap_utf7_LINK) $(fuzz_imap_utf7_OBJECTS) $(fuzz_imap_utf7_LDADD) $(LIBS)
+
+test-imap-bodystructure$(EXEEXT): $(test_imap_bodystructure_OBJECTS) $(test_imap_bodystructure_DEPENDENCIES) $(EXTRA_test_imap_bodystructure_DEPENDENCIES)
+ @rm -f test-imap-bodystructure$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_imap_bodystructure_OBJECTS) $(test_imap_bodystructure_LDADD) $(LIBS)
+
+test-imap-envelope$(EXEEXT): $(test_imap_envelope_OBJECTS) $(test_imap_envelope_DEPENDENCIES) $(EXTRA_test_imap_envelope_DEPENDENCIES)
+ @rm -f test-imap-envelope$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_imap_envelope_OBJECTS) $(test_imap_envelope_LDADD) $(LIBS)
+
+test-imap-match$(EXEEXT): $(test_imap_match_OBJECTS) $(test_imap_match_DEPENDENCIES) $(EXTRA_test_imap_match_DEPENDENCIES)
+ @rm -f test-imap-match$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_imap_match_OBJECTS) $(test_imap_match_LDADD) $(LIBS)
+
+test-imap-parser$(EXEEXT): $(test_imap_parser_OBJECTS) $(test_imap_parser_DEPENDENCIES) $(EXTRA_test_imap_parser_DEPENDENCIES)
+ @rm -f test-imap-parser$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_imap_parser_OBJECTS) $(test_imap_parser_LDADD) $(LIBS)
+
+test-imap-quote$(EXEEXT): $(test_imap_quote_OBJECTS) $(test_imap_quote_DEPENDENCIES) $(EXTRA_test_imap_quote_DEPENDENCIES)
+ @rm -f test-imap-quote$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_imap_quote_OBJECTS) $(test_imap_quote_LDADD) $(LIBS)
+
+test-imap-url$(EXEEXT): $(test_imap_url_OBJECTS) $(test_imap_url_DEPENDENCIES) $(EXTRA_test_imap_url_DEPENDENCIES)
+ @rm -f test-imap-url$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_imap_url_OBJECTS) $(test_imap_url_LDADD) $(LIBS)
+
+test-imap-utf7$(EXEEXT): $(test_imap_utf7_OBJECTS) $(test_imap_utf7_DEPENDENCIES) $(EXTRA_test_imap_utf7_DEPENDENCIES)
+ @rm -f test-imap-utf7$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_imap_utf7_OBJECTS) $(test_imap_utf7_LDADD) $(LIBS)
+
+test-imap-util$(EXEEXT): $(test_imap_util_OBJECTS) $(test_imap_util_DEPENDENCIES) $(EXTRA_test_imap_util_DEPENDENCIES)
+ @rm -f test-imap-util$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_imap_util_OBJECTS) $(test_imap_util_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-arg.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-base-subject.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-bodystructure.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-date.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-envelope.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-id.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-keepalive.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-match.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-quote.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-seqset.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-url.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-utf7.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-util.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-bodystructure.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-envelope.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-match.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-parser.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-quote.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-url.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-utf7.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-util.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 $@ $<
+
+fuzz_imap_bodystructure-fuzz-imap-bodystructure.o: fuzz-imap-bodystructure.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_bodystructure-fuzz-imap-bodystructure.o -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.o `test -f 'fuzz-imap-bodystructure.c' || echo '$(srcdir)/'`fuzz-imap-bodystructure.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-bodystructure.c' object='fuzz_imap_bodystructure-fuzz-imap-bodystructure.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.o `test -f 'fuzz-imap-bodystructure.c' || echo '$(srcdir)/'`fuzz-imap-bodystructure.c
+
+fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj: fuzz-imap-bodystructure.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj `if test -f 'fuzz-imap-bodystructure.c'; then $(CYGPATH_W) 'fuzz-imap-bodystructure.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-bodystructure.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-bodystructure.c' object='fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj `if test -f 'fuzz-imap-bodystructure.c'; then $(CYGPATH_W) 'fuzz-imap-bodystructure.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-bodystructure.c'; fi`
+
+fuzz_imap_utf7-fuzz-imap-utf7.o: fuzz-imap-utf7.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_utf7-fuzz-imap-utf7.o -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo -c -o fuzz_imap_utf7-fuzz-imap-utf7.o `test -f 'fuzz-imap-utf7.c' || echo '$(srcdir)/'`fuzz-imap-utf7.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-utf7.c' object='fuzz_imap_utf7-fuzz-imap-utf7.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_utf7-fuzz-imap-utf7.o `test -f 'fuzz-imap-utf7.c' || echo '$(srcdir)/'`fuzz-imap-utf7.c
+
+fuzz_imap_utf7-fuzz-imap-utf7.obj: fuzz-imap-utf7.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_utf7-fuzz-imap-utf7.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo -c -o fuzz_imap_utf7-fuzz-imap-utf7.obj `if test -f 'fuzz-imap-utf7.c'; then $(CYGPATH_W) 'fuzz-imap-utf7.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-utf7.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-utf7.c' object='fuzz_imap_utf7-fuzz-imap-utf7.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_utf7-fuzz-imap-utf7.obj `if test -f 'fuzz-imap-utf7.c'; then $(CYGPATH_W) 'fuzz-imap-utf7.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-utf7.c'; fi`
+
+.cxx.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cxx.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cxx.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+fuzz_imap_bodystructure-force-cxx-linking.o: force-cxx-linking.cxx
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_bodystructure-force-cxx-linking.o -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo -c -o fuzz_imap_bodystructure-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_bodystructure-force-cxx-linking.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_bodystructure-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx
+
+fuzz_imap_bodystructure-force-cxx-linking.obj: force-cxx-linking.cxx
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_bodystructure-force-cxx-linking.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo -c -o fuzz_imap_bodystructure-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_bodystructure-force-cxx-linking.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_bodystructure-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi`
+
+fuzz_imap_utf7-force-cxx-linking.o: force-cxx-linking.cxx
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_utf7-force-cxx-linking.o -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo -c -o fuzz_imap_utf7-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_utf7-force-cxx-linking.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_utf7-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx
+
+fuzz_imap_utf7-force-cxx-linking.obj: force-cxx-linking.cxx
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_utf7-force-cxx-linking.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo -c -o fuzz_imap_utf7-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_utf7-force-cxx-linking.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_utf7-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-local
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(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)/fuzz_imap_bodystructure-force-cxx-linking.Po
+ -rm -f ./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po
+ -rm -f ./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po
+ -rm -f ./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po
+ -rm -f ./$(DEPDIR)/imap-arg.Plo
+ -rm -f ./$(DEPDIR)/imap-base-subject.Plo
+ -rm -f ./$(DEPDIR)/imap-bodystructure.Plo
+ -rm -f ./$(DEPDIR)/imap-date.Plo
+ -rm -f ./$(DEPDIR)/imap-envelope.Plo
+ -rm -f ./$(DEPDIR)/imap-id.Plo
+ -rm -f ./$(DEPDIR)/imap-keepalive.Plo
+ -rm -f ./$(DEPDIR)/imap-match.Plo
+ -rm -f ./$(DEPDIR)/imap-parser.Plo
+ -rm -f ./$(DEPDIR)/imap-quote.Plo
+ -rm -f ./$(DEPDIR)/imap-seqset.Plo
+ -rm -f ./$(DEPDIR)/imap-url.Plo
+ -rm -f ./$(DEPDIR)/imap-utf7.Plo
+ -rm -f ./$(DEPDIR)/imap-util.Plo
+ -rm -f ./$(DEPDIR)/test-imap-bodystructure.Po
+ -rm -f ./$(DEPDIR)/test-imap-envelope.Po
+ -rm -f ./$(DEPDIR)/test-imap-match.Po
+ -rm -f ./$(DEPDIR)/test-imap-parser.Po
+ -rm -f ./$(DEPDIR)/test-imap-quote.Po
+ -rm -f ./$(DEPDIR)/test-imap-url.Po
+ -rm -f ./$(DEPDIR)/test-imap-utf7.Po
+ -rm -f ./$(DEPDIR)/test-imap-util.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)/fuzz_imap_bodystructure-force-cxx-linking.Po
+ -rm -f ./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po
+ -rm -f ./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po
+ -rm -f ./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po
+ -rm -f ./$(DEPDIR)/imap-arg.Plo
+ -rm -f ./$(DEPDIR)/imap-base-subject.Plo
+ -rm -f ./$(DEPDIR)/imap-bodystructure.Plo
+ -rm -f ./$(DEPDIR)/imap-date.Plo
+ -rm -f ./$(DEPDIR)/imap-envelope.Plo
+ -rm -f ./$(DEPDIR)/imap-id.Plo
+ -rm -f ./$(DEPDIR)/imap-keepalive.Plo
+ -rm -f ./$(DEPDIR)/imap-match.Plo
+ -rm -f ./$(DEPDIR)/imap-parser.Plo
+ -rm -f ./$(DEPDIR)/imap-quote.Plo
+ -rm -f ./$(DEPDIR)/imap-seqset.Plo
+ -rm -f ./$(DEPDIR)/imap-url.Plo
+ -rm -f ./$(DEPDIR)/imap-utf7.Plo
+ -rm -f ./$(DEPDIR)/imap-util.Plo
+ -rm -f ./$(DEPDIR)/test-imap-bodystructure.Po
+ -rm -f ./$(DEPDIR)/test-imap-envelope.Po
+ -rm -f ./$(DEPDIR)/test-imap-match.Po
+ -rm -f ./$(DEPDIR)/test-imap-parser.Po
+ -rm -f ./$(DEPDIR)/test-imap-quote.Po
+ -rm -f ./$(DEPDIR)/test-imap-url.Po
+ -rm -f ./$(DEPDIR)/test-imap-utf7.Po
+ -rm -f ./$(DEPDIR)/test-imap-util.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: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \
+ check-local clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES clean-noinstPROGRAMS 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-local:
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib-imap/fuzz-imap-bodystructure.c b/src/lib-imap/fuzz-imap-bodystructure.c
new file mode 100644
index 0000000..a9d4499
--- /dev/null
+++ b/src/lib-imap/fuzz-imap-bodystructure.c
@@ -0,0 +1,52 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "fuzzer.h"
+#include "imap-bodystructure.c"
+#include <ctype.h>
+
+static const char *str_sanitize_binary(const char *input)
+{
+ string_t *dest = t_str_new(strlen(input));
+ for (;*input != '\0';input++) {
+ if (i_isprint(*input) == 0)
+ str_printfa(dest, "<%02x>", (unsigned char)*input);
+ else
+ str_append_c(dest, *input);
+ }
+ return str_c(dest);
+}
+
+FUZZ_BEGIN_STR(const char *str)
+{
+ pool_t pool =
+ pool_alloconly_create(MEMPOOL_GROWING"fuzz bodystructure", 1024);
+ struct message_part parts;
+ string_t *dest = str_new(pool, 32);
+ const char *error ATTR_UNUSED;
+ i_zero(&parts);
+
+ if (imap_bodystructure_parse(str, pool, &parts, &error) == 0) {
+ if (imap_bodystructure_write(&parts, dest, TRUE, &error) < 0)
+ i_panic("Failed to write bodystructure: %s", error);
+ /* The written bodystructure must be parseable *and*
+ it must come out exactly the same again */
+ if (imap_bodystructure_parse(str_c(dest), pool, &parts, &error) != 0) {
+ i_panic("Failed to reparse bodystructure '%s'",
+ str_sanitize_binary(str_c(dest)));
+ } else {
+ const char *new_str = t_strdup(str_c(dest));
+ str_truncate(dest, 0);
+ if (imap_bodystructure_write(&parts, dest, TRUE, &error) < 0)
+ i_panic("Failed to write reparsed bodystructure: %s", error);
+ if (strcmp(str_c(dest), new_str) != 0) {
+ i_panic("Parsed bodystructure '%s' does not match '%s'",
+ str_sanitize_binary(new_str),
+ str_sanitize_binary(str_c(dest)));
+ }
+ }
+ }
+ pool_unref(&pool);
+}
+FUZZ_END
diff --git a/src/lib-imap/fuzz-imap-utf7.c b/src/lib-imap/fuzz-imap-utf7.c
new file mode 100644
index 0000000..2a561f1
--- /dev/null
+++ b/src/lib-imap/fuzz-imap-utf7.c
@@ -0,0 +1,15 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "fuzzer.h"
+#include "imap-utf7.h"
+
+FUZZ_BEGIN_STR(const char *str)
+{
+ string_t *dest = t_str_new(32);
+
+ imap_utf8_to_utf7(str, dest);
+ imap_utf7_to_utf8(str, dest);
+}
+FUZZ_END
diff --git a/src/lib-imap/imap-arg.c b/src/lib-imap/imap-arg.c
new file mode 100644
index 0000000..0b947ce
--- /dev/null
+++ b/src/lib-imap/imap-arg.c
@@ -0,0 +1,137 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "imap-arg.h"
+
+bool imap_arg_get_atom(const struct imap_arg *arg, const char **str_r)
+{
+ if (arg->type != IMAP_ARG_ATOM && arg->type != IMAP_ARG_NIL)
+ return FALSE;
+
+ *str_r = arg->_data.str;
+ return TRUE;
+}
+
+bool imap_arg_get_quoted(const struct imap_arg *arg, const char **str_r)
+{
+ if (arg->type != IMAP_ARG_STRING)
+ return FALSE;
+
+ *str_r = arg->_data.str;
+ return TRUE;
+}
+
+bool imap_arg_get_string(const struct imap_arg *arg, const char **str_r)
+{
+ if (arg->type != IMAP_ARG_STRING && arg->type != IMAP_ARG_LITERAL)
+ return FALSE;
+
+ *str_r = arg->_data.str;
+ return TRUE;
+}
+
+bool imap_arg_get_astring(const struct imap_arg *arg, const char **str_r)
+{
+ /* RFC 3501 4.5. specifies that NIL is the same as "NIL" when
+ reading astring. */
+ if (!IMAP_ARG_IS_ASTRING(arg) && arg->type != IMAP_ARG_NIL)
+ return FALSE;
+
+ *str_r = arg->_data.str;
+ return TRUE;
+}
+
+bool imap_arg_get_nstring(const struct imap_arg *arg, const char **str_r)
+{
+ if (arg->type == IMAP_ARG_NIL) {
+ *str_r = NULL;
+ return TRUE;
+ }
+ return imap_arg_get_string(arg, str_r);
+}
+
+bool imap_arg_get_literal_size(const struct imap_arg *arg, uoff_t *size_r)
+{
+ if (arg->type != IMAP_ARG_LITERAL_SIZE &&
+ arg->type != IMAP_ARG_LITERAL_SIZE_NONSYNC)
+ return FALSE;
+
+ *size_r = arg->_data.literal_size;
+ return TRUE;
+}
+
+bool imap_arg_get_list(const struct imap_arg *arg,
+ const struct imap_arg **list_r)
+{
+ unsigned int count;
+
+ return imap_arg_get_list_full(arg, list_r, &count);
+}
+
+bool imap_arg_get_list_full(const struct imap_arg *arg,
+ const struct imap_arg **list_r,
+ unsigned int *list_count_r)
+{
+ unsigned int count;
+
+ if (arg->type != IMAP_ARG_LIST)
+ return FALSE;
+
+ *list_r = array_get(&arg->_data.list, &count);
+
+ if (count > 0 && (*list_r)[count-1].type == IMAP_ARG_EOL)
+ count--;
+ else {
+ /* imap-parser stopped early (e.g. due to reading literal size).
+ The IMAP_ARG_EOL was added to the list only temporarily. */
+ i_assert((*list_r)[count].type == IMAP_ARG_EOL);
+ }
+ *list_count_r = count;
+ return TRUE;
+}
+
+const char *imap_arg_as_astring(const struct imap_arg *arg)
+{
+ const char *str;
+
+ if (!imap_arg_get_astring(arg, &str))
+ i_unreached();
+ return str;
+}
+
+const char *imap_arg_as_nstring(const struct imap_arg *arg)
+{
+ const char *str;
+
+ if (!imap_arg_get_nstring(arg, &str))
+ i_unreached();
+ return str;
+}
+
+uoff_t imap_arg_as_literal_size(const struct imap_arg *arg)
+{
+ uoff_t size;
+
+ if (!imap_arg_get_literal_size(arg, &size))
+ i_unreached();
+ return size;
+}
+
+const struct imap_arg *
+imap_arg_as_list(const struct imap_arg *arg)
+{
+ const struct imap_arg *ret;
+
+ if (!imap_arg_get_list(arg, &ret))
+ i_unreached();
+ return ret;
+}
+
+bool imap_arg_atom_equals(const struct imap_arg *arg, const char *str)
+{
+ const char *value;
+
+ if (!imap_arg_get_atom(arg, &value))
+ return FALSE;
+ return strcasecmp(value, str) == 0;
+}
diff --git a/src/lib-imap/imap-arg.h b/src/lib-imap/imap-arg.h
new file mode 100644
index 0000000..d55ee15
--- /dev/null
+++ b/src/lib-imap/imap-arg.h
@@ -0,0 +1,108 @@
+#ifndef IMAP_ARG_H
+#define IMAP_ARG_H
+
+#include "array.h"
+
+/* ABNF:
+
+ CHAR = %x01-7F
+ CTL = %x00-1F / %x7F
+ SP = %x20
+ DQUOTE = %x22 */
+
+/* ASTRING-CHAR = ATOM-CHAR / resp-specials */
+#define IS_ASTRING_CHAR(c) (IS_ATOM_CHAR(c) || IS_RESP_SPECIAL(c))
+/* ATOM-CHAR = <any CHAR except atom-specials> */
+#define IS_ATOM_CHAR(c) (!IS_ATOM_SPECIAL(c))
+/* atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards /
+ quoted-specials / resp-specials
+ Since atoms are only 7bit, we'll also optimize a bit by assuming 8bit chars
+ are also atom-specials. */
+#define IS_ATOM_SPECIAL(c) \
+ ((unsigned char)(c) <= 0x20 || (unsigned char)(c) >= 0x7f || \
+ (c) == '(' || (c) == ')' || (c) == '{' || IS_LIST_WILDCARD(c) || \
+ IS_QUOTED_SPECIAL(c) || IS_RESP_SPECIAL(c))
+
+/* list-wildcards = "%" / "*" */
+#define IS_LIST_WILDCARD(c) ((c) == '%' || (c) == '*')
+/* quoted-specials = DQUOTE / "\" */
+#define IS_QUOTED_SPECIAL(c) ((c) == '\"' || (c) == '\\')
+/* resp-specials = "]" */
+#define IS_RESP_SPECIAL(c) ((c) == ']')
+
+enum imap_arg_type {
+ IMAP_ARG_NIL = 0,
+ IMAP_ARG_ATOM,
+ IMAP_ARG_STRING,
+ IMAP_ARG_LIST,
+
+ /* literals are returned as IMAP_ARG_STRING by default */
+ IMAP_ARG_LITERAL,
+ IMAP_ARG_LITERAL_SIZE,
+ IMAP_ARG_LITERAL_SIZE_NONSYNC,
+
+ IMAP_ARG_EOL /* end of argument list */
+};
+
+ARRAY_DEFINE_TYPE(imap_arg_list, struct imap_arg);
+struct imap_arg {
+ enum imap_arg_type type;
+ struct imap_arg *parent; /* always of type IMAP_ARG_LIST */
+
+ /* Set when _data.str is set */
+ size_t str_len;
+
+ union {
+ const char *str;
+ uoff_t literal_size;
+ ARRAY_TYPE(imap_arg_list) list;
+ } _data;
+ bool literal8:1; /* BINARY literal8 used */
+};
+
+/* RFC 3501's astring type. Note that this doesn't return TRUE for
+ IMAP_ARG_NIL, although it should be treated the same as "NIL" string when
+ reading an astring. */
+#define IMAP_ARG_TYPE_IS_ASTRING(type) \
+ ((type) == IMAP_ARG_ATOM || \
+ (type) == IMAP_ARG_STRING || \
+ (type) == IMAP_ARG_LITERAL)
+#define IMAP_ARG_IS_ASTRING(arg) \
+ IMAP_ARG_TYPE_IS_ASTRING((arg)->type)
+#define IMAP_ARG_IS_NSTRING(arg) \
+ (IMAP_ARG_IS_ASTRING(arg) || (arg)->type == IMAP_ARG_NIL)
+#define IMAP_ARG_IS_EOL(arg) \
+ ((arg)->type == IMAP_ARG_EOL)
+
+bool imap_arg_get_atom(const struct imap_arg *arg, const char **str_r)
+ ATTR_WARN_UNUSED_RESULT;
+bool imap_arg_get_quoted(const struct imap_arg *arg, const char **str_r)
+ ATTR_WARN_UNUSED_RESULT;
+bool imap_arg_get_string(const struct imap_arg *arg, const char **str_r)
+ ATTR_WARN_UNUSED_RESULT;
+bool imap_arg_get_astring(const struct imap_arg *arg, const char **str_r)
+ ATTR_WARN_UNUSED_RESULT;
+/* str is set to NULL for NIL. */
+bool imap_arg_get_nstring(const struct imap_arg *arg, const char **str_r)
+ ATTR_WARN_UNUSED_RESULT;
+
+bool imap_arg_get_literal_size(const struct imap_arg *arg, uoff_t *size_r)
+ ATTR_WARN_UNUSED_RESULT;
+
+bool imap_arg_get_list(const struct imap_arg *arg,
+ const struct imap_arg **list_r)
+ ATTR_WARN_UNUSED_RESULT;
+bool imap_arg_get_list_full(const struct imap_arg *arg,
+ const struct imap_arg **list_r,
+ unsigned int *list_count_r) ATTR_WARN_UNUSED_RESULT;
+
+/* Similar to above, but assumes that arg is already of correct type. */
+const char *imap_arg_as_astring(const struct imap_arg *arg);
+const char *imap_arg_as_nstring(const struct imap_arg *arg);
+uoff_t imap_arg_as_literal_size(const struct imap_arg *arg);
+const struct imap_arg *imap_arg_as_list(const struct imap_arg *arg);
+
+/* Returns TRUE if arg is atom and case-insensitively matches str */
+bool imap_arg_atom_equals(const struct imap_arg *arg, const char *str);
+
+#endif
diff --git a/src/lib-imap/imap-base-subject.c b/src/lib-imap/imap-base-subject.c
new file mode 100644
index 0000000..02469d3
--- /dev/null
+++ b/src/lib-imap/imap-base-subject.c
@@ -0,0 +1,248 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+/* Implemented against draft-ietf-imapext-sort-10 and
+ draft-ietf-imapext-thread-12 */
+
+#include "lib.h"
+#include "buffer.h"
+#include "charset-utf8.h"
+#include "message-header-decode.h"
+#include "imap-base-subject.h"
+
+static void pack_whitespace(buffer_t *buf)
+{
+ char *data, *dest;
+ bool last_lwsp;
+
+ data = buffer_get_modifiable_data(buf, NULL);
+
+ /* check if we need to do anything */
+ while (*data != '\0') {
+ if (*data == '\t' || *data == '\n' || *data == '\r' ||
+ (*data == ' ' && (data[1] == ' ' || data[1] == '\t')))
+ break;
+ data++;
+ }
+
+ if (*data == '\0')
+ return;
+
+ /* @UNSAFE: convert/pack the whitespace */
+ dest = data; last_lwsp = FALSE;
+ while (*data != '\0') {
+ if (*data == '\t' || *data == ' ' ||
+ *data == '\r' || *data == '\n') {
+ if (!last_lwsp) {
+ *dest++ = ' ';
+ last_lwsp = TRUE;
+ }
+ } else {
+ *dest++ = *data;
+ last_lwsp = FALSE;
+ }
+ data++;
+ }
+ *dest = '\0';
+
+ data = buffer_get_modifiable_data(buf, NULL);
+ buffer_set_used_size(buf, (size_t) (dest - data)+1);
+}
+
+static void remove_subj_trailers(buffer_t *buf, size_t start_pos,
+ bool *is_reply_or_forward_r)
+{
+ const char *data;
+ size_t orig_size, size;
+
+ /* subj-trailer = "(fwd)" / WSP */
+ data = buffer_get_data(buf, &orig_size);
+
+ if (orig_size < 1) /* size includes trailing \0 */
+ return;
+
+ for (size = orig_size-1; size > start_pos; ) {
+ if (data[size-1] == ' ')
+ size--;
+ else if (size >= 5 &&
+ memcmp(data + size - 5, "(FWD)", 5) == 0) {
+ *is_reply_or_forward_r = TRUE;
+ size -= 5;
+ } else {
+ break;
+ }
+ }
+
+ if (size != orig_size-1) {
+ buffer_set_used_size(buf, size);
+ buffer_append_c(buf, '\0');
+ }
+}
+
+static bool remove_blob(const char **datap)
+{
+ const char *data = *datap;
+
+ if (*data != '[')
+ return FALSE;
+
+ data++;
+ while (*data != '\0' && *data != '[' && *data != ']')
+ data++;
+
+ if (*data != ']')
+ return FALSE;
+
+ data++;
+ if (*data == ' ')
+ data++;
+
+ *datap = data;
+ return TRUE;
+}
+
+static bool remove_subj_leader(buffer_t *buf, size_t *start_pos,
+ bool *is_reply_or_forward_r)
+{
+ const char *data, *orig_data;
+ bool ret = FALSE;
+
+ /* subj-leader = (*subj-blob subj-refwd) / WSP
+
+ subj-blob = "[" *BLOBCHAR "]" *WSP
+ subj-refwd = ("re" / ("fw" ["d"])) *WSP [subj-blob] ":"
+
+ BLOBCHAR = %x01-5a / %x5c / %x5e-7f
+ ; any CHAR except '[' and ']' */
+ orig_data = buf->data;
+ orig_data += *start_pos;
+ data = orig_data;
+
+ if (*data == ' ') {
+ /* independent from checks below - always removed */
+ data++; orig_data++;
+ *start_pos += 1;
+ ret = TRUE;
+ }
+
+ while (*data == '[') {
+ if (!remove_blob(&data))
+ return ret;
+ }
+
+ if (str_begins(data, "RE"))
+ data += 2;
+ else if (str_begins(data, "FWD"))
+ data += 3;
+ else if (str_begins(data, "FW"))
+ data += 2;
+ else
+ return ret;
+
+ if (*data == ' ')
+ data++;
+
+ if (*data == '[' && !remove_blob(&data))
+ return ret;
+
+ if (*data != ':')
+ return ret;
+
+ data++;
+ *start_pos += (size_t)(data - orig_data);
+ *is_reply_or_forward_r = TRUE;
+ return TRUE;
+}
+
+static bool remove_blob_when_nonempty(buffer_t *buf, size_t *start_pos)
+{
+ const char *data, *orig_data;
+
+ orig_data = buf->data;
+ orig_data += *start_pos;
+ data = orig_data;
+ if (*data == '[' && remove_blob(&data) && *data != '\0') {
+ *start_pos += (size_t)(data - orig_data);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static bool remove_subj_fwd_hdr(buffer_t *buf, size_t *start_pos,
+ bool *is_reply_or_forward_r)
+{
+ const char *data = buf->data;
+ size_t size = buf->used;
+
+ /* subj-fwd = subj-fwd-hdr subject subj-fwd-trl
+ subj-fwd-hdr = "[fwd:"
+ subj-fwd-trl = "]" */
+
+ if (!str_begins(data + *start_pos, "[FWD:"))
+ return FALSE;
+
+ if (data[size-2] != ']')
+ return FALSE;
+
+ *is_reply_or_forward_r = TRUE;
+
+ buffer_set_used_size(buf, size-2);
+ buffer_append_c(buf, '\0');
+
+ *start_pos += 5;
+ return TRUE;
+}
+
+const char *imap_get_base_subject_cased(pool_t pool, const char *subject,
+ bool *is_reply_or_forward_r)
+{
+ buffer_t *buf;
+ size_t start_pos, subject_len;
+ bool found;
+
+ *is_reply_or_forward_r = FALSE;
+
+ subject_len = strlen(subject);
+ buf = buffer_create_dynamic(pool, subject_len);
+
+ /* (1) Convert any RFC 2047 encoded-words in the subject to
+ UTF-8. Convert all tabs and continuations to space.
+ Convert all multiple spaces to a single space. */
+ message_header_decode_utf8((const unsigned char *)subject, subject_len,
+ buf, uni_utf8_to_decomposed_titlecase);
+ buffer_append_c(buf, '\0');
+
+ pack_whitespace(buf);
+
+ start_pos = 0;
+ do {
+ /* (2) Remove all trailing text of the subject that matches
+ the subj-trailer ABNF, repeat until no more matches are
+ possible. */
+ remove_subj_trailers(buf, start_pos, is_reply_or_forward_r);
+
+ do {
+ /* (3) Remove all prefix text of the subject that
+ matches the subj-leader ABNF. */
+ found = remove_subj_leader(buf, &start_pos,
+ is_reply_or_forward_r);
+
+ /* (4) If there is prefix text of the subject that
+ matches the subj-blob ABNF, and removing that prefix
+ leaves a non-empty subj-base, then remove the prefix
+ text. */
+ found = remove_blob_when_nonempty(buf, &start_pos) ||
+ found;
+
+ /* (5) Repeat (3) and (4) until no matches remain. */
+ } while (found);
+
+ /* (6) If the resulting text begins with the subj-fwd-hdr ABNF
+ and ends with the subj-fwd-trl ABNF, remove the
+ subj-fwd-hdr and subj-fwd-trl and repeat from step (2). */
+ } while (remove_subj_fwd_hdr(buf, &start_pos, is_reply_or_forward_r));
+
+ /* (7) The resulting text is the "base subject" used in the
+ SORT. */
+ return (const char *)buf->data + start_pos;
+}
diff --git a/src/lib-imap/imap-base-subject.h b/src/lib-imap/imap-base-subject.h
new file mode 100644
index 0000000..f086284
--- /dev/null
+++ b/src/lib-imap/imap-base-subject.h
@@ -0,0 +1,13 @@
+#ifndef IMAP_BASE_SUBJECT_H
+#define IMAP_BASE_SUBJECT_H
+
+/* Returns the base subject of the given string, according to
+ draft-ietf-imapext-sort-10. String is returned so that it's suitable for
+ strcmp() comparing with another base subject.
+
+ is_reply_or_forward is set to TRUE if message looks like reply or forward,
+ according to draft-ietf-imapext-thread-12 rules. */
+const char *imap_get_base_subject_cased(pool_t pool, const char *subject,
+ bool *is_reply_or_forward_r);
+
+#endif
diff --git a/src/lib-imap/imap-bodystructure.c b/src/lib-imap/imap-bodystructure.c
new file mode 100644
index 0000000..522c250
--- /dev/null
+++ b/src/lib-imap/imap-bodystructure.c
@@ -0,0 +1,956 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "istream.h"
+#include "str.h"
+#include "message-part-data.h"
+#include "message-parser.h"
+#include "rfc822-parser.h"
+#include "rfc2231-parser.h"
+#include "imap-parser.h"
+#include "imap-quote.h"
+#include "imap-envelope.h"
+#include "imap-bodystructure.h"
+
+#define EMPTY_BODY "(\"text\" \"plain\" " \
+ "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0)"
+#define EMPTY_BODYSTRUCTURE "(\"text\" \"plain\" " \
+ "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0 " \
+ "NIL NIL NIL NIL)"
+
+/*
+ * IMAP BODY/BODYSTRUCTURE write
+ */
+
+static void
+params_write(const struct message_part_param *params,
+ unsigned int params_count, string_t *str,
+ bool default_charset)
+{
+ unsigned int i;
+ bool seen_charset;
+
+ if (!default_charset && params_count == 0) {
+ str_append(str, "NIL");
+ return;
+ }
+ str_append_c(str, '(');
+
+ seen_charset = FALSE;
+ for (i = 0; i < params_count; i++) {
+ i_assert(params[i].name != NULL);
+ i_assert(params[i].value != NULL);
+
+ if (i > 0)
+ str_append_c(str, ' ');
+ if (default_charset &&
+ strcasecmp(params[i].name, "charset") == 0)
+ seen_charset = TRUE;
+ imap_append_string(str, params[i].name);
+ str_append_c(str, ' ');
+ imap_append_string(str, params[i].value);
+ }
+ if (default_charset && !seen_charset) {
+ if (i > 0)
+ str_append_c(str, ' ');
+ str_append(str, "\"charset\" "
+ "\""MESSAGE_PART_DEFAULT_CHARSET"\"");
+ }
+ str_append_c(str, ')');
+}
+
+static int
+part_write_bodystructure_siblings(const struct message_part *part,
+ string_t *dest, bool extended,
+ const char **error_r)
+{
+ for (; part != NULL; part = part->next) {
+ str_append_c(dest, '(');
+ if (imap_bodystructure_write(part, dest, extended, error_r) < 0)
+ return -1;
+ str_append_c(dest, ')');
+ }
+ return 0;
+}
+
+static void
+part_write_bodystructure_common(const struct message_part_data *data,
+ string_t *str)
+{
+ str_append_c(str, ' ');
+ if (data->content_disposition == NULL)
+ str_append(str, "NIL");
+ else {
+ str_append_c(str, '(');
+ imap_append_string(str, data->content_disposition);
+
+ str_append_c(str, ' ');
+ params_write(data->content_disposition_params,
+ data->content_disposition_params_count, str, FALSE);
+
+ str_append_c(str, ')');
+ }
+
+ str_append_c(str, ' ');
+ if (data->content_language == NULL)
+ str_append(str, "NIL");
+ else {
+ const char *const *lang = data->content_language;
+
+ i_assert(*lang != NULL);
+ str_append_c(str, '(');
+ imap_append_string(str, *lang);
+ lang++;
+ while (*lang != NULL) {
+ str_append_c(str, ' ');
+ imap_append_string(str, *lang);
+ lang++;
+ }
+ str_append_c(str, ')');
+ }
+
+ str_append_c(str, ' ');
+ imap_append_nstring_nolf(str, data->content_location);
+}
+
+static int part_write_body_multipart(const struct message_part *part,
+ string_t *str, bool extended,
+ const char **error_r)
+{
+ const struct message_part_data *data = part->data;
+
+ i_assert(part->data != NULL);
+
+ if (part->children != NULL) {
+ if (part_write_bodystructure_siblings(part->children, str,
+ extended, error_r) < 0)
+ return -1;
+ } else {
+ /* no parts in multipart message,
+ that's not allowed. write a single
+ 0-length text/plain structure */
+ if (!extended)
+ str_append(str, EMPTY_BODY);
+ else
+ str_append(str, EMPTY_BODYSTRUCTURE);
+ }
+
+ str_append_c(str, ' ');
+ imap_append_string(str, data->content_subtype);
+
+ if (!extended)
+ return 0;
+
+ /* BODYSTRUCTURE data */
+
+ str_append_c(str, ' ');
+ params_write(data->content_type_params,
+ data->content_type_params_count, str, FALSE);
+
+ part_write_bodystructure_common(data, str);
+ return 0;
+}
+
+static bool part_is_truncated(const struct message_part *part)
+{
+ const struct message_part_data *data = part->data;
+
+ i_assert((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0);
+ i_assert((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0);
+
+ if (data->content_type != NULL) {
+ if (strcasecmp(data->content_type, "message") == 0 &&
+ strcasecmp(data->content_subtype, "rfc822") == 0) {
+ /* It's message/rfc822, but without
+ MESSAGE_PART_FLAG_MESSAGE_RFC822. */
+ return TRUE;
+ }
+ if (strcasecmp(data->content_type, "multipart") == 0) {
+ /* It's multipart/, but without
+ MESSAGE_PART_FLAG_MULTIPART. */
+ return TRUE;
+ }
+ } else {
+ /* No Content-Type */
+ if (part->parent != NULL &&
+ (part->parent->flags & MESSAGE_PART_FLAG_MULTIPART_DIGEST) != 0) {
+ /* Parent is MESSAGE_PART_FLAG_MULTIPART_DIGEST
+ (so this should have been message/rfc822). */
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static int part_write_body(const struct message_part *part,
+ string_t *str, bool extended, const char **error_r)
+{
+ const struct message_part_data *data = part->data;
+ bool text;
+
+ i_assert(part->data != NULL);
+
+ if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) {
+ str_append(str, "\"message\" \"rfc822\"");
+ text = FALSE;
+ } else if (part_is_truncated(part)) {
+ /* Maximum MIME part count was reached while parsing the mail.
+ Write this part out as application/octet-stream instead.
+ We're not using text/plain, because it would require
+ message-parser to use MESSAGE_PART_FLAG_TEXT for this part
+ to avoid losing line count in message_part serialization. */
+ str_append(str, "\"application\" \"octet-stream\"");
+ text = FALSE;
+ } else {
+ /* "content type" "subtype" */
+ if (data->content_type == NULL) {
+ text = TRUE;
+ str_append(str, "\"text\" \"plain\"");
+ } else {
+ text = (strcasecmp(data->content_type, "text") == 0);
+ imap_append_string(str, data->content_type);
+ str_append_c(str, ' ');
+ imap_append_string(str, data->content_subtype);
+ }
+ bool part_is_text = (part->flags & MESSAGE_PART_FLAG_TEXT) != 0;
+ if (text != part_is_text) {
+ *error_r = "text flag mismatch";
+ return -1;
+ }
+ }
+
+ /* ("content type param key" "value" ...) */
+ str_append_c(str, ' ');
+ params_write(data->content_type_params,
+ data->content_type_params_count, str, text);
+
+ str_append_c(str, ' ');
+ imap_append_nstring_nolf(str, data->content_id);
+ str_append_c(str, ' ');
+ imap_append_nstring_nolf(str, data->content_description);
+ str_append_c(str, ' ');
+ if (data->content_transfer_encoding != NULL)
+ imap_append_string(str, data->content_transfer_encoding);
+ else
+ str_append(str, "\"7bit\"");
+ str_printfa(str, " %"PRIuUOFF_T, part->body_size.virtual_size);
+
+ if (text) {
+ /* text/.. contains line count */
+ str_printfa(str, " %u", part->body_size.lines);
+ } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) {
+ /* message/rfc822 contains envelope + body + line count */
+ const struct message_part_data *child_data;
+
+ i_assert(part->children != NULL);
+ i_assert(part->children->next == NULL);
+
+ child_data = part->children->data;
+
+ str_append(str, " (");
+ imap_envelope_write(child_data->envelope, str);
+ str_append(str, ") ");
+
+ if (part_write_bodystructure_siblings(part->children, str,
+ extended, error_r) < 0)
+ return -1;
+ str_printfa(str, " %u", part->body_size.lines);
+ }
+
+ if (!extended)
+ return 0;
+
+ /* BODYSTRUCTURE data */
+
+ /* "md5" ("content disposition" ("disposition" "params"))
+ ("body" "language" "params") "location" */
+ str_append_c(str, ' ');
+ imap_append_nstring_nolf(str, data->content_md5);
+ part_write_bodystructure_common(data, str);
+ return 0;
+}
+
+int imap_bodystructure_write(const struct message_part *part,
+ string_t *dest, bool extended,
+ const char **error_r)
+{
+ if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0)
+ return part_write_body_multipart(part, dest, extended, error_r);
+ else
+ return part_write_body(part, dest, extended, error_r);
+}
+
+/*
+ * IMAP BODYSTRUCTURE parsing
+ */
+
+static int
+imap_bodystructure_strlist_parse(const struct imap_arg *arg,
+ pool_t pool, const char *const **list_r)
+{
+ const char *item, **list;
+ const struct imap_arg *list_args;
+ unsigned int list_count, i;
+
+ if (arg->type == IMAP_ARG_NIL) {
+ *list_r = NULL;
+ return 0;
+ }
+ if (imap_arg_get_nstring(arg, &item)) {
+ list = p_new(pool, const char *, 2);
+ list[0] = p_strdup(pool, item);
+ } else {
+ if (!imap_arg_get_list_full(arg, &list_args, &list_count))
+ return -1;
+ if (list_count == 0)
+ return -1;
+
+ list = p_new(pool, const char *, list_count+1);
+ for (i = 0; i < list_count; i++) {
+ if (!imap_arg_get_string(&list_args[i], &item))
+ return -1;
+ list[i] = p_strdup(pool, item);
+ }
+ }
+ *list_r = list;
+ return 0;
+}
+
+static int
+imap_bodystructure_params_parse(const struct imap_arg *arg,
+ pool_t pool, const struct message_part_param **params_r,
+ unsigned int *count_r)
+{
+ struct message_part_param *params;
+ const struct imap_arg *list_args;
+ unsigned int list_count, params_count, i;
+
+ if (arg->type == IMAP_ARG_NIL) {
+ *params_r = NULL;
+ return 0;
+ }
+ if (!imap_arg_get_list_full(arg, &list_args, &list_count))
+ return -1;
+ if ((list_count % 2) != 0)
+ return -1;
+ if (list_count == 0)
+ return -1;
+
+ params_count = list_count/2;
+ params = p_new(pool, struct message_part_param, params_count+1);
+ for (i = 0; i < params_count; i++) {
+ const char *name, *value;
+
+ if (!imap_arg_get_string(&list_args[i*2+0], &name))
+ return -1;
+ if (!imap_arg_get_string(&list_args[i*2+1], &value))
+ return -1;
+ params[i].name = p_strdup(pool, name);
+ params[i].value = p_strdup(pool, value);
+ }
+ *params_r = params;
+ *count_r = params_count;
+ return 0;
+}
+
+static int
+imap_bodystructure_parse_args_common(struct message_part *part,
+ pool_t pool, const struct imap_arg *args,
+ const char **error_r)
+{
+ struct message_part_data *data = part->data;
+ const struct imap_arg *list_args;
+
+ if (args->type == IMAP_ARG_EOL)
+ return 0;
+ if (args->type == IMAP_ARG_NIL)
+ args++;
+ else if (!imap_arg_get_list(args, &list_args)) {
+ *error_r = "Invalid content-disposition list";
+ return -1;
+ } else {
+ if (!imap_arg_get_string
+ (list_args++, &data->content_disposition)) {
+ *error_r = "Invalid content-disposition";
+ return -1;
+ }
+ data->content_disposition = p_strdup(pool, data->content_disposition);
+ if (imap_bodystructure_params_parse(list_args, pool,
+ &data->content_disposition_params,
+ &data->content_disposition_params_count) < 0) {
+ *error_r = "Invalid content-disposition params";
+ return -1;
+ }
+ args++;
+ }
+ if (args->type == IMAP_ARG_EOL)
+ return 0;
+ if (imap_bodystructure_strlist_parse
+ (args++, pool, &data->content_language) < 0) {
+ *error_r = "Invalid content-language";
+ return -1;
+ }
+ if (args->type == IMAP_ARG_EOL)
+ return 0;
+ if (!imap_arg_get_nstring
+ (args++, &data->content_location)) {
+ *error_r = "Invalid content-location";
+ return -1;
+ }
+ data->content_location = p_strdup(pool, data->content_location);
+ return 0;
+}
+
+int
+imap_bodystructure_parse_args(const struct imap_arg *args, pool_t pool,
+ struct message_part **_part,
+ const char **error_r)
+{
+ struct message_part *part = *_part, *child_part;;
+ struct message_part **child_part_p;
+ struct message_part_data *data;
+ const struct imap_arg *list_args;
+ const char *value, *content_type, *subtype, *error;
+ bool multipart, text, message_rfc822, parsing_tree, has_lines;
+ unsigned int lines;
+ uoff_t vsize;
+
+ if (part != NULL) {
+ /* parsing with pre-existing message_part tree */
+ parsing_tree = FALSE;
+ } else {
+ /* parsing message_part tree from BODYSTRUCTURE as well */
+ part = *_part = p_new(pool, struct message_part, 1);
+ parsing_tree = TRUE;
+ }
+ part->data = data = p_new(pool, struct message_part_data, 1);
+
+ multipart = FALSE;
+ if (!parsing_tree) {
+ if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
+ part->children == NULL) {
+ struct message_part_data dummy_part_data = {
+ .content_type = "text",
+ .content_subtype = "plain",
+ .content_transfer_encoding = "7bit"
+ };
+ struct message_part dummy_part = {
+ .parent = part,
+ .data = &dummy_part_data,
+ .flags = MESSAGE_PART_FLAG_TEXT
+ };
+ struct message_part *dummy_partp = &dummy_part;
+
+ /* no parts in multipart message,
+ that's not allowed. expect a single
+ 0-length text/plain structure */
+ if (args->type != IMAP_ARG_LIST ||
+ (args+1)->type == IMAP_ARG_LIST) {
+ *error_r = "message_part hierarchy "
+ "doesn't match BODYSTRUCTURE";
+ return -1;
+ }
+
+ list_args = imap_arg_as_list(args);
+ if (imap_bodystructure_parse_args(list_args, pool,
+ &dummy_partp, error_r) < 0)
+ return -1;
+ child_part = NULL;
+
+ multipart = TRUE;
+ args++;
+
+ } else {
+ child_part = part->children;
+ while (args->type == IMAP_ARG_LIST) {
+ if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 ||
+ child_part == NULL) {
+ *error_r = "message_part hierarchy "
+ "doesn't match BODYSTRUCTURE";
+ return -1;
+ }
+
+ list_args = imap_arg_as_list(args);
+ if (imap_bodystructure_parse_args(list_args, pool,
+ &child_part, error_r) < 0)
+ return -1;
+ child_part = child_part->next;
+
+ multipart = TRUE;
+ args++;
+ }
+ }
+ if (multipart) {
+ if (child_part != NULL) {
+ *error_r = "message_part hierarchy "
+ "doesn't match BODYSTRUCTURE";
+ return -1;
+ }
+ } else if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
+ *error_r = "message_part multipart flag "
+ "doesn't match BODYSTRUCTURE";
+ return -1;
+ }
+ } else {
+ child_part_p = &part->children;
+ while (args->type == IMAP_ARG_LIST) {
+ list_args = imap_arg_as_list(args);
+ if (imap_bodystructure_parse_args(list_args, pool,
+ child_part_p, error_r) < 0)
+ return -1;
+ (*child_part_p)->parent = part;
+ child_part_p = &(*child_part_p)->next;
+
+ multipart = TRUE;
+ args++;
+ }
+ if (multipart) {
+ part->flags |= MESSAGE_PART_FLAG_MULTIPART;
+ }
+ }
+
+ if (multipart) {
+ data->content_type = "multipart";
+ if (!imap_arg_get_string(args++, &data->content_subtype)) {
+ *error_r = "Invalid multipart content-type";
+ return -1;
+ }
+ data->content_subtype = p_strdup(pool, data->content_subtype);
+ if (args->type == IMAP_ARG_EOL)
+ return 0;
+ if (imap_bodystructure_params_parse(args++, pool,
+ &data->content_type_params,
+ &data->content_type_params_count) < 0) {
+ *error_r = "Invalid content params";
+ return -1;
+ }
+ return imap_bodystructure_parse_args_common
+ (part, pool, args, error_r);
+ }
+
+ /* "content type" "subtype" */
+ if (!imap_arg_get_string(&args[0], &content_type) ||
+ !imap_arg_get_string(&args[1], &subtype)) {
+ *error_r = "Invalid content-type";
+ return -1;
+ }
+ data->content_type = p_strdup(pool, content_type);
+ data->content_subtype = p_strdup(pool, subtype);
+ args += 2;
+
+ text = strcasecmp(content_type, "text") == 0;
+ message_rfc822 = strcasecmp(content_type, "message") == 0 &&
+ strcasecmp(subtype, "rfc822") == 0;
+
+ if (!parsing_tree) {
+#if 0
+ /* Disabled for now. Earlier Dovecot versions handled broken
+ Content-Type headers by writing them as "text" "plain" to
+ BODYSTRUCTURE reply, but the message_part didn't have
+ MESSAGE_PART_FLAG_TEXT. */
+ if (text != ((part->flags & MESSAGE_PART_FLAG_TEXT) != 0)) {
+ *error_r = "message_part text flag "
+ "doesn't match BODYSTRUCTURE";
+ return -1;
+ }
+#endif
+ if (message_rfc822 !=
+ ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0)) {
+ *error_r = "message_part message/rfc822 flag "
+ "doesn't match BODYSTRUCTURE";
+ return -1;
+ }
+ } else {
+ if (text)
+ part->flags |= MESSAGE_PART_FLAG_TEXT;
+ if (message_rfc822)
+ part->flags |= MESSAGE_PART_FLAG_MESSAGE_RFC822;
+ }
+
+ /* ("content type param key" "value" ...) | NIL */
+ if (imap_bodystructure_params_parse(args++, pool,
+ &data->content_type_params,
+ &data->content_type_params_count) < 0) {
+ *error_r = "Invalid content params";
+ return -1;
+ }
+
+ /* "content id" "content description" "transfer encoding" size */
+ if (!imap_arg_get_nstring(args++, &data->content_id)) {
+ *error_r = "Invalid content-id";
+ return -1;
+ }
+ if (!imap_arg_get_nstring(args++, &data->content_description)) {
+ *error_r = "Invalid content-description";
+ return -1;
+ }
+ if (!imap_arg_get_string(args++, &data->content_transfer_encoding)) {
+ *error_r = "Invalid content-transfer-encoding";
+ return -1;
+ }
+ data->content_id = p_strdup(pool, data->content_id);
+ data->content_description = p_strdup(pool, data->content_description);
+ data->content_transfer_encoding =
+ p_strdup(pool, data->content_transfer_encoding);
+ if (!imap_arg_get_atom(args++, &value) ||
+ str_to_uoff(value, &vsize) < 0) {
+ *error_r = "Invalid size field";
+ return -1;
+ }
+ if (!parsing_tree) {
+ if (vsize != part->body_size.virtual_size) {
+ *error_r = "message_part virtual_size doesn't match "
+ "size in BODYSTRUCTURE";
+ return -1;
+ }
+ } else {
+ part->body_size.virtual_size = vsize;
+ }
+
+ if (text) {
+ /* text/xxx - text lines */
+ if (!imap_arg_get_atom(args++, &value) ||
+ str_to_uint(value, &lines) < 0) {
+ *error_r = "Invalid lines field";
+ return -1;
+ }
+ i_assert(part->children == NULL);
+ has_lines = TRUE;
+ } else if (message_rfc822) {
+ /* message/rfc822 - envelope + bodystructure + text lines */
+
+ if (!parsing_tree) {
+ i_assert(part->children != NULL &&
+ part->children->next == NULL);
+ }
+
+ if (!imap_arg_get_list(&args[1], &list_args)) {
+ *error_r = "Child bodystructure list expected";
+ return -1;
+ }
+ if (imap_bodystructure_parse_args
+ (list_args, pool, &part->children, error_r) < 0)
+ return -1;
+ if (parsing_tree) {
+ i_assert(part->children != NULL &&
+ part->children->next == NULL);
+ part->children->parent = part;
+ }
+
+ if (!imap_arg_get_list(&args[0], &list_args)) {
+ *error_r = "Envelope list expected";
+ return -1;
+ }
+ if (!imap_envelope_parse_args(list_args, pool,
+ &part->children->data->envelope, &error)) {
+ *error_r = t_strdup_printf
+ ("Invalid envelope list: %s", error);
+ return -1;
+ }
+ args += 2;
+ if (!imap_arg_get_atom(args++, &value) ||
+ str_to_uint(value, &lines) < 0) {
+ *error_r = "Invalid lines field";
+ return -1;
+ }
+ has_lines = TRUE;
+ } else {
+ i_assert(part->children == NULL);
+ lines = 0;
+ has_lines = FALSE;
+ }
+ if (!parsing_tree) {
+ if (has_lines && lines != part->body_size.lines) {
+ *error_r = "message_part lines "
+ "doesn't match lines in BODYSTRUCTURE";
+ return -1;
+ }
+ } else {
+ part->body_size.lines = lines;
+ }
+ if (args->type == IMAP_ARG_EOL)
+ return 0;
+ if (!imap_arg_get_nstring(args++, &data->content_md5)) {
+ *error_r = "Invalid content-md5";
+ return -1;
+ }
+ data->content_md5 = p_strdup(pool, data->content_md5);
+ return imap_bodystructure_parse_args_common
+ (part, pool, args, error_r);
+}
+
+int imap_bodystructure_parse_full(const char *bodystructure,
+ pool_t pool, struct message_part **parts,
+ const char **error_r)
+{
+ struct istream *input;
+ struct imap_parser *parser;
+ const struct imap_arg *args;
+ int ret;
+
+ i_assert(*parts == NULL || (*parts)->next == NULL);
+
+ input = i_stream_create_from_data(bodystructure, strlen(bodystructure));
+ (void)i_stream_read(input);
+
+ parser = imap_parser_create(input, NULL, SIZE_MAX);
+ ret = imap_parser_finish_line(parser, 0,
+ IMAP_PARSE_FLAG_LITERAL_TYPE, &args);
+ if (ret < 0) {
+ *error_r = t_strdup_printf("IMAP parser failed: %s",
+ imap_parser_get_error(parser, NULL));
+ } else if (ret == 0) {
+ *error_r = "Empty bodystructure";
+ ret = -1;
+ } else {
+ T_BEGIN {
+ ret = imap_bodystructure_parse_args
+ (args, pool, parts, error_r);
+ } T_END_PASS_STR_IF(ret < 0, error_r);
+ }
+
+ imap_parser_unref(&parser);
+ i_stream_destroy(&input);
+ return ret;
+}
+
+int imap_bodystructure_parse(const char *bodystructure,
+ pool_t pool, struct message_part *parts,
+ const char **error_r)
+{
+ i_assert(parts != NULL);
+
+ return imap_bodystructure_parse_full(bodystructure,
+ pool, &parts, error_r);
+}
+
+/*
+ * IMAP BODYSTRUCTURE to BODY conversion
+ */
+
+static bool str_append_nstring(string_t *str, const struct imap_arg *arg)
+{
+ const char *cstr;
+
+ if (!imap_arg_get_nstring(arg, &cstr))
+ return FALSE;
+
+ switch (arg->type) {
+ case IMAP_ARG_NIL:
+ str_append(str, "NIL");
+ break;
+ case IMAP_ARG_STRING:
+ str_append_c(str, '"');
+ /* NOTE: we're parsing with no-unescape flag,
+ so don't double-escape it here */
+ str_append(str, cstr);
+ str_append_c(str, '"');
+ break;
+ case IMAP_ARG_LITERAL: {
+ str_printfa(str, "{%zu}\r\n", strlen(cstr));
+ str_append(str, cstr);
+ break;
+ }
+ default:
+ i_unreached();
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+imap_write_envelope_list(const struct imap_arg *args, string_t *str,
+ bool toplevel)
+{
+ const struct imap_arg *children;
+
+ /* don't do any typechecking, just write it out */
+ while (!IMAP_ARG_IS_EOL(args)) {
+ bool list = FALSE;
+
+ if (!str_append_nstring(str, args)) {
+ if (!imap_arg_get_list(args, &children)) {
+ /* everything is either nstring or list */
+ i_unreached();
+ }
+
+ str_append_c(str, '(');
+ imap_write_envelope_list(children, str, FALSE);
+ str_append_c(str, ')');
+
+ list = TRUE;
+ }
+ args++;
+
+ if ((toplevel || !list) && !IMAP_ARG_IS_EOL(args))
+ str_append_c(str, ' ');
+ }
+}
+
+static void
+imap_write_envelope(const struct imap_arg *args, string_t *str)
+{
+ imap_write_envelope_list(args, str, TRUE);
+}
+
+static int imap_parse_bodystructure_args(const struct imap_arg *args,
+ string_t *str, const char **error_r)
+{
+ const struct imap_arg *subargs;
+ const struct imap_arg *list_args;
+ const char *value, *content_type, *subtype;
+ bool multipart, text, message_rfc822;
+ int i;
+
+ multipart = FALSE;
+ while (args->type == IMAP_ARG_LIST) {
+ str_append_c(str, '(');
+ list_args = imap_arg_as_list(args);
+ if (imap_parse_bodystructure_args(list_args, str, error_r) < 0)
+ return -1;
+ str_append_c(str, ')');
+
+ multipart = TRUE;
+ args++;
+ }
+
+ if (multipart) {
+ /* next is subtype of Content-Type. rest is skipped. */
+ str_append_c(str, ' ');
+ if (!str_append_nstring(str, args)) {
+ *error_r = "Invalid multipart content-type";
+ return -1;
+ }
+ return 0;
+ }
+
+ /* "content type" "subtype" */
+ if (!imap_arg_get_string(&args[0], &content_type) ||
+ !imap_arg_get_string(&args[1], &subtype)) {
+ *error_r = "Invalid content-type";
+ return -1;
+ }
+
+ if (!str_append_nstring(str, &args[0]))
+ i_unreached();
+ str_append_c(str, ' ');
+ if (!str_append_nstring(str, &args[1]))
+ i_unreached();
+
+ text = strcasecmp(content_type, "text") == 0;
+ message_rfc822 = strcasecmp(content_type, "message") == 0 &&
+ strcasecmp(subtype, "rfc822") == 0;
+
+ args += 2;
+
+ /* ("content type param key" "value" ...) | NIL */
+ if (imap_arg_get_list(args, &subargs)) {
+ str_append(str, " (");
+ while (!IMAP_ARG_IS_EOL(subargs)) {
+ if (!str_append_nstring(str, &subargs[0])) {
+ *error_r = "Invalid content param key";
+ return -1;
+ }
+ str_append_c(str, ' ');
+ if (!str_append_nstring(str, &subargs[1])) {
+ *error_r = "Invalid content param value";
+ return -1;
+ }
+
+ subargs += 2;
+ if (IMAP_ARG_IS_EOL(subargs))
+ break;
+ str_append_c(str, ' ');
+ }
+ str_append(str, ")");
+ } else if (args->type == IMAP_ARG_NIL) {
+ str_append(str, " NIL");
+ } else {
+ *error_r = "list/NIL expected";
+ return -1;
+ }
+ args++;
+
+ /* "content id" "content description" "transfer encoding" size */
+ for (i = 0; i < 3; i++, args++) {
+ str_append_c(str, ' ');
+
+ if (!str_append_nstring(str, args)) {
+ *error_r = "nstring expected";
+ return -1;
+ }
+ }
+ if (!imap_arg_get_atom(args, &value)) {
+ *error_r = "atom expected for size";
+ return -1;
+ }
+ str_printfa(str, " %s", value);
+ args++;
+
+ if (text) {
+ /* text/xxx - text lines */
+ if (!imap_arg_get_atom(args, &value)) {
+ *error_r = "Text lines expected";
+ return -1;
+ }
+
+ str_append_c(str, ' ');
+ str_append(str, value);
+ } else if (message_rfc822) {
+ /* message/rfc822 - envelope + bodystructure + text lines */
+ str_append_c(str, ' ');
+
+ if (!imap_arg_get_list(&args[0], &list_args)) {
+ *error_r = "Envelope list expected";
+ return -1;
+ }
+ str_append_c(str, '(');
+ imap_write_envelope(list_args, str);
+ str_append(str, ") (");
+
+ if (!imap_arg_get_list(&args[1], &list_args)) {
+ *error_r = "Child bodystructure list expected";
+ return -1;
+ }
+ if (imap_parse_bodystructure_args(list_args, str, error_r) < 0)
+ return -1;
+
+ str_append(str, ") ");
+ if (!imap_arg_get_atom(&args[2], &value)) {
+ *error_r = "Text lines expected";
+ return -1;
+ }
+ str_append(str, value);
+ }
+ return 0;
+}
+
+int imap_body_parse_from_bodystructure(const char *bodystructure,
+ string_t *dest, const char **error_r)
+{
+ struct istream *input;
+ struct imap_parser *parser;
+ const struct imap_arg *args;
+ int ret;
+
+ input = i_stream_create_from_data(bodystructure, strlen(bodystructure));
+ (void)i_stream_read(input);
+
+ parser = imap_parser_create(input, NULL, SIZE_MAX);
+ ret = imap_parser_finish_line(parser, 0, IMAP_PARSE_FLAG_NO_UNESCAPE |
+ IMAP_PARSE_FLAG_LITERAL_TYPE, &args);
+ if (ret < 0) {
+ *error_r = t_strdup_printf("IMAP parser failed: %s",
+ imap_parser_get_error(parser, NULL));
+ } else if (ret == 0) {
+ *error_r = "Empty bodystructure";
+ ret = -1;
+ } else {
+ ret = imap_parse_bodystructure_args(args, dest, error_r);
+ }
+
+ imap_parser_unref(&parser);
+ i_stream_destroy(&input);
+ return ret;
+}
diff --git a/src/lib-imap/imap-bodystructure.h b/src/lib-imap/imap-bodystructure.h
new file mode 100644
index 0000000..a7cc6cd
--- /dev/null
+++ b/src/lib-imap/imap-bodystructure.h
@@ -0,0 +1,39 @@
+#ifndef IMAP_BODYSTRUCTURE_H
+#define IMAP_BODYSTRUCTURE_H
+
+struct message_part;
+struct message_header_line;
+struct imap_arg;
+
+/* Write a BODY/BODYSTRUCTURE from given message_part. The message_part->data
+ field must be set. part->body_size.virtual_size and .lines are also used
+ for writing it. Returns 0 on success, -1 if parts don't internally match
+ (e.g. broken cached mime.parts mixed with parsed message). */
+int imap_bodystructure_write(const struct message_part *part,
+ string_t *dest, bool extended,
+ const char **error_r);
+
+/* Parse BODYSTRUCTURE and save the contents to message_part->data for each
+ message tree node. If the parts argument points to NULL, the message_part
+ tree is created from the BODYSTRUCTURE. Otherwise, existing tree is used.
+ Returns 0 if ok, -1 if bodystructure wasn't valid. */
+int imap_bodystructure_parse_full(const char *bodystructure, pool_t pool,
+ struct message_part **parts, const char **error_r);
+
+/* Same as imap_bodystructure_parse_full(), but read the input from imap_args
+ instead of a string. */
+int imap_bodystructure_parse_args(const struct imap_arg *args, pool_t pool,
+ struct message_part **parts, const char **error_r);
+
+/* Parse BODYSTRUCTURE and save the contents to message_part->data for each
+ message tree node. The parts argument must point to an existing message_part
+ tree. Returns 0 if ok, -1 if bodystructure wasn't valid. */
+int imap_bodystructure_parse(const char *bodystructure, pool_t pool,
+ struct message_part *parts, const char **error_r);
+
+/* Get BODY part from BODYSTRUCTURE and write it to dest.
+ Returns 0 if ok, -1 if bodystructure wasn't valid. */
+int imap_body_parse_from_bodystructure(const char *bodystructure,
+ string_t *dest, const char **error_r);
+
+#endif
diff --git a/src/lib-imap/imap-date.c b/src/lib-imap/imap-date.c
new file mode 100644
index 0000000..2e5569a
--- /dev/null
+++ b/src/lib-imap/imap-date.c
@@ -0,0 +1,247 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "utc-offset.h"
+#include "utc-mktime.h"
+#include "imap-date.h"
+
+#include <ctype.h>
+
+static const char *month_names[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static int parse_timezone(const char *str)
+{
+ int offset;
+
+ /* +|-zone */
+ if ((*str != '+' && *str != '-') ||
+ !i_isdigit(str[1]) || !i_isdigit(str[2]) ||
+ !i_isdigit(str[3]) || !i_isdigit(str[4]))
+ return 0;
+
+ offset = (str[1]-'0') * 10*60 + (str[2]-'0') * 60 +
+ (str[3]-'0') * 10 + (str[4]-'0');
+ return *str == '+' ? offset : -offset;
+}
+
+static const char *imap_parse_date_internal(const char *str, struct tm *tm)
+{
+ int i;
+
+ if (str == NULL || *str == '\0')
+ return NULL;
+
+ i_zero(tm);
+
+ /* "dd-mon-yyyy [hh:mi:ss +|-zone]"
+ dd is 1-2 digits and may be prefixed with space or zero. */
+
+ if (str[0] == ' ') {
+ /* " d-..." */
+ str++;
+ }
+
+ if (!(i_isdigit(str[0]) && (str[1] == '-' ||
+ (i_isdigit(str[1]) && str[2] == '-'))))
+ return NULL;
+
+ tm->tm_mday = (str[0]-'0');
+ if (str[1] == '-')
+ str += 2;
+ else {
+ tm->tm_mday = (tm->tm_mday * 10) + (str[1]-'0');
+ str += 3;
+ }
+
+ /* month name */
+ for (i = 0; i < 12; i++) {
+ if (strncasecmp(month_names[i], str, 3) == 0) {
+ tm->tm_mon = i;
+ break;
+ }
+ }
+ if (i == 12 || str[3] != '-')
+ return NULL;
+ str += 4;
+
+ /* yyyy */
+ if (!i_isdigit(str[0]) || !i_isdigit(str[1]) ||
+ !i_isdigit(str[2]) || !i_isdigit(str[3]))
+ return NULL;
+
+ tm->tm_year = (str[0]-'0') * 1000 + (str[1]-'0') * 100 +
+ (str[2]-'0') * 10 + (str[3]-'0') - 1900;
+
+ str += 4;
+ return str;
+}
+
+static bool imap_mktime(struct tm *tm, time_t *time_r)
+{
+ *time_r = utc_mktime(tm);
+ if (*time_r != (time_t)-1)
+ return TRUE;
+
+ /* the date is outside valid range for time_t. it might still be
+ technically valid though, so try to handle this case.
+ with 64bit time_t the full 0..9999 year range is valid. */
+ if (tm->tm_year <= 100) {
+ /* too old. time_t can be signed or unsigned, handle
+ both cases. */
+ *time_r = (time_t)-1 < (int)0 ? INT_MIN : 0;
+ } else {
+ /* too high. return the highest allowed value.
+ we shouldn't get here with 64bit time_t,
+ but handle that anyway. */
+#if (TIME_T_MAX_BITS == 32 || TIME_T_MAX_BITS == 64)
+ *time_r = (1UL << (TIME_T_MAX_BITS-1)) - 1;
+#else
+ *time_r = (1UL << TIME_T_MAX_BITS) - 1;
+#endif
+ }
+ return FALSE;
+}
+
+bool imap_parse_date(const char *str, time_t *timestamp_r)
+{
+ struct tm tm;
+
+ str = imap_parse_date_internal(str, &tm);
+ if (str == NULL || str[0] != '\0')
+ return FALSE;
+
+ tm.tm_isdst = -1;
+ (void)imap_mktime(&tm, timestamp_r);
+ return TRUE;
+}
+
+bool imap_parse_datetime(const char *str, time_t *timestamp_r,
+ int *timezone_offset_r)
+{
+ struct tm tm;
+
+ str = imap_parse_date_internal(str, &tm);
+ if (str == NULL)
+ return FALSE;
+
+ if (str[0] != ' ')
+ return FALSE;
+ str++;
+
+ /* hh: */
+ if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ':')
+ return FALSE;
+ tm.tm_hour = (str[0]-'0') * 10 + (str[1]-'0');
+ str += 3;
+
+ /* mm: */
+ if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ':')
+ return FALSE;
+ tm.tm_min = (str[0]-'0') * 10 + (str[1]-'0');
+ str += 3;
+
+ /* ss */
+ if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ' ')
+ return FALSE;
+ tm.tm_sec = (str[0]-'0') * 10 + (str[1]-'0');
+ str += 3;
+
+ /* timezone */
+ *timezone_offset_r = parse_timezone(str);
+
+ tm.tm_isdst = -1;
+ if (imap_mktime(&tm, timestamp_r))
+ *timestamp_r -= *timezone_offset_r * 60;
+ return TRUE;
+}
+
+static void imap_to_date_tm(char buf[11], const struct tm *tm)
+{
+ int year;
+
+ /* dd-mon- */
+ buf[0] = (tm->tm_mday / 10) + '0';
+ buf[1] = (tm->tm_mday % 10) + '0';
+ buf[2] = '-';
+ memcpy(buf+3, month_names[tm->tm_mon], 3);
+ buf[6] = '-';
+
+ /* yyyy */
+ year = tm->tm_year + 1900;
+ buf[7] = (year / 1000) + '0';
+ buf[8] = ((year / 100) % 10) + '0';
+ buf[9] = ((year / 10) % 10) + '0';
+ buf[10] = (year % 10) + '0';
+}
+
+static const char *
+imap_to_datetime_tm(const struct tm *tm, int timezone_offset)
+{
+ char *buf;
+
+ /* @UNSAFE: but faster than t_strdup_printf() call.. */
+ buf = t_malloc0(27);
+ imap_to_date_tm(buf, tm);
+ buf[11] = ' ';
+
+ /* hh:mi:ss */
+ buf[12] = (tm->tm_hour / 10) + '0';
+ buf[13] = (tm->tm_hour % 10) + '0';
+ buf[14] = ':';
+ buf[15] = (tm->tm_min / 10) + '0';
+ buf[16] = (tm->tm_min % 10) + '0';
+ buf[17] = ':';
+ buf[18] = (tm->tm_sec / 10) + '0';
+ buf[19] = (tm->tm_sec % 10) + '0';
+ buf[20] = ' ';
+
+ /* timezone */
+ if (timezone_offset >= 0)
+ buf[21] = '+';
+ else {
+ buf[21] = '-';
+ timezone_offset = -timezone_offset;
+ }
+ buf[22] = (timezone_offset / 600) + '0';
+ buf[23] = ((timezone_offset / 60) % 10) + '0';
+ buf[24] = ((timezone_offset % 60) / 10) + '0';
+ buf[25] = (timezone_offset % 10) + '0';
+ buf[26] = '\0';
+
+ return buf;
+}
+
+const char *imap_to_datetime(time_t timestamp)
+{
+ struct tm *tm;
+ int timezone_offset;
+
+ tm = localtime(&timestamp);
+ timezone_offset = utc_offset(tm, timestamp);
+ return imap_to_datetime_tm(tm, timezone_offset);
+}
+
+const char *imap_to_datetime_tz(time_t timestamp, int timezone_offset)
+{
+ const struct tm *tm;
+ time_t adjusted = timestamp + timezone_offset*60;
+
+ tm = gmtime(&adjusted);
+ return imap_to_datetime_tm(tm, timezone_offset);
+}
+
+bool imap_to_date(time_t timestamp, const char **str_r)
+{
+ const struct tm *tm;
+ char *buf;
+
+ tm = localtime(&timestamp);
+
+ buf = t_malloc0(12);
+ imap_to_date_tm(buf, tm);
+ *str_r = buf;
+ return tm->tm_hour == 0 && tm->tm_min == 0 && tm->tm_sec == 0;
+}
diff --git a/src/lib-imap/imap-date.h b/src/lib-imap/imap-date.h
new file mode 100644
index 0000000..b8ea982
--- /dev/null
+++ b/src/lib-imap/imap-date.h
@@ -0,0 +1,25 @@
+#ifndef IMAP_DATE_H
+#define IMAP_DATE_H
+
+/* Parses IMAP date/time string and returns TRUE if the string is valid.
+ time_t is filled with UTC date. timezone_offset is filled with parsed
+ timezone. If no timezone is given, local timezone is assumed.
+
+ If date is too low or too high to fit to time_t, it's set to lowest/highest
+ allowed value. This allows you to use the value directly for comparing
+ timestamps. */
+bool imap_parse_date(const char *str, time_t *timestamp_r);
+bool imap_parse_datetime(const char *str, time_t *timestamp_r,
+ int *timezone_offset_r);
+
+/* Returns given UTC timestamp as IMAP date-time string in local timezone. */
+const char *imap_to_datetime(time_t timestamp);
+/* Returns given UTC timestamp as IMAP date-time string in given timezone. */
+const char *imap_to_datetime_tz(time_t timestamp, int timezone_offset);
+
+/* Returns TRUE if timestamp was successfully converted to a date,
+ FALSE if time would have been required as well (but the string is still
+ returned). */
+bool imap_to_date(time_t timestamp, const char **str_r);
+
+#endif
diff --git a/src/lib-imap/imap-envelope.c b/src/lib-imap/imap-envelope.c
new file mode 100644
index 0000000..87297f4
--- /dev/null
+++ b/src/lib-imap/imap-envelope.c
@@ -0,0 +1,248 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "str.h"
+#include "message-address.h"
+#include "message-part-data.h"
+#include "message-parser.h"
+#include "imap-parser.h"
+#include "imap-envelope.h"
+#include "imap-quote.h"
+
+/*
+ * Envelope write
+ */
+
+static void imap_write_address(string_t *str, struct message_address *addr)
+{
+ if (addr == NULL) {
+ str_append(str, "NIL");
+ return;
+ }
+
+ str_append_c(str, '(');
+ while (addr != NULL) {
+ str_append_c(str, '(');
+ if (addr->name == NULL)
+ str_append(str, "NIL");
+ else {
+ imap_append_string_for_humans(str,
+ (const void *)addr->name, strlen(addr->name));
+ }
+ str_append_c(str, ' ');
+ imap_append_nstring(str, addr->route);
+ str_append_c(str, ' ');
+ imap_append_nstring(str, addr->mailbox);
+ str_append_c(str, ' ');
+ imap_append_nstring(str, addr->domain);
+ str_append_c(str, ')');
+
+ addr = addr->next;
+ }
+ str_append_c(str, ')');
+}
+
+void imap_envelope_write(struct message_part_envelope *data,
+ string_t *str)
+{
+#define NVL(str, nullstr) ((str) != NULL ? (str) : (nullstr))
+ static const char *empty_envelope =
+ "NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL";
+
+ if (data == NULL) {
+ str_append(str, empty_envelope);
+ return;
+ }
+
+ imap_append_nstring_nolf(str, data->date);
+ str_append_c(str, ' ');
+ if (data->subject == NULL)
+ str_append(str, "NIL");
+ else {
+ imap_append_string_for_humans(str,
+ (const unsigned char *)data->subject,
+ strlen(data->subject));
+ }
+
+ str_append_c(str, ' ');
+ imap_write_address(str, data->from);
+ str_append_c(str, ' ');
+ imap_write_address(str, NVL(data->sender, data->from));
+ str_append_c(str, ' ');
+ imap_write_address(str, NVL(data->reply_to, data->from));
+ str_append_c(str, ' ');
+ imap_write_address(str, data->to);
+ str_append_c(str, ' ');
+ imap_write_address(str, data->cc);
+ str_append_c(str, ' ');
+ imap_write_address(str, data->bcc);
+
+ str_append_c(str, ' ');
+ imap_append_nstring_nolf(str, data->in_reply_to);
+ str_append_c(str, ' ');
+ imap_append_nstring_nolf(str, data->message_id);
+}
+
+/*
+ * ENVELOPE parsing
+ */
+
+static bool
+imap_envelope_parse_address(const struct imap_arg *arg,
+ pool_t pool, struct message_address **addr_r)
+{
+ struct message_address *addr;
+ const struct imap_arg *list_args;
+ const char *name, *route, *mailbox, *domain;
+ unsigned int list_count;
+
+ if (!imap_arg_get_list_full(arg, &list_args, &list_count))
+ return FALSE;
+
+ /* we require 4 arguments, strings or NILs */
+ if (list_count < 4)
+ return FALSE;
+
+ if (!imap_arg_get_nstring(&list_args[0], &name))
+ return FALSE;
+ if (!imap_arg_get_nstring(&list_args[1], &route))
+ return FALSE;
+ if (!imap_arg_get_nstring(&list_args[2], &mailbox))
+ return FALSE;
+ if (!imap_arg_get_nstring(&list_args[3], &domain))
+ return FALSE;
+
+ addr = p_new(pool, struct message_address, 1);
+ addr->name = p_strdup(pool, name);
+ addr->route = p_strdup(pool, route);
+ addr->mailbox = p_strdup(pool, mailbox);
+ addr->domain = p_strdup(pool, domain);
+
+ *addr_r = addr;
+ return TRUE;
+}
+
+static bool
+imap_envelope_parse_addresses(const struct imap_arg *arg,
+ pool_t pool, struct message_address **addrs_r)
+{
+ struct message_address *first, *addr, *prev;
+ const struct imap_arg *list_args;
+
+ if (arg->type == IMAP_ARG_NIL) {
+ *addrs_r = NULL;
+ return TRUE;
+ }
+
+ if (!imap_arg_get_list(arg, &list_args))
+ return FALSE;
+
+ first = addr = prev = NULL;
+ for (; !IMAP_ARG_IS_EOL(list_args); list_args++) {
+ if (!imap_envelope_parse_address
+ (list_args, pool, &addr))
+ return FALSE;
+ if (first == NULL)
+ first = addr;
+ if (prev != NULL)
+ prev->next = addr;
+ prev = addr;
+ }
+
+ *addrs_r = first;
+ return TRUE;
+}
+
+bool imap_envelope_parse_args(const struct imap_arg *args,
+ pool_t pool, struct message_part_envelope **envlp_r,
+ const char **error_r)
+{
+ struct message_part_envelope *envlp;
+
+ envlp = p_new(pool, struct message_part_envelope, 1);
+
+ if (!imap_arg_get_nstring(args++, &envlp->date)) {
+ *error_r = "Invalid date field";
+ return FALSE;
+ }
+ envlp->date = p_strdup(pool, envlp->date);
+
+ if (!imap_arg_get_nstring(args++, &envlp->subject)) {
+ *error_r = "Invalid subject field";
+ return FALSE;
+ }
+ envlp->subject = p_strdup(pool, envlp->subject);
+
+ if (!imap_envelope_parse_addresses(args++, pool, &envlp->from)) {
+ *error_r = "Invalid from field";
+ return FALSE;
+ }
+ if (!imap_envelope_parse_addresses(args++, pool, &envlp->sender)) {
+ *error_r = "Invalid sender field";
+ return FALSE;
+ }
+ if (!imap_envelope_parse_addresses(args++, pool, &envlp->reply_to)) {
+ *error_r = "Invalid reply_to field";
+ return FALSE;
+ }
+ if (!imap_envelope_parse_addresses(args++, pool, &envlp->to)) {
+ *error_r = "Invalid to field";
+ return FALSE;
+ }
+ if (!imap_envelope_parse_addresses(args++, pool, &envlp->cc)) {
+ *error_r = "Invalid cc field";
+ return FALSE;
+ }
+ if (!imap_envelope_parse_addresses(args++, pool, &envlp->bcc)) {
+ *error_r = "Invalid bcc field";
+ return FALSE;
+ }
+
+ if (!imap_arg_get_nstring(args++, &envlp->in_reply_to)) {
+ *error_r = "Invalid in_reply_to field";
+ return FALSE;
+ }
+ envlp->in_reply_to = p_strdup(pool, envlp->in_reply_to);
+
+ if (!imap_arg_get_nstring(args++, &envlp->message_id)) {
+ *error_r = "Invalid message_id field";
+ return FALSE;
+ }
+ envlp->message_id = p_strdup(pool, envlp->message_id);
+
+ *envlp_r = envlp;
+ return TRUE;
+}
+
+bool imap_envelope_parse(const char *envelope,
+ pool_t pool, struct message_part_envelope **envlp_r,
+ const char **error_r)
+{
+ struct istream *input;
+ struct imap_parser *parser;
+ const struct imap_arg *args;
+ int ret;
+ bool success;
+
+ input = i_stream_create_from_data(envelope, strlen(envelope));
+ (void)i_stream_read(input);
+
+ parser = imap_parser_create(input, NULL, SIZE_MAX);
+ ret = imap_parser_finish_line(parser, 0,
+ IMAP_PARSE_FLAG_LITERAL_TYPE, &args);
+ if (ret < 0) {
+ *error_r = t_strdup_printf("IMAP parser failed: %s",
+ imap_parser_get_error(parser, NULL));
+ success = FALSE;
+ } else if (ret == 0) {
+ *error_r = "Empty envelope";
+ success = FALSE;
+ } else {
+ success = imap_envelope_parse_args(args, pool, envlp_r, error_r);
+ }
+
+ imap_parser_unref(&parser);
+ i_stream_destroy(&input);
+ return success;
+}
diff --git a/src/lib-imap/imap-envelope.h b/src/lib-imap/imap-envelope.h
new file mode 100644
index 0000000..f9e4c0b
--- /dev/null
+++ b/src/lib-imap/imap-envelope.h
@@ -0,0 +1,20 @@
+#ifndef IMAP_ENVELOPE_H
+#define IMAP_ENVELOPE_H
+
+struct imap_arg;
+struct message_part_envelope;
+
+/* Write envelope to given string */
+void imap_envelope_write(struct message_part_envelope *data,
+ string_t *str);
+
+/* Parse envelope from arguments */
+bool imap_envelope_parse_args(const struct imap_arg *args,
+ pool_t pool, struct message_part_envelope **envlp_r,
+ const char **error_r);
+/* Parse envelope from string */
+bool imap_envelope_parse(const char *envelope,
+ pool_t pool, struct message_part_envelope **envlp_r,
+ const char **error_r);
+
+#endif
diff --git a/src/lib-imap/imap-id.c b/src/lib-imap/imap-id.c
new file mode 100644
index 0000000..f873c57
--- /dev/null
+++ b/src/lib-imap/imap-id.c
@@ -0,0 +1,173 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "imap-parser.h"
+#include "imap-quote.h"
+#include "imap-id.h"
+#include "dovecot-version.h"
+
+#ifdef HAVE_SYS_UTSNAME_H
+# include <sys/utsname.h>
+#endif
+
+#ifdef HAVE_UNAME
+static struct utsname utsname_result;
+static bool utsname_set = FALSE;
+
+static const char *imap_id_get_uname(const char *key)
+{
+ if (!utsname_set) {
+ utsname_set = TRUE;
+ if (uname(&utsname_result) < 0) {
+ i_error("uname() failed: %m");
+ i_zero(&utsname_result);
+ }
+ }
+
+ if (strcasecmp(key, "os") == 0)
+ return utsname_result.sysname;
+ if (strcasecmp(key, "os-version") == 0)
+ return utsname_result.release;
+ return NULL;
+}
+#endif
+
+static const char *imap_id_get_default(const char *key)
+{
+ if (strcasecmp(key, "name") == 0)
+ return PACKAGE_NAME;
+ if (strcasecmp(key, "version") == 0)
+ return PACKAGE_VERSION;
+ if (strcasecmp(key, "revision") == 0)
+ return DOVECOT_REVISION;
+ if (strcasecmp(key, "support-url") == 0)
+ return PACKAGE_WEBPAGE;
+ if (strcasecmp(key, "support-email") == 0)
+ return PACKAGE_BUGREPORT;
+#ifdef HAVE_UNAME
+ return imap_id_get_uname(key);
+#endif
+}
+
+static const char *
+imap_id_reply_generate_from_imap_args(const struct imap_arg *args)
+{
+ string_t *str;
+ const char *key, *value;
+
+ if (IMAP_ARG_IS_EOL(args))
+ return "NIL";
+
+ str = t_str_new(256);
+ str_append_c(str, '(');
+ for (; !IMAP_ARG_IS_EOL(args); args++) {
+ if (!imap_arg_get_astring(args, &key)) {
+ /* broken input */
+ if (IMAP_ARG_IS_EOL(&args[1]))
+ break;
+ args++;
+ } else {
+ /* key */
+ if (str_len(str) > 1)
+ str_append_c(str, ' ');
+ imap_append_quoted(str, key);
+ str_append_c(str, ' ');
+ /* value */
+ if (IMAP_ARG_IS_EOL(&args[1])) {
+ str_append(str, "NIL");
+ break;
+ }
+ args++;
+ if (!imap_arg_get_astring(args, &value))
+ value = NULL;
+ else {
+ if (strcmp(value, "*") == 0)
+ value = imap_id_get_default(key);
+ }
+ imap_append_nstring(str, value);
+ }
+ }
+ if (str_len(str) == 1) {
+ /* broken */
+ return "NIL";
+ }
+ str_append_c(str, ')');
+ return str_c(str);
+}
+
+const char *imap_id_reply_generate(const char *settings)
+{
+ struct istream *input;
+ struct imap_parser *parser;
+ const struct imap_arg *args;
+ const char *ret;
+
+ if (settings == NULL)
+ return "NIL";
+
+ input = i_stream_create_from_data(settings, strlen(settings));
+ (void)i_stream_read(input);
+
+ parser = imap_parser_create(input, NULL, SIZE_MAX);
+ if (imap_parser_finish_line(parser, 0, 0, &args) <= 0)
+ ret = "NIL";
+ else
+ ret = imap_id_reply_generate_from_imap_args(args);
+
+ imap_parser_unref(&parser);
+ i_stream_destroy(&input);
+ return ret;
+}
+
+void imap_id_log_reply_append(string_t *reply, const char *key,
+ const char *value)
+{
+ if (str_len(reply) > 0)
+ str_append(reply, ", ");
+ str_append(reply, str_sanitize(key, IMAP_ID_KEY_MAX_LEN));
+ str_append_c(reply, '=');
+ str_append(reply, value == NULL ? "NIL" : str_sanitize(value, 80));
+}
+
+const char *imap_id_args_get_log_reply(const struct imap_arg *args,
+ const char *settings)
+{
+ const char *const *keys, *key, *value;
+ string_t *reply;
+ bool log_all;
+
+ if (settings == NULL || *settings == '\0')
+ return NULL;
+ if (!imap_arg_get_list(args, &args))
+ return NULL;
+
+ log_all = strcmp(settings, "*") == 0;
+ reply = t_str_new(256);
+ keys = t_strsplit_spaces(settings, " ");
+ while (!IMAP_ARG_IS_EOL(&args[0]) &&
+ !IMAP_ARG_IS_EOL(&args[1])) {
+ if (!imap_arg_get_string(args, &key)) {
+ /* broken input */
+ args += 2;
+ continue;
+ }
+ args++;
+ if (strlen(key) > 30) {
+ /* broken: ID spec requires fields to be max. 30
+ octets */
+ args++;
+ continue;
+ }
+
+ if (log_all || str_array_icase_find(keys, key)) {
+ if (!imap_arg_get_nstring(args, &value))
+ value = "";
+ imap_id_log_reply_append(reply, key, value);
+ }
+ args++;
+ }
+ return str_len(reply) == 0 ? NULL : str_c(reply);
+}
diff --git a/src/lib-imap/imap-id.h b/src/lib-imap/imap-id.h
new file mode 100644
index 0000000..853153a
--- /dev/null
+++ b/src/lib-imap/imap-id.h
@@ -0,0 +1,19 @@
+#ifndef IMAP_ID_H
+#define IMAP_ID_H
+
+struct imap_arg;
+
+/* RFC 2971 says keys are max. 30 octets */
+#define IMAP_ID_KEY_MAX_LEN 30
+
+/* Return ID reply based on given settings. */
+const char *imap_id_reply_generate(const char *settings);
+/* Return a line that should be logged based on given args and settings.
+ Returns NULL if nothing should be logged. */
+const char *imap_id_args_get_log_reply(const struct imap_arg *args,
+ const char *settings);
+/* Append [, ]key=value to the reply sanitized. */
+void imap_id_log_reply_append(string_t *reply, const char *key,
+ const char *value);
+
+#endif
diff --git a/src/lib-imap/imap-keepalive.c b/src/lib-imap/imap-keepalive.c
new file mode 100644
index 0000000..0756ce8
--- /dev/null
+++ b/src/lib-imap/imap-keepalive.c
@@ -0,0 +1,47 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "crc32.h"
+#include "net.h"
+#include "imap-keepalive.h"
+
+#include <time.h>
+
+static bool imap_remote_ip_is_usable(const struct ip_addr *ip)
+{
+ unsigned int addr;
+
+ if (ip->family == 0)
+ return FALSE;
+ if (ip->family == AF_INET) {
+#define IP4(a,b,c,d) ((unsigned)(a)<<24|(unsigned)(b)<<16|(unsigned)(c)<<8|(unsigned)(d))
+ addr = ip->u.ip4.s_addr;
+ if (addr >= IP4(10,0,0,0) && addr <= IP4(10,255,255,255))
+ return FALSE; /* 10/8 */
+ if (addr >= IP4(192,168,0,0) && addr <= IP4(192,168,255,255))
+ return FALSE; /* 192.168/16 */
+ if (addr >= IP4(172,16,0,0) && addr <= IP4(172,31,255,255))
+ return FALSE; /* 172.16/12 */
+ if (addr >= IP4(127,0,0,0) && addr <= IP4(127,255,255,255))
+ return FALSE; /* 127/8 */
+#undef IP4
+ }
+ else if (ip->family == AF_INET6) {
+ addr = ip->u.ip6.s6_addr[0];
+ if (addr == 0xfc || addr == 0xfd)
+ return FALSE; /* fc00::/7 */
+ }
+ return TRUE;
+}
+
+unsigned int
+imap_keepalive_interval_msecs(const char *username, const struct ip_addr *ip,
+ unsigned int interval_secs)
+{
+ unsigned int client_hash;
+
+ client_hash = ip != NULL && imap_remote_ip_is_usable(ip) ?
+ net_ip_hash(ip) : crc32_str(username);
+ interval_secs -= (time(NULL) + client_hash) % interval_secs;
+ return interval_secs * 1000;
+}
diff --git a/src/lib-imap/imap-keepalive.h b/src/lib-imap/imap-keepalive.h
new file mode 100644
index 0000000..238cdbd
--- /dev/null
+++ b/src/lib-imap/imap-keepalive.h
@@ -0,0 +1,24 @@
+#ifndef IMAP_KEEPALIVE_H
+#define IMAP_KEEPALIVE_H
+
+/* This function can be used to set IMAP IDLE keepalive notification timeout
+ interval so that the client gets the keepalive notifications at exactly the
+ same time for all the IMAP connections. This helps to reduce battery usage
+ in mobile devices.
+
+ One problem with this is that we don't really want to send the notifications
+ to everyone at the same time, because it would cause huge peaks of activity.
+ Basing the notifications on the username works well for one account, but
+ basing it on the IP address allows the client to get all of the
+ notifications at the same time for multiple accounts as well (of course
+ assuming Dovecot is running on all the servers :)
+
+ One potential downside to using IP is that if a proxy hides the client's IP
+ address, the notifications are sent to everyone at the same time. This can
+ be avoided by using a properly configured Dovecot proxy, but we'll also try
+ to avoid this by not doing it for the commonly used intranet IP ranges. */
+unsigned int
+imap_keepalive_interval_msecs(const char *username, const struct ip_addr *ip,
+ unsigned int interval_secs);
+
+#endif
diff --git a/src/lib-imap/imap-match.c b/src/lib-imap/imap-match.c
new file mode 100644
index 0000000..8603e00
--- /dev/null
+++ b/src/lib-imap/imap-match.c
@@ -0,0 +1,382 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+/* imap_match_init() logic originates from Cyrus, but the code is fully
+ rewritten. */
+
+#include "lib.h"
+#include "array.h"
+#include "imap-match.h"
+
+#include <ctype.h>
+
+struct imap_match_pattern {
+ const char *pattern;
+ bool inboxcase;
+};
+
+struct imap_match_glob {
+ pool_t pool;
+
+ struct imap_match_pattern *patterns;
+
+ char sep;
+ char patterns_data[FLEXIBLE_ARRAY_MEMBER];
+};
+
+struct imap_match_context {
+ const char *inboxcase_end;
+
+ char sep;
+ bool inboxcase;
+};
+
+/* name of "INBOX" - must not have repeated substrings */
+static const char inbox[] = "INBOX";
+#define INBOXLEN (sizeof(inbox) - 1)
+
+struct imap_match_glob *
+imap_match_init(pool_t pool, const char *pattern,
+ bool inboxcase, char separator)
+{
+ const char *patterns[2];
+
+ patterns[0] = pattern;
+ patterns[1] = NULL;
+ return imap_match_init_multiple(pool, patterns, inboxcase, separator);
+}
+
+static const char *pattern_compress(const char *pattern)
+{
+ char *dest, *ret;
+
+ dest = ret = t_strdup_noconst(pattern);
+
+ /* @UNSAFE: compress the pattern */
+ while (*pattern != '\0') {
+ if (*pattern == '*' || *pattern == '%') {
+ /* remove duplicate hierarchy wildcards */
+ while (*pattern == '%') pattern++;
+
+ /* "%*" -> "*" */
+ if (*pattern == '*') {
+ /* remove duplicate wildcards */
+ while (*pattern == '*' || *pattern == '%')
+ pattern++;
+ *dest++ = '*';
+ } else {
+ *dest++ = '%';
+ }
+ } else {
+ *dest++ = *pattern++;
+ }
+ }
+ *dest = '\0';
+ return ret;
+}
+
+static bool pattern_is_inboxcase(const char *pattern, char separator)
+{
+ const char *p = pattern, *inboxp = inbox;
+
+ /* skip over exact matches */
+ while (*inboxp == i_toupper(*p) && *p != '\0') {
+ inboxp++; p++;
+ }
+ if (*p != '%') {
+ return *p == '*' || *p == separator ||
+ (*inboxp == '\0' && *p == '\0');
+ }
+
+ /* handle 'I%B%X' style checks */
+ for (; *p != '\0' && *p != '*' && *p != separator; p++) {
+ if (*p != '%') {
+ inboxp = strchr(inboxp, i_toupper(*p));
+ if (inboxp == NULL)
+ return FALSE;
+
+ if (*++inboxp == '\0') {
+ /* now check that it doesn't end with
+ any invalid chars */
+ if (*++p == '%') p++;
+ if (*p != '\0' && *p != '*' &&
+ *p != separator)
+ return FALSE;
+ break;
+ }
+ }
+ }
+ return TRUE;
+}
+
+static struct imap_match_glob *
+imap_match_init_multiple_real(pool_t pool, const char *const *patterns,
+ bool inboxcase, char separator)
+{
+ struct imap_match_glob *glob;
+ struct imap_match_pattern *match_patterns;
+ unsigned int i, patterns_count;
+ size_t len, pos, patterns_data_len = 0;
+
+ patterns_count = str_array_length(patterns);
+ match_patterns = p_new(pool, struct imap_match_pattern,
+ patterns_count + 1);
+
+ /* compress the patterns */
+ for (i = 0; i < patterns_count; i++) {
+ match_patterns[i].pattern = pattern_compress(patterns[i]);
+ match_patterns[i].inboxcase = inboxcase &&
+ pattern_is_inboxcase(match_patterns[i].pattern,
+ separator);
+
+ patterns_data_len += strlen(match_patterns[i].pattern) + 1;
+ }
+ patterns_count = i;
+
+ /* now we know how much memory we need */
+ glob = p_malloc(pool, sizeof(struct imap_match_glob) +
+ patterns_data_len);
+ glob->pool = pool;
+ glob->sep = separator;
+
+ /* copy pattern strings to our allocated memory */
+ for (i = 0, pos = 0; i < patterns_count; i++) {
+ len = strlen(match_patterns[i].pattern) + 1;
+ i_assert(pos + len <= patterns_data_len);
+
+ /* @UNSAFE */
+ memcpy(glob->patterns_data + pos,
+ match_patterns[i].pattern, len);
+ match_patterns[i].pattern = glob->patterns_data + pos;
+ pos += len;
+ }
+ glob->patterns = match_patterns;
+ return glob;
+}
+
+struct imap_match_glob *
+imap_match_init_multiple(pool_t pool, const char *const *patterns,
+ bool inboxcase, char separator)
+{
+ struct imap_match_glob *glob;
+
+ if (pool->datastack_pool) {
+ return imap_match_init_multiple_real(pool, patterns,
+ inboxcase, separator);
+ }
+ T_BEGIN {
+ glob = imap_match_init_multiple_real(pool, patterns,
+ inboxcase, separator);
+ } T_END;
+ return glob;
+}
+
+void imap_match_deinit(struct imap_match_glob **glob)
+{
+ if (glob == NULL || *glob == NULL)
+ return;
+ p_free((*glob)->pool, (*glob)->patterns);
+ p_free((*glob)->pool, *glob);
+ *glob = NULL;
+}
+
+static struct imap_match_glob *
+imap_match_dup_real(pool_t pool, const struct imap_match_glob *glob)
+{
+ ARRAY_TYPE(const_string) patterns;
+ const struct imap_match_pattern *p;
+ bool inboxcase = FALSE;
+
+ t_array_init(&patterns, 8);
+ for (p = glob->patterns; p->pattern != NULL; p++) {
+ if (p->inboxcase)
+ inboxcase = TRUE;
+ array_push_back(&patterns, &p->pattern);
+ }
+ array_append_zero(&patterns);
+ return imap_match_init_multiple_real(pool, array_front(&patterns),
+ inboxcase, glob->sep);
+}
+
+struct imap_match_glob *
+imap_match_dup(pool_t pool, const struct imap_match_glob *glob)
+{
+ struct imap_match_glob *new_glob;
+
+ if (pool->datastack_pool) {
+ return imap_match_dup_real(pool, glob);
+ } else {
+ T_BEGIN {
+ new_glob = imap_match_dup_real(pool, glob);
+ } T_END;
+ return new_glob;
+ }
+}
+
+bool imap_match_globs_equal(const struct imap_match_glob *glob1,
+ const struct imap_match_glob *glob2)
+{
+ const struct imap_match_pattern *p1 = glob1->patterns;
+ const struct imap_match_pattern *p2 = glob2->patterns;
+
+ if (glob1->sep != glob2->sep)
+ return FALSE;
+
+ for (; p1->pattern != NULL && p2->pattern != NULL; p1++, p2++) {
+ if (strcmp(p1->pattern, p2->pattern) != 0)
+ return FALSE;
+ if (p1->inboxcase != p2->inboxcase)
+ return FALSE;
+ }
+ return p1->pattern == p2->pattern;
+}
+
+#define CMP_CUR_CHR(ctx, data, pattern) \
+ (*(data) == *(pattern) || \
+ (i_toupper(*(data)) == i_toupper(*(pattern)) && \
+ (data) < (ctx)->inboxcase_end))
+
+static enum imap_match_result
+match_sub(struct imap_match_context *ctx, const char **data_p,
+ const char **pattern_p)
+{
+ enum imap_match_result ret, match;
+ unsigned int i;
+ const char *data = *data_p, *pattern = *pattern_p;
+
+ /* match all non-wildcards */
+ i = 0;
+ while (pattern[i] != '\0' && pattern[i] != '*' && pattern[i] != '%') {
+ if (!CMP_CUR_CHR(ctx, data+i, pattern+i)) {
+ if (data[i] != '\0')
+ return IMAP_MATCH_NO;
+ if (pattern[i] == ctx->sep)
+ return IMAP_MATCH_CHILDREN;
+ if (i > 0 && pattern[i-1] == ctx->sep) {
+ /* data="foo/" pattern = "foo/bar/%" */
+ return IMAP_MATCH_CHILDREN;
+ }
+ return IMAP_MATCH_NO;
+ }
+ i++;
+ }
+ data += i;
+ pattern += i;
+
+ if (*data == '\0' && *data_p != data && data[-1] == ctx->sep &&
+ *pattern != '\0') {
+ /* data="/" pattern="/%..." */
+ match = IMAP_MATCH_CHILDREN;
+ } else {
+ match = IMAP_MATCH_NO;
+ }
+ while (*pattern == '%') {
+ pattern++;
+
+ if (*pattern == '\0') {
+ /* match, if this is the last hierarchy */
+ while (*data != '\0' && *data != ctx->sep)
+ data++;
+ break;
+ }
+
+ /* skip over this hierarchy */
+ while (*data != '\0') {
+ if (CMP_CUR_CHR(ctx, data, pattern)) {
+ ret = match_sub(ctx, &data, &pattern);
+ if (ret == IMAP_MATCH_YES)
+ break;
+
+ match |= ret;
+ }
+
+ if (*data == ctx->sep)
+ break;
+
+ data++;
+ }
+ }
+
+ if (*pattern != '*') {
+ if (*data == '\0' && *pattern != '\0') {
+ if (*pattern == ctx->sep)
+ match |= IMAP_MATCH_CHILDREN;
+ return match;
+ }
+
+ if (*data != '\0') {
+ if (*pattern == '\0' && *data == ctx->sep)
+ match |= IMAP_MATCH_PARENT;
+ return match;
+ }
+ }
+
+ *data_p = data;
+ *pattern_p = pattern;
+ return IMAP_MATCH_YES;
+}
+
+static enum imap_match_result
+imap_match_pattern(struct imap_match_context *ctx,
+ const char *data, const char *pattern)
+{
+ enum imap_match_result ret, match;
+
+ ctx->inboxcase_end = data;
+ if (ctx->inboxcase && strncasecmp(data, inbox, INBOXLEN) == 0 &&
+ (data[INBOXLEN] == '\0' || data[INBOXLEN] == ctx->sep)) {
+ /* data begins with INBOX/, use case-insensitive comparison
+ for it */
+ ctx->inboxcase_end += INBOXLEN;
+ }
+
+ if (*pattern != '*') {
+ /* handle the pattern up to the first '*' */
+ ret = match_sub(ctx, &data, &pattern);
+ if (ret != IMAP_MATCH_YES || *pattern == '\0')
+ return ret;
+ }
+
+ match = IMAP_MATCH_CHILDREN;
+ while (*pattern == '*') {
+ pattern++;
+
+ if (*pattern == '\0')
+ return IMAP_MATCH_YES;
+
+ while (*data != '\0') {
+ if (CMP_CUR_CHR(ctx, data, pattern)) {
+ ret = match_sub(ctx, &data, &pattern);
+ if (ret == IMAP_MATCH_YES)
+ break;
+ match |= ret;
+ }
+
+ data++;
+ }
+ }
+
+ return *data == '\0' && *pattern == '\0' ?
+ IMAP_MATCH_YES : match;
+}
+
+enum imap_match_result
+imap_match(struct imap_match_glob *glob, const char *data)
+{
+ struct imap_match_context ctx;
+ unsigned int i;
+ enum imap_match_result ret, match;
+
+ match = IMAP_MATCH_NO;
+ ctx.sep = glob->sep;
+ for (i = 0; glob->patterns[i].pattern != NULL; i++) {
+ ctx.inboxcase = glob->patterns[i].inboxcase;
+
+ ret = imap_match_pattern(&ctx, data, glob->patterns[i].pattern);
+ if (ret == IMAP_MATCH_YES)
+ return IMAP_MATCH_YES;
+
+ match |= ret;
+ }
+
+ return match;
+}
diff --git a/src/lib-imap/imap-match.h b/src/lib-imap/imap-match.h
new file mode 100644
index 0000000..f2e5b51
--- /dev/null
+++ b/src/lib-imap/imap-match.h
@@ -0,0 +1,42 @@
+#ifndef IMAP_MATCH_H
+#define IMAP_MATCH_H
+
+enum imap_match_result {
+ IMAP_MATCH_NO = 0x00, /* definite non-match */
+ IMAP_MATCH_YES = 0x01, /* match */
+
+ /* YES and NO are returned alone, but CHILDREN and PARENT may be
+ returned both (eg. "foo*bar" vs. "foobar/baz") */
+
+ /* non-match, but its children could match (eg. "box" vs "box/%") */
+ IMAP_MATCH_CHILDREN = 0x02,
+ /* non-match, but one of its parents does match. This should often be
+ handled with YES matches, because when listing for "%" and "box/foo"
+ exists but "box" doesn't, you should still list "box" as
+ (Nonexistent HasChildren) mailbox. */
+ IMAP_MATCH_PARENT = 0x04
+};
+
+struct imap_match_glob;
+
+/* If inboxcase is TRUE, the "INBOX" string at the beginning of line is
+ compared case-insensitively */
+struct imap_match_glob *
+imap_match_init(pool_t pool, const char *pattern,
+ bool inboxcase, char separator);
+struct imap_match_glob *
+imap_match_init_multiple(pool_t pool, const char *const *patterns,
+ bool inboxcase, char separator);
+void imap_match_deinit(struct imap_match_glob **glob);
+
+struct imap_match_glob *
+imap_match_dup(pool_t pool, const struct imap_match_glob *glob);
+/* Returns TRUE if two globs were created with same init() parameters
+ (but inboxcase is ignored if no patterns can match INBOX) */
+bool imap_match_globs_equal(const struct imap_match_glob *glob1,
+ const struct imap_match_glob *glob2);
+
+enum imap_match_result
+imap_match(struct imap_match_glob *glob, const char *data);
+
+#endif
diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c
new file mode 100644
index 0000000..2deb75f
--- /dev/null
+++ b/src/lib-imap/imap-parser.c
@@ -0,0 +1,1023 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "ostream.h"
+#include "strescape.h"
+#include "imap-parser.h"
+
+/* We use this macro to read atoms from input. It should probably contain
+ everything some day, but for now we can't handle some input otherwise:
+
+ ']' is required for parsing section (FETCH BODY[])
+ '%', '*' and ']' are valid list-chars for LIST patterns
+ '\' is used in flags */
+#define IS_ATOM_PARSER_INPUT(c) \
+ ((c) == '(' || (c) == ')' || (c) == '{' || \
+ (c) == '"' || (c) <= 32 || (c) == 0x7f)
+
+#define is_linebreak(c) \
+ ((c) == '\r' || (c) == '\n')
+
+#define LIST_INIT_COUNT 7
+
+enum arg_parse_type {
+ ARG_PARSE_NONE = 0,
+ ARG_PARSE_ATOM,
+ ARG_PARSE_STRING,
+ ARG_PARSE_LITERAL,
+ ARG_PARSE_LITERAL8,
+ ARG_PARSE_LITERAL_DATA,
+ ARG_PARSE_LITERAL_DATA_FORCED,
+ ARG_PARSE_TEXT
+};
+
+struct imap_parser {
+ /* permanent */
+ int refcount;
+ pool_t pool;
+ struct istream *input;
+ struct ostream *output;
+ size_t max_line_size;
+ enum imap_parser_flags flags;
+
+ /* reset by imap_parser_reset(): */
+ size_t line_size;
+ ARRAY_TYPE(imap_arg_list) root_list;
+ ARRAY_TYPE(imap_arg_list) *cur_list;
+ struct imap_arg *list_arg;
+
+ enum arg_parse_type cur_type;
+ size_t cur_pos; /* parser position in input buffer */
+ bool cur_resp_text; /* we're parsing [resp-text-code] */
+
+ int str_first_escape; /* ARG_PARSE_STRING: index to first '\' */
+ uoff_t literal_size; /* ARG_PARSE_LITERAL: string size */
+
+ enum imap_parser_error error;
+ const char *error_msg;
+
+ bool literal_minus:1;
+ bool literal_skip_crlf:1;
+ bool literal_nonsync:1;
+ bool literal8:1;
+ bool literal_size_return:1;
+ bool eol:1;
+ bool args_added_extra_eol:1;
+ bool fatal_error:1;
+};
+
+struct imap_parser *
+imap_parser_create(struct istream *input, struct ostream *output,
+ size_t max_line_size)
+{
+ struct imap_parser *parser;
+
+ parser = i_new(struct imap_parser, 1);
+ parser->refcount = 1;
+ parser->pool = pool_alloconly_create(MEMPOOL_GROWING"IMAP parser",
+ 1024);
+ parser->input = input;
+ parser->output = output;
+ parser->max_line_size = max_line_size;
+
+ p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT);
+ parser->cur_list = &parser->root_list;
+ return parser;
+}
+
+void imap_parser_ref(struct imap_parser *parser)
+{
+ i_assert(parser->refcount > 0);
+
+ parser->refcount++;
+}
+
+void imap_parser_unref(struct imap_parser **_parser)
+{
+ struct imap_parser *parser = *_parser;
+
+ *_parser = NULL;
+
+ i_assert(parser->refcount > 0);
+ if (--parser->refcount > 0)
+ return;
+
+ pool_unref(&parser->pool);
+ i_free(parser);
+}
+
+void imap_parser_enable_literal_minus(struct imap_parser *parser)
+{
+ parser->literal_minus = TRUE;
+}
+
+void imap_parser_reset(struct imap_parser *parser)
+{
+ p_clear(parser->pool);
+
+ parser->line_size = 0;
+
+ p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT);
+ parser->cur_list = &parser->root_list;
+ parser->list_arg = NULL;
+
+ parser->cur_type = ARG_PARSE_NONE;
+ parser->cur_pos = 0;
+ parser->cur_resp_text = FALSE;
+
+ parser->str_first_escape = 0;
+ parser->literal_size = 0;
+
+ parser->error = IMAP_PARSE_ERROR_NONE;
+ parser->error_msg = NULL;
+
+ parser->literal_skip_crlf = FALSE;
+ parser->eol = FALSE;
+ parser->args_added_extra_eol = FALSE;
+ parser->literal_size_return = FALSE;
+}
+
+void imap_parser_set_streams(struct imap_parser *parser, struct istream *input,
+ struct ostream *output)
+{
+ parser->input = input;
+ parser->output = output;
+}
+
+const char *imap_parser_get_error(struct imap_parser *parser,
+ enum imap_parser_error *error_r)
+{
+ if (error_r != NULL)
+ *error_r = parser->error;
+ return parser->error_msg;
+}
+
+/* skip over everything parsed so far, plus the following whitespace */
+static bool imap_parser_skip_to_next(struct imap_parser *parser,
+ const unsigned char **data,
+ size_t *data_size)
+{
+ size_t i;
+
+ for (i = parser->cur_pos; i < *data_size; i++) {
+ if ((*data)[i] != ' ')
+ break;
+ }
+
+ parser->line_size += i;
+ i_stream_skip(parser->input, i);
+ parser->cur_pos = 0;
+
+ *data += i;
+ *data_size -= i;
+ return *data_size > 0;
+}
+
+static struct imap_arg *imap_arg_create(struct imap_parser *parser)
+{
+ struct imap_arg *arg;
+
+ arg = array_append_space(parser->cur_list);
+ arg->parent = parser->list_arg;
+ return arg;
+}
+
+static void imap_parser_open_list(struct imap_parser *parser)
+{
+ parser->list_arg = imap_arg_create(parser);
+ parser->list_arg->type = IMAP_ARG_LIST;
+ p_array_init(&parser->list_arg->_data.list, parser->pool,
+ LIST_INIT_COUNT);
+ parser->cur_list = &parser->list_arg->_data.list;
+
+ parser->cur_type = ARG_PARSE_NONE;
+}
+
+static bool imap_parser_close_list(struct imap_parser *parser)
+{
+ struct imap_arg *arg;
+
+ if (parser->list_arg == NULL) {
+ /* we're not inside list */
+ if ((parser->flags & IMAP_PARSE_FLAG_INSIDE_LIST) != 0) {
+ parser->eol = TRUE;
+ parser->cur_type = ARG_PARSE_NONE;
+ return TRUE;
+ }
+ parser->error_msg = "Unexpected ')'";
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ return FALSE;
+ }
+
+ arg = imap_arg_create(parser);
+ arg->type = IMAP_ARG_EOL;
+
+ parser->list_arg = parser->list_arg->parent;
+ if (parser->list_arg == NULL) {
+ parser->cur_list = &parser->root_list;
+ } else {
+ parser->cur_list = &parser->list_arg->_data.list;
+ }
+
+ parser->cur_type = ARG_PARSE_NONE;
+ return TRUE;
+}
+
+static char *
+imap_parser_strdup(struct imap_parser *parser,
+ const void *data, size_t len)
+{
+ char *ret;
+
+ ret = p_malloc(parser->pool, len + 1);
+ memcpy(ret, data, len);
+ return ret;
+}
+
+static void imap_parser_save_arg(struct imap_parser *parser,
+ const unsigned char *data, size_t size)
+{
+ struct imap_arg *arg;
+ char *str;
+
+ arg = imap_arg_create(parser);
+
+ switch (parser->cur_type) {
+ case ARG_PARSE_ATOM:
+ case ARG_PARSE_TEXT:
+ if (size == 3 && i_memcasecmp(data, "NIL", 3) == 0) {
+ /* NIL argument. it might be an actual NIL, but if
+ we're reading astring, it's an atom and we can't
+ lose its case. */
+ arg->type = IMAP_ARG_NIL;
+ } else {
+ /* simply save the string */
+ arg->type = IMAP_ARG_ATOM;
+ }
+ arg->_data.str = imap_parser_strdup(parser, data, size);
+ arg->str_len = size;
+ break;
+ case ARG_PARSE_STRING:
+ /* data is quoted and may contain escapes. */
+ i_assert(size > 0);
+
+ arg->type = IMAP_ARG_STRING;
+ str = p_strndup(parser->pool, data+1, size-1);
+
+ /* remove the escapes */
+ if (parser->str_first_escape >= 0 &&
+ (parser->flags & IMAP_PARSE_FLAG_NO_UNESCAPE) == 0)
+ (void)str_unescape(str);
+ arg->_data.str = str;
+ arg->str_len = strlen(str);
+ break;
+ case ARG_PARSE_LITERAL_DATA:
+ if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) != 0) {
+ /* save literal size */
+ arg->type = parser->literal_nonsync ?
+ IMAP_ARG_LITERAL_SIZE_NONSYNC :
+ IMAP_ARG_LITERAL_SIZE;
+ arg->_data.literal_size = parser->literal_size;
+ arg->literal8 = parser->literal8;
+ break;
+ }
+ /* fall through */
+ case ARG_PARSE_LITERAL_DATA_FORCED:
+ if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_TYPE) != 0)
+ arg->type = IMAP_ARG_LITERAL;
+ else
+ arg->type = IMAP_ARG_STRING;
+ arg->_data.str = imap_parser_strdup(parser, data, size);
+ arg->literal8 = parser->literal8;
+ arg->str_len = size;
+ break;
+ default:
+ i_unreached();
+ }
+
+ parser->cur_type = ARG_PARSE_NONE;
+}
+
+static bool is_valid_atom_char(struct imap_parser *parser, char chr)
+{
+ const char *error_msg;
+
+ if (IS_ATOM_PARSER_INPUT((unsigned char)chr))
+ error_msg = "Invalid characters in atom";
+ else if ((((unsigned char)chr) & 0x80) != 0)
+ error_msg = "8bit data in atom";
+ else
+ return TRUE;
+
+ if ((parser->flags & IMAP_PARSE_FLAG_ATOM_ALLCHARS) != 0)
+ return TRUE;
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = error_msg;
+ return FALSE;
+}
+
+static bool imap_parser_read_atom(struct imap_parser *parser,
+ const unsigned char *data, size_t data_size)
+{
+ size_t i;
+
+ /* read until we've found space, CR or LF. */
+ for (i = parser->cur_pos; i < data_size; i++) {
+ if (data[i] == ' ' || is_linebreak(data[i])) {
+ imap_parser_save_arg(parser, data, i);
+ break;
+ } else if (data[i] == ')') {
+ if (parser->list_arg != NULL ||
+ (parser->flags & IMAP_PARSE_FLAG_INSIDE_LIST) != 0) {
+ imap_parser_save_arg(parser, data, i);
+ break;
+ } else if ((parser->flags &
+ IMAP_PARSE_FLAG_ATOM_ALLCHARS) == 0) {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "Unexpected ')'";
+ return FALSE;
+ }
+ /* assume it's part of the atom */
+ } else if (!is_valid_atom_char(parser, data[i]))
+ return FALSE;
+ }
+
+ parser->cur_pos = i;
+ return parser->cur_type == ARG_PARSE_NONE;
+}
+
+static bool imap_parser_read_string(struct imap_parser *parser,
+ const unsigned char *data, size_t data_size)
+{
+ size_t i;
+
+ /* read until we've found non-escaped ", CR or LF */
+ for (i = parser->cur_pos; i < data_size; i++) {
+ if (data[i] == '"') {
+ imap_parser_save_arg(parser, data, i);
+
+ i++; /* skip the trailing '"' too */
+ break;
+ }
+
+ if (data[i] == '\0') {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "NULs not allowed in strings";
+ return FALSE;
+ }
+
+ if (data[i] == '\\') {
+ if (i+1 == data_size) {
+ /* known data ends with '\' - leave it to
+ next time as well if it happens to be \" */
+ break;
+ }
+
+ /* save the first escaped char */
+ if (parser->str_first_escape < 0)
+ parser->str_first_escape = i;
+
+ /* skip the escaped char */
+ i++;
+ }
+
+ /* check linebreaks here, so escaping CR/LF isn't possible.
+ string always ends with '"', so it's an error if we found
+ a linebreak.. */
+ if (is_linebreak(data[i]) &&
+ (parser->flags & IMAP_PARSE_FLAG_MULTILINE_STR) == 0) {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "Missing '\"'";
+ return FALSE;
+ }
+ }
+
+ parser->cur_pos = i;
+ return parser->cur_type == ARG_PARSE_NONE;
+}
+
+static bool imap_parser_literal_end(struct imap_parser *parser)
+{
+ if (parser->literal_minus && parser->literal_nonsync &&
+ parser->literal_size > 4096) {
+ parser->error_msg = "Non-synchronizing literal size too large";
+ parser->error = IMAP_PARSE_ERROR_LITERAL_TOO_BIG;
+ return FALSE;
+ }
+
+ if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) == 0) {
+ if (parser->line_size >= parser->max_line_size ||
+ parser->literal_size >
+ parser->max_line_size - parser->line_size) {
+ /* too long string, abort. */
+ parser->error = IMAP_PARSE_ERROR_LITERAL_TOO_BIG;
+ parser->error_msg = "Literal size too large";
+ return FALSE;
+ }
+
+ if (parser->output != NULL && !parser->literal_nonsync) {
+ o_stream_nsend(parser->output, "+ OK\r\n", 6);
+ if (o_stream_is_corked(parser->output)) {
+ /* make sure this continuation is sent to the
+ client as soon as possible */
+ o_stream_uncork(parser->output);
+ o_stream_cork(parser->output);
+ }
+ }
+ }
+
+ parser->cur_type = ARG_PARSE_LITERAL_DATA;
+ parser->literal_skip_crlf = TRUE;
+
+ parser->cur_pos = 0;
+ return TRUE;
+}
+
+static bool imap_parser_read_literal(struct imap_parser *parser,
+ const unsigned char *data,
+ size_t data_size)
+{
+ size_t i;
+
+ /* expecting digits + "}" */
+ for (i = parser->cur_pos; i < data_size; i++) {
+ if (data[i] == '}') {
+ parser->line_size += i+1;
+ i_stream_skip(parser->input, i+1);
+ return imap_parser_literal_end(parser);
+ }
+
+ if (parser->literal_nonsync) {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "Expecting '}' after '+'";
+ return FALSE;
+ }
+
+ if (data[i] == '+') {
+ parser->literal_nonsync = TRUE;
+ continue;
+ }
+
+ if (data[i] < '0' || data[i] > '9') {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "Invalid literal size";
+ return FALSE;
+ }
+
+ if (parser->literal_size >= ((uoff_t)-1 / 10)) {
+ if (parser->literal_size > ((uoff_t)-1 / 10) ||
+ (uoff_t)(data[i] - '0') > ((uoff_t)-1 % 10)) {
+ parser->error = IMAP_PARSE_ERROR_LITERAL_TOO_BIG;
+ parser->error_msg = "Literal size too large";
+ return FALSE;
+ }
+ }
+ parser->literal_size = parser->literal_size * 10 +
+ (data[i] - '0');
+ }
+
+ parser->cur_pos = i;
+ return FALSE;
+}
+
+static bool imap_parser_read_literal_data(struct imap_parser *parser,
+ const unsigned char *data,
+ size_t data_size)
+{
+ if (parser->literal_skip_crlf) {
+ /* skip \r\n or \n, anything else gives an error */
+ if (data_size == 0)
+ return FALSE;
+
+ if (*data == '\r') {
+ parser->line_size++;
+ data++; data_size--;
+ i_stream_skip(parser->input, 1);
+
+ if (data_size == 0)
+ return FALSE;
+ }
+
+ if (*data != '\n') {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "Missing LF after literal size";
+ return FALSE;
+ }
+
+ parser->line_size++;
+ data++; data_size--;
+ i_stream_skip(parser->input, 1);
+
+ parser->literal_skip_crlf = FALSE;
+
+ i_assert(parser->cur_pos == 0);
+ }
+
+ if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) == 0 ||
+ parser->cur_type == ARG_PARSE_LITERAL_DATA_FORCED) {
+ /* now we just wait until we've read enough data */
+ if (data_size < parser->literal_size)
+ return FALSE;
+ else {
+ imap_parser_save_arg(parser, data,
+ (size_t)parser->literal_size);
+ parser->cur_pos = (size_t)parser->literal_size;
+ return TRUE;
+ }
+ } else {
+ /* we want to save only literal size, not the literal itself. */
+ parser->literal_size_return = TRUE;
+ imap_parser_save_arg(parser, uchar_empty_ptr, 0);
+ return FALSE;
+ }
+}
+
+static bool imap_parser_is_next_resp_text(struct imap_parser *parser)
+{
+ const struct imap_arg *arg;
+
+ if (parser->cur_list != &parser->root_list ||
+ array_count(parser->cur_list) != 1)
+ return FALSE;
+
+ arg = array_front(&parser->root_list);
+ if (arg->type != IMAP_ARG_ATOM)
+ return FALSE;
+
+ return strcasecmp(arg->_data.str, "OK") == 0 ||
+ strcasecmp(arg->_data.str, "NO") == 0 ||
+ strcasecmp(arg->_data.str, "BAD") == 0 ||
+ strcasecmp(arg->_data.str, "BYE") == 0;
+}
+
+static bool imap_parser_is_next_text(struct imap_parser *parser)
+{
+ const struct imap_arg *arg;
+ size_t len;
+
+ if (parser->cur_list != &parser->root_list)
+ return FALSE;
+
+ arg = array_back(&parser->root_list);
+ if (arg->type != IMAP_ARG_ATOM)
+ return FALSE;
+
+ len = strlen(arg->_data.str);
+ return len > 0 && arg->_data.str[len-1] == ']';
+}
+
+static bool imap_parser_read_text(struct imap_parser *parser,
+ const unsigned char *data, size_t data_size)
+{
+ size_t i;
+
+ /* read until end of line */
+ for (i = parser->cur_pos; i < data_size; i++) {
+ if (is_linebreak(data[i])) {
+ imap_parser_save_arg(parser, data, i);
+ break;
+ }
+ }
+ parser->cur_pos = i;
+ return parser->cur_type == ARG_PARSE_NONE;
+}
+
+/* Returns TRUE if argument was fully processed. Also returns TRUE if
+ an argument inside a list was processed. */
+static bool imap_parser_read_arg(struct imap_parser *parser)
+{
+ const unsigned char *data;
+ size_t data_size;
+
+ data = i_stream_get_data(parser->input, &data_size);
+ if (data_size == 0)
+ return FALSE;
+
+ while (parser->cur_type == ARG_PARSE_NONE) {
+ /* we haven't started parsing yet */
+ if (!imap_parser_skip_to_next(parser, &data, &data_size))
+ return FALSE;
+ i_assert(parser->cur_pos == 0);
+
+ if (parser->cur_resp_text &&
+ imap_parser_is_next_text(parser)) {
+ /* we just parsed [resp-text-code] */
+ parser->cur_type = ARG_PARSE_TEXT;
+ break;
+ }
+
+ switch (data[0]) {
+ case '\r':
+ if (data_size == 1) {
+ /* wait for LF */
+ return FALSE;
+ }
+ if (data[1] != '\n') {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "CR sent without LF";
+ return FALSE;
+ }
+ /* fall through */
+ case '\n':
+ /* unexpected end of line */
+ if ((parser->flags & IMAP_PARSE_FLAG_INSIDE_LIST) != 0) {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "Missing ')'";
+ return FALSE;
+ }
+ parser->eol = TRUE;
+ return FALSE;
+ case '"':
+ parser->cur_type = ARG_PARSE_STRING;
+ parser->str_first_escape = -1;
+ break;
+ case '~':
+ /* This could be either literal8 or atom */
+ if (data_size == 1) {
+ /* wait for the next char */
+ return FALSE;
+ }
+ if (data[1] != '{') {
+ parser->cur_type = ARG_PARSE_ATOM;
+ break;
+ }
+ if ((parser->flags & IMAP_PARSE_FLAG_LITERAL8) == 0) {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "literal8 not allowed here";
+ return FALSE;
+ }
+ parser->cur_type = ARG_PARSE_LITERAL8;
+ parser->literal_size = 0;
+ parser->literal_nonsync = FALSE;
+ parser->literal8 = TRUE;
+ break;
+ case '{':
+ parser->cur_type = ARG_PARSE_LITERAL;
+ parser->literal_size = 0;
+ parser->literal_nonsync = FALSE;
+ parser->literal8 = FALSE;
+ break;
+ case '(':
+ imap_parser_open_list(parser);
+ if ((parser->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) != 0) {
+ i_stream_skip(parser->input, 1);
+ return FALSE;
+ }
+ break;
+ case ')':
+ if (!imap_parser_close_list(parser))
+ return FALSE;
+
+ if (parser->list_arg == NULL) {
+ /* end of argument */
+ parser->cur_pos++;
+ return TRUE;
+ }
+ break;
+ default:
+ if (!is_valid_atom_char(parser, data[0]))
+ return FALSE;
+ parser->cur_type = ARG_PARSE_ATOM;
+ break;
+ }
+
+ parser->cur_pos++;
+ }
+
+ i_assert(data_size > 0);
+
+ switch (parser->cur_type) {
+ case ARG_PARSE_ATOM:
+ if (!imap_parser_read_atom(parser, data, data_size))
+ return FALSE;
+ if ((parser->flags & IMAP_PARSE_FLAG_SERVER_TEXT) == 0)
+ break;
+
+ if (imap_parser_is_next_resp_text(parser)) {
+ /* we just parsed OK/NO/BAD/BYE. after parsing the
+ [resp-text-code] the rest of the message can contain
+ pretty much any random text, which we can't parse
+ as if it was valid IMAP input */
+ parser->cur_resp_text = TRUE;
+ }
+ break;
+ case ARG_PARSE_STRING:
+ if (!imap_parser_read_string(parser, data, data_size))
+ return FALSE;
+ break;
+ case ARG_PARSE_LITERAL8:
+ if (parser->cur_pos == data_size)
+ return FALSE;
+ if (data[parser->cur_pos] != '{') {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "Expected '{'";
+ return FALSE;
+ }
+ parser->cur_type = ARG_PARSE_LITERAL;
+ parser->cur_pos++;
+ /* fall through */
+ case ARG_PARSE_LITERAL:
+ if (!imap_parser_read_literal(parser, data, data_size))
+ return FALSE;
+
+ /* pass through to parsing data. since input->skip was
+ modified, we need to get the data start position again. */
+ data = i_stream_get_data(parser->input, &data_size);
+
+ /* fall through */
+ case ARG_PARSE_LITERAL_DATA:
+ case ARG_PARSE_LITERAL_DATA_FORCED:
+ if (!imap_parser_read_literal_data(parser, data, data_size))
+ return FALSE;
+ break;
+ case ARG_PARSE_TEXT:
+ if (!imap_parser_read_text(parser, data, data_size))
+ return FALSE;
+ break;
+ default:
+ i_unreached();
+ }
+
+ i_assert(parser->cur_type == ARG_PARSE_NONE);
+ return TRUE;
+}
+
+static void list_add_ghost_eol(struct imap_arg *list_arg)
+{
+ struct imap_arg *arg;
+
+ i_assert(list_arg->type == IMAP_ARG_LIST);
+
+ arg = array_append_space(&list_arg->_data.list);
+ arg->type = IMAP_ARG_EOL;
+ array_pop_back(&list_arg->_data.list);
+
+ if (list_arg->parent != NULL)
+ list_add_ghost_eol(list_arg->parent);
+}
+
+/* ARG_PARSE_NONE checks that last argument isn't only partially parsed. */
+#define IS_UNFINISHED(parser) \
+ ((parser)->cur_type != ARG_PARSE_NONE || \
+ ((parser)->cur_list != &parser->root_list && \
+ ((parser)->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) == 0))
+
+static int finish_line(struct imap_parser *parser, unsigned int count,
+ const struct imap_arg **args_r)
+{
+ struct imap_arg *arg;
+ int ret = array_count(&parser->root_list);
+
+ parser->line_size += parser->cur_pos;
+ i_stream_skip(parser->input, parser->cur_pos);
+ parser->cur_pos = 0;
+ parser->cur_resp_text = FALSE;
+
+ if (parser->list_arg == NULL) {
+ /* no open list */
+ } else if (!parser->literal_size_return &&
+ (parser->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) == 0) {
+ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+ parser->error_msg = "Missing ')'";
+ *args_r = NULL;
+ return -1;
+ } else {
+ list_add_ghost_eol(parser->list_arg);
+ }
+
+ arg = array_append_space(&parser->root_list);
+ arg->type = IMAP_ARG_EOL;
+ parser->args_added_extra_eol = TRUE;
+
+ *args_r = array_get(&parser->root_list, &count);
+ return ret;
+}
+
+static void imap_parser_delete_extra_eol(struct imap_parser *parser)
+{
+ array_pop_back(&parser->root_list);
+ parser->args_added_extra_eol = FALSE;
+}
+
+int imap_parser_read_args(struct imap_parser *parser, unsigned int count,
+ enum imap_parser_flags flags,
+ const struct imap_arg **args_r)
+{
+ parser->flags = flags;
+
+ if (parser->args_added_extra_eol) {
+ /* delete EOL */
+ imap_parser_delete_extra_eol(parser);
+ parser->literal_size_return = FALSE;
+ }
+
+ while (!parser->eol && (count == 0 || IS_UNFINISHED(parser) ||
+ array_count(&parser->root_list) < count)) {
+ if (!imap_parser_read_arg(parser))
+ break;
+
+ if (parser->line_size > parser->max_line_size) {
+ parser->error = IMAP_PARSE_ERROR_LINE_TOO_LONG;
+ parser->error_msg = "IMAP command line too large";
+ break;
+ }
+ }
+
+ if (parser->error != IMAP_PARSE_ERROR_NONE) {
+ /* error, abort */
+ parser->line_size += parser->cur_pos;
+ i_stream_skip(parser->input, parser->cur_pos);
+ parser->cur_pos = 0;
+ *args_r = NULL;
+ return -1;
+ } else if ((!IS_UNFINISHED(parser) && count > 0 &&
+ array_count(&parser->root_list) >= count) ||
+ parser->eol || parser->literal_size_return) {
+ /* all arguments read / end of line. */
+ return finish_line(parser, count, args_r);
+ } else {
+ /* need more data */
+ *args_r = NULL;
+ return -2;
+ }
+}
+
+static struct imap_arg *
+imap_parser_get_last_literal_size(struct imap_parser *parser,
+ ARRAY_TYPE(imap_arg_list) **list_r)
+{
+ ARRAY_TYPE(imap_arg_list) *list;
+ struct imap_arg *args;
+ unsigned int count;
+
+ list = &parser->root_list;
+ args = array_get_modifiable(&parser->root_list, &count);
+ i_assert(count > 1 && args[count-1].type == IMAP_ARG_EOL);
+ count--;
+
+ while (args[count-1].type != IMAP_ARG_LITERAL_SIZE &&
+ args[count-1].type != IMAP_ARG_LITERAL_SIZE_NONSYNC) {
+ if (args[count-1].type != IMAP_ARG_LIST)
+ return NULL;
+
+ /* maybe the list ends with literal size */
+ list = &args[count-1]._data.list;
+ args = array_get_modifiable(list, &count);
+ if (count == 0)
+ return NULL;
+ }
+
+ *list_r = list;
+ return &args[count-1];
+}
+
+bool imap_parser_get_literal_size(struct imap_parser *parser, uoff_t *size_r)
+{
+ ARRAY_TYPE(imap_arg_list) *list;
+ struct imap_arg *last_arg;
+
+ last_arg = imap_parser_get_last_literal_size(parser, &list);
+ if (last_arg == NULL)
+ return FALSE;
+
+ return imap_arg_get_literal_size(last_arg, size_r);
+}
+
+void imap_parser_read_last_literal(struct imap_parser *parser)
+{
+ ARRAY_TYPE(imap_arg_list) *list;
+ struct imap_arg *last_arg;
+
+ i_assert(parser->literal_size_return);
+ i_assert(parser->args_added_extra_eol);
+
+ last_arg = imap_parser_get_last_literal_size(parser, &list);
+ i_assert(last_arg != NULL);
+
+ parser->cur_type = ARG_PARSE_LITERAL_DATA_FORCED;
+ i_assert(parser->literal_size == last_arg->_data.literal_size);
+
+ /* delete EOL */
+ imap_parser_delete_extra_eol(parser);
+
+ /* delete literal size */
+ array_pop_back(list);
+ parser->literal_size_return = FALSE;
+}
+
+int imap_parser_finish_line(struct imap_parser *parser, unsigned int count,
+ enum imap_parser_flags flags,
+ const struct imap_arg **args_r)
+{
+ const unsigned char *data;
+ size_t data_size;
+ int ret;
+
+ ret = imap_parser_read_args(parser, count, flags, args_r);
+ if (ret == -1)
+ return -1;
+ if (ret == -2) {
+ /* we should have noticed end of everything except atom */
+ if (parser->cur_type == ARG_PARSE_ATOM) {
+ data = i_stream_get_data(parser->input, &data_size);
+ imap_parser_save_arg(parser, data, data_size);
+ }
+ }
+ return finish_line(parser, count, args_r);
+}
+
+const char *imap_parser_read_word(struct imap_parser *parser)
+{
+ const unsigned char *data;
+ size_t i, data_size;
+
+ data = i_stream_get_data(parser->input, &data_size);
+
+ for (i = 0; i < data_size; i++) {
+ if (data[i] == ' ' || data[i] == '\r' || data[i] == '\n')
+ break;
+ }
+
+ if (i < data_size) {
+ data_size = i + (data[i] == ' ' ? 1 : 0);
+ parser->line_size += data_size;
+ i_stream_skip(parser->input, data_size);
+ return p_strndup(parser->pool, data, i);
+ } else {
+ return NULL;
+ }
+}
+
+static int
+imap_parser_read_next_atom(struct imap_parser *parser, bool parsing_tag,
+ const char **atom_r)
+{
+ const unsigned char *data;
+ size_t i, data_size;
+
+ data = i_stream_get_data(parser->input, &data_size);
+
+ /*
+ tag = 1*<any ASTRING-CHAR except "+">
+ ASTRING-CHAR = ATOM-CHAR / resp-specials
+ ATOM-CHAR = <any CHAR except atom-specials>
+
+ x-command = "X" atom <experimental command arguments>
+ atom = 1*ATOM-CHAR
+ */
+ for (i = 0; i < data_size; i++) {
+ /* explicitly check for atom-specials, because
+ IS_ATOM_PARSER_INPUT() allows some atom-specials */
+ switch (data[i]) {
+ case ' ':
+ case '\r':
+ case '\n':
+ data_size = i + (data[i] == ' ' ? 1 : 0);
+ parser->line_size += data_size;
+ i_stream_skip(parser->input, data_size);
+ *atom_r = p_strndup(parser->pool, data, i);
+ /* don't allow empty string */
+ return i == 0 ? -1 : 1;
+ /* atom-specials: */
+ case '(':
+ case ')':
+ case '{':
+ /* list-wildcards: */
+ case '%':
+ case '*':
+ /* quoted-specials: */
+ case '"':
+ case '\\':
+ /* resp-specials: */
+ case ']':
+ return -1;
+ case '+':
+ if (parsing_tag)
+ return -1;
+ break;
+ default:
+ if ((unsigned char)data[i] < ' ' ||
+ (unsigned char)data[i] >= 0x80)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r)
+{
+ return imap_parser_read_next_atom(parser, TRUE, tag_r);
+}
+
+int imap_parser_read_command_name(struct imap_parser *parser,
+ const char **name_r)
+{
+ return imap_parser_read_next_atom(parser, FALSE, name_r);
+}
+
+int imap_parser_client_read_tag(struct imap_parser *parser,
+ const char **tag_r)
+{
+ return imap_parser_read_next_atom(parser, FALSE, tag_r);
+}
diff --git a/src/lib-imap/imap-parser.h b/src/lib-imap/imap-parser.h
new file mode 100644
index 0000000..cd3748c
--- /dev/null
+++ b/src/lib-imap/imap-parser.h
@@ -0,0 +1,117 @@
+#ifndef IMAP_PARSER_H
+#define IMAP_PARSER_H
+
+#include "imap-arg.h"
+
+enum imap_parser_flags {
+ /* Set this flag if you wish to read only size of literal argument
+ and not convert literal into string. Useful when you need to deal
+ with large literal sizes. The literal must be the last read
+ parameter. */
+ IMAP_PARSE_FLAG_LITERAL_SIZE = 0x01,
+ /* Don't remove '\' chars from string arguments */
+ IMAP_PARSE_FLAG_NO_UNESCAPE = 0x02,
+ /* Return literals as IMAP_ARG_LITERAL instead of IMAP_ARG_STRING */
+ IMAP_PARSE_FLAG_LITERAL_TYPE = 0x04,
+ /* Don't check if atom contains invalid characters */
+ IMAP_PARSE_FLAG_ATOM_ALLCHARS = 0x08,
+ /* Allow strings to contain CRLFs */
+ IMAP_PARSE_FLAG_MULTILINE_STR = 0x10,
+ /* Parse in list context; ')' parses as EOL */
+ IMAP_PARSE_FLAG_INSIDE_LIST = 0x20,
+ /* Parse literal8 and set it as flag to imap_arg. */
+ IMAP_PARSE_FLAG_LITERAL8 = 0x40,
+ /* We're parsing IMAP server replies. Parse the "text" after
+ OK/NO/BAD/BYE replies as a single atom. We assume that the initial
+ "*" or tag was already skipped over. */
+ IMAP_PARSE_FLAG_SERVER_TEXT = 0x80,
+ /* Parse until '(' and return it as an empty list */
+ IMAP_PARSE_FLAG_STOP_AT_LIST = 0x100
+};
+
+enum imap_parser_error {
+ /* not fatal */
+ IMAP_PARSE_ERROR_NONE = 0,
+ IMAP_PARSE_ERROR_BAD_SYNTAX,
+ IMAP_PARSE_ERROR_LINE_TOO_LONG,
+ /* fatal */
+ IMAP_PARSE_ERROR_LITERAL_TOO_BIG
+};
+
+struct imap_parser;
+
+/* Create new IMAP argument parser. output is used for sending command
+ continuation requests for literals.
+
+ max_line_size can be used to approximately limit the maximum amount of
+ memory that gets allocated when parsing a line. Input buffer size limits
+ the maximum size of each parsed token.
+
+ Usually the largest lines are large only because they have a one huge
+ message set token, so you'll probably want to keep input buffer size the
+ same as max_line_size. That means the maximum memory usage is around
+ 2 * max_line_size. */
+struct imap_parser *
+imap_parser_create(struct istream *input, struct ostream *output,
+ size_t max_line_size) ATTR_NULL(2);
+void imap_parser_ref(struct imap_parser *parser);
+void imap_parser_unref(struct imap_parser **parser);
+
+/* Enable LITERAL- parser semantics: non-synchronizing literals must not
+ exceed 4096 bytes */
+void imap_parser_enable_literal_minus(struct imap_parser *parser);
+
+/* Reset the parser to initial state. */
+void imap_parser_reset(struct imap_parser *parser);
+
+/* Change parser's input and output streams */
+void imap_parser_set_streams(struct imap_parser *parser, struct istream *input,
+ struct ostream *output) ATTR_NULL(3);
+
+/* Return the last error in parser. fatal is set to TRUE if there's no way to
+ continue parsing, currently only if too large non-sync literal size was
+ given. */
+const char *imap_parser_get_error(struct imap_parser *parser,
+ enum imap_parser_error *error_r) ATTR_NULL(2);
+
+/* Read a number of arguments. This function doesn't call i_stream_read(), you
+ need to do that. Returns number of arguments read (may be less than count
+ in case of EOL), -2 if more data is needed or -1 if error occurred.
+
+ count-sized array of arguments are stored into args when return value is
+ 0 or larger. If all arguments weren't read, they're set to NIL. count
+ can be set to 0 to read all arguments in the line. Last element in
+ args is always of type IMAP_ARG_EOL. */
+int imap_parser_read_args(struct imap_parser *parser, unsigned int count,
+ enum imap_parser_flags flags,
+ const struct imap_arg **args_r);
+/* If parsing ended with literal size, return it. */
+bool imap_parser_get_literal_size(struct imap_parser *parser, uoff_t *size_r);
+/* IMAP_PARSE_FLAG_LITERAL_SIZE is set and last read argument was a literal.
+ Calling this function causes the literal size to be replaced with the actual
+ literal data when continuing argument parsing. */
+void imap_parser_read_last_literal(struct imap_parser *parser);
+
+/* just like imap_parser_read_args(), but assume \n at end of data in
+ input stream. */
+int imap_parser_finish_line(struct imap_parser *parser, unsigned int count,
+ enum imap_parser_flags flags,
+ const struct imap_arg **args_r);
+
+/* Read one word - used for reading tag and command name.
+ Returns NULL if more data is needed. */
+const char *imap_parser_read_word(struct imap_parser *parser);
+/* Read command tag. Returns 1 if tag was returned, 0 if more data is needed,
+ -1 if input isn't a valid tag. */
+int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r);
+/* Read command name. Returns 1 if command name was returned, 0 if more data is
+ needed, -1 if input isn't a valid command name string. */
+int imap_parser_read_command_name(struct imap_parser *parser,
+ const char **name_r);
+/* For IMAP clients: Read the command tag, which could also be "+" or "*".
+ Returns 1 if tag was returned, 0 if more data is needed, -1 if input isn't
+ valid. */
+int imap_parser_client_read_tag(struct imap_parser *parser,
+ const char **tag_r);
+
+#endif
diff --git a/src/lib-imap/imap-quote.c b/src/lib-imap/imap-quote.c
new file mode 100644
index 0000000..622e21c
--- /dev/null
+++ b/src/lib-imap/imap-quote.c
@@ -0,0 +1,239 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "imap-arg.h"
+#include "imap-quote.h"
+
+/* If we have quoted-specials (<">, <\>) in a string, the minimum quoted-string
+ overhead is 3 bytes ("\") while the minimum literal overhead is 5 bytes
+ ("{n}\r\n"). But the literal overhead also depends on the string size. If
+ the string length is less than 10, literal catches up to quoted-string after
+ 3 quoted-specials. If the string length is 10..99, it catches up after 4
+ quoted-specials, and so on. We'll assume that the string lengths are usually
+ in double digits, so we'll switch to literals after seeing 4
+ quoted-specials. */
+#define QUOTED_MAX_ESCAPE_CHARS 4
+
+void imap_append_string(string_t *dest, const char *src)
+{
+ i_assert(src != NULL);
+
+ imap_append_nstring(dest, src);
+}
+
+void imap_append_astring(string_t *dest, const char *src)
+{
+ unsigned int i;
+
+ i_assert(src != NULL);
+
+ for (i = 0; src[i] != '\0'; i++) {
+ if (!IS_ASTRING_CHAR(src[i])) {
+ imap_append_string(dest, src);
+ return;
+ }
+ }
+ /* don't mix up NIL and "NIL"! */
+ if (i == 0 || strcasecmp(src, "NIL") == 0)
+ imap_append_string(dest, src);
+ else
+ str_append(dest, src);
+}
+
+static void
+imap_append_literal(string_t *dest, const char *src, unsigned int pos)
+{
+ size_t full_len = pos + strlen(src+pos);
+
+ str_printfa(dest, "{%zu}\r\n", full_len);
+ buffer_append(dest, src, full_len);
+}
+
+void imap_append_nstring(string_t *dest, const char *src)
+{
+ unsigned int escape_count = 0;
+ size_t i;
+
+ if (src == NULL) {
+ str_append(dest, "NIL");
+ return;
+ }
+
+ /* first check if we can (or want to) write this as quoted or
+ as literal.
+
+ quoted-specials = DQUOTE / "\"
+ QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
+ "\" quoted-specials
+ TEXT-CHAR = <any CHAR except CR and LF>
+ */
+ for (i = 0; src[i] != '\0'; i++) {
+ switch (src[i]) {
+ case '"':
+ case '\\':
+ if (escape_count++ < QUOTED_MAX_ESCAPE_CHARS)
+ break;
+ /* fall through */
+ case 13:
+ case 10:
+ imap_append_literal(dest, src, i);
+ return;
+ default:
+ if ((unsigned char)src[i] >= 0x80) {
+ imap_append_literal(dest, src, i);
+ return;
+ }
+ break;
+ }
+ }
+ imap_append_quoted(dest, src);
+}
+
+static void remove_newlines_and_append(string_t *dest, const char *src)
+{
+ size_t src_len;
+ string_t *src_nolf;
+ src_len = strlen(src);
+ src_nolf = t_str_new(src_len + 1);
+ for (size_t i = 0; i < src_len; ++i) {
+ if (src[i] != '\r' && src[i] != '\n') {
+ str_append_c(src_nolf, src[i]);
+ } else if (src[i+1] != ' ' &&
+ src[i+1] != '\t' &&
+ src[i+1] != '\r' &&
+ src[i+1] != '\n' &&
+ src[i+1] != '\0') {
+ /* ensure whitespace between lines if new line doesn't start with whitespace */
+ str_append_c(src_nolf, ' ');
+ }
+ }
+ imap_append_nstring(dest, str_c(src_nolf));
+}
+
+void imap_append_nstring_nolf(string_t *dest, const char *src)
+{
+ if (src == NULL || strpbrk(src, "\r\n") == NULL)
+ imap_append_nstring(dest, src);
+ else if (buffer_get_pool(dest)->datastack_pool)
+ remove_newlines_and_append(dest, src);
+ else T_BEGIN {
+ remove_newlines_and_append(dest, src);
+ } T_END;
+}
+
+void imap_append_quoted(string_t *dest, const char *src)
+{
+ str_append_c(dest, '"');
+ for (; *src != '\0'; src++) {
+ switch (*src) {
+ case 13:
+ case 10:
+ /* not allowed */
+ break;
+ case '"':
+ case '\\':
+ str_append_c(dest, '\\');
+ str_append_c(dest, *src);
+ break;
+ default:
+ if ((unsigned char)*src >= 0x80) {
+ /* 8bit input not allowed in dquotes */
+ break;
+ }
+
+ str_append_c(dest, *src);
+ break;
+ }
+ }
+ str_append_c(dest, '"');
+}
+
+void imap_append_string_for_humans(string_t *dest,
+ const unsigned char *src, size_t size)
+{
+ size_t i, pos, remove_count = 0;
+ bool whitespace_prefix = TRUE, last_lwsp = TRUE, modify = FALSE;
+
+ /* first check if there is anything to change */
+ for (i = 0; i < size; i++) {
+ switch (src[i]) {
+ case 0:
+ /* convert NUL to #0x80 */
+ last_lwsp = FALSE;
+ modify = TRUE;
+ break;
+ case 13:
+ case 10:
+ case '\t':
+ modify = TRUE;
+ /* fall through */
+ case ' ':
+ if (last_lwsp) {
+ modify = TRUE;
+ remove_count++;
+ }
+ last_lwsp = TRUE;
+ break;
+ case '"':
+ case '\\':
+ modify = TRUE;
+ last_lwsp = FALSE;
+ break;
+ default:
+ if ((src[i] & 0x80) != 0)
+ modify = TRUE;
+ last_lwsp = FALSE;
+ break;
+ }
+ if (!last_lwsp)
+ whitespace_prefix = FALSE;
+ }
+ if (last_lwsp && i > 0 && !whitespace_prefix) {
+ modify = TRUE;
+ remove_count++;
+ }
+ if (!modify) {
+ /* fast path: we can simply write it as quoted string
+ without any escaping */
+ str_append_c(dest, '"');
+ str_append_data(dest, src, size);
+ str_append_c(dest, '"');
+ return;
+ }
+ if (size == remove_count) {
+ /* contained only whitespace */
+ str_append(dest, "\"\"");
+ return;
+ }
+
+ str_printfa(dest, "{%zu}\r\n", size - remove_count);
+ pos = str_len(dest);
+
+ last_lwsp = TRUE; whitespace_prefix = TRUE;
+ for (i = 0; i < size; i++) {
+ switch (src[i]) {
+ case 0:
+ str_append_c(dest, 128);
+ last_lwsp = FALSE;
+ break;
+ case 13:
+ case 10:
+ case '\t':
+ case ' ':
+ if (!last_lwsp)
+ str_append_c(dest, ' ');
+ last_lwsp = TRUE;
+ break;
+ default:
+ last_lwsp = FALSE;
+ str_append_c(dest, src[i]);
+ break;
+ }
+ if (!last_lwsp)
+ whitespace_prefix = FALSE;
+ }
+ if (last_lwsp && i > 0 && !whitespace_prefix)
+ str_truncate(dest, str_len(dest)-1);
+ i_assert(str_len(dest) - pos == size - remove_count);
+}
diff --git a/src/lib-imap/imap-quote.h b/src/lib-imap/imap-quote.h
new file mode 100644
index 0000000..a397ec3
--- /dev/null
+++ b/src/lib-imap/imap-quote.h
@@ -0,0 +1,21 @@
+#ifndef IMAP_QUOTE_H
+#define IMAP_QUOTE_H
+
+/* Append "quoted" or literal. */
+void imap_append_string(string_t *dest, const char *src);
+/* Append atom, "quoted" or literal. */
+void imap_append_astring(string_t *dest, const char *src);
+/* Append NIL, "quoted" or literal. */
+void imap_append_nstring(string_t *dest, const char *src);
+/* Append NIL, "quoted" or literal, CRs and LFs skipped. */
+void imap_append_nstring_nolf(string_t *dest, const char *src);
+/* Append "quoted". If src has 8bit chars, skip over them. */
+void imap_append_quoted(string_t *dest, const char *src);
+
+/* Otherwise the same as imap_append_string(), but cleanup the input data
+ so that it's more readable by humans. This includes converting TABs to
+ spaces, multiple spaces into a single space and NULs to #0x80. */
+void imap_append_string_for_humans(string_t *dest,
+ const unsigned char *src, size_t size);
+
+#endif
diff --git a/src/lib-imap/imap-resp-code.h b/src/lib-imap/imap-resp-code.h
new file mode 100644
index 0000000..a7a4de8
--- /dev/null
+++ b/src/lib-imap/imap-resp-code.h
@@ -0,0 +1,28 @@
+#ifndef IMAP_RESP_CODE_H
+#define IMAP_RESP_CODE_H
+
+/* IMAP response codes (RFC 5530) */
+#define IMAP_RESP_CODE_UNAVAILABLE "UNAVAILABLE"
+#define IMAP_RESP_CODE_AUTHFAILED "AUTHENTICATIONFAILED"
+#define IMAP_RESP_CODE_AUTHZFAILED "AUTHORIZATIONFAILED"
+#define IMAP_RESP_CODE_EXPIRED "EXPIRED"
+#define IMAP_RESP_CODE_PRIVACYREQUIRED "PRIVACYREQUIRED"
+#define IMAP_RESP_CODE_CONTACTADMIN "CONTACTADMIN"
+#define IMAP_RESP_CODE_NOPERM "NOPERM"
+#define IMAP_RESP_CODE_INUSE "INUSE"
+#define IMAP_RESP_CODE_EXPUNGEISSUED "EXPUNGEISSUED"
+#define IMAP_RESP_CODE_CORRUPTION "CORRUPTION"
+#define IMAP_RESP_CODE_SERVERBUG "SERVERBUG"
+#define IMAP_RESP_CODE_CLIENTBUG "CLIENTBUG"
+#define IMAP_RESP_CODE_CANNOT "CANNOT"
+#define IMAP_RESP_CODE_LIMIT "LIMIT"
+#define IMAP_RESP_CODE_OVERQUOTA "OVERQUOTA"
+#define IMAP_RESP_CODE_ALREADYEXISTS "ALREADYEXISTS"
+#define IMAP_RESP_CODE_NONEXISTENT "NONEXISTENT"
+
+#define IMAP_RESP_CODE_UNKNOWN_CTE "UNKNOWN-CTE" /* BINARY */
+
+/* IMAP standard (RFC 3501) */
+#define IMAP_RESP_CODE_PARSE "PARSE"
+
+#endif
diff --git a/src/lib-imap/imap-seqset.c b/src/lib-imap/imap-seqset.c
new file mode 100644
index 0000000..5e7ea21
--- /dev/null
+++ b/src/lib-imap/imap-seqset.c
@@ -0,0 +1,105 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "imap-seqset.h"
+
+static uint32_t get_next_number(const char **str)
+{
+ uint32_t num;
+
+ num = 0;
+ while (**str != '\0') {
+ if (**str < '0' || **str > '9')
+ break;
+
+ num = num*10 + (**str - '0');
+ (*str)++;
+ }
+
+ if (num == (uint32_t)-1) {
+ /* FIXME: ugly hack, we're using this number to mean the
+ last existing message. In reality UIDs should never get
+ this high, so we can quite safely just drop this one down. */
+ num--;
+ }
+
+ return num;
+}
+
+static int
+get_next_seq_range(const char **str, uint32_t *seq1_r, uint32_t *seq2_r)
+{
+ uint32_t seq1, seq2;
+
+ if (**str == '*') {
+ /* last message */
+ seq1 = (uint32_t)-1;
+ *str += 1;
+ } else {
+ seq1 = get_next_number(str);
+ if (seq1 == 0)
+ return -1;
+ }
+
+ if (**str != ':')
+ seq2 = seq1;
+ else {
+ /* first:last range */
+ *str += 1;
+
+ if (**str == '*') {
+ seq2 = (uint32_t)-1;
+ *str += 1;
+ } else {
+ seq2 = get_next_number(str);
+ if (seq2 == 0)
+ return -1;
+ }
+ }
+
+ if (seq1 > seq2) {
+ /* swap, as specified by RFC-3501 */
+ *seq1_r = seq2;
+ *seq2_r = seq1;
+ } else {
+ *seq1_r = seq1;
+ *seq2_r = seq2;
+ }
+ return 0;
+}
+
+int imap_seq_set_parse(const char *str, ARRAY_TYPE(seq_range) *dest)
+{
+ uint32_t seq1, seq2;
+
+ while (*str != '\0') {
+ if (get_next_seq_range(&str, &seq1, &seq2) < 0)
+ return -1;
+ seq_range_array_add_range(dest, seq1, seq2);
+
+ if (*str == ',')
+ str++;
+ else if (*str != '\0')
+ return -1;
+ }
+ return 0;
+}
+
+int imap_seq_set_nostar_parse(const char *str, ARRAY_TYPE(seq_range) *dest)
+{
+ if (imap_seq_set_parse(str, dest) < 0)
+ return -1;
+
+ if (seq_range_exists(dest, (uint32_t)-1)) {
+ /* '*' used */
+ return -1;
+ }
+ return 0;
+}
+
+int imap_seq_range_parse(const char *str, uint32_t *seq1_r, uint32_t *seq2_r)
+{
+ if (get_next_seq_range(&str, seq1_r, seq2_r) < 0)
+ return -1;
+ return *str == '\0' ? 0 : -1;
+}
diff --git a/src/lib-imap/imap-seqset.h b/src/lib-imap/imap-seqset.h
new file mode 100644
index 0000000..a7e1ffd
--- /dev/null
+++ b/src/lib-imap/imap-seqset.h
@@ -0,0 +1,15 @@
+#ifndef IMAP_SEQSET_H
+#define IMAP_SEQSET_H
+
+#include "seq-range-array.h"
+
+/* Parse IMAP sequence-set and store the result in dest. '*' is stored as
+ (uint32_t)-1. Returns 0 if successful, -1 if input is invalid. */
+int imap_seq_set_parse(const char *str, ARRAY_TYPE(seq_range) *dest);
+/* Like imap_seq_set_parse(), but fail if '*' is used. */
+int imap_seq_set_nostar_parse(const char *str, ARRAY_TYPE(seq_range) *dest);
+
+/* Parse IMAP seq-number / seq-range. */
+int imap_seq_range_parse(const char *str, uint32_t *seq1_r, uint32_t *seq2_r);
+
+#endif
diff --git a/src/lib-imap/imap-url.c b/src/lib-imap/imap-url.c
new file mode 100644
index 0000000..6da6e21
--- /dev/null
+++ b/src/lib-imap/imap-url.c
@@ -0,0 +1,1009 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "str-sanitize.h"
+#include "hex-binary.h"
+#include "net.h"
+#include "iso8601-date.h"
+#include "uri-util.h"
+
+#include "imap-url.h"
+
+#include <ctype.h>
+
+/*
+ * IMAP URL parsing
+ */
+
+/*
+IMAP URL Grammar overview
+
+RFC5092 Section 11:
+
+imapurl = "imap://" iserver ipath-query
+ ; Defines an absolute IMAP URL
+iserver = [iuserinfo "@"] host [ ":" port ]
+ ; This is the same as "authority" defined in [URI-GEN].
+iuserinfo = enc-user [iauth] / [enc-user] iauth
+ ; conforms to the generic syntax of "userinfo" as
+ ; defined in [URI-GEN].
+enc-user = 1*achar
+ ; %-encoded version of [IMAP4] authorization identity or
+ ; "userid".
+iauth = ";AUTH=" ( "*" / enc-auth-type )
+enc-auth-type = 1*achar
+ ; %-encoded version of [IMAP4] "auth-type"
+ipath-query = ["/" [ icommand ]]
+ ; Corresponds to "path-abempty [ "?" query ]" in
+ ; [URI-GEN]
+icommand = imessagelist /
+ imessagepart [iurlauth]
+imessagelist = imailbox-ref [ "?" enc-search ]
+ ; "enc-search" is [URI-GEN] "query".
+imessagepart = imailbox-ref iuid [isection] [ipartial]
+imailbox-ref = enc-mailbox [uidvalidity]
+uidvalidity = ";UIDVALIDITY=" nz-number
+ ; See [IMAP4] for "nz-number" definition
+iuid = "/" iuid-only
+iuid-only = ";UID=" nz-number
+ ; See [IMAP4] for "nz-number" definition
+isection = "/" isection-only
+isection-only = ";SECTION=" enc-section
+ipartial = "/" ipartial-only
+ipartial-only = ";PARTIAL=" partial-range
+enc-search = 1*bchar
+ ; %-encoded version of [IMAPABNF]
+ ; "search-program". Note that IMAP4
+ ; literals may not be used in
+ ; a "search-program", i.e., only
+ ; quoted or non-synchronizing
+ ; literals (if the server supports
+ ; LITERAL+ [LITERAL+]) are allowed.
+enc-mailbox = 1*bchar
+ ; %-encoded version of [IMAP4] "mailbox"
+enc-section = 1*bchar
+ ; %-encoded version of [IMAP4] "section-spec"
+partial-range = number ["." nz-number]
+ ; partial FETCH. The first number is
+ ; the offset of the first byte,
+ ; the second number is the length of
+ ; the fragment.
+bchar = achar / ":" / "@" / "/"
+achar = uchar / "&" / "="
+ ;; Same as [URI-GEN] 'unreserved / sub-delims /
+ ;; pct-encoded', but ";" is disallowed.
+uchar = unreserved / sub-delims-sh / pct-encoded
+sub-delims-sh = "!" / "$" / "'" / "(" / ")" /
+ "*" / "+" / ","
+ ;; Same as [URI-GEN] sub-delims,
+ ;; but without ";", "&" and "=".
+
+The following rules are only used in the presence of the IMAP
+[URLAUTH] extension:
+
+authimapurl = "imap://" iserver "/" imessagepart
+ ; Same as "imapurl" when "[icommand]" is
+ ; "imessagepart"
+authimapurlfull = authimapurl iurlauth
+ ; Same as "imapurl" when "[icommand]" is
+ ; "imessagepart iurlauth"
+authimapurlrump = authimapurl iurlauth-rump
+
+iurlauth = iurlauth-rump iua-verifier
+enc-urlauth = 32*HEXDIG
+iua-verifier = ":" uauth-mechanism ":" enc-urlauth
+iurlauth-rump = [expire] ";URLAUTH=" access
+access = ("submit+" enc-user) / ("user+" enc-user) /
+ "authuser" / "anonymous"
+expire = ";EXPIRE=" date-time
+ ; date-time is defined in [DATETIME]
+uauth-mechanism = "INTERNAL" / 1*(ALPHA / DIGIT / "-" / ".")
+ ; Case-insensitive.
+
+[URI-GEN] RFC3986 Appendix A:
+
+Implemented in src/lib/uri-util.c
+
+*/
+
+/*
+ * Imap URL parser
+ */
+
+struct imap_url_parser {
+ struct uri_parser parser;
+
+ enum imap_url_parse_flags flags;
+
+ struct imap_url *url;
+ const struct imap_url *base;
+
+ bool relative:1;
+};
+
+static int
+imap_url_parse_number(struct uri_parser *parser, const char *data,
+ uint32_t *number_r)
+{
+ /* [IMAP4] RFC3501, Section 9
+ *
+ * number = 1*DIGIT
+ * ; Unsigned 32-bit integer
+ * ; (0 <= n < 4,294,967,296)
+ */
+
+ if (i_isdigit(*data)) {
+ if (str_to_uint32(data, number_r) == 0)
+ return 1;
+ parser->error = "IMAP number is too high";
+ return -1;
+ }
+
+ parser->error = t_strdup_printf(
+ "Value '%s' is not a valid IMAP number", data);
+ return -1;
+}
+
+static int
+imap_url_parse_offset(struct uri_parser *parser, const char *data,
+ uoff_t *number_r)
+{
+ /* Syntax for big (uoff_t) numbers. Not strictly IMAP syntax, but this
+ is handled similarly for Dovecot IMAP FETCH BODY partial <.>
+ implementation. */
+ if (i_isdigit(*data)) {
+ if (str_to_uoff(data, number_r) == 0)
+ return 1;
+ parser->error = "IMAP number is too high";
+ return -1;
+ }
+
+ parser->error = t_strdup_printf(
+ "Value '%s' is not a valid IMAP number", data);
+ return -1;
+}
+
+static int imap_url_parse_iserver(struct imap_url_parser *url_parser)
+{
+ struct uri_parser *parser = &url_parser->parser;
+ struct uri_authority auth;
+ struct imap_url *url = url_parser->url;
+ const char *data;
+ int ret = 0;
+
+ /* imapurl = "imap://" iserver {...}
+ * inetwork-path = "//" iserver {...}
+ * iserver = [iuserinfo "@"] host [":" port]
+ * ; This is the same as "authority" defined
+ * ; in [URI-GEN].
+ * iuserinfo = enc-user [iauth] / [enc-user] iauth
+ * ; conforms to the generic syntax of "userinfo" as
+ * ; defined in [URI-GEN].
+ * enc-user = 1*achar
+ * ; %-encoded version of [IMAP4] authorization identity or
+ * ; "userid".
+ * iauth = ";AUTH=" ( "*" / enc-auth-type )
+ * enc-auth-type = 1*achar
+ * ; %-encoded version of [IMAP4] "auth-type"
+ */
+
+ /* "//" iserver */
+ if ((ret = uri_parse_slashslash_host_authority
+ (parser, &auth)) <= 0)
+ return ret;
+ if (auth.host.name == NULL || *auth.host.name == '\0') {
+ /* This situation is not documented anywhere, but it is not
+ currently useful either and potentially problematic if not
+ handled explicitly everywhere. So, it is denied hier for now.
+ */
+ parser->error = "IMAP URL does not allow empty host identifier";
+ return -1;
+ }
+ /* iuserinfo = enc-user [iauth] / [enc-user] iauth */
+ if (auth.enc_userinfo != NULL) {
+ const char *p, *uend;
+
+ /* Scan for ";AUTH=" */
+ for (p = auth.enc_userinfo; *p != '\0'; p++) {
+ if (*p == ';')
+ break;
+ /* check for unallowed userinfo characters */
+ if (*p == ':') {
+ parser->error = t_strdup_printf(
+ "Stray ':' in userinfo `%s'", auth.enc_userinfo);
+ return -1;
+ }
+ }
+
+ uend = p;
+
+ if (*p == ';') {
+ if (strncasecmp(p, ";AUTH=", 6) != 0) {
+ parser->error = t_strdup_printf(
+ "Stray ';' in userinfo `%s'",
+ auth.enc_userinfo);
+ return -1;
+ }
+
+ for (p += 6; *p != '\0'; p++) {
+ if (*p == ';' || *p == ':') {
+ parser->error = t_strdup_printf(
+ "Stray '%c' in userinfo `%s'", *p, auth.enc_userinfo);
+ return -1;
+ }
+ }
+ }
+
+ /* enc-user */
+ if (url != NULL && uend > auth.enc_userinfo) {
+ if (!uri_data_decode(parser, auth.enc_userinfo, uend, &data))
+ return -1;
+ url->userid = p_strdup(parser->pool, data);
+ }
+
+ /* ( "*" / enc-auth-type ) */
+ if (*uend == ';') {
+ p = uend + 6;
+ if (*p == '\0') {
+ parser->error = "Empty auth-type value after ';AUTH='";
+ return -1;
+ }
+ if (url != NULL) {
+ if (!uri_data_decode(parser, p, NULL, &data))
+ return -1;
+ url->auth_type = p_strdup(parser->pool, data);
+ }
+ }
+ }
+
+ if (url != NULL) {
+ url->host = auth.host;
+ url->port = auth.port;
+ }
+ return 1;
+}
+
+static int
+imap_url_parse_urlauth(struct imap_url_parser *url_parser, const char *urlext)
+{
+ struct uri_parser *parser = &url_parser->parser;
+ struct imap_url *url = url_parser->url;
+ const char *p, *q, *data;
+ buffer_t *uauth_token;
+ time_t expire = (time_t)-1;
+ int tz;
+
+ /* iurlauth = iurlauth-rump iua-verifier
+ * enc-urlauth = 32*HEXDIG
+ * iua-verifier = ":" uauth-mechanism ":" enc-urlauth
+ * iurlauth-rump = [expire] ";URLAUTH=" access
+ * access = ("submit+" enc-user) / ("user+" enc-user) /
+ * "authuser" / "anonymous"
+ * expire = ";EXPIRE=" date-time
+ * ; date-time is defined in [DATETIME]
+ * uauth-mechanism = "INTERNAL" / 1*(ALPHA / DIGIT / "-" / ".")
+ * ; Case-insensitive.
+ */
+
+ /* ";EXPIRE=" date-time */
+ if (strncasecmp(urlext, ";EXPIRE=", 8) == 0) {
+ if ((url_parser->flags & IMAP_URL_PARSE_ALLOW_URLAUTH) == 0) {
+ parser->error = "`;EXPIRE=' is not allowed in this context";
+ return -1;
+ }
+
+ if ((p = strchr(urlext+8, ';')) != NULL) {
+ if (!iso8601_date_parse((const unsigned char *)urlext+8,
+ p-urlext-8, &expire, &tz)) {
+ parser->error = "invalid date-time for `;EXPIRE='";
+ return -1;
+ }
+ urlext = p;
+ }
+ }
+
+ /* ";URLAUTH=" access */
+ if (strncasecmp(urlext, ";URLAUTH=", 9) != 0) {
+ if (expire != (time_t)-1) {
+ parser->error = "`;EXPIRE=' without `;URLAUTH='";
+ return -1;
+ }
+ return 0;
+ }
+ urlext += 9;
+
+ if (url != NULL)
+ url->uauth_expire = expire;
+
+ if ((url_parser->flags & IMAP_URL_PARSE_ALLOW_URLAUTH) == 0) {
+ parser->error = "`;URLAUTH=' is not allowed in this context";
+ return -1;
+ }
+
+ if (url_parser->relative) {
+ parser->error = "IMAP URLAUTH requires absolute URL";
+ return -1;
+ }
+
+ if ((p = strchr(urlext, ':')) == NULL) {
+ size_t len = strlen(urlext);
+ if (len == 0) {
+ parser->error = "Missing URLAUTH access specifier";
+ return -1;
+ }
+ p = urlext+len;
+ } else if (p == urlext) {
+ parser->error = "Empty URLAUTH access specifier";
+ return -1;
+ }
+
+ /* parse access */
+ if ((q = strchr(urlext, '+')) == NULL) {
+ /* application */
+ if (url != NULL) {
+ url->uauth_access_application =
+ p_strdup_until(parser->pool, urlext, p);
+ }
+ } else {
+ /* application "+" enc-user */
+ if (urlext == q) {
+ parser->error = "Empty URLAUTH access application";
+ return -1;
+ }
+ if (q+1 == p) {
+ parser->error = t_strdup_printf(
+ "Empty URLAUTH access user for `%s' application",
+ t_strdup_until(urlext, q));
+ return -1;
+ }
+ if (!uri_data_decode(parser, q+1, p, &data))
+ return -1;
+ if (url != NULL) {
+ url->uauth_access_application =
+ p_strdup_until(parser->pool, urlext, q);
+ url->uauth_access_user = p_strdup(parser->pool, data);
+ }
+ }
+
+ if (url != NULL) {
+ /* get rump url */
+ if ((url_parser->flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0) {
+ url->uauth_rumpurl = p_strdup_until(parser->pool,
+ parser->begin, parser->end-strlen(p));
+ } else {
+ url->uauth_rumpurl = p_strconcat(parser->pool, "imap:",
+ t_strdup_until(parser->begin, parser->end-strlen(p)),
+ NULL);
+ }
+ }
+
+ if (*p == '\0') {
+ /* rump url; caller should check whether this is appropriate */
+ return 1;
+ }
+
+ /* iua-verifier = ":" uauth-mechanism ":" enc-urlauth */
+
+ q = p + 1;
+ if (*q == '\0') {
+ parser->error = "Missing URLAUTH verifier";
+ return -1;
+ }
+ if ((p = strchr(q, ':')) == NULL || p[1] == '\0') {
+ parser->error = "Missing URLAUTH token";
+ return -1;
+ }
+ if (p == q) {
+ parser->error = "Missing URLAUTH mechanism";
+ return -1;
+ }
+ if (url != NULL) {
+ /* get mechanism */
+ url->uauth_mechanism = p_strdup_until(parser->pool, q, p);
+ }
+
+ /* enc-urlauth = 32*HEXDIG */
+
+ q = p+1;
+ if (strlen(q) < 32) {
+ parser->error = "Too short URLAUTH token";
+ return -1;
+ }
+
+ uauth_token = t_buffer_create(64);
+ if (hex_to_binary(q, uauth_token) < 0) {
+ parser->error = "Invalid URLAUTH token";
+ return -1;
+ }
+
+ if (url != NULL) {
+ url->uauth_token = uauth_token->data;
+ url->uauth_token_size = uauth_token->used;
+ }
+ return 1;
+}
+
+static int
+imap_url_parse_path(struct imap_url_parser *url_parser,
+ const char *const *path, int relative,
+ bool *is_messagelist_r)
+{
+ struct uri_parser *parser = &url_parser->parser;
+ struct imap_url *url = url_parser->url;
+ const char *const *segment;
+ string_t *mailbox, *section = NULL;
+ uint32_t uid = 0, uidvalidity = 0;
+ uoff_t partial_offset = 0, partial_size = 0;
+ bool have_partial = FALSE;
+ const char *p, *value, *urlext = NULL;
+ bool mailbox_endslash = FALSE, section_endslash = FALSE;
+ int ret;
+
+ /* icommand = imessagelist /
+ * imessagepart [iurlauth]
+ * imessagelist = imailbox-ref [ "?" enc-search ]
+ * ; "enc-search" is [URI-GEN] "query".
+ * imessagepart = imailbox-ref iuid [isection] [ipartial]
+ * imailbox-ref = enc-mailbox [uidvalidity]
+ * uidvalidity = ";UIDVALIDITY=" nz-number
+ * iuid = "/" iuid-only
+ * iuid-only = ";UID=" nz-number
+ * ; See [IMAP4] for "nz-number" definition
+ * isection = "/" isection-only
+ * isection-only = ";SECTION=" enc-section
+ * ipartial = "/" ipartial-only
+ * ipartial-only = ";PARTIAL=" partial-range
+ * enc-mailbox = 1*bchar
+ * ; %-encoded version of [IMAP4] "mailbox"
+ * enc-section = 1*bchar
+ * ; %-encoded version of [IMAP4] "section-spec"
+ * partial-range = number ["." nz-number]
+ * ; partial FETCH. The first number is
+ * ; the offset of the first byte,
+ * ; the second number is the length of
+ * ; the fragment.
+ */
+
+ /* IMAP URL syntax is quite horrible to parse. It relies upon the
+ generic URI path resolution, but the icommand syntax also relies on
+ ';' separators. We use the generic URI path parse functions to
+ adhere to the URI path resolution rules and glue back together path
+ segments when these are part of the same (mailbox or section) value.
+ */
+
+ mailbox = t_str_new(256);
+ segment = path;
+
+ /* Resolve relative URI path; determine what to copy from the base URI */
+ if (url != NULL && url_parser->base != NULL && relative > 0) {
+ const struct imap_url *base = url_parser->base;
+ int rel = relative;
+
+ /* /;PARTIAL= */
+ if (base->have_partial && --rel <= 0) {
+ have_partial = base->have_partial;
+ partial_offset = base->partial_offset;
+ partial_size = base->partial_size;
+ }
+ /* /;SECTION= */
+ if (base->section != NULL) {
+ p = base->section + strlen(base->section);
+ /* determine what to retain from base section path */
+ for (; p > base->section && rel > 0; p--) {
+ if (*p =='/' && --rel <= 0) break;
+ }
+ if (--rel <= 0 && p > base->section) {
+ if (p[-1] == '/') section_endslash = TRUE;
+ if (section == NULL)
+ section = t_str_new(256);
+ str_append_data(section, base->section, p-base->section);
+ }
+ }
+ /* /;UID= */
+ if (base->uid > 0 && --rel <= 0) {
+ uid = base->uid;
+ }
+ /* /mail/box;UIDVALIDITY= */
+ if (base->mailbox != NULL) {
+ uidvalidity = base->uidvalidity;
+ p = base->mailbox + strlen(base->mailbox);
+ /* mailbox has implicit trailing '/' */
+ if (p[-1] != '/' && base->uid == 0 && rel > 0)
+ rel--;
+ /* determine what to retain from base mailbox path */
+ for (; p > base->mailbox && rel > 0; p--) {
+ if (*p =='/') {
+ uidvalidity = 0;
+ if (--rel <= 0)
+ break;
+ }
+ }
+ if (--rel <= 0 && p > base->mailbox) {
+ if (p[-1] == '/')
+ mailbox_endslash = TRUE;
+ str_append_data(mailbox, base->mailbox,
+ p - base->mailbox);
+ }
+ }
+ }
+
+ /* Scan for last mailbox-ref segment */
+ if (segment != NULL) {
+ if (relative == 0 || (!have_partial && section == NULL)) {
+ p = NULL;
+ while (*segment != NULL) {
+ /* ';' must be pct-encoded; if it is not, this is
+ either the last mailbox-ref path segment containing
+ ';UIDVALIDITY=' or the subsequent iuid ';UID=' path
+ segment */
+ if ((p = strchr(*segment, ';')) != NULL)
+ break;
+
+ if (**segment != '\0') {
+ if (segment > path ||
+ (!mailbox_endslash && str_len(mailbox) > 0))
+ str_append_c(mailbox, '/');
+ if (!uri_data_decode(parser, *segment, NULL, &value))
+ return -1;
+ str_append(mailbox, value);
+ mailbox_endslash = FALSE;
+ }
+ segment++;
+ }
+
+ /* Handle ';' */
+ if (p != NULL) {
+ /* [uidvalidity] */
+ if (strncasecmp(p, ";UIDVALIDITY=", 13) == 0) {
+ /* append last bit of mailbox */
+ if (*segment != p) {
+ if (segment > path ||
+ (!mailbox_endslash && str_len(mailbox) > 0))
+ str_append_c(mailbox, '/');
+ if (!uri_data_decode(parser, *segment, p, &value))
+ return -1;
+ str_append(mailbox, value);
+ }
+
+ /* ";UIDVALIDITY=" nz-number */
+ if (strchr(p+13, ';') != NULL) {
+ parser->error = "Encountered stray ';' after UIDVALIDITY";
+ return -1;
+ }
+
+ /* nz-number */
+ if (p[13] == '\0') {
+ parser->error = "Empty UIDVALIDITY value";
+ return -1;
+ }
+ if (imap_url_parse_number(parser, p+13, &uidvalidity) <= 0)
+ return -1;
+ if (uidvalidity == 0) {
+ parser->error = "UIDVALIDITY cannot be zero";
+ return -1;
+ }
+ segment++;
+ } else if (p != *segment) {
+ parser->error = "Encountered stray ';' in mailbox reference";
+ return -1;
+ }
+ }
+
+ /* iuid */
+ if (*segment != NULL && strncasecmp(*segment, ";UID=", 5) == 0) {
+ /* ";UID=" nz-number */
+ value = (*segment)+5;
+ if ((p = strchr(value,';')) != NULL) {
+ if (segment[1] != NULL ) {
+ /* not the last segment, so it cannot be extension like iurlauth */
+ parser->error = "Encountered stray ';' in UID path segment";
+ return -1;
+ }
+ urlext = p;
+ value = t_strdup_until(value, p);
+ }
+ /* nz-number */
+ if (*value == '\0') {
+ parser->error = "Empty UID value";
+ return -1;
+ }
+ if (imap_url_parse_number(parser, value, &uid) <= 0)
+ return -1;
+ if (uid == 0) {
+ parser->error = "UID cannot be zero";
+ return -1;
+ }
+ segment++;
+ }
+ }
+
+ /* [isection] [ipartial] */
+ if (*segment != NULL && uid > 0) {
+ /* [isection] */
+ if (section != NULL ||
+ strncasecmp(*segment, ";SECTION=", 9) == 0) {
+ /* ";SECTION=" enc-section */
+ if (section == NULL) {
+ section = t_str_new(256);
+ value = (*segment) + 9;
+ } else {
+ value = *segment;
+ }
+
+ /* enc-section can contain slashes, so we merge path segments until one
+ contains ';' */
+ while ((p = strchr(value,';')) == NULL) {
+ if (!section_endslash && str_len(section) > 0)
+ str_append_c(section, '/');
+ if (*value != '\0') {
+ if (!uri_data_decode(parser, value, NULL, &value))
+ return -1;
+ str_append(section, value);
+ section_endslash = FALSE;
+ }
+
+ segment++;
+ if (*segment == NULL)
+ break;
+ value = *segment;
+ }
+
+ if (p != NULL) {
+ /* found ';' */
+ if (p != value) {
+ /* it is not at the beginning of the path segment */
+ if (segment[1] != NULL) {
+ /* not the last segment, so it cannot be extension like iurlauth */
+ parser->error = "Encountered stray ';' in SECTION path segment";
+ return -1;
+ }
+ urlext = p;
+ value = t_strdup_until(value, p);
+ if (!section_endslash && str_len(section) > 0)
+ str_append_c(section, '/');
+ if (!uri_data_decode(parser, value, NULL, &value))
+ return -1;
+ str_append(section, value);
+ segment++;
+ }
+ }
+
+ if (str_len(section) == 0) {
+ parser->error = "Empty SECTION value";
+ return -1;
+ }
+ }
+
+ /* [ipartial] */
+ if (*segment != NULL &&
+ strncasecmp(*segment, ";PARTIAL=", 9) == 0) {
+ have_partial = TRUE;
+
+ /* ";PARTIAL=" partial-range */
+ value = (*segment) + 9;
+ if ((p = strchr(value,';')) != NULL) {
+ urlext = p;
+ value = t_strdup_until(value, p);
+ }
+ if (*value == '\0') {
+ parser->error = "Empty PARTIAL value";
+ return -1;
+ }
+ /* partial-range = number ["." nz-number] */
+ if ((p = strchr(value,'.')) != NULL) {
+ if (p[1] == '\0') {
+ parser->error = "Empty PARTIAL size";
+ return -1;
+ }
+ if (imap_url_parse_offset(parser, p+1, &partial_size) <= 0)
+ return -1;
+ if (partial_size == 0) {
+ parser->error = "PARTIAL size cannot be zero";
+ return -1;
+ }
+ value = t_strdup_until(value, p);
+ if (*value == '\0') {
+ parser->error = "Empty PARTIAL offset";
+ return -1;
+ }
+ }
+ if (imap_url_parse_offset(parser,value, &partial_offset) <= 0)
+ return -1;
+ segment++;
+ }
+ }
+
+ if (*segment != NULL) {
+ if (urlext != NULL || **segment != '\0' || *(segment+1) != NULL ) {
+ parser->error = t_strdup_printf(
+ "Unexpected IMAP URL path segment: `%s'",
+ str_sanitize(*segment, 80));
+ return -1;
+ }
+ }
+ }
+
+ /* ";" {...} at end of URL */
+ if (urlext != NULL) {
+ /* [iurlauth] */
+ if ((ret = imap_url_parse_urlauth(url_parser, urlext)) < 0)
+ return ret;
+ else if (ret == 0) {
+ /* something else */
+ parser->error = t_strdup_printf(
+ "Unrecognized IMAP URL extension: %s",
+ str_sanitize(urlext, 80));
+ return -1;
+ }
+ }
+
+ if (is_messagelist_r != NULL)
+ *is_messagelist_r = (uid == 0);
+
+ if (url != NULL) {
+ if (str_len(mailbox) > 0)
+ url->mailbox = p_strdup(parser->pool, str_c(mailbox));
+ url->uidvalidity = uidvalidity;
+ url->uid = uid;
+ if (section != NULL)
+ url->section = p_strdup(parser->pool, str_c(section));
+ url->have_partial = have_partial;
+ url->partial_offset = partial_offset;
+ url->partial_size = partial_size;
+ }
+ return 1;
+}
+
+static bool imap_url_do_parse(struct imap_url_parser *url_parser)
+{
+ struct uri_parser *parser = &url_parser->parser;
+ const char *const *path;
+ bool is_messagelist = FALSE;
+ bool have_scheme = FALSE;
+ int relative;
+ const char *query;
+ int ret, sret;
+
+ /*
+ * imapurl = "imap://" iserver ipath-query
+ * ; Defines an absolute IMAP URL
+ * iserver = [iuserinfo "@"] host [":" port]
+ * ; This is the same as "authority" defined
+ * ; in [URI-GEN].
+ * ipath-query = ["/" [ icommand ]]
+ * ; Corresponds to "path-abempty [ "?" query ]" in
+ * ; [URI-GEN]
+ * icommand = imessagelist /
+ * imessagepart [iurlauth]
+ * imessagelist = imailbox-ref [ "?" enc-search ]
+ * ; "enc-search" is [URI-GEN] "query".
+ * imessagepart = imailbox-ref iuid [isection] [ipartial]
+ * enc-search = 1*bchar
+ * ; %-encoded version of [IMAPABNF]
+ * ; "search-program". Note that IMAP4
+ * ; literals may not be used in
+ * ; a "search-program", i.e., only
+ * ; quoted or non-synchronizing
+ * ; literals (if the server supports
+ * ; LITERAL+ [LITERAL+]) are allowed.
+ */
+
+ /* "imap:" */
+ if ((url_parser->flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0) {
+ const char *scheme;
+
+ if (uri_parse_scheme(parser, &scheme) <= 0) {
+ parser->cur = parser->begin;
+ } else {
+ if (strcasecmp(scheme, "imap") != 0) {
+ parser->error = "Not an IMAP URL";
+ return FALSE;
+ }
+ have_scheme = TRUE;
+ }
+ } else {
+ have_scheme = TRUE;
+ }
+
+ /* "//" iserver */
+ if ((sret = imap_url_parse_iserver(url_parser)) < 0)
+ return FALSE;
+
+ if (have_scheme && sret == 0) {
+ parser->error = "Absolute IMAP URL requires `//' after `imap:'";
+ return FALSE;
+ }
+
+ if (sret > 0 &&
+ (url_parser->flags & IMAP_URL_PARSE_REQUIRE_RELATIVE) != 0) {
+ parser->error = "Relative URL required";
+ return FALSE;
+ }
+
+ /* ipath-query = ["/" [ icommand ]] ; excludes `[ "?" enc-search ]` */
+ if ((ret = uri_parse_path(parser, &relative, &path)) < 0)
+ return FALSE;
+
+ /* Relative urls are only valid when we have a base url */
+ if (sret == 0) {
+ if (url_parser->base == NULL) {
+ parser->error = "Relative URL not allowed";
+ return FALSE;
+ } else if (url_parser->url != NULL) {
+ struct imap_url *url = url_parser->url;
+ const struct imap_url *base = url_parser->base;
+
+ uri_host_copy(parser->pool, &url->host, &base->host);
+ url->port = base->port;
+ url->userid = p_strdup_empty(parser->pool, base->userid);
+ url->auth_type = p_strdup_empty(parser->pool, base->auth_type);
+ }
+
+ url_parser->relative = TRUE;
+ }
+
+ /* Parse path, i.e. `[ icommand ]` from `*( "/" segment )` */
+ if (ret > 0 || url_parser->relative) {
+ if (imap_url_parse_path(url_parser, path, relative,
+ &is_messagelist) < 0)
+ return FALSE;
+ }
+
+ /* [ "?" enc-search ] */
+ if ((ret = uri_parse_query(parser, &query)) != 0) {
+ if (ret < 0)
+ return FALSE;
+
+ if (!is_messagelist) {
+ parser->error =
+ "Search query part only valid for messagelist-type IMAP URL";
+ return FALSE;
+ } else if (*query == '\0') {
+ parser->error = "Empty IMAP URL search query not allowed";
+ return FALSE;
+ }
+
+ if (url_parser->url != NULL) {
+ if (!uri_data_decode(parser, query, NULL, &query))
+ return FALSE;
+ url_parser->url->search_program =
+ p_strdup(parser->pool, query);
+ }
+ }
+
+ /* IMAP URL has no fragment */
+ if ((ret = uri_parse_fragment(parser, &query)) != 0) {
+ if (ret == 1)
+ parser->error = "Fragment component not allowed in IMAP URL";
+ return FALSE;
+ }
+
+ /* must be at end of URL now */
+ i_assert(parser->cur == parser->end);
+
+ return TRUE;
+}
+
+/* Public API */
+
+int imap_url_parse(const char *url, const struct imap_url *base,
+ enum imap_url_parse_flags flags,
+ struct imap_url **url_r, const char **error_r)
+{
+ struct imap_url_parser url_parser;
+
+ /* base != NULL indicates whether relative URLs are allowed. However, certain
+ flags may also dictate whether relative URLs are allowed/required. */
+ i_assert((flags & IMAP_URL_PARSE_REQUIRE_RELATIVE) == 0 || base != NULL);
+ i_assert((flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0 || base == NULL);
+
+ i_zero(&url_parser);
+ uri_parser_init(&url_parser.parser, pool_datastack_create(), url);
+
+ url_parser.url = t_new(struct imap_url, 1);
+ url_parser.url->uauth_expire = (time_t)-1;
+ url_parser.base = base;
+ url_parser.flags = flags;
+
+ if (!imap_url_do_parse(&url_parser)) {
+ *error_r = url_parser.parser.error;
+ return -1;
+ }
+ *url_r = url_parser.url;
+ return 0;
+}
+
+/*
+ * IMAP URL construction
+ */
+
+static void
+imap_url_append_mailbox(const struct imap_url *url, string_t *urlstr)
+{
+ uri_append_path_data(urlstr, ";", url->mailbox);
+ if (url->uidvalidity != 0)
+ str_printfa(urlstr, ";UIDVALIDITY=%u", url->uidvalidity);
+ if (url->uid == 0) {
+ /* message list */
+ if (url->search_program != NULL) {
+ str_append_c(urlstr, '?');
+ uri_append_query_data(urlstr, ";", url->search_program);
+ }
+ } else {
+ /* message part */
+ str_printfa(urlstr, "/;UID=%u", url->uid);
+ if (url->section != NULL) {
+ str_append(urlstr, "/;SECTION=");
+ uri_append_path_data(urlstr, ";", url->section);
+ }
+ if (url->have_partial) {
+ str_append(urlstr, "/;PARTIAL=");
+ if (url->partial_size == 0) {
+ str_printfa(urlstr, "%"PRIuUOFF_T,
+ url->partial_offset);
+ } else {
+ str_printfa(urlstr, "%"PRIuUOFF_T".%"PRIuUOFF_T,
+ url->partial_offset,
+ url->partial_size);
+ }
+ }
+
+ /* urlauth */
+ if (url->uauth_access_application != NULL) {
+ if (url->uauth_expire != (time_t)-1) {
+ str_append(urlstr, ";EXPIRE=");
+ str_append(urlstr, iso8601_date_create(url->uauth_expire));
+ }
+ str_append(urlstr, ";URLAUTH=");
+ str_append(urlstr, url->uauth_access_application);
+ if (url->uauth_access_user != NULL) {
+ str_append_c(urlstr, '+');
+ uri_append_user_data(urlstr, ";",
+ url->uauth_access_user);
+ }
+ }
+ }
+}
+
+const char *imap_url_create(const struct imap_url *url)
+{
+ string_t *urlstr = t_str_new(512);
+
+ /* scheme */
+ uri_append_scheme(urlstr, "imap");
+ str_append(urlstr, "//");
+
+ /* user */
+ if (url->userid != NULL || url->auth_type != NULL) {
+ if (url->userid != NULL)
+ uri_append_user_data(urlstr, ";:", url->userid);
+ if (url->auth_type != NULL) {
+ str_append(urlstr, ";AUTH=");
+ uri_append_user_data(urlstr, ";:", url->auth_type);
+ }
+ str_append_c(urlstr, '@');
+ }
+
+ /* server */
+ uri_append_host(urlstr, &url->host);
+ uri_append_port(urlstr, url->port);
+
+ /* Older syntax (RFC 2192) requires this slash at all times */
+ str_append_c(urlstr, '/');
+
+ /* mailbox */
+ if (url->mailbox != NULL)
+ imap_url_append_mailbox(url, urlstr);
+ return str_c(urlstr);
+}
+
+const char *
+imap_url_add_urlauth(const char *rumpurl, const char *mechanism,
+ const unsigned char *token, size_t token_len)
+{
+ return t_strconcat(rumpurl, ":", t_str_lcase(mechanism), ":",
+ binary_to_hex(token, token_len), NULL);
+}
diff --git a/src/lib-imap/imap-url.h b/src/lib-imap/imap-url.h
new file mode 100644
index 0000000..9f0b2aa
--- /dev/null
+++ b/src/lib-imap/imap-url.h
@@ -0,0 +1,71 @@
+#ifndef IMAP_URL_H
+#define IMAP_URL_H
+
+#include "uri-util.h"
+
+struct imap_url {
+ /* server */
+ struct uri_host host;
+ in_port_t port;
+
+ /* user */
+ const char *userid;
+ const char *auth_type;
+
+ /* mailbox */
+ const char *mailbox;
+ uint32_t uidvalidity; /* 0 if not set */
+
+ /* message part */
+ uint32_t uid;
+ const char *section;
+ uoff_t partial_offset;
+ uoff_t partial_size; /* 0 if not set */
+
+ /* message list (uid == 0) */
+ const char *search_program;
+
+ /* urlauth */
+ const char *uauth_rumpurl;
+ const char *uauth_access_application;
+ const char *uauth_access_user;
+ const char *uauth_mechanism;
+ const unsigned char *uauth_token;
+ size_t uauth_token_size;
+ time_t uauth_expire; /* (time_t)-1 if not set */
+
+ bool have_partial:1;
+};
+
+/*
+ * IMAP URL parsing
+ */
+
+enum imap_url_parse_flags {
+ /* Scheme part 'imap:' is already parsed externally. This implies that
+ this is an absolute IMAP URL. */
+ IMAP_URL_PARSE_SCHEME_EXTERNAL = 0x01,
+ /* Require relative URL (omitting _both_ scheme and authority), e.g.
+ /MAILBOX/;UID=uid or even ;UID=uid. This flag means that an absolute
+ URL makes no sense in this context. Relative URLs are allowed once a
+ base URL is provided to the parser. */
+ IMAP_URL_PARSE_REQUIRE_RELATIVE = 0x02,
+ /* Allow URLAUTH URL */
+ IMAP_URL_PARSE_ALLOW_URLAUTH = 0x04
+};
+
+/* Parses full IMAP URL. The returned URL is allocated from data stack. */
+int imap_url_parse(const char *url, const struct imap_url *base,
+ enum imap_url_parse_flags flags,
+ struct imap_url **url_r, const char **error_r);
+
+/*
+ * IMAP URL construction
+ */
+
+const char *imap_url_create(const struct imap_url *url);
+
+const char *imap_url_add_urlauth(const char *rumpurl, const char *mechanism,
+ const unsigned char *token, size_t token_len);
+
+#endif
diff --git a/src/lib-imap/imap-utf7.c b/src/lib-imap/imap-utf7.c
new file mode 100644
index 0000000..7ea53f5
--- /dev/null
+++ b/src/lib-imap/imap-utf7.c
@@ -0,0 +1,380 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "unichar.h"
+#include "imap-utf7.h"
+
+static const char imap_b64enc[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
+
+#define XX 0xff
+static const unsigned char imap_b64dec[256] = {
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, 63,XX,XX,XX,
+ 52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
+ XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
+ 15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
+ XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+ 41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
+ XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX
+};
+
+static void
+mbase64_encode(string_t *dest, const unsigned char *in, size_t len)
+{
+ str_append_c(dest, '&');
+ while (len >= 3) {
+ str_append_c(dest, imap_b64enc[in[0] >> 2]);
+ str_append_c(dest, imap_b64enc[((in[0] & 3) << 4) |
+ (in[1] >> 4)]);
+ str_append_c(dest, imap_b64enc[((in[1] & 0x0f) << 2) |
+ ((in[2] & 0xc0) >> 6)]);
+ str_append_c(dest, imap_b64enc[in[2] & 0x3f]);
+ in += 3;
+ len -= 3;
+ }
+ if (len > 0) {
+ str_append_c(dest, imap_b64enc[in[0] >> 2]);
+ if (len == 1)
+ str_append_c(dest, imap_b64enc[(in[0] & 0x03) << 4]);
+ else {
+ str_append_c(dest, imap_b64enc[((in[0] & 0x03) << 4) |
+ (in[1] >> 4)]);
+ str_append_c(dest, imap_b64enc[(in[1] & 0x0f) << 2]);
+ }
+ }
+ str_append_c(dest, '-');
+}
+
+static const char *
+imap_utf8_first_encode_char(const char *str, char escape_char)
+{
+ const char *p;
+
+ for (p = str; *p != '\0'; p++) {
+ if (*p == '&' || *p < 0x20 || *p >= 0x7f || *p == escape_char)
+ return p;
+ }
+ return NULL;
+}
+
+int imap_escaped_utf8_hex_to_char(const char *str, unsigned char *chr_r)
+{
+ unsigned int i = 0;
+ unsigned char c = 0;
+
+ /* NOTE: Only lowercase hex characters are allowed so the output is
+ reversible. */
+ for (;;) {
+ if (str[i] >= '0' && str[i] <= '9')
+ c += str[i] - '0';
+ else if (str[i] >= 'a' && str[i] <= 'f')
+ c += str[i] - 'a' + 10;
+ else
+ return -1;
+ if (++i == 2)
+ break;
+ c *= 0x10;
+ }
+ *chr_r = c;
+ return 0;
+}
+
+static int
+imap_utf8_to_utf7_int(const char *src, char escape_char, string_t *dest)
+{
+ const char *p;
+ unichar_t chr;
+ uint8_t *utf16, *u;
+ uint16_t u16;
+ unsigned char c;
+
+ p = imap_utf8_first_encode_char(src, escape_char);
+ if (p == NULL) {
+ /* no characters that need to be encoded */
+ str_append(dest, src);
+ return 0;
+ }
+
+ /* at least one encoded character */
+ str_append_data(dest, src, p-src);
+ utf16 = t_malloc0(MALLOC_MULTIPLY(strlen(p), 2));
+ while (*p != '\0') {
+ if (*p == escape_char &&
+ imap_escaped_utf8_hex_to_char(p+1, &c) == 0) {
+ str_append_c(dest, c);
+ p += 3;
+ continue;
+ }
+ if (*p == '&') {
+ str_append(dest, "&-");
+ p++;
+ continue;
+ }
+ if (*p >= 0x20 && *p < 0x7f) {
+ str_append_c(dest, *p);
+ p++;
+ continue;
+ }
+
+ u = utf16;
+ while (*p != '\0' && (*p < 0x20 || *p >= 0x7f)) {
+ if (uni_utf8_get_char(p, &chr) <= 0)
+ return -1;
+ /* @UNSAFE */
+ if (chr < UTF16_SURROGATE_BASE) {
+ *u++ = chr >> 8;
+ *u++ = chr & 0xff;
+ } else {
+ u16 = UTF16_SURROGATE_HIGH(chr);
+ *u++ = u16 >> 8;
+ *u++ = u16 & 0xff;
+ u16 = UTF16_SURROGATE_LOW(chr);
+ *u++ = u16 >> 8;
+ *u++ = u16 & 0xff;
+ }
+ p += uni_utf8_char_bytes((unsigned char)*p);
+ }
+ mbase64_encode(dest, utf16, u-utf16);
+ }
+ return 0;
+}
+
+int imap_utf8_to_utf7(const char *src, string_t *dest)
+{
+ return imap_utf8_to_utf7_int(src, '\0', dest);
+}
+
+int imap_escaped_utf8_to_utf7(const char *src, char escape_char, string_t *dest)
+{
+ i_assert(escape_char != '&');
+
+ return imap_utf8_to_utf7_int(src, escape_char, dest);
+}
+
+int t_imap_utf8_to_utf7(const char *src, const char **dest_r)
+{
+ string_t *str;
+ int ret;
+
+ if (imap_utf8_first_encode_char(src, '\0') == NULL) {
+ *dest_r = src;
+ return 0;
+ }
+
+ str = t_str_new(64);
+ ret = imap_utf8_to_utf7(src, str);
+ *dest_r = str_c(str);
+ return ret;
+}
+
+static int utf16buf_to_utf8(string_t *dest, const unsigned char output[4],
+ unsigned int *_pos, unsigned int len)
+{
+ unsigned int pos = *_pos;
+ uint16_t high, low;
+ unichar_t chr;
+
+ if (len % 2 != 0)
+ return -1;
+
+ high = (output[pos % 4] << 8) | output[(pos+1) % 4];
+ if (high < UTF16_SURROGATE_HIGH_FIRST ||
+ high > UTF16_SURROGATE_HIGH_MAX) {
+ /* single byte */
+ size_t oldlen = str_len(dest);
+
+ if (high == 0) {
+ /* Encoded NUL isn't going to work in Dovecot code,
+ even though it's technically valid. Return failure
+ so the callers don't even get a chance to handle the
+ NUL in the string inconsistently. */
+ return -1;
+ }
+ uni_ucs4_to_utf8_c(high, dest);
+ if (str_len(dest) - oldlen == 1) {
+ unsigned char last = str_data(dest)[oldlen];
+ if (last >= 0x20 && last < 0x7f)
+ return -1;
+ }
+ *_pos = (pos + 2) % 4;
+ return 0;
+ }
+
+ if (high > UTF16_SURROGATE_HIGH_LAST)
+ return -1;
+ if (len != 4) {
+ /* missing the second character */
+ return -1;
+ }
+
+ low = (output[(pos+2)%4] << 8) | output[(pos+3) % 4];
+ if (low < UTF16_SURROGATE_LOW_FIRST || low > UTF16_SURROGATE_LOW_LAST)
+ return -1;
+
+ chr = UTF16_SURROGATE_BASE +
+ (((high & UTF16_SURROGATE_MASK) << UTF16_SURROGATE_SHIFT) |
+ (low & UTF16_SURROGATE_MASK));
+ uni_ucs4_to_utf8_c(chr, dest);
+ return 0;
+}
+
+static int mbase64_decode_to_utf8(string_t *dest, const char **_src)
+{
+ const char *src = *_src;
+ unsigned char input[4], output[4];
+ unsigned int outstart = 0, outpos = 0;
+
+ while (*src != '-') {
+ input[0] = imap_b64dec[(uint8_t)src[0]];
+ if (input[0] == 0xff)
+ return -1;
+ input[1] = imap_b64dec[(uint8_t)src[1]];
+ if (input[1] == 0xff)
+ return -1;
+
+ output[outpos % 4] = (input[0] << 2) | (input[1] >> 4);
+ if (++outpos % 4 == outstart) {
+ if (utf16buf_to_utf8(dest, output, &outstart, 4) < 0)
+ return -1;
+ }
+
+ input[2] = imap_b64dec[(uint8_t)src[2]];
+ if (input[2] == 0xff) {
+ if (src[2] != '-')
+ return -1;
+
+ src += 2;
+ break;
+ }
+
+ output[outpos % 4] = ((input[1] << 4) & 0xff) | (input[2] >> 2);
+ if (++outpos % 4 == outstart) {
+ if (utf16buf_to_utf8(dest, output, &outstart, 4) < 0)
+ return -1;
+ }
+
+ input[3] = imap_b64dec[(uint8_t)src[3]];
+ if (input[3] == 0xff) {
+ if (src[3] != '-')
+ return -1;
+
+ src += 3;
+ break;
+ }
+
+ output[outpos % 4] = ((input[2] << 6) & 0xc0) | input[3];
+ if (++outpos % 4 == outstart) {
+ if (utf16buf_to_utf8(dest, output, &outstart, 4) < 0)
+ return -1;
+ }
+
+ src += 4;
+ }
+ if (outstart != outpos % 4) {
+ if (utf16buf_to_utf8(dest, output, &outstart,
+ (4 + outpos - outstart) % 4) < 0)
+ return -1;
+ }
+
+ /* Found the ending '-'. Make sure it's not followed by unnecessary
+ shift. Note that '&' is always escaped as "&-" so it's not an
+ unnecessary shift. */
+ if (src[1] == '&' && src[2] != '-')
+ return -1;
+
+ *_src = src + 1;
+ return 0;
+}
+
+static int
+imap_utf7_to_utf8_int(const char *src, const char *escape_chars, string_t *dest)
+{
+ const char *p;
+
+ for (p = src; *p != '\0'; p++) {
+ if (*p < 0x20 || *p >= 0x7f) {
+ if (escape_chars[0] == '\0')
+ return -1;
+ break;
+ }
+ if (*p == '&' || strchr(escape_chars, *p) != NULL)
+ break;
+ }
+ if (*p == '\0') {
+ /* no IMAP-UTF-7 encoded characters */
+ str_append(dest, src);
+ return 0;
+ }
+
+ /* at least one encoded character */
+ str_append_data(dest, src, p-src);
+ while (*p != '\0') {
+ if (strchr(escape_chars, *p) != NULL ||
+ *p < 0x20 || *p >= 0x7f) {
+ str_printfa(dest, "%c%02x", escape_chars[0],
+ (unsigned char)*p);
+ p++;
+ } else if (*p == '&') {
+ if (*++p == '-') {
+ str_append_c(dest, '&');
+ p++;
+ } else {
+ size_t orig_size = str_len(dest);
+ if (mbase64_decode_to_utf8(dest, &p) < 0) {
+ if (escape_chars[0] == '\0')
+ return -1;
+ str_truncate(dest, orig_size);
+ str_printfa(dest, "%c26", escape_chars[0]);
+ }
+ }
+ } else {
+ str_append_c(dest, *p++);
+ }
+ }
+ return 0;
+}
+
+int imap_utf7_to_utf8(const char *src, string_t *dest)
+{
+ return imap_utf7_to_utf8_int(src, "", dest);
+}
+
+void imap_utf7_to_utf8_escaped(const char *src, const char *escape_chars,
+ string_t *dest)
+{
+ i_assert(escape_chars[0] != '&');
+
+ if (imap_utf7_to_utf8_int(src, escape_chars, dest) < 0)
+ i_unreached();
+}
+
+bool imap_utf7_is_valid(const char *src)
+{
+ const char *p;
+ int ret;
+
+ for (p = src; *p != '\0'; p++) {
+ if (*p < 0x20 || *p >= 0x7f)
+ return FALSE;
+ if (*p == '&') {
+ /* slow scan */
+ T_BEGIN {
+ string_t *tmp = t_str_new(128);
+ ret = imap_utf7_to_utf8(p, tmp);
+ } T_END;
+ if (ret < 0)
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
diff --git a/src/lib-imap/imap-utf7.h b/src/lib-imap/imap-utf7.h
new file mode 100644
index 0000000..d7ae306
--- /dev/null
+++ b/src/lib-imap/imap-utf7.h
@@ -0,0 +1,28 @@
+#ifndef IMAP_UTF7_H
+#define IMAP_UTF7_H
+
+/* Convert an UTF-8 string to IMAP-UTF-7. Returns 0 if ok, -1 if src isn't
+ valid UTF-8. */
+int imap_utf8_to_utf7(const char *src, string_t *dest);
+int t_imap_utf8_to_utf7(const char *src, const char **dest_r);
+/* Like imap_utf8_to_utf7(), but decode all <escape_char><hex> instances.
+ Returns -1 if src isn't valid UTF-8. Note that invalid <escape_char> content
+ isn't treated as an error - it's simply passed through. */
+int imap_escaped_utf8_to_utf7(const char *src, char escape_char, string_t *dest);
+/* For manually parsing the <hex> after <escape_char>. Returns 0 on success,
+ -1 if str doesn't point to valid <hex>. */
+int imap_escaped_utf8_hex_to_char(const char *str, unsigned char *chr_r);
+
+/* Convert IMAP-UTF-7 string to UTF-8. Returns 0 if ok, -1 if src isn't
+ valid IMAP-UTF-7. */
+int imap_utf7_to_utf8(const char *src, string_t *dest);
+/* Like imap_utf7_to_utf8(), but write invalid input as <escape_chars[0]><hex>.
+ All the characters in escape_chars[] are escaped in the same way. This
+ allows converting the escaped output back to the original (broken)
+ IMAP-UTF-7 input. */
+void imap_utf7_to_utf8_escaped(const char *src, const char *escape_chars,
+ string_t *dest);
+/* Returns TRUE if the string is valid IMAP-UTF-7 string. */
+bool imap_utf7_is_valid(const char *src);
+
+#endif
diff --git a/src/lib-imap/imap-util.c b/src/lib-imap/imap-util.c
new file mode 100644
index 0000000..dc1ae24
--- /dev/null
+++ b/src/lib-imap/imap-util.c
@@ -0,0 +1,202 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "strescape.h"
+#include "unichar.h"
+#include "mail-types.h"
+#include "imap-parser.h"
+#include "imap-util.h"
+
+void imap_write_flags(string_t *dest, enum mail_flags flags,
+ const char *const *keywords)
+{
+ size_t size;
+
+ size = str_len(dest);
+ if ((flags & MAIL_ANSWERED) != 0)
+ str_append(dest, "\\Answered ");
+ if ((flags & MAIL_FLAGGED) != 0)
+ str_append(dest, "\\Flagged ");
+ if ((flags & MAIL_DELETED) != 0)
+ str_append(dest, "\\Deleted ");
+ if ((flags & MAIL_SEEN) != 0)
+ str_append(dest, "\\Seen ");
+ if ((flags & MAIL_DRAFT) != 0)
+ str_append(dest, "\\Draft ");
+ if ((flags & MAIL_RECENT) != 0)
+ str_append(dest, "\\Recent ");
+
+ if (keywords != NULL) {
+ /* we have keywords too */
+ while (*keywords != NULL) {
+ str_append(dest, *keywords);
+ str_append_c(dest, ' ');
+ keywords++;
+ }
+ }
+
+ if (str_len(dest) != size)
+ str_truncate(dest, str_len(dest)-1);
+}
+
+enum mail_flags imap_parse_system_flag(const char *str)
+{
+ if (strcasecmp(str, "\\Answered") == 0)
+ return MAIL_ANSWERED;
+ else if (strcasecmp(str, "\\Flagged") == 0)
+ return MAIL_FLAGGED;
+ else if (strcasecmp(str, "\\Deleted") == 0)
+ return MAIL_DELETED;
+ else if (strcasecmp(str, "\\Seen") == 0)
+ return MAIL_SEEN;
+ else if (strcasecmp(str, "\\Draft") == 0)
+ return MAIL_DRAFT;
+ else if (strcasecmp(str, "\\Recent") == 0)
+ return MAIL_RECENT;
+ else
+ return 0;
+}
+
+void imap_write_seq_range(string_t *dest, const ARRAY_TYPE(seq_range) *array)
+{
+ const struct seq_range *range;
+ unsigned int i, count;
+
+ range = array_get(array, &count);
+ for (i = 0; i < count; i++) {
+ if (i > 0)
+ str_append_c(dest, ',');
+ str_printfa(dest, "%u", range[i].seq1);
+ if (range[i].seq1 != range[i].seq2)
+ str_printfa(dest, ":%u", range[i].seq2);
+ }
+}
+
+void imap_write_arg(string_t *dest, const struct imap_arg *arg)
+{
+ switch (arg->type) {
+ case IMAP_ARG_NIL:
+ str_append(dest, "NIL");
+ break;
+ case IMAP_ARG_ATOM:
+ str_append(dest, imap_arg_as_astring(arg));
+ break;
+ case IMAP_ARG_STRING: {
+ const char *strarg = imap_arg_as_astring(arg);
+ str_append_c(dest, '"');
+ str_append_escaped(dest, strarg, strlen(strarg));
+ str_append_c(dest, '"');
+ break;
+ }
+ case IMAP_ARG_LITERAL: {
+ const char *strarg = imap_arg_as_astring(arg);
+ str_printfa(dest, "{%zu}\r\n",
+ strlen(strarg));
+ str_append(dest, strarg);
+ break;
+ }
+ case IMAP_ARG_LIST:
+ str_append_c(dest, '(');
+ imap_write_args(dest, imap_arg_as_list(arg));
+ str_append_c(dest, ')');
+ break;
+ case IMAP_ARG_LITERAL_SIZE:
+ case IMAP_ARG_LITERAL_SIZE_NONSYNC:
+ str_printfa(dest, "<%"PRIuUOFF_T" byte literal>",
+ imap_arg_as_literal_size(arg));
+ break;
+ case IMAP_ARG_EOL:
+ i_unreached();
+ }
+}
+
+void imap_write_args(string_t *dest, const struct imap_arg *args)
+{
+ bool first = TRUE;
+
+ for (; !IMAP_ARG_IS_EOL(args); args++) {
+ if (first)
+ first = FALSE;
+ else
+ str_append_c(dest, ' ');
+ imap_write_arg(dest, args);
+ }
+}
+
+static void imap_human_args_fix_control_chars(char *str)
+{
+ size_t i;
+
+ for (i = 0; str[i] != '\0'; i++) {
+ if (str[i] < 0x20 || str[i] == 0x7f)
+ str[i] = '?';
+ }
+}
+
+void imap_write_args_for_human(string_t *dest, const struct imap_arg *args)
+{
+ bool first = TRUE;
+
+ for (; !IMAP_ARG_IS_EOL(args); args++) {
+ if (first)
+ first = FALSE;
+ else
+ str_append_c(dest, ' ');
+
+ switch (args->type) {
+ case IMAP_ARG_NIL:
+ str_append(dest, "NIL");
+ break;
+ case IMAP_ARG_ATOM:
+ /* atom has only printable us-ascii chars */
+ str_append(dest, imap_arg_as_astring(args));
+ break;
+ case IMAP_ARG_STRING:
+ case IMAP_ARG_LITERAL: {
+ const char *strarg = imap_arg_as_astring(args);
+
+ if (strpbrk(strarg, "\r\n") != NULL) {
+ str_printfa(dest, "<%zu byte multi-line literal>",
+ strlen(strarg));
+ break;
+ }
+ strarg = str_escape(strarg);
+
+ str_append_c(dest, '"');
+ size_t start_pos = str_len(dest);
+ /* append only valid UTF-8 chars */
+ if (uni_utf8_get_valid_data((const unsigned char *)strarg,
+ strlen(strarg), dest))
+ str_append(dest, strarg);
+ /* replace all control chars */
+ imap_human_args_fix_control_chars(
+ str_c_modifiable(dest) + start_pos);
+ str_append_c(dest, '"');
+ break;
+ }
+ case IMAP_ARG_LIST:
+ str_append_c(dest, '(');
+ imap_write_args_for_human(dest, imap_arg_as_list(args));
+ str_append_c(dest, ')');
+ break;
+ case IMAP_ARG_LITERAL_SIZE:
+ case IMAP_ARG_LITERAL_SIZE_NONSYNC:
+ str_printfa(dest, "<%"PRIuUOFF_T" byte literal>",
+ imap_arg_as_literal_size(args));
+ break;
+ case IMAP_ARG_EOL:
+ i_unreached();
+ }
+ }
+}
+
+const char *imap_args_to_str(const struct imap_arg *args)
+{
+ string_t *str;
+
+ str = t_str_new(128);
+ imap_write_args(str, args);
+ return str_c(str);
+}
diff --git a/src/lib-imap/imap-util.h b/src/lib-imap/imap-util.h
new file mode 100644
index 0000000..47b7d2c
--- /dev/null
+++ b/src/lib-imap/imap-util.h
@@ -0,0 +1,29 @@
+#ifndef IMAP_UTIL_H
+#define IMAP_UTIL_H
+
+#include "seq-range-array.h"
+#include "mail-types.h"
+
+struct imap_arg;
+
+/* Write flags as a space separated string. */
+void imap_write_flags(string_t *dest, enum mail_flags flags,
+ const char *const *keywords) ATTR_NULL(3);
+/* Parse system flag from a string, or return 0 if it's invalid. */
+enum mail_flags imap_parse_system_flag(const char *str);
+
+/* Write sequence range as IMAP sequence-set */
+void imap_write_seq_range(string_t *dest, const ARRAY_TYPE(seq_range) *array);
+/* Write IMAP arg to the given string. Because IMAP_ARG_LITERAL_SIZE* have no
+ content, they're written as "{size}\r\n<too large>". */
+void imap_write_arg(string_t *dest, const struct imap_arg *arg);
+/* Same as imap_write_arg(), but write all the args until EOL. */
+void imap_write_args(string_t *dest, const struct imap_arg *args);
+/* Write IMAP args in a human-readable format to given string (e.g. for
+ logging). The output is a single valid UTF-8 line without control
+ characters. Multi-line literals are replaced with a generic placeholder. */
+void imap_write_args_for_human(string_t *dest, const struct imap_arg *args);
+/* Like imap_write_args(), but return the string allocated from data stack. */
+const char *imap_args_to_str(const struct imap_arg *args);
+
+#endif
diff --git a/src/lib-imap/test-imap-bodystructure.c b/src/lib-imap/test-imap-bodystructure.c
new file mode 100644
index 0000000..6035118
--- /dev/null
+++ b/src/lib-imap/test-imap-bodystructure.c
@@ -0,0 +1,733 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "str.h"
+#include "message-part-data.h"
+#include "message-part-serialize.h"
+#include "message-parser.h"
+#include "imap-bodystructure.h"
+#include "test-common.h"
+
+struct parse_test {
+ const char *message;
+ const char *body;
+ const char *bodystructure;
+};
+
+struct parse_test parse_tests[] = {
+ {
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "\n"
+ "body\n",
+ .bodystructure =
+ "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL",
+ .body =
+ "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1"
+ },{
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: text/plain; charset=utf-8\n"
+ "Content-Transfer-Encoding: 8bit\n"
+ "\n"
+ "body\n"
+ "\n",
+ .bodystructure =
+ "\"text\" \"plain\" (\"charset\" \"utf-8\") NIL NIL \"8bit\" 8 2 NIL NIL NIL NIL",
+ .body =
+ "\"text\" \"plain\" (\"charset\" \"utf-8\") NIL NIL \"8bit\" 8 2"
+ },{
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2007 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: multipart/mixed; boundary=\"foo\n"
+ " bar\"\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: text/x-myown; charset=us-ascii\n"
+ "\n"
+ "hello\n"
+ "\n"
+ "--foo bar--\n"
+ "\n",
+ .bodystructure =
+ "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 7 1 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL",
+ .body =
+ "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 7 1) \"mixed\""
+ },{
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: multipart/mixed; boundary=\"foo bar\"\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "\n"
+ "See attached...\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: message/rfc822\n"
+ "\n"
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "\n"
+ "body\n"
+ "\n"
+ "--foo bar--\n"
+ "\n",
+ .bodystructure =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL NIL)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL) 6 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL",
+ .body =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1) 6) \"mixed\""
+ },{
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: multipart/mixed; boundary=\"foo bar\"\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "Content-ID: <A.frop.example.com>\n"
+ "Content-Description: Container message\n"
+ "\n"
+ "See attached...\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: message/rfc822\n"
+ "Content-ID: <B.frop.example.com>\n"
+ "Content-Description: Forwarded\n"
+ "\n"
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "\n"
+ "body\n"
+ "\n"
+ "--foo bar--\n",
+ .bodystructure =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") \"<A.frop.example.com>\" \"Container message\" \"7bit\" 17 1 NIL NIL NIL NIL)(\"message\" \"rfc822\" NIL \"<B.frop.example.com>\" \"Forwarded\" \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL) 6 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL",
+ .body =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") \"<A.frop.example.com>\" \"Container message\" \"7bit\" 17 1)(\"message\" \"rfc822\" NIL \"<B.frop.example.com>\" \"Forwarded\" \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1) 6) \"mixed\""
+ },{
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: multipart/mixed; boundary=\"foo bar\"\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: text/plain; charset=us-ascii; format=\"flowed\";\n"
+ " delsp=\"no\"\n"
+ "Content-Language: la\n"
+ "\n"
+ "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo\n"
+ "ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis\n"
+ "parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec,\n"
+ "pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec\n"
+ "pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo,\n"
+ "rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede\n"
+ "mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper\n"
+ "nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu,\n"
+ "consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra\n"
+ "quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet.\n"
+ "Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur\n"
+ "ullamcorper ultricies nisi. Nam eget dui.\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: image/png\n"
+ "Content-Transfer-Encoding: base64\n"
+ "Content-Disposition: attachment; filename=\"pigeon.png\"\n"
+ "\n"
+ "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n"
+ "AAAGJwAABicBTVTYxwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAN2SURB\n"
+ "VEiJ7ZfdK7tvHMffZuWpKTlR1my3bTEPU0hJcsABh0QrKYUTJ5KyHCzlyPwBkpSyg1mUpB3ILYna\n"
+ "lEYKcbADm4cDD7NpM2bv75HF1/az8l3fX/1+77pO7vu6rtf1/jx0X3caSeIvSfK3wP/DPykcDsNs\n"
+ "NqOqqgpdXV3Y2NhIGVz6+4PJyUlcXFxAFEV4vV709fVBrVZDpVL9eTo/aGVlhXl5efT5fDw/P2c4\n"
+ "HKbdbmd3dzdTIZDk6+srh4eHqdPpqNfrSZJGo5H7+/sMBoNUKpUpgUsAQCqVQq1WIxgMfgp/dXU1\n"
+ "srKykJ+fD6/X+8ejHiu4wcFB2Gy2uJPq6uqwt7eXOjgAaLXahHCn05laeCKlyvmXVosnrVaLQCCA\n"
+ "4uJiyOVyCIIQGyqVCoIgoKCgIDXwtLQ0HBwc4O3tDR6PB263G263G8fHx1hbW4Pb7cbNzQ1yc3Nj\n"
+ "hxEEAdXV1WhoaEi88cfSf3h4iLXaR01MTFChUFChULCjo4M7OztxW8fn89HlcnF5eZlTU1Nsb29n\n"
+ "W1sb/X5/3PlJwd/19vbG7e1tlpWVcXV19ftGJjk0NESbzRb33aeCy8zMhM/nSxgliUSCxsZGiKKI\n"
+ "ra2tb9N1e3uLSCSC8fFxGAwGDAwMfC7c309TWFjI09PTpFwlis76+jo7OzspCAJNJhN3d3fpcDho\n"
+ "tVqpUCgYCoVIkl8Krr6+Hna7HSUlJd86+yiPx4P5+XlYLBZUVFSgv78fVqsV6enpAIC7uzt4vV5E\n"
+ "IhFIJJL4zp1OJ2traxmJRL51+fLywuXlZba2trK0tJRms5mXl5c8PT3l4uIix8bG2NbWRqVSyfLy\n"
+ "cvb09PDw8DC2Po38eocbGRlBIBDA9PQ0pNKv3Xh2doa5uTksLCwgJycHNTU1yM3NxdHREa6vr6HR\n"
+ "aKDX62NDp9MhIyPjyz5x4SQxOjoKURTR29sLmUyG7Oxs+P1+WCwW7O7uQiKRQKvVQq/Xo7KyMgaS\n"
+ "y+VJpyou/F0nJydYWlqCy+XC1dUV7u/vYTQa8fT0BIfDgaWlpaRBcZVsFc/OznJsbIwkaTKZODMz\n"
+ "k+zShEr6Arm5uYnm5mYAgCiKaGlp+ZnrZJ1Ho1EWFRXx+fmZj4+P1Gg0P3ZNJqj2dxkMhth3PBAI\n"
+ "QCaTIRqNIhQKoamp6cc5/0d4qvXv+mn4z8B/AV1UVu6zi+zUAAAAAElFTkSuQmCC\n"
+ "--foo bar--\n",
+ .bodystructure =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\" \"format\" \"flowed\" \"delsp\" \"no\") NIL NIL \"7bit\" 881 12 NIL NIL (\"la\") NIL)(\"image\" \"png\" NIL NIL NIL \"base64\" 1390 NIL (\"attachment\" (\"filename\" \"pigeon.png\")) NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL",
+ .body =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\" \"format\" \"flowed\" \"delsp\" \"no\") NIL NIL \"7bit\" 881 12)(\"image\" \"png\" NIL NIL NIL \"base64\" 1390) \"mixed\""
+ },{
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2007 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: multipart/mixed; boundary=\"foo\n"
+ " bar\"\n"
+ "\n"
+ "Root MIME prologue\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: text/x-myown; charset=us-ascii; foo=\"quoted\\\"string\"\n"
+ "Content-ID: <foo@example.com>\n"
+ "Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==\n"
+ "Content-Disposition: inline; foo=bar\n"
+ "Content-Description: hellodescription\n"
+ "Content-Language: en, fi, se\n"
+ "Content-Location: http://example.com/test.txt\n"
+ "\n"
+ "hello\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: message/rfc822\n"
+ "\n"
+ "From: sub@domain.org\n"
+ "To: sub-to1@domain.org, sub-to2@domain.org\n"
+ "Date: Sun, 12 Aug 2012 12:34:56 +0300\n"
+ "Subject: submsg\n"
+ "Content-Type: multipart/alternative; boundary=\"sub1\"\n"
+ "\n"
+ "Sub MIME prologue\n"
+ "--sub1\n"
+ "Content-Type: text/html\n"
+ "Content-Transfer-Encoding: 8bit\n"
+ "\n"
+ "<p>Hello world</p>\n"
+ "\n"
+ "--sub1\n"
+ "Content-Type: text/plain\n"
+ "Content-Transfer-Encoding: ?invalid\n"
+ "\n"
+ "Hello another world\n"
+ "\n"
+ "--sub1--\n"
+ "Sub MIME epilogue\n"
+ "\n"
+ "--foo bar--\n"
+ "Root MIME epilogue\n",
+ .bodystructure =
+ "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\" \"foo\" \"quoted\\\"string\") \"<foo@example.com>\" \"hellodescription\" \"7bit\" 7 1 \"Q2hlY2sgSW50ZWdyaXR5IQ==\" (\"inline\" (\"foo\" \"bar\")) (\"en\" \"fi\" \"se\") \"http://example.com/test.txt\")(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 412 (\"Sun, 12 Aug 2012 12:34:56 +0300\" \"submsg\" ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub-to1\" \"domain.org\")(NIL NIL \"sub-to2\" \"domain.org\")) NIL NIL NIL NIL) ((\"text\" \"html\" (\"charset\" \"us-ascii\") NIL NIL \"8bit\" 20 1 NIL NIL NIL NIL)(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 21 1 NIL NIL NIL NIL) \"alternative\" (\"boundary\" \"sub1\") NIL NIL NIL) 21 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL",
+ .body =
+ "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\" \"foo\" \"quoted\\\"string\") \"<foo@example.com>\" \"hellodescription\" \"7bit\" 7 1)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 412 (\"Sun, 12 Aug 2012 12:34:56 +0300\" \"submsg\" ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub-to1\" \"domain.org\")(NIL NIL \"sub-to2\" \"domain.org\")) NIL NIL NIL NIL) ((\"text\" \"html\" (\"charset\" \"us-ascii\") NIL NIL \"8bit\" 20 1)(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 21 1) \"alternative\") 21) \"mixed\""
+ },{
+ .message =
+ "Content-Type: multipart/mixed; boundary=\"foo\"\n"
+ "\n",
+ .bodystructure =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo\") NIL NIL NIL",
+ .body =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0) \"mixed\""
+
+ }
+};
+
+static const unsigned int parse_tests_count = N_ELEMENTS(parse_tests);
+
+struct normalize_test {
+ const char *message;
+ const char *input;
+ const char *output;
+};
+
+struct normalize_test normalize_tests[] = {
+ {
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "\n"
+ "body\n",
+ .input =
+ "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1",
+ .output =
+ "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL",
+ }, {
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "Content-MD5: ae6ba5b4c6eb1efd4a9fac3708046cbe\n"
+ "\n"
+ "body\n",
+ .input =
+ "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 \"ae6ba5b4c6eb1efd4a9fac3708046cbe\"",
+ .output =
+ "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 \"ae6ba5b4c6eb1efd4a9fac3708046cbe\" NIL NIL NIL",
+ }, {
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: multipart/mixed; boundary=\"foo bar\"\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "\n"
+ "See attached...\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: message/rfc822\n"
+ "\n"
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "\n"
+ "body\n"
+ "\n"
+ "--foo bar--\n"
+ "\n",
+ .input =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1) 6) \"mixed\"",
+ .output =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL NIL)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL) 6 NIL NIL NIL NIL) \"mixed\" NIL NIL NIL NIL"
+ }, {
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: multipart/mixed; boundary=\"foo bar\"\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "\n"
+ "See attached...\n"
+ "\n"
+ "--foo bar--\n"
+ "\n",
+ .input =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1) \"mixed\" (\"boundary\" \"foo bar\")",
+ .output =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL"
+ }, {
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: multipart/mixed; boundary=\"foo bar\"\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "Content-MD5: 6537bae18ed07779c9dc25f24635b0f3\n"
+ "\n"
+ "See attached...\n"
+ "\n"
+ "--foo bar--\n"
+ "\n",
+ .input =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 \"6537bae18ed07779c9dc25f24635b0f3\") \"mixed\" (\"boundary\" \"foo bar\")",
+ .output =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 \"6537bae18ed07779c9dc25f24635b0f3\" NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL"
+ }, {
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: multipart/mixed; boundary=\"foo bar\"\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "Content-Language: en\n"
+ "\n"
+ "See attached...\n"
+ "\n"
+ "--foo bar--\n"
+ "\n",
+ .input =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL \"en\") \"mixed\" (\"boundary\" \"foo bar\")",
+ .output =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL (\"en\") NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL"
+ }, {
+ .message =
+ "From: user@domain.org\n"
+ "Date: Sat, 24 Mar 2017 23:00:00 +0200\n"
+ "Mime-Version: 1.0\n"
+ "Content-Type: multipart/mixed; boundary=\"foo bar\"\n"
+ "\n"
+ "--foo bar\n"
+ "Content-Type: text/plain; charset=us-ascii\n"
+ "Content-Location: http://www.example.com/frop.txt\n"
+ "\n"
+ "See attached...\n"
+ "\n"
+ "--foo bar--\n"
+ "\n",
+ .input =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL \"http://www.example.com/frop.txt\") \"mixed\" (\"boundary\" \"foo bar\")",
+ .output =
+ "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL \"http://www.example.com/frop.txt\") \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL"
+ }
+};
+
+static const unsigned int normalize_tests_count = N_ELEMENTS(normalize_tests);
+
+static struct message_part *
+msg_parse(pool_t pool, const char *message, unsigned int max_nested_mime_parts,
+ unsigned int max_total_mime_parts, bool parse_bodystructure)
+{
+ const struct message_parser_settings parser_set = {
+ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+ MESSAGE_HEADER_PARSER_FLAG_DROP_CR,
+ .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK,
+ .max_nested_mime_parts = max_nested_mime_parts,
+ .max_total_mime_parts = max_total_mime_parts,
+ };
+ struct message_parser_ctx *parser;
+ struct istream *input;
+ struct message_block block;
+ struct message_part *parts;
+ int ret;
+
+ input = i_stream_create_from_data(message, strlen(message));
+ parser = message_parser_init(pool, input, &parser_set);
+ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
+ if (parse_bodystructure) {
+ message_part_data_parse_from_header(pool, block.part,
+ block.hdr);
+ }
+ }
+ test_assert(ret < 0);
+
+ message_parser_deinit(&parser, &parts);
+ i_stream_unref(&input);
+ return parts;
+}
+
+static void test_imap_bodystructure_write(void)
+{
+ struct message_part *parts;
+ const char *error;
+ unsigned int i;
+
+ for (i = 0; i < parse_tests_count; i++) T_BEGIN {
+ struct parse_test *test = &parse_tests[i];
+ string_t *str = t_str_new(128);
+ pool_t pool = pool_alloconly_create("imap bodystructure write", 1024);
+
+ test_begin(t_strdup_printf("imap bodystructure write [%u]", i));
+ parts = msg_parse(pool, test->message, 0, 0, TRUE);
+
+ test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0);
+ test_assert(strcmp(str_c(str), test->bodystructure) == 0);
+
+ str_truncate(str, 0);
+ test_assert(imap_bodystructure_write(parts, str, FALSE, &error) == 0);
+ test_assert(strcmp(str_c(str), test->body) == 0);
+
+ pool_unref(&pool);
+ test_end();
+ } T_END;
+
+ T_BEGIN {
+ test_begin("imap bodystructure write - corrupted");
+ pool_t pool = pool_alloconly_create("imap bodystructure write", 1024);
+
+ parts = msg_parse(pool, "Subject: hello world", 0, 0, TRUE);
+ i_assert((parts->flags & MESSAGE_PART_FLAG_TEXT) != 0);
+ parts->flags &= ENUM_NEGATE(MESSAGE_PART_FLAG_TEXT);
+
+ string_t *str = t_str_new(128);
+ test_assert(imap_bodystructure_write(parts, str, FALSE, &error) < 0);
+ test_assert_strcmp(error, "text flag mismatch");
+ pool_unref(&pool);
+ test_end();
+ } T_END;
+}
+
+static void test_imap_bodystructure_parse(void)
+{
+ struct message_part *parts;
+ const char *error;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < parse_tests_count; i++) T_BEGIN {
+ struct parse_test *test = &parse_tests[i];
+ string_t *str = t_str_new(128);
+ pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024);
+
+ test_begin(t_strdup_printf("imap bodystructure parser [%u]", i));
+ parts = msg_parse(pool, test->message, 0, 0, FALSE);
+
+ test_assert(imap_body_parse_from_bodystructure(test->bodystructure,
+ str, &error) == 0);
+ test_assert(strcmp(str_c(str), test->body) == 0);
+
+ ret = imap_bodystructure_parse(test->bodystructure,
+ pool, parts, &error);
+ test_assert(ret == 0);
+
+ if (ret == 0) {
+ str_truncate(str, 0);
+ test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0);
+ test_assert(strcmp(str_c(str), test->bodystructure) == 0);
+ } else {
+ i_error("Invalid BODYSTRUCTURE: %s", error);
+ }
+
+ pool_unref(&pool);
+ test_end();
+ } T_END;
+}
+
+static void test_imap_bodystructure_parse_invalid(void)
+{
+ static const struct parse_test_invalid {
+ const char *message;
+ const char *bodystructure;
+ const char *error;
+ } invalid_bodystructure_tests[] = {
+ /* Make sure NILs aren't allowed where strings are expected */
+ { "foo", "NIL \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" },
+ { "foo", "\"text\" NIL (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" },
+ { "foo", "\"text\" \"plain\" (NIL \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" },
+ { "foo", "\"text\" \"plain\" (\"charset\" NIL) NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL NIL 0 0 NIL NIL NIL NIL", "Invalid content-transfer-encoding" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" NIL 0 NIL NIL NIL NIL", "Invalid size field" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 NIL NIL NIL NIL NIL", "Invalid lines field" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (NIL (\"foo\" \"bar\")) NIL NIL", "Invalid content-disposition" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (NIL \"bar\")) NIL NIL", "Invalid content-disposition params" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (\"foo\" NIL)) NIL NIL", "Invalid content-disposition params" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (NIL \"bar\") NIL", "Invalid content-language" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (\"foo\" NIL) NIL", "Invalid content-language" },
+
+ /* Make sure atoms aren't allowed anywhere */
+ { "foo", "ATOM \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" },
+ { "foo", "\"text\" ATOM (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" },
+ { "foo", "\"text\" \"plain\" (ATOM \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" },
+ { "foo", "\"text\" \"plain\" (\"charset\" ATOM) NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") ATOM NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-id" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL ATOM \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-description" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL ATOM 0 0 NIL NIL NIL NIL", "Invalid content-transfer-encoding" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" ATOM 0 NIL NIL NIL NIL", "Invalid size field" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 ATOM NIL NIL NIL NIL", "Invalid lines field" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 ATOM NIL NIL NIL", "Invalid content-md5" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL ATOM NIL NIL", "Invalid content-disposition list" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (ATOM (\"foo\" \"bar\")) NIL NIL", "Invalid content-disposition" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (ATOM \"bar\")) NIL NIL", "Invalid content-disposition params" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (\"foo\" ATOM)) NIL NIL", "Invalid content-disposition params" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL ATOM NIL", "Invalid content-language" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (ATOM \"bar\") NIL", "Invalid content-language" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (\"foo\" ATOM) NIL", "Invalid content-language" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL ATOM", "Invalid content-location" },
+
+ /* Make sure empty lists aren't allowed anywhere */
+ { "foo", "\"text\" \"plain\" () NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") () NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-id" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL () \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-description" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL () 0 0 NIL NIL NIL NIL", "Invalid content-transfer-encoding" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" () 0 NIL NIL NIL NIL", "Invalid size field" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 () NIL NIL NIL NIL", "Invalid lines field" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 () NIL NIL NIL", "Invalid content-md5" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL () NIL NIL", "Invalid content-disposition" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL () NIL", "Invalid content-language" },
+ { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL ()", "Invalid content-location" },
+ };
+ struct message_part *parts;
+ const char *error;
+ unsigned int i;
+
+ test_begin("imap bodystructure parser invalid");
+ for (i = 0; i < N_ELEMENTS(invalid_bodystructure_tests); i++) T_BEGIN {
+ const struct parse_test_invalid *test =
+ &invalid_bodystructure_tests[i];
+ pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024);
+
+ parts = msg_parse(pool, test->message, 0, 0, FALSE);
+ test_assert_idx(imap_bodystructure_parse(test->bodystructure,
+ pool, parts, &error) == -1, i);
+ test_assert_strcmp_idx(error, test->error, i);
+ pool_unref(&pool);
+ } T_END;
+ test_end();
+}
+
+static void test_imap_bodystructure_parse_full(void)
+{
+ const char *error;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < parse_tests_count; i++) T_BEGIN {
+ struct parse_test *test = &parse_tests[i];
+ struct message_part *parts = NULL;
+ string_t *str = t_str_new(128);
+ pool_t pool = pool_alloconly_create("imap bodystructure parse full", 1024);
+
+ test_begin(t_strdup_printf("imap bodystructure parser full [%u]", i));
+
+ ret = imap_bodystructure_parse_full(test->bodystructure,
+ pool, &parts, &error);
+ test_assert(ret == 0);
+
+ if (ret == 0) {
+ str_truncate(str, 0);
+ test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0);
+ test_assert(strcmp(str_c(str), test->bodystructure) == 0);
+ } else {
+ i_error("Invalid BODYSTRUCTURE: %s", error);
+ }
+
+ pool_unref(&pool);
+ test_end();
+ } T_END;
+}
+
+static void test_imap_bodystructure_normalize(void)
+{
+ struct message_part *parts;
+ const char *error;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < normalize_tests_count; i++) T_BEGIN {
+ struct normalize_test *test = &normalize_tests[i];
+ string_t *str = t_str_new(128);
+ pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024);
+
+ test_begin(t_strdup_printf("imap bodystructure normalize [%u]", i));
+ parts = msg_parse(pool, test->message, 0, 0, FALSE);
+
+ ret = imap_bodystructure_parse(test->input,
+ pool, parts, &error);
+ test_assert(ret == 0);
+
+ if (ret == 0) {
+ str_truncate(str, 0);
+ test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0);
+ test_assert(strcmp(str_c(str), test->output) == 0);
+ } else {
+ i_error("Invalid BODYSTRUCTURE: %s", error);
+ }
+
+ pool_unref(&pool);
+ test_end();
+ } T_END;
+}
+
+static const struct {
+ const char *input;
+ const char *bodystructure;
+ unsigned int max_depth;
+ unsigned int max_total;
+} truncation_tests[] = {
+ {
+ .input = "Content-Type: message/rfc822\n"
+ "\n"
+ "Content-Type: message/rfc822\n"
+ "Header2: value2\n"
+ "\n"
+ "Subject: hello world\n"
+ "Header2: value2\n"
+ "Header3: value3\n"
+ "\n"
+ "body line 1\n"
+ "body line 2\n"
+ "body line 4\n"
+ "body line 3\n",
+ .bodystructure = "\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 159 (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"application\" \"octet-stream\" NIL NIL NIL \"7bit\" 110 NIL NIL NIL NIL) 11 NIL NIL NIL NIL",
+ .max_depth = 2,
+ },
+ {
+ .input = "Content-Type: multipart/mixed; boundary=1\n"
+ "\n"
+ "--1\n"
+ "Content-Type: multipart/mixed; boundary=2\n"
+ "\n"
+ "--2\n"
+ "Content-Type: multipart/mixed; boundary=3\n"
+ "\n"
+ "--3\n"
+ "\n"
+ "body\n",
+ .bodystructure = "(\"application\" \"octet-stream\" (\"boundary\" \"2\") NIL NIL \"7bit\" 63 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"1\") NIL NIL NIL",
+ .max_depth = 2,
+ },
+ {
+ .input = "Content-Type: multipart/digest; boundary=1\n"
+ "\n"
+ "--1\n"
+ "\n"
+ "Subject: hdr1\n"
+ "\n"
+ "body1\n"
+ "--1\n"
+ "\n"
+ "Subject: hdr2\n"
+ "\n"
+ "body2\n",
+ .bodystructure = "(\"application\" \"octet-stream\" NIL NIL NIL \"7bit\" 55 NIL NIL NIL NIL) \"digest\" (\"boundary\" \"1\") NIL NIL NIL",
+ .max_total = 2,
+ },
+
+};
+
+static void test_imap_bodystructure_truncation(void)
+{
+ struct message_part *parts;
+ const char *error;
+ string_t *str_body = t_str_new(128);
+ string_t *str_parts = t_str_new(128);
+ pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024);
+
+ test_begin("imap bodystructure truncation");
+
+ for (unsigned int i = 0; i < N_ELEMENTS(truncation_tests); i++) {
+ p_clear(pool);
+ str_truncate(str_body, 0);
+ str_truncate(str_parts, 0);
+
+ parts = msg_parse(pool, truncation_tests[i].input,
+ truncation_tests[i].max_depth,
+ truncation_tests[i].max_total,
+ TRUE);
+
+ /* write out BODYSTRUCTURE and serialize message_parts */
+ test_assert(imap_bodystructure_write(parts, str_body, TRUE, &error) == 0);
+ message_part_serialize(parts, str_parts);
+
+ /* now deserialize message_parts and make sure they can be used
+ to parse BODYSTRUCTURE */
+ parts = message_part_deserialize(pool, str_data(str_parts),
+ str_len(str_parts), &error);
+ test_assert(parts != NULL);
+ test_assert(imap_bodystructure_parse(str_c(str_body), pool,
+ parts, &error) == 0);
+ test_assert_strcmp(str_c(str_body),
+ truncation_tests[i].bodystructure);
+ }
+ pool_unref(&pool);
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_imap_bodystructure_write,
+ test_imap_bodystructure_parse,
+ test_imap_bodystructure_parse_invalid,
+ test_imap_bodystructure_normalize,
+ test_imap_bodystructure_parse_full,
+ test_imap_bodystructure_truncation,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/lib-imap/test-imap-envelope.c b/src/lib-imap/test-imap-envelope.c
new file mode 100644
index 0000000..1f295e5
--- /dev/null
+++ b/src/lib-imap/test-imap-envelope.c
@@ -0,0 +1,205 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "str.h"
+#include "message-part-data.h"
+#include "message-parser.h"
+#include "imap-envelope.h"
+#include "test-common.h"
+
+struct parse_test {
+ const char *message;
+ const char *envelope;
+};
+
+struct parse_test parse_tests[] = {
+ /* Tests copied from imaptest */
+ {
+ .message =
+ "Message-ID: <msg@id>\n"
+ "In-Reply-To: <reply@to.id>\n"
+ "Date: Thu, 15 Feb 2007 01:02:03 +0200\n"
+ "Subject: subject header\n"
+ "From: From Real <fromuser@fromdomain.org>\n"
+ "To: To Real <touser@todomain.org>\n"
+ "Cc: Cc Real <ccuser@ccdomain.org>\n"
+ "Bcc: Bcc Real <bccuser@bccdomain.org>\n"
+ "Sender: Sender Real <senderuser@senderdomain.org>\n"
+ "Reply-To: ReplyTo Real <replytouser@replytodomain.org>\n"
+ "\n"
+ "body\n",
+ .envelope =
+ "\"Thu, 15 Feb 2007 01:02:03 +0200\" "
+ "\"subject header\" "
+ "((\"From Real\" NIL \"fromuser\" \"fromdomain.org\")) "
+ "((\"Sender Real\" NIL \"senderuser\" \"senderdomain.org\")) "
+ "((\"ReplyTo Real\" NIL \"replytouser\" \"replytodomain.org\")) "
+ "((\"To Real\" NIL \"touser\" \"todomain.org\")) "
+ "((\"Cc Real\" NIL \"ccuser\" \"ccdomain.org\")) "
+ "((\"Bcc Real\" NIL \"bccuser\" \"bccdomain.org\")) "
+ "\"<reply@to.id>\" \"<msg@id>\""
+ }, {
+ .message =
+ "Date: Thu, 15 Feb 2007 01:02:03 +0200\n"
+ "From: user@domain\n"
+ "\n"
+ "body\n",
+ .envelope =
+ "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL "
+ "((NIL NIL \"user\" \"domain\")) "
+ "((NIL NIL \"user\" \"domain\")) "
+ "((NIL NIL \"user\" \"domain\")) NIL NIL NIL NIL NIL"
+ }, {
+ .message =
+ "Date: Thu, 15 Feb 2007 01:02:03 +0200\n"
+ "From: user@domain\n"
+ "\n"
+ "body\n",
+ .envelope =
+ "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL "
+ "((NIL NIL \"user\" \"domain\")) "
+ "((NIL NIL \"user\" \"domain\")) "
+ "((NIL NIL \"user\" \"domain\")) NIL NIL NIL NIL NIL"
+ }, {
+ .message =
+ "Date: Thu, 15 Feb 2007 01:02:03 +0200\n"
+ "From: user@domain (Real Name)\n"
+ "To: group: g1@d1.org, g2@d2.org;, group2: g3@d3.org;\n"
+ "Cc: group:;, group2: (foo) ;\n"
+ "\n"
+ "body\n",
+ .envelope =
+ "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL "
+ "((\"Real Name\" NIL \"user\" \"domain\")) "
+ "((\"Real Name\" NIL \"user\" \"domain\")) "
+ "((\"Real Name\" NIL \"user\" \"domain\")) "
+ "((NIL NIL \"group\" NIL)"
+ "(NIL NIL \"g1\" \"d1.org\")"
+ "(NIL NIL \"g2\" \"d2.org\")"
+ "(NIL NIL NIL NIL)"
+ "(NIL NIL \"group2\" NIL)"
+ "(NIL NIL \"g3\" \"d3.org\")"
+ "(NIL NIL NIL NIL)) "
+ "((NIL NIL \"group\" NIL)(NIL NIL NIL NIL)"
+ "(NIL NIL \"group2\" NIL)(NIL NIL NIL NIL)) "
+ "NIL NIL NIL"
+ }, {
+ .message =
+ "Date: Thu, 15 Feb 2007 01:02:03 +0200\n"
+ "From: user@domain (Real Name)\n"
+ "Sender: \n"
+ "Reply-To: \n"
+ "\n"
+ "body\n",
+ .envelope =
+ "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL "
+ "((\"Real Name\" NIL \"user\" \"domain\")) "
+ "((\"Real Name\" NIL \"user\" \"domain\")) "
+ "((\"Real Name\" NIL \"user\" \"domain\")) "
+ "NIL NIL NIL NIL NIL"
+ }, {
+ .message =
+ "Date: Thu, 15 Feb 2007 01:02:03 +0200\n"
+ "From: <@route:user@domain>\n"
+ "\n"
+ "body\n",
+ .envelope =
+ "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL "
+ "((NIL \"@route\" \"user\" \"domain\")) "
+ "((NIL \"@route\" \"user\" \"domain\")) "
+ "((NIL \"@route\" \"user\" \"domain\")) "
+ "NIL NIL NIL NIL NIL"
+ }
+};
+
+static const unsigned int parse_tests_count = N_ELEMENTS(parse_tests);
+
+static struct message_part_envelope *
+msg_parse(pool_t pool, const char *message)
+{
+ const struct message_parser_settings parser_set = {
+ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+ MESSAGE_HEADER_PARSER_FLAG_DROP_CR,
+ .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK,
+ };
+ struct message_parser_ctx *parser;
+ struct message_part_envelope *envlp = NULL;
+ struct istream *input;
+ struct message_block block;
+ struct message_part *parts;
+ int ret;
+
+ input = i_stream_create_from_data(message, strlen(message));
+ parser = message_parser_init(pool, input, &parser_set);
+ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
+ i_assert(block.part->parent == NULL);
+ message_part_envelope_parse_from_header(pool, &envlp, block.hdr);
+ }
+ test_assert(ret < 0);
+
+ message_parser_deinit(&parser, &parts);
+ i_stream_unref(&input);
+ return envlp;
+}
+
+static void test_imap_envelope_write(void)
+{
+ struct message_part_envelope *envlp;
+ unsigned int i;
+
+ for (i = 0; i < parse_tests_count; i++) T_BEGIN {
+ struct parse_test *test = &parse_tests[i];
+ string_t *str = t_str_new(128);
+ pool_t pool = pool_alloconly_create("imap envelope write", 1024);
+
+ test_begin(t_strdup_printf("imap envelope write [%u]", i));
+ envlp = msg_parse(pool, test->message);
+
+ imap_envelope_write(envlp, str);
+ test_assert(strcmp(str_c(str), test->envelope) == 0);
+
+ pool_unref(&pool);
+ test_end();
+ } T_END;
+}
+
+static void test_imap_envelope_parse(void)
+{
+ struct message_part_envelope *envlp;
+ const char *error;
+ unsigned int i;
+ bool ret;
+
+ for (i = 0; i < parse_tests_count; i++) T_BEGIN {
+ struct parse_test *test = &parse_tests[i];
+ string_t *str = t_str_new(128);
+ pool_t pool = pool_alloconly_create("imap envelope parse", 1024);
+
+ test_begin(t_strdup_printf("imap envelope parser [%u]", i));
+
+ ret = imap_envelope_parse(test->envelope, pool, &envlp, &error);
+ test_assert(ret);
+
+ if (ret) {
+ str_truncate(str, 0);
+ imap_envelope_write(envlp, str);
+ test_assert(strcmp(str_c(str), test->envelope) == 0);
+ } else {
+ i_error("Invalid envelope: %s", error);
+ }
+
+ pool_unref(&pool);
+ test_end();
+ } T_END;
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_imap_envelope_write,
+ test_imap_envelope_parse,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/lib-imap/test-imap-match.c b/src/lib-imap/test-imap-match.c
new file mode 100644
index 0000000..df911da
--- /dev/null
+++ b/src/lib-imap/test-imap-match.c
@@ -0,0 +1,127 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "imap-match.h"
+#include "test-common.h"
+
+struct test_imap_match {
+ const char *pattern;
+ const char *input;
+ enum imap_match_result result;
+};
+
+static void test_imap_match(void)
+{
+ struct test_imap_match test[] = {
+ { "", "", IMAP_MATCH_YES },
+ { "a", "b", IMAP_MATCH_NO },
+ { "foo", "foo", IMAP_MATCH_YES },
+ { "foo", "foo/", IMAP_MATCH_PARENT },
+ { "%", "", IMAP_MATCH_YES },
+ { "%", "foo", IMAP_MATCH_YES },
+ { "%", "foo/", IMAP_MATCH_PARENT },
+ { "%/", "foo/", IMAP_MATCH_YES },
+ { "%", "foo/bar", IMAP_MATCH_PARENT },
+ { "%/%", "foo", IMAP_MATCH_CHILDREN },
+ { "%/%", "foo/", IMAP_MATCH_YES },
+ { "foo/bar/%", "foo", IMAP_MATCH_CHILDREN },
+ { "foo/bar/%", "foo/", IMAP_MATCH_CHILDREN },
+ { "foo*", "foo", IMAP_MATCH_YES },
+ { "foo*", "foo/", IMAP_MATCH_YES },
+ { "foo*", "fobo", IMAP_MATCH_NO },
+ { "*foo*", "bar/foo/", IMAP_MATCH_YES },
+ { "*foo*", "fobo", IMAP_MATCH_CHILDREN },
+ { "foo*bar", "foobar/baz", IMAP_MATCH_CHILDREN | IMAP_MATCH_PARENT },
+ { "*foo*", "fobo", IMAP_MATCH_CHILDREN },
+ { "%/%/%", "foo/", IMAP_MATCH_CHILDREN },
+ { "%/%o/%", "foo/", IMAP_MATCH_CHILDREN },
+ { "%/%o/%", "foo", IMAP_MATCH_CHILDREN },
+ { "inbox", "inbox", IMAP_MATCH_YES },
+ { "inbox", "INBOX", IMAP_MATCH_NO }
+ };
+ struct test_imap_match inbox_test[] = {
+ { "inbox", "inbox", IMAP_MATCH_YES },
+ { "inbox", "iNbOx", IMAP_MATCH_YES },
+ { "i%X", "iNbOx", IMAP_MATCH_YES },
+ { "%I%N%B%O%X%", "inbox", IMAP_MATCH_YES },
+ { "i%X/foo", "iNbOx/foo", IMAP_MATCH_YES },
+ { "%I%N%B%O%X%/foo", "inbox/foo", IMAP_MATCH_YES },
+ { "i%X/foo", "inbx/foo", IMAP_MATCH_NO }
+ };
+ struct imap_match_glob *glob, *glob2;
+ unsigned int i;
+ pool_t pool;
+
+ pool = pool_alloconly_create("imap match", 1024);
+
+ /* first try tests without inboxcasing */
+ test_begin("imap match");
+ for (i = 0; i < N_ELEMENTS(test); i++) {
+ glob = imap_match_init(pool, test[i].pattern,
+ FALSE, '/');
+ test_assert(imap_match(glob, test[i].input) == test[i].result);
+
+ glob2 = imap_match_dup(default_pool, glob);
+ test_assert(imap_match_globs_equal(glob, glob2));
+ p_clear(pool);
+
+ /* test the dup after clearing first one's memory */
+ test_assert(imap_match(glob2, test[i].input) == test[i].result);
+ imap_match_deinit(&glob2);
+ }
+
+ /* inboxcasing tests */
+ for (i = 0; i < N_ELEMENTS(inbox_test); i++) {
+ glob = imap_match_init(pool, inbox_test[i].pattern,
+ TRUE, '/');
+ test_assert(imap_match(glob, inbox_test[i].input) == inbox_test[i].result);
+
+ glob2 = imap_match_dup(default_pool, glob);
+ test_assert(imap_match_globs_equal(glob, glob2));
+ p_clear(pool);
+
+ /* test the dup after clearing first one's memory */
+ test_assert(imap_match(glob2, inbox_test[i].input) == inbox_test[i].result);
+ imap_match_deinit(&glob2);
+ }
+ pool_unref(&pool);
+ test_end();
+}
+
+static void test_imap_match_globs_equal(void)
+{
+ struct imap_match_glob *glob;
+ pool_t pool;
+
+ pool = pool_alloconly_create("imap match globs equal", 1024);
+ test_begin("imap match globs equal");
+
+ glob = imap_match_init(pool, "1", FALSE, '/');
+ test_assert(imap_match_globs_equal(glob,
+ imap_match_init(pool, "1", FALSE, '/')));
+ test_assert(imap_match_globs_equal(glob,
+ imap_match_init(pool, "1", TRUE, '/')));
+ test_assert(!imap_match_globs_equal(glob,
+ imap_match_init(pool, "1", FALSE, '.')));
+ test_assert(!imap_match_globs_equal(glob,
+ imap_match_init(pool, "11", FALSE, '/')));
+
+ glob = imap_match_init(pool, "in%", TRUE, '/');
+ test_assert(!imap_match_globs_equal(glob,
+ imap_match_init(pool, "in%", FALSE, '/')));
+ test_assert(!imap_match_globs_equal(glob,
+ imap_match_init(pool, "In%", TRUE, '/')));
+
+ pool_unref(&pool);
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_imap_match,
+ test_imap_match_globs_equal,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/lib-imap/test-imap-parser.c b/src/lib-imap/test-imap-parser.c
new file mode 100644
index 0000000..3ca4e34
--- /dev/null
+++ b/src/lib-imap/test-imap-parser.c
@@ -0,0 +1,157 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "imap-parser.h"
+#include "test-common.h"
+
+static void test_imap_parser_crlf(void)
+{
+ static const char *test_input = "foo\r\nx\ry\n";
+ struct istream *input;
+ struct imap_parser *parser;
+ const struct imap_arg *args;
+ unsigned int i;
+ enum imap_parser_error parse_error;
+
+ test_begin("imap parser crlf handling");
+ input = test_istream_create(test_input);
+ parser = imap_parser_create(input, NULL, 1024);
+
+ /* must return -2 until LF is read */
+ for (i = 0; test_input[i] != '\n'; i++) {
+ test_istream_set_size(input, i+1);
+ (void)i_stream_read(input);
+ test_assert(imap_parser_read_args(parser, 0, 0, &args) == -2);
+ }
+ test_istream_set_size(input, i+1);
+ (void)i_stream_read(input);
+ test_assert(imap_parser_read_args(parser, 0, 0, &args) == 1);
+ test_assert(args[0].type == IMAP_ARG_ATOM);
+ test_assert(args[1].type == IMAP_ARG_EOL);
+
+ /* CR without LF should fail with error */
+ imap_parser_reset(parser);
+ i_stream_seek(input, ++i);
+ test_istream_set_size(input, ++i);
+ (void)i_stream_read(input);
+ test_assert(imap_parser_read_args(parser, 0, 0, &args) == -2);
+ test_istream_set_size(input, ++i);
+ (void)i_stream_read(input);
+ test_assert(imap_parser_read_args(parser, 0, 0, &args) == -2);
+ test_istream_set_size(input, ++i);
+ (void)i_stream_read(input);
+ test_assert(imap_parser_read_args(parser, 0, 0, &args) == -1);
+ test_assert(strcmp(imap_parser_get_error
+ (parser, &parse_error), "CR sent without LF") == 0 &&
+ parse_error == IMAP_PARSE_ERROR_BAD_SYNTAX);
+
+ imap_parser_unref(&parser);
+ i_stream_destroy(&input);
+ test_end();
+}
+
+static void test_imap_parser_partial_list(void)
+{
+ static const char *test_input = "((((foo {1000000}\r\n";
+ struct istream *input;
+ struct imap_parser *parser;
+ const struct imap_arg *args, *sub_list;
+
+ test_begin("imap parser partial list");
+ input = test_istream_create(test_input);
+ parser = imap_parser_create(input, NULL, 1024);
+
+ (void)i_stream_read(input);
+ test_assert(imap_parser_read_args(parser, 0,
+ IMAP_PARSE_FLAG_LITERAL_SIZE, &args) == 1);
+ for (unsigned int i = 0; i < 4; i++) {
+ sub_list = imap_arg_as_list(&args[0]);
+ test_assert(IMAP_ARG_IS_EOL(&args[1]));
+ args = sub_list;
+ }
+ test_assert(imap_arg_atom_equals(&args[0], "foo"));
+ test_assert(args[1].type == IMAP_ARG_LITERAL_SIZE);
+ test_assert(IMAP_ARG_IS_EOL(&args[2]));
+
+ imap_parser_unref(&parser);
+ i_stream_destroy(&input);
+ test_end();
+}
+
+static void test_imap_parser_read_tag_cmd(void)
+{
+ enum read_type {
+ BOTH,
+ TAG,
+ COMMAND
+ };
+ struct {
+ const char *input;
+ const char *tag;
+ int ret;
+ enum read_type type;
+ } tests[] = {
+ { "tag foo", "tag", 1, BOTH },
+ { "tag\r", "tag", 1, BOTH },
+ { "tag\rfoo", "tag", 1, BOTH },
+ { "tag\nfoo", "tag", 1, BOTH },
+ { "tag\r\nfoo", "tag", 1, BOTH },
+ { "\n", NULL, -1, BOTH },
+ { "tag", NULL, 0, BOTH },
+ { "tag\t", NULL, -1, BOTH },
+ { "tag\001", NULL, -1, BOTH },
+ { "tag\x80", NULL, -1, BOTH },
+ { "tag(", NULL, -1, BOTH },
+ { "tag)", NULL, -1, BOTH },
+ { "tag{", NULL, -1, BOTH },
+ { "tag/ ", "tag/", 1, BOTH },
+ { "tag%", NULL, -1, BOTH },
+ { "tag*", NULL, -1, BOTH },
+ { "tag\"", NULL, -1, BOTH },
+ { "tag\\", NULL, -1, BOTH },
+ { "tag+", NULL, -1, TAG },
+ { "tag+ ", "tag+", 1, COMMAND },
+ };
+ struct istream *input;
+ struct imap_parser *parser;
+ const char *atom;
+ int ret;
+
+ test_begin("imap_parser_read_tag and imap_parser_read_command_name");
+ for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
+ if (tests[i].type != COMMAND) {
+ input = test_istream_create(tests[i].input);
+ test_assert(i_stream_read(input) > 0);
+ parser = imap_parser_create(input, NULL, 1024);
+ ret = imap_parser_read_tag(parser, &atom);
+ test_assert_idx(ret == tests[i].ret, i);
+ test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i);
+ imap_parser_unref(&parser);
+ i_stream_destroy(&input);
+ }
+
+ if (tests[i].type != TAG) {
+ input = test_istream_create(tests[i].input);
+ test_assert(i_stream_read(input) > 0);
+ parser = imap_parser_create(input, NULL, 1024);
+ ret = imap_parser_read_command_name(parser, &atom);
+ test_assert_idx(ret == tests[i].ret, i);
+ test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i);
+ imap_parser_unref(&parser);
+ i_stream_destroy(&input);
+ }
+ }
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_imap_parser_crlf,
+ test_imap_parser_partial_list,
+ test_imap_parser_read_tag_cmd,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/lib-imap/test-imap-quote.c b/src/lib-imap/test-imap-quote.c
new file mode 100644
index 0000000..77b46b3
--- /dev/null
+++ b/src/lib-imap/test-imap-quote.c
@@ -0,0 +1,171 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "imap-quote.h"
+#include "test-common.h"
+
+static void test_imap_append_string_for_humans(void)
+{
+ static const struct {
+ const char *input, *output;
+ } tests[] = {
+ { "", "\"\"" },
+ { " ", "\"\"" },
+ { " ", "\"\"" },
+ { "\t", "\"\"" },
+ { " \t", "\"\"" },
+ { " \t ", "\"\"" },
+ { " foo", "{3}\r\nfoo" },
+ { "\tfoo", "{3}\r\nfoo" },
+ { "\t \tfoo", "{3}\r\nfoo" },
+ { " foo ", "{3}\r\nfoo" },
+ { " foo ", "{3}\r\nfoo" },
+ { " foo \t \t", "{3}\r\nfoo" },
+ { "hello\"world", "{11}\r\nhello\"world" },
+ { "hello\\world", "{11}\r\nhello\\world" },
+ { "hello\rworld", "{11}\r\nhello world" },
+ { "hello\nworld", "{11}\r\nhello world" },
+ { "hello\r\nworld", "{11}\r\nhello world" },
+ { "hello\r\n world", "{11}\r\nhello world" },
+ { "hello \r\n world", "{11}\r\nhello world" },
+ };
+ string_t *str = t_str_new(128);
+ unsigned int i;
+
+ test_begin("imap_append_string_for_humans()");
+
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ str_truncate(str, 0);
+ imap_append_string_for_humans(str, (const void *)tests[i].input,
+ strlen(tests[i].input));
+ test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i);
+ }
+ test_end();
+}
+
+static void test_imap_append_astring(void)
+{
+ static const struct {
+ const char *input, *output;
+ } tests[] = {
+ { "", "\"\"" },
+ { "NIL", "\"NIL\"" },
+ { "niL", "\"niL\"" },
+ { "ni", "ni" },
+ { "\\", "\"\\\\\"" },
+ { "\\\\", "\"\\\\\\\\\"" },
+ { "\\\\\\", "\"\\\\\\\\\\\\\"" },
+ { "\\\\\\\\", "\"\\\\\\\\\\\\\\\\\"" },
+ { "\\\\\\\\\\", "{5}\r\n\\\\\\\\\\" },
+ { "\\\\\\\\\\\\", "{6}\r\n\\\\\\\\\\\\" },
+ { "\"", "\"\\\"\"" },
+ { "\"\"", "\"\\\"\\\"\"" },
+ { "\"\"\"", "\"\\\"\\\"\\\"\"" },
+ { "\"\"\"\"", "\"\\\"\\\"\\\"\\\"\"" },
+ { "\"\"\"\"\"", "{5}\r\n\"\"\"\"\"" },
+ { "\"\"\"\"\"\"", "{6}\r\n\"\"\"\"\"\"" },
+ { "\r", "{1}\r\n\r" },
+ { "\n", "{1}\r\n\n" },
+ { "\r\n", "{2}\r\n\r\n" },
+ { "\x7f", "\"\x7f\"" },
+ { "\x80", "{1}\r\n\x80" },
+ { "\xff", "{1}\r\n\xff" },
+ };
+ string_t *str = t_str_new(128);
+ unsigned int i;
+
+ test_begin("test_imap_append_astring()");
+
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ str_truncate(str, 0);
+ imap_append_astring(str, tests[i].input);
+ test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i);
+ }
+ test_end();
+}
+
+static void test_imap_append_nstring(void)
+{
+ static const struct {
+ const char *input, *output;
+ } tests[] = {
+ { "", "\"\"" },
+ { NULL, "NIL" },
+ { "NIL", "\"NIL\"" },
+ { "\"America N.\"", "\"\\\"America N.\\\"\"" },
+ { "\"America N.\", \"America S.\"", "\"\\\"America N.\\\", \\\"America S.\\\"\"" },
+ { "\"America N.\", \"America S.\", \"Africa\"", "{36}\r\n\"America N.\", \"America S.\", \"Africa\"" },
+ { "Antarctica\n Australia", "{21}\r\nAntarctica\n Australia" },
+ { "ni", "\"ni\"" }
+ };
+ string_t *str = t_str_new(128);
+ unsigned int i;
+
+ test_begin("test_imap_append_nstring()");
+
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ str_truncate(str, 0);
+ imap_append_nstring(str, tests[i].input);
+ test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i);
+ }
+ test_end();
+}
+
+static void test_imap_append_nstring_nolf(void)
+{
+ static const struct {
+ const char *input, *output;
+ } tests[] = {
+ { "", "\"\"" },
+ { NULL, "NIL" },
+ { "NIL", "\"NIL\"" },
+ { "ni", "\"ni\"" },
+ { "\"NIL\n foo", "\"\\\"NIL foo\"" },
+ { "\"America N.\", \"America S.\", \"Africa\"", "{36}\r\n\"America N.\", \"America S.\", \"Africa\"" },
+ { "foo\nbar", "\"foo bar\"" },
+ { "foo\r\nbar", "\"foo bar\"" },
+ { "foo\rbar", "\"foo bar\"" },
+ { "foo\n bar", "\"foo bar\"" },
+ { "foo\r\n bar", "\"foo bar\"" },
+ { "foo\r bar", "\"foo bar\"" },
+ { "foo\n\tbar", "\"foo\tbar\"" },
+ { "foo\r\n\tbar", "\"foo\tbar\"" },
+ { "foo\r\tbar", "\"foo\tbar\"" },
+ { "foo\n bar", "\"foo bar\"" },
+ { "foo\r\n bar", "\"foo bar\"" },
+ { "foo\r bar", "\"foo bar\"" },
+ { "\nfoo\r bar\r\n", "\" foo bar\"" }
+ };
+ unsigned int i;
+
+ test_begin("test_imap_append_nstring_nolf()");
+
+ for (i = 0; i < N_ELEMENTS(tests); i++) T_BEGIN {
+ string_t *str = t_str_new(1);
+ string_t *str2 = str_new(default_pool, 1);
+
+ str_truncate(str, 0);
+ imap_append_nstring_nolf(str, tests[i].input);
+ test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i);
+
+ str_truncate(str2, 0);
+ imap_append_nstring_nolf(str2, tests[i].input);
+ test_assert_idx(strcmp(tests[i].output, str_c(str2)) == 0, i);
+
+ str_free(&str2);
+ } T_END;
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_imap_append_string_for_humans,
+ test_imap_append_astring,
+ test_imap_append_nstring,
+ test_imap_append_nstring_nolf,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/lib-imap/test-imap-url.c b/src/lib-imap/test-imap-url.c
new file mode 100644
index 0000000..a63ca33
--- /dev/null
+++ b/src/lib-imap/test-imap-url.c
@@ -0,0 +1,1029 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "net.h"
+#include "imap-url.h"
+#include "test-common.h"
+
+struct valid_imap_url_test {
+ const char *url;
+ enum imap_url_parse_flags flags;
+ struct imap_url url_base;
+
+ struct imap_url url_parsed;
+};
+
+/* Valid IMAP URL tests */
+static const struct valid_imap_url_test valid_url_tests[] = {
+ {
+ .url = "imap://localhost",
+ .url_parsed = {
+ .host = { .name = "localhost" } }
+ },{
+ .url = "imap://user@localhost",
+ .url_parsed = {
+ .host = { .name = "localhost" },
+ .userid = "user" }
+ },{
+ .url = "imap://user;AUTH=PLAIN@localhost",
+ .url_parsed = {
+ .host = { .name = "localhost" },
+ .userid = "user",
+ .auth_type = "PLAIN" }
+ },{
+ .url = "imap://;AUTH=PLAIN@localhost",
+ .url_parsed = {
+ .host = { .name = "localhost" },
+ .auth_type = "PLAIN" }
+ },{
+ .url = "imap://%68endri%6B;AUTH=GSS%41PI@%65%78%61%6d%70%6c%65.com",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "hendrik",
+ .auth_type = "GSSAPI" }
+ },{
+ .url = "imap://user@localhost:993",
+ .url_parsed = {
+ .host = { .name = "localhost" },
+ .userid = "user",
+ .port = 993 }
+ },{
+ .url = "imap://user@127.0.0.1",
+ .url_parsed = {
+ .host = {
+ .name = "127.0.0.1",
+ .ip = { .family = AF_INET } },
+ .userid = "user" }
+ },{
+ .url = "imap://user@[::1]",
+ .url_parsed = {
+ .host = {
+ .name = "[::1]",
+ .ip = { .family = AF_INET6 } },
+ .userid = "user" }
+ },{
+ .url = "imap://user@4example.com:423",
+ .url_parsed = {
+ .host = { .name = "4example.com" },
+ .userid = "user",
+ .port = 423 }
+ },{
+ .url = "imap://beelzebub@666.4example.com:999",
+ .url_parsed = {
+ .host = { .name = "666.4example.com" },
+ .userid = "beelzebub",
+ .port = 999 }
+ },{
+ .url = "imap://user@example.com/",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = NULL }
+ },{
+ .url = "imap://user@example.com/./",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = NULL }
+ },{
+ .url = "imap://user@example.com/INBOX",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX" }
+ },{
+ .url = "imap://user@example.com/INBOX/",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX" }
+ },{
+ .url = "imap://user@example.com//",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user"}
+ },{
+ .url = "imap://user@example.com/INBOX/Trash",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash" }
+ },{
+ .url = "imap://user@example.com/INBOX/Trash/..",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX" }
+ },{
+ .url = "imap://user@example.com/INBOX/Trash/../",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX" }
+ },{
+ .url = "imap://user@example.com/INBOX/Trash/../..",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = NULL }
+ },{
+ .url = "imap://user@example.com/INBOX.Trash",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX.Trash" }
+ },{
+ .url = "imap://user@example.com/INBOX%3BTrash",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX;Trash" }
+ },{
+ .url = "imap://user@example.com/INBOX;UIDVALIDITY=1341",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX", .uidvalidity = 1341 }
+ },{
+ .url = "imap://user@example.com/INBOX/;UIDVALIDITY=23423",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX", .uidvalidity = 23423 }
+ },{
+ .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=6567",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts", .uidvalidity = 6567 }
+ },{
+ .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=788/;UID=16",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts", .uidvalidity = 788,
+ .uid = 16 }
+ },{
+ .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=788/;UID=16/..",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts", .uidvalidity = 788,
+ .uid = 0 }
+ },{
+ .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=788/;UID=16/../..",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX", .uidvalidity = 0,
+ .uid = 0 }
+ },{
+ .url = "imap://user@example.com/INBOX/Junk;UIDVALIDITY=27667/"
+ ";UID=434/;SECTION=HEADER",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Junk", .uidvalidity = 27667,
+ .uid = 434, .section = "HEADER" }
+ },{
+ .url = "imap://user@example.com/INBOX/Important/"
+ ";UID=437/;SECTION=1.2.MIME",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Important",
+ .uid = 437, .section = "1.2.MIME" }
+ },{
+ .url = "imap://user@example.com/INBOX/Important/;UID=56/;SECTION=AA/BB",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Important",
+ .uid = 56, .section = "AA/BB" }
+ },{
+ .url = "imap://user@example.com/INBOX/Important/;UID=56/;SECTION=AA/BB/..",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Important",
+ .uid = 56, .section = "AA/" }
+ },{
+ .url = "imap://user@example.com/INBOX/Important/;UID=56/"
+ ";SECTION=AA/BB/../..",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Important",
+ .uid = 56, .section = NULL }
+ },{
+ .url = "imap://user@example.com/INBOX/Important/;UID=234/"
+ ";SECTION=HEADER.FIELDS%20(%22To%22%20%22From%22)",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Important",
+ .uid = 234, .section = "HEADER.FIELDS (\"To\" \"From\")" }
+ },{
+ .url = "imap://user@example.com/INBOX/Important/;UID=234/"
+ ";PARTIAL=10.250",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Important",
+ .uid = 234, .section = NULL, .partial_offset = 10, .partial_size = 250 }
+ },{
+ .url = "imap://hendrik@example.com/INBOX/Important/;UID=34534/"
+ ";SECTION=1.3.TEXT/;PARTIAL=0.34254",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "hendrik",
+ .mailbox = "INBOX/Important",
+ .uid = 34534, .section = "1.3.TEXT",
+ .partial_offset = 0, .partial_size = 34254 }
+ },{
+ .url = "imap://hendrik@example.com/INBOX/Sent"
+ ";UIDVALIDITY=534?SUBJECT%20%22Frop?%22",
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "hendrik",
+ .mailbox = "INBOX/Sent", .uidvalidity = 534,
+ .search_program = "SUBJECT \"Frop?\"" }
+ },{
+ .url = "//hendrik@example.org/INBOX/Trash",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user" },
+ .url_parsed = {
+ .host = { .name = "example.org" },
+ .userid = "hendrik",
+ .mailbox = "INBOX/Trash" }
+ },{
+ .url = "/INBOX/Trash",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user" },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash" }
+ },{
+ .url = "user@example.com",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Accounts" },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Accounts/user@example.com" }
+ },{
+ .url = "Drafts",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/" },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts" }
+ },{
+ .url = "../Drafts",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash" },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts" }
+ },{
+ .url = "../Junk",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Junk",
+ .uidvalidity = 0 }
+ },{
+ .url = "../Junk;UIDVALIDITY=23",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Junk",
+ .uidvalidity = 23 }
+ },{
+ .url = "../../%23shared;UIDVALIDITY=23452",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 764 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "#shared",
+ .uidvalidity = 23452 }
+ },{
+ .url = "../../%23news;UIDVALIDITY=546/;UID=456",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 65 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "#news",
+ .uidvalidity = 546,
+ .uid = 456 }
+ },{
+ .url = "",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452 }
+ },{
+ .url = "",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 65 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 65 }
+ },{
+ .url = "",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 65,
+ .section = "AA/BB",
+ .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 65,
+ .section = "AA/BB",
+ .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 }
+ },{
+ .url = "",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 65,
+ .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 65,
+ .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 }
+ },{
+ .url = ";UID=4767",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 65 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 4767 }
+ },{
+ .url = ";UID=4767",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452},
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 4767 }
+ },{
+ .url = "../;UID=4767",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 65 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX",
+ .uidvalidity = 0,
+ .uid = 4767 }
+ },{
+ .url = "../;UID=4767/;SECTION=TEXT",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 65,
+ .section = "1.2.3.MIME" },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Trash",
+ .uidvalidity = 23452,
+ .uid = 4767,
+ .section = "TEXT" }
+ },{
+ .url = ";SECTION=TEXT",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .section = "1.2.3.MIME" },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .section = "TEXT" }
+ },{
+ .url = "..",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .section = "AA/BB" },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43 }
+ },{
+ .url = "../;SECTION=CC",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .section = "AA/BB" },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .section = "CC" }
+ },{
+ .url = "CC",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .section = "AA/BB" },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .section = "AA/CC" }
+ },{
+ .url = ";PARTIAL=1024.1024",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .have_partial = TRUE, .partial_offset = 0, .partial_size = 1024 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 }
+ },{
+ .url = "../CC/;PARTIAL=0.512",
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .section = "AA/BB",
+ .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 },
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX/Drafts",
+ .uidvalidity = 769,
+ .uid = 43,
+ .section = "AA/CC",
+ .have_partial = TRUE, .partial_offset = 0, .partial_size = 512 }
+ },{
+ .url = "imap://user@example.com/INBOX/;UID=377;URLAUTH=anonymous",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH,
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX",
+ .uid = 377,
+ .uauth_rumpurl = "imap://user@example.com/INBOX/;UID=377"
+ ";URLAUTH=anonymous",
+ .uauth_access_application = "anonymous"}
+ },{
+ .url = "imap://user@example.com/INBOX/;UID=377"
+ ";URLAUTH=anonymous:internal:4142434445464748494A4B4C4D4E4F5051525354",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH,
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX",
+ .uid = 377,
+ .uauth_rumpurl = "imap://user@example.com/INBOX/;UID=377"
+ ";URLAUTH=anonymous",
+ .uauth_access_application = "anonymous",
+ .uauth_mechanism = "internal",
+ .uauth_token = (const unsigned char *)"ABCDEFGHIJKLMNOPQRST",
+ .uauth_token_size = 20}
+ },{
+ .url = "imap://user@example.com/INBOX/;UID=377"
+ ";EXPIRE=2011-02-12T12:45:14+01:00"
+ ";URLAUTH=user+frop:internal:4142434445464748494A4B4C4D4E4F5051525354",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH,
+ .url_parsed = {
+ .host = { .name = "example.com" },
+ .userid = "user",
+ .mailbox = "INBOX",
+ .uid = 377,
+ .uauth_rumpurl = "imap://user@example.com/INBOX/;UID=377"
+ ";EXPIRE=2011-02-12T12:45:14+01:00;URLAUTH=user+frop",
+ .uauth_access_application = "user",
+ .uauth_access_user = "frop",
+ .uauth_mechanism = "internal",
+ .uauth_token = (const unsigned char *)"ABCDEFGHIJKLMNOPQRST",
+ .uauth_token_size = 20}
+ }
+};
+
+static const unsigned int valid_url_test_count = N_ELEMENTS(valid_url_tests);
+
+static void test_imap_url_valid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < valid_url_test_count; i++) T_BEGIN {
+ const char *url = valid_url_tests[i].url;
+ enum imap_url_parse_flags flags = valid_url_tests[i].flags;
+ const struct imap_url *urlt = &valid_url_tests[i].url_parsed;
+ const struct imap_url *urlb = &valid_url_tests[i].url_base;
+ struct imap_url *urlp;
+ const char *error = NULL;
+
+ test_begin(t_strdup_printf("imap url valid [%d]", i));
+
+ if (urlb->host.name == NULL) urlb = NULL;
+ if (imap_url_parse(url, urlb, flags, &urlp, &error) < 0)
+ urlp = NULL;
+
+ test_out_reason(t_strdup_printf("imap_url_parse(%s)",
+ valid_url_tests[i].url), urlp != NULL, error);
+ if (urlp != NULL) {
+ if (urlp->host.name == NULL || urlt->host.name == NULL) {
+ test_out_quiet(t_strdup_printf("url->host.name = %s", urlp->host.name),
+ urlp->host.name == urlt->host.name);
+ } else {
+ test_out_quiet(t_strdup_printf("url->host.name = %s", urlp->host.name),
+ strcmp(urlp->host.name, urlt->host.name) == 0);
+ }
+ if (urlp->userid == NULL || urlt->userid == NULL) {
+ test_out_quiet(t_strdup_printf("url->userid = %s", urlp->userid),
+ urlp->userid == urlt->userid);
+ } else {
+ test_out_quiet(t_strdup_printf("url->userid = %s", urlp->userid),
+ strcmp(urlp->userid, urlt->userid) == 0);
+ }
+ if (urlp->auth_type == NULL || urlt->auth_type == NULL) {
+ test_out_quiet(t_strdup_printf("url->auth_type = %s", urlp->auth_type),
+ urlp->auth_type == urlt->auth_type);
+ } else {
+ test_out_quiet(t_strdup_printf("url->auth_type = %s", urlp->auth_type),
+ strcmp(urlp->auth_type, urlt->auth_type) == 0);
+ }
+ if (urlp->port == 0) {
+ test_out_quiet("url->port = (unspecified)",
+ urlp->port == urlt->port);
+ } else {
+ test_out_quiet(t_strdup_printf("url->port = %u", urlp->port),
+ urlp->port == urlt->port);
+ }
+ if (urlp->host.ip.family == 0) {
+ test_out_quiet("url->host.ip = (unspecified)",
+ urlp->host.ip.family == urlt->host.ip.family);
+ } else {
+ test_out_quiet("url->host.ip = (valid)",
+ urlp->host.ip.family == urlt->host.ip.family);
+ }
+ if (urlp->mailbox == NULL || urlt->mailbox == NULL) {
+ test_out_quiet(t_strdup_printf("url->mailbox = %s", urlp->mailbox),
+ urlp->mailbox == urlt->mailbox);
+ } else {
+ test_out_quiet(t_strdup_printf("url->mailbox = %s", urlp->mailbox),
+ strcmp(urlp->mailbox, urlt->mailbox) == 0);
+ }
+ test_out_quiet(t_strdup_printf("url->uidvalidity = %u", urlp->uidvalidity),
+ urlp->uidvalidity == urlt->uidvalidity);
+ test_out_quiet(t_strdup_printf("url->uid = %u", urlp->uid),
+ urlp->uid == urlt->uid);
+ if (urlp->section == NULL || urlt->section == NULL) {
+ test_out_quiet(t_strdup_printf("url->section = %s", urlp->section),
+ urlp->section == urlt->section);
+ } else {
+ test_out_quiet(t_strdup_printf("url->section = %s", urlp->section),
+ strcmp(urlp->section, urlt->section) == 0);
+ }
+ test_out_quiet(t_strdup_printf("url->partial = %"PRIuUOFF_T".%"PRIuUOFF_T,
+ urlp->partial_offset, urlp->partial_size),
+ urlp->partial_offset == urlt->partial_offset &&
+ urlp->partial_size == urlt->partial_size);
+ if (urlp->search_program == NULL || urlt->search_program == NULL) {
+ test_out_quiet(t_strdup_printf(
+ "url->search_program = %s", urlp->search_program),
+ urlp->search_program == urlt->search_program);
+ } else {
+ test_out_quiet(t_strdup_printf(
+ "url->search_program = %s", urlp->search_program),
+ strcmp(urlp->search_program, urlt->search_program) == 0);
+ }
+ if (urlt->uauth_rumpurl != NULL) {
+ if (urlp->uauth_rumpurl == NULL) {
+ test_out_quiet("url->uauth_rumpurl = NULL", FALSE);
+ } else {
+ test_out_quiet(t_strdup_printf(
+ "url->uauth_rumpurl = %s", urlp->uauth_rumpurl),
+ strcmp(urlp->uauth_rumpurl, urlt->uauth_rumpurl) == 0);
+ }
+ if (urlp->uauth_access_application == NULL ||
+ urlt->uauth_access_application == NULL) {
+ test_out_quiet(t_strdup_printf("url->uauth_access_application = %s",
+ urlp->uauth_access_application),
+ urlp->uauth_access_application == urlt->uauth_access_application);
+ } else {
+ test_out_quiet(t_strdup_printf("url->uauth_access_application = %s",
+ urlp->uauth_access_application),
+ strcmp(urlp->uauth_access_application,
+ urlt->uauth_access_application) == 0);
+ }
+ if (urlp->uauth_access_user == NULL ||
+ urlt->uauth_access_user == NULL) {
+ test_out_quiet(t_strdup_printf("url->uauth_access_user = %s",
+ urlp->uauth_access_user),
+ urlp->uauth_access_user == urlt->uauth_access_user);
+ } else {
+ test_out_quiet(t_strdup_printf("url->uauth_access_user = %s",
+ urlp->uauth_access_user),
+ strcmp(urlp->uauth_access_user,
+ urlt->uauth_access_user) == 0);
+ }
+ if (urlp->uauth_mechanism == NULL || urlt->uauth_mechanism == NULL) {
+ test_out_quiet(t_strdup_printf(
+ "url->uauth_mechanism = %s", urlp->uauth_mechanism),
+ urlp->uauth_mechanism == urlt->uauth_mechanism);
+ } else {
+ test_out_quiet(t_strdup_printf(
+ "url->uauth_mechanism = %s", urlp->uauth_mechanism),
+ strcmp(urlp->uauth_mechanism, urlt->uauth_mechanism) == 0);
+ }
+ if (urlp->uauth_token == NULL || urlt->uauth_token == NULL) {
+ test_out_quiet(t_strdup_printf(
+ "url->uauth_token = %s", urlp->uauth_token),
+ urlp->uauth_token == urlt->uauth_token);
+ } else {
+ bool equal = urlp->uauth_token_size == urlt->uauth_token_size;
+ size_t i;
+ test_out_quiet(t_strdup_printf(
+ "url->uauth_token_size = %zu", urlp->uauth_token_size),
+ equal);
+
+ if (equal) {
+ for (i = 0; i < urlp->uauth_token_size; i++) {
+ if (urlp->uauth_token[i] != urlt->uauth_token[i]) {
+ equal = FALSE;
+ break;
+ }
+ }
+ test_out_quiet(t_strdup_printf("url->uauth_token [index=%d]", (int)i),
+ equal);
+ }
+ }
+ }
+ }
+
+ test_end();
+ } T_END;
+}
+
+struct invalid_imap_url_test {
+ const char *url;
+ enum imap_url_parse_flags flags;
+ struct imap_url url_base;
+};
+
+static const struct invalid_imap_url_test invalid_url_tests[] = {
+ {
+ .url = "http://www.dovecot.org"
+ },{
+ .url = "imap:/INBOX"
+ },{
+ .url = "imap://user@example.com/INBOX",
+ .flags = IMAP_URL_PARSE_REQUIRE_RELATIVE,
+ .url_base = {
+ .host = { .name = "example.com" },
+ .userid = "user" }
+ },{
+ .url = ""
+ },{
+ .url = "/INBOX/;UID=377"
+ },{
+ .url = "imap://user@example.com/INBOX/;UID=377/;SECTION=TEXT?ALL"
+ },{
+ .url = "imap://user@example.com/INBOX/?"
+ },{
+ .url = "imap://user@example.com/INBOX/#Fragment"
+ },{
+ .url = "imap://user@example.com/INBOX/\""
+ },{
+ .url = "imap:///INBOX"
+ },{
+ .url = "imap://[]/INBOX"
+ },{
+ .url = "imap://[v08.234:232:234:234:2221]/INBOX"
+ },{
+ .url = "imap://[1::34a:34:234::6]/INBOX"
+ },{
+ .url = "imap://example%a.com/INBOX"
+ },{
+ .url = "imap://example.com%/INBOX"
+ },{
+ .url = "imap://example%00.com/INBOX"
+ },{
+ .url = "imap://example.com:65539/INBOX"
+ },{
+ .url = "imap://user;ATH=frop@example.com"
+ },{
+ .url = "imap://user;AUTH=frop;friep@example.com"
+ },{
+ .url = "imap://user;AUTH=@example.com"
+ },{
+ .url = "imap://user:password@example.com"
+ },{
+ .url = "imap://user;AUTH=A:B@example.com"
+ },{
+ .url = "imap://user%@example.com"
+ },{
+ .url = "imap://user%00@example.com"
+ },{
+ .url = "imap://user%ar;AUTH=*@example.com"
+ },{
+ .url = "imap://;AUTH=FR%etD@example.com"
+ },{
+ .url = "imap://user;AUTH=%@example.com"
+ },{
+ .url = "imap://user;AUTH=%00@example.com"
+ },{
+ .url = "imap://example.com/INBOX/%00/"
+ },{
+ .url = "imap://example.com/INBOX/%0r/"
+ },{
+ .url = "imap://example.com/INBOX/Trash/%/"
+ },{
+ .url = "imap://example.com/INBOX;UIDVALIDITY=23423;FROP=friep/"
+ },{
+ .url = "imap://example.com/INBOX;UIDVALIDITY=0/;UID=377"
+ },{
+ .url = "imap://example.com/INBOX;UIDVALIDITY=/"
+ },{
+ .url = "imap://example.com/INBOX;UIDVALIDITY=33a/"
+ },{
+ .url = "imap://example.com/INBOX;FROP=friep/"
+ },{
+ .url = "imap://example.com/INBOX/;UID=377;FROP=friep/"
+ },{
+ .url = "imap://example.com/INBOX/;UID=0/"
+ },{
+ .url = "imap://example.com/INBOX/;UID=/"
+ },{
+ .url = "imap://example.com/INBOX/;UID=5e6/"
+ },{
+ .url = "imap://example.com/INBOX/;UID=35/;SECTION=ALL;FROP=43/"
+ },{
+ .url = "imap://example.com/INBOX/;UID=35/;SECTION=/"
+ },{
+ .url = "imap://example.com/INBOX/;UID=34/;PARTIAL="
+ },{
+ .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0."
+ },{
+ .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0.e10"
+ },{
+ .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=.3"
+ },{
+ .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=5t4.3"
+ },{
+ .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0.0"
+ },{
+ .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0.23409823409820938409823"
+ },{
+ .url = "imap://example.com/INBOX/;UID=377/;FROP=34"
+ },{
+ .url = "imap://example.com/INBOX/;UID=377;FROP=34"
+ },{
+ .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2010-02-02T12:00:12Z"
+ },{
+ .url = "imap://example.com/INBOX/;UID=377"
+ ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523f45aadf2afe"
+ },{
+ .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2011-15-02T00:00:00Z"
+ ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523f45aadf2afe",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2011-10-02T00:00:00Z",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "/INBOX/;UID=377;EXPIRE=2011-10-02T00:00:00Z"
+ ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523f45aadf2afe",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "imap://example.com/INBOX/;UID=377;URLAUTH=",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "imap://example.com/INBOX/;UID=377"
+ ";URLAUTH=:internal:0ad89fafd79f54afe4523f45aadf2afe",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "imap://example.com/INBOX/;UID=377"
+ ";URLAUTH=user+:internal:0ad89fafd79f54afe4523f45aadf2afe",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "imap://example.com/INBOX/;UID=377"
+ ";URLAUTH=+frop:internal:0ad89fafd79f54afe4523f45aadf2afe",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "imap://example.com/INBOX/;UID=377;URLAUTH=anonymous:",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "imap://example.com/INBOX/;UID=377"
+ ";URLAUTH=anonymous::0ad89fafd79f54afe4523f45aadf2afe",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "imap://example.com/INBOX/;UID=377;URLAUTH=anonymous:internal:",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "imap://example.com/INBOX/;UID=377"
+ ";URLAUTH=anonymous:internal:fd79f54afe4523",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },{
+ .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2011-10-02T00:00:00Z"
+ ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523q45aadf2afe",
+ .flags = IMAP_URL_PARSE_ALLOW_URLAUTH
+ },
+};
+
+static const unsigned int invalid_url_test_count = N_ELEMENTS(invalid_url_tests);
+
+static void test_imap_url_invalid(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < invalid_url_test_count; i++) T_BEGIN {
+ const char *url = invalid_url_tests[i].url;
+ enum imap_url_parse_flags flags = invalid_url_tests[i].flags;
+ const struct imap_url *urlb = &invalid_url_tests[i].url_base;
+ struct imap_url *urlp;
+ const char *error = NULL;
+
+ if (urlb->host.name == NULL)
+ urlb = NULL;
+
+ test_begin(t_strdup_printf("imap url invalid [%d]", i));
+
+ if (imap_url_parse(url, urlb, flags, &urlp, &error) < 0)
+ urlp = NULL;
+ test_out_reason(t_strdup_printf("parse %s", url), urlp == NULL, error);
+
+ test_end();
+ } T_END;
+
+}
+
+static const char *parse_create_url_tests[] = {
+ "imap://host.example.com/",
+ "imap://10.0.0.1/",
+ "imap://[::1]/",
+ "imap://user@host.example.com/",
+ "imap://user@host.example.com:993/",
+ "imap://su%3auser@host.example.com/",
+ "imap://user;AUTH=PLAIN@host.example.com/",
+ "imap://user;AUTH=PLAIN@host.example.com/INBOX",
+ "imap://user;AUTH=PLAIN@host.example.com/INBOX/;UID=5",
+ "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5",
+ "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5"
+ "/;SECTION=TEXT",
+ "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5"
+ "/;SECTION=TEXT/;PARTIAL=1",
+ "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5"
+ "/;SECTION=TEXT/;PARTIAL=1.14",
+ "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5"
+ "/;SECTION=TEXT/;PARTIAL=1.14;URLAUTH=anonymous",
+ "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5"
+ "/;SECTION=TEXT/;PARTIAL=1.14;URLAUTH=user+username",
+ "imap://user;AUTH=PLAIN@host.example.com/INBOX?SUBJECT%20%22Frop?%22",
+ "imap://user%3ba@host.example.com/",
+ "imap://user%40example.com@host.example.com/",
+ "imap://user%40example.com;AUTH=STR%23ANGE@host.example.com/",
+ "imap://user;AUTH=PLAIN@host.example.com/INBOX/Important%3bWork",
+ "imap://user@host.example.com/%23shared/news",
+ "imap://user@host.example.com/INBOX;UIDVALIDITY=15/;UID=5"
+ "/;SECTION=HEADER.FIELDS%20(DATE%20FROM)",
+ "imap://user@host.example.com/INBOX;UIDVALIDITY=15/;UID=5"
+ "/;SECTION=TEXT/;PARTIAL=1.14;URLAUTH=user+user%3bname",
+};
+
+static const unsigned int parse_create_url_test_count = N_ELEMENTS(parse_create_url_tests);
+
+static void test_imap_url_parse_create(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < parse_create_url_test_count; i++) T_BEGIN {
+ const char *url = parse_create_url_tests[i];
+ struct imap_url *urlp;
+ const char *error = NULL;
+
+ test_begin(t_strdup_printf("imap url parse/create [%d]", i));
+
+ if (imap_url_parse
+ (url, NULL, IMAP_URL_PARSE_ALLOW_URLAUTH, &urlp, &error) < 0)
+ urlp = NULL;
+ test_out_reason(t_strdup_printf("parse %s", url), urlp != NULL, error);
+ if (urlp != NULL) {
+ const char *urlnew = imap_url_create(urlp);
+ test_out(t_strdup_printf
+ ("create %s", urlnew), strcmp(url, urlnew) == 0);
+ }
+
+ test_end();
+ } T_END;
+
+}
+
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_imap_url_valid,
+ test_imap_url_invalid,
+ test_imap_url_parse_create,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/lib-imap/test-imap-utf7.c b/src/lib-imap/test-imap-utf7.c
new file mode 100644
index 0000000..216eebf
--- /dev/null
+++ b/src/lib-imap/test-imap-utf7.c
@@ -0,0 +1,216 @@
+/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "unichar.h"
+#include "imap-utf7.h"
+#include "test-common.h"
+
+static void test_imap_utf7_by_example(void)
+{
+ static const struct test {
+ const char *utf8;
+ const char *mutf7;
+ } tests[] = {
+ { "&&x&&", "&-&-x&-&-" },
+ { "~peter/mail/\xe5\x8f\xb0\xe5\x8c\x97/\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e",
+ "~peter/mail/&U,BTFw-/&ZeVnLIqe-" },
+ { "tiet\xc3\xa4&j\xc3\xa4&", "tiet&AOQ-&-j&AOQ-&-" }, /* & is always encoded as &- */
+ { "p\xe4\xe4", NULL },
+ { NULL, "&" },
+ { NULL, "&Jjo" },
+ { NULL, "&Jjo!" },
+ { NULL, "&U,BTFw-&ZeVnLIqe-" } /* unnecessary shift */
+ };
+ string_t *dest, *dest2;
+ unsigned int i;
+
+ dest = t_str_new(256);
+ dest2 = t_str_new(256);
+
+ test_begin("imap mutf7 examples");
+ for (i = 0; i < N_ELEMENTS(tests); i++) {
+ str_truncate(dest, 0);
+ if (tests[i].utf8 != NULL) {
+ if (imap_utf8_to_utf7(tests[i].utf8, dest) < 0)
+ test_assert_idx(tests[i].mutf7 == NULL, i);
+ else
+ test_assert_idx(null_strcmp(tests[i].mutf7, str_c(dest)) == 0, i);
+ } else {
+ /* invalid mUTF-7 - test that escaping works */
+ str_truncate(dest2, 0);
+ imap_utf7_to_utf8_escaped(tests[i].mutf7, "%", dest);
+ imap_escaped_utf8_to_utf7(str_c(dest), '%', dest2);
+ test_assert_idx(strcmp(tests[i].mutf7, str_c(dest2)) == 0, i);
+ }
+ if (tests[i].mutf7 != NULL) {
+ str_truncate(dest, 0);
+ if (imap_utf7_to_utf8(tests[i].mutf7, dest) < 0)
+ test_assert_idx(tests[i].utf8 == NULL, i);
+ else
+ test_assert_idx(null_strcmp(tests[i].utf8, str_c(dest)) == 0, i);
+ test_assert_idx(imap_utf7_is_valid(tests[i].mutf7) != (tests[i].utf8 == NULL), i);
+ }
+ }
+
+ str_truncate(dest, 0);
+ imap_utf7_to_utf8_escaped(".foo%", "%.", dest);
+ test_assert_strcmp(str_c(dest), "%2efoo%25");
+
+ str_truncate(dest, 0);
+ test_assert(imap_escaped_utf8_to_utf7("%foo%2ebar", '%', dest) == 0);
+ test_assert_strcmp(str_c(dest), "%foo.bar");
+
+ test_end();
+}
+
+static void test_imap_utf7_ucs4_cases(void)
+{
+ string_t *src, *dest;
+ const char *orig_src;
+ unsigned int i, j;
+ unichar_t chr;
+
+ src = t_str_new(256);
+ dest = t_str_new(256);
+
+ test_begin("imap mutf7 ucs4 cases");
+ for (chr = 0xffff; chr <= 0x10010; chr++) T_BEGIN {
+ for (i = 1; i <= 10; i++) {
+ str_truncate(src, 0);
+ str_truncate(dest, 0);
+ for (j = 0; j < i; j++) {
+ if (j % 3 == 0)
+ str_append_c(src, 'x');
+ if (j % 5 == 0)
+ str_append_c(src, '&');
+ uni_ucs4_to_utf8_c(chr, src);
+ }
+
+ orig_src = t_strdup(str_c(src));
+ str_truncate(src, 0);
+
+ test_assert_idx(imap_utf8_to_utf7(orig_src, dest) == 0, chr*100+i);
+ test_assert_idx(imap_utf7_to_utf8(str_c(dest), src) == 0, chr*100+i);
+ test_assert_idx(strcmp(str_c(src), orig_src) == 0, chr+100+i);
+ }
+ } T_END;
+ test_end();
+}
+
+static const char mb64[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
+static void test_imap_utf7_non_utf16(void)
+{
+ string_t *dest, *dest2;
+ unsigned int i;
+
+ test_begin("imap mutf7 non-utf16");
+ dest = t_str_new(32);
+ dest2 = t_str_new(32);
+ for (i = 0; i <= 255; ++i) {
+ /* Invalid, code a single 8-bit octet */
+ const char csrc[] = {
+ '&',
+ mb64[i >> 2],
+ mb64[(i & 3) << 4],
+ '-',
+ '\0'
+ };
+ test_assert_idx(!imap_utf7_is_valid(csrc), i);
+
+ /* escaping can reverse the original string */
+ str_truncate(dest, 0);
+ str_truncate(dest2, 0);
+ imap_utf7_to_utf8_escaped(csrc, "%", dest);
+ imap_escaped_utf8_to_utf7(str_c(dest), '%', dest2);
+ test_assert_idx(strcmp(csrc, str_c(dest2)) == 0, i);
+ }
+ for (i = 0; i <= 255; ++i) {
+ /* Invalid, U+00E4 followed by a single octet */
+ const char csrc[] = {
+ '&',
+ mb64[ (0x00 >> 2)],
+ mb64[((0x00 & 0x03) << 4) | (0xe4 >> 4)],
+ mb64[((0xe4 & 0x0f) << 2) | ( i >> 6)],
+ mb64[ i & 0x3f ],
+ '-',
+ '\0'
+ };
+ test_assert_idx(!imap_utf7_is_valid(csrc), i);
+
+ /* escaping can reverse the original string */
+ str_truncate(dest, 0);
+ str_truncate(dest2, 0);
+ imap_utf7_to_utf8_escaped(csrc, "%", dest);
+ imap_escaped_utf8_to_utf7(str_c(dest), '%', dest2);
+ test_assert_idx(strcmp(csrc, str_c(dest2)) == 0, i);
+ }
+ test_end();
+}
+
+static void test_imap_utf7_bad_ascii(void)
+{
+ string_t *dest;
+ char csrc[1+1];
+ unsigned int i;
+
+ dest = t_str_new(256);
+
+ test_begin("imap mutf7 bad ascii");
+ for (i = 1; i <= 0x7f; ++i) {
+ if (i == ' ')
+ i = 0x7f;
+ csrc[0] = i;
+ csrc[1] = '\0';
+ test_assert_idx(!imap_utf7_is_valid(csrc), i);
+ str_truncate(dest, 0);
+ test_assert_idx(imap_utf7_to_utf8(csrc, dest) < 0, i);
+ }
+ test_end();
+}
+
+static void test_imap_utf7_unnecessary(void)
+{
+ string_t *dest;
+ char csrc[1+3+1+1];
+ unsigned int i;
+
+ dest = t_str_new(256);
+
+ test_begin("imap mutf7 unnecessary");
+ for (i = 0x20; i < 0x7f; ++i) {
+ /* Create an invalid escaped encoding of a simple char or '&' */
+ csrc[0] = '&';
+ csrc[1] = mb64[ (0x00 >> 2)];
+ csrc[2] = mb64[((0x00 & 0x03) << 4) | ( i >> 4)];
+ csrc[3] = mb64[(( i & 0x0f) << 2) | 0 ];
+ csrc[4] = '-';
+ csrc[5] = '\0';
+ test_assert_idx(!imap_utf7_is_valid(csrc), i);
+ str_truncate(dest, 0);
+ test_assert_idx(imap_utf7_to_utf8(csrc, dest) < 0, i);
+
+ /* All self-coding characters must self-code */
+ if (i == '&')
+ continue;
+ csrc[0] = i;
+ csrc[1] = '\0';
+ str_truncate(dest, 0);
+ test_assert_idx(imap_utf8_to_utf7(csrc, dest) >= 0, i);
+ test_assert_idx(strcmp(csrc, str_c(dest)) == 0, i);
+ }
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_imap_utf7_by_example,
+ test_imap_utf7_ucs4_cases,
+ test_imap_utf7_non_utf16,
+ test_imap_utf7_bad_ascii,
+ test_imap_utf7_unnecessary,
+ NULL
+ };
+ return test_run(test_functions);
+}
diff --git a/src/lib-imap/test-imap-util.c b/src/lib-imap/test-imap-util.c
new file mode 100644
index 0000000..90f59a1
--- /dev/null
+++ b/src/lib-imap/test-imap-util.c
@@ -0,0 +1,79 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "mail-types.h"
+#include "imap-arg.h"
+#include "imap-util.h"
+#include "test-common.h"
+
+static void test_imap_parse_system_flag(void)
+{
+ test_begin("imap_parse_system_flag");
+ test_assert(imap_parse_system_flag("\\aNswered") == MAIL_ANSWERED);
+ test_assert(imap_parse_system_flag("\\fLagged") == MAIL_FLAGGED);
+ test_assert(imap_parse_system_flag("\\dEleted") == MAIL_DELETED);
+ test_assert(imap_parse_system_flag("\\sEen") == MAIL_SEEN);
+ test_assert(imap_parse_system_flag("\\dRaft") == MAIL_DRAFT);
+ test_assert(imap_parse_system_flag("\\rEcent") == MAIL_RECENT);
+ test_assert(imap_parse_system_flag("answered") == 0);
+ test_assert(imap_parse_system_flag("\\broken") == 0);
+ test_assert(imap_parse_system_flag("\\") == 0);
+ test_assert(imap_parse_system_flag("") == 0);
+ test_end();
+}
+
+static void test_imap_write_arg(void)
+{
+ ARRAY_TYPE(imap_arg_list) list_root, list_sub;
+ struct imap_arg *arg;
+
+ t_array_init(&list_sub, 2);
+ arg = array_append_space(&list_sub);
+ arg->type = IMAP_ARG_ATOM;
+ arg->_data.str = "foo";
+ arg = array_append_space(&list_sub);
+ arg->type = IMAP_ARG_EOL;
+
+ t_array_init(&list_root, 2);
+ arg = array_append_space(&list_root);
+ arg->type = IMAP_ARG_LIST;
+ arg->_data.list = list_sub;
+ arg = array_append_space(&list_root);
+ arg->type = IMAP_ARG_STRING;
+ arg->_data.str = "bar";
+ arg = array_append_space(&list_root);
+ arg->type = IMAP_ARG_EOL;
+
+ const struct {
+ struct imap_arg input;
+ const char *output;
+ } tests[] = {
+ { { .type = IMAP_ARG_NIL }, "NIL" },
+ { { .type = IMAP_ARG_ATOM, ._data.str = "atom" }, "atom" },
+ { { .type = IMAP_ARG_STRING, ._data.str = "s\\t\"ring" }, "\"s\\\\t\\\"ring\"" },
+ { { .type = IMAP_ARG_LITERAL, ._data.str = "l\\i\"t\r\neral" }, "{11}\r\nl\\i\"t\r\neral" },
+ { { .type = IMAP_ARG_LITERAL_SIZE, ._data.literal_size = 12345678 }, "<12345678 byte literal>" },
+ { { .type = IMAP_ARG_LITERAL_SIZE_NONSYNC, ._data.literal_size = 12345678 }, "<12345678 byte literal>" },
+ { { .type = IMAP_ARG_LIST, ._data.list = list_root }, "((foo) \"bar\")" },
+ };
+ string_t *str = t_str_new(100);
+
+ test_begin("imap_write_arg");
+ for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
+ str_truncate(str, 0);
+ imap_write_arg(str, &tests[i].input);
+ test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i);
+ }
+ test_end();
+}
+
+int main(void)
+{
+ static void (*const test_functions[])(void) = {
+ test_imap_parse_system_flag,
+ test_imap_write_arg,
+ NULL
+ };
+ return test_run(test_functions);
+}