summaryrefslogtreecommitdiffstats
path: root/src/lib-imap
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/lib-imap
parentInitial commit. (diff)
downloaddovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz
dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-imap')
-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.c972
-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.c734
-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
41 files changed, 10112 insertions, 0 deletions
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..f5e6f36
--- /dev/null
+++ b/src/lib-imap/Makefile.in
@@ -0,0 +1,1196 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 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..d4f4220
--- /dev/null
+++ b/src/lib-imap/imap-bodystructure.c
@@ -0,0 +1,972 @@
+/* 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);
+}
+
+
+static void imap_bodystructure_reset_data(struct message_part *parts)
+{
+ for (; parts != NULL; parts = parts->next) {
+ parts->data = NULL;
+ imap_bodystructure_reset_data(parts->children);
+ }
+}
+
+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);
+ }
+
+ if (ret < 0) {
+ /* Don't leave partially filled data to message_parts. Some of
+ the code expects that if the first message_part->data is
+ filled, they all are. */
+ imap_bodystructure_reset_data(*parts);
+ }
+
+ 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..736eb99
--- /dev/null
+++ b/src/lib-imap/test-imap-bodystructure.c
@@ -0,0 +1,734 @@
+/* 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(parts->data == NULL);
+ 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);
+}