diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-imap | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-imap')
41 files changed, 10095 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..5fd6ea4 --- /dev/null +++ b/src/lib-imap/Makefile.in @@ -0,0 +1,1196 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +noinst_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) +@USE_FUZZER_TRUE@am__append_1 = \ +@USE_FUZZER_TRUE@ fuzz-imap-utf7 \ +@USE_FUZZER_TRUE@ fuzz-imap-bodystructure + +subdir = src/lib-imap +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \ + $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \ + $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \ + $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \ + $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \ + $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \ + $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \ + $(top_srcdir)/m4/flexible_array_member.m4 \ + $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \ + $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \ + $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \ + $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \ + $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \ + $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \ + $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \ + $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \ + $(top_srcdir)/m4/pr_set_dumpable.m4 \ + $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \ + $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \ + $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \ + $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \ + $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \ + $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \ + $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \ + $(top_srcdir)/m4/typeof_dev_t.m4 \ + $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \ + $(top_srcdir)/m4/want_apparmor.m4 \ + $(top_srcdir)/m4/want_bsdauth.m4 \ + $(top_srcdir)/m4/want_bzlib.m4 \ + $(top_srcdir)/m4/want_cassandra.m4 \ + $(top_srcdir)/m4/want_cdb.m4 \ + $(top_srcdir)/m4/want_checkpassword.m4 \ + $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \ + $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \ + $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \ + $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \ + $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \ + $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \ + $(top_srcdir)/m4/want_prefetch.m4 \ + $(top_srcdir)/m4/want_shadow.m4 \ + $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \ + $(top_srcdir)/m4/want_sqlite.m4 \ + $(top_srcdir)/m4/want_stemmer.m4 \ + $(top_srcdir)/m4/want_systemd.m4 \ + $(top_srcdir)/m4/want_textcat.m4 \ + $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \ + $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test-imap-bodystructure$(EXEEXT) \ + test-imap-envelope$(EXEEXT) test-imap-match$(EXEEXT) \ + test-imap-parser$(EXEEXT) test-imap-quote$(EXEEXT) \ + test-imap-url$(EXEEXT) test-imap-utf7$(EXEEXT) \ + test-imap-util$(EXEEXT) +@USE_FUZZER_TRUE@am__EXEEXT_2 = fuzz-imap-utf7$(EXEEXT) \ +@USE_FUZZER_TRUE@ fuzz-imap-bodystructure$(EXEEXT) +PROGRAMS = $(noinst_PROGRAMS) +LTLIBRARIES = $(noinst_LTLIBRARIES) +libimap_la_LIBADD = +am_libimap_la_OBJECTS = imap-arg.lo imap-base-subject.lo \ + imap-bodystructure.lo imap-date.lo imap-envelope.lo imap-id.lo \ + imap-keepalive.lo imap-match.lo imap-parser.lo imap-quote.lo \ + imap-url.lo imap-seqset.lo imap-utf7.lo imap-util.lo +libimap_la_OBJECTS = $(am_libimap_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +am__fuzz_imap_bodystructure_SOURCES_DIST = fuzz-imap-bodystructure.c +@USE_FUZZER_TRUE@am_fuzz_imap_bodystructure_OBJECTS = fuzz_imap_bodystructure-fuzz-imap-bodystructure.$(OBJEXT) +fuzz_imap_bodystructure_OBJECTS = \ + $(am_fuzz_imap_bodystructure_OBJECTS) +fuzz_imap_bodystructure_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(fuzz_imap_bodystructure_LDFLAGS) \ + $(LDFLAGS) -o $@ +am__fuzz_imap_utf7_SOURCES_DIST = fuzz-imap-utf7.c +@USE_FUZZER_TRUE@am_fuzz_imap_utf7_OBJECTS = \ +@USE_FUZZER_TRUE@ fuzz_imap_utf7-fuzz-imap-utf7.$(OBJEXT) +fuzz_imap_utf7_OBJECTS = $(am_fuzz_imap_utf7_OBJECTS) +fuzz_imap_utf7_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(fuzz_imap_utf7_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_test_imap_bodystructure_OBJECTS = \ + test-imap-bodystructure.$(OBJEXT) +test_imap_bodystructure_OBJECTS = \ + $(am_test_imap_bodystructure_OBJECTS) +am_test_imap_envelope_OBJECTS = test-imap-envelope.$(OBJEXT) +test_imap_envelope_OBJECTS = $(am_test_imap_envelope_OBJECTS) +am_test_imap_match_OBJECTS = test-imap-match.$(OBJEXT) +test_imap_match_OBJECTS = $(am_test_imap_match_OBJECTS) +am_test_imap_parser_OBJECTS = test-imap-parser.$(OBJEXT) +test_imap_parser_OBJECTS = $(am_test_imap_parser_OBJECTS) +am_test_imap_quote_OBJECTS = test-imap-quote.$(OBJEXT) +test_imap_quote_OBJECTS = $(am_test_imap_quote_OBJECTS) +am_test_imap_url_OBJECTS = test-imap-url.$(OBJEXT) +test_imap_url_OBJECTS = $(am_test_imap_url_OBJECTS) +am_test_imap_utf7_OBJECTS = test-imap-utf7.$(OBJEXT) +test_imap_utf7_OBJECTS = $(am_test_imap_utf7_OBJECTS) +am_test_imap_util_OBJECTS = test-imap-util.$(OBJEXT) +test_imap_util_OBJECTS = $(am_test_imap_util_OBJECTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = \ + ./$(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po \ + ./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po \ + ./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po \ + ./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po \ + ./$(DEPDIR)/imap-arg.Plo ./$(DEPDIR)/imap-base-subject.Plo \ + ./$(DEPDIR)/imap-bodystructure.Plo ./$(DEPDIR)/imap-date.Plo \ + ./$(DEPDIR)/imap-envelope.Plo ./$(DEPDIR)/imap-id.Plo \ + ./$(DEPDIR)/imap-keepalive.Plo ./$(DEPDIR)/imap-match.Plo \ + ./$(DEPDIR)/imap-parser.Plo ./$(DEPDIR)/imap-quote.Plo \ + ./$(DEPDIR)/imap-seqset.Plo ./$(DEPDIR)/imap-url.Plo \ + ./$(DEPDIR)/imap-utf7.Plo ./$(DEPDIR)/imap-util.Plo \ + ./$(DEPDIR)/test-imap-bodystructure.Po \ + ./$(DEPDIR)/test-imap-envelope.Po \ + ./$(DEPDIR)/test-imap-match.Po ./$(DEPDIR)/test-imap-parser.Po \ + ./$(DEPDIR)/test-imap-quote.Po ./$(DEPDIR)/test-imap-url.Po \ + ./$(DEPDIR)/test-imap-utf7.Po ./$(DEPDIR)/test-imap-util.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(libimap_la_SOURCES) $(fuzz_imap_bodystructure_SOURCES) \ + $(nodist_EXTRA_fuzz_imap_bodystructure_SOURCES) \ + $(fuzz_imap_utf7_SOURCES) \ + $(nodist_EXTRA_fuzz_imap_utf7_SOURCES) \ + $(test_imap_bodystructure_SOURCES) \ + $(test_imap_envelope_SOURCES) $(test_imap_match_SOURCES) \ + $(test_imap_parser_SOURCES) $(test_imap_quote_SOURCES) \ + $(test_imap_url_SOURCES) $(test_imap_utf7_SOURCES) \ + $(test_imap_util_SOURCES) +DIST_SOURCES = $(libimap_la_SOURCES) \ + $(am__fuzz_imap_bodystructure_SOURCES_DIST) \ + $(am__fuzz_imap_utf7_SOURCES_DIST) \ + $(test_imap_bodystructure_SOURCES) \ + $(test_imap_envelope_SOURCES) $(test_imap_match_SOURCES) \ + $(test_imap_parser_SOURCES) $(test_imap_quote_SOURCES) \ + $(test_imap_url_SOURCES) $(test_imap_utf7_SOURCES) \ + $(test_imap_util_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkginc_libdir)" +HEADERS = $(pkginc_lib_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_LIBS = @APPARMOR_LIBS@ +AR = @AR@ +AUTH_CFLAGS = @AUTH_CFLAGS@ +AUTH_LIBS = @AUTH_LIBS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BINARY_CFLAGS = @BINARY_CFLAGS@ +BINARY_LDFLAGS = @BINARY_LDFLAGS@ +BISON = @BISON@ +CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@ +CASSANDRA_LIBS = @CASSANDRA_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CDB_LIBS = @CDB_LIBS@ +CFLAGS = @CFLAGS@ +CLUCENE_CFLAGS = @CLUCENE_CFLAGS@ +CLUCENE_LIBS = @CLUCENE_LIBS@ +COMPRESS_LIBS = @COMPRESS_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPT_LIBS = @CRYPT_LIBS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DICT_LIBS = @DICT_LIBS@ +DLLIB = @DLLIB@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLEX = @FLEX@ +FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@ +FUZZER_LDFLAGS = @FUZZER_LDFLAGS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KRB5CONFIG = @KRB5CONFIG@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_LIBS = @KRB5_LIBS@ +LD = @LD@ +LDAP_LIBS = @LDAP_LIBS@ +LDFLAGS = @LDFLAGS@ +LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@ +LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@ +LIBCAP = @LIBCAP@ +LIBDOVECOT = @LIBDOVECOT@ +LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@ +LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@ +LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@ +LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@ +LIBDOVECOT_LDA = @LIBDOVECOT_LDA@ +LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@ +LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@ +LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@ +LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@ +LIBDOVECOT_LUA = @LIBDOVECOT_LUA@ +LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@ +LIBDOVECOT_SQL = @LIBDOVECOT_SQL@ +LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@ +LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@ +LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@ +LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@ +LIBICONV = @LIBICONV@ +LIBICU_CFLAGS = @LIBICU_CFLAGS@ +LIBICU_LIBS = @LIBICU_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@ +LIBSODIUM_LIBS = @LIBSODIUM_LIBS@ +LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@ +LIBTIRPC_LIBS = @LIBTIRPC_LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@ +LIBUNWIND_LIBS = @LIBUNWIND_LIBS@ +LIBWRAP_LIBS = @LIBWRAP_LIBS@ +LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MODULE_LIBS = @MODULE_LIBS@ +MODULE_SUFFIX = @MODULE_SUFFIX@ +MYSQL_CFLAGS = @MYSQL_CFLAGS@ +MYSQL_CONFIG = @MYSQL_CONFIG@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PANDOC = @PANDOC@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PGSQL_CFLAGS = @PGSQL_CFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PG_CONFIG = @PG_CONFIG@ +PIE_CFLAGS = @PIE_CFLAGS@ +PIE_LDFLAGS = @PIE_LDFLAGS@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +QUOTA_LIBS = @QUOTA_LIBS@ +RANLIB = @RANLIB@ +RELRO_LDFLAGS = @RELRO_LDFLAGS@ +RPCGEN = @RPCGEN@ +RUN_TEST = @RUN_TEST@ +SED = @SED@ +SETTING_FILES = @SETTING_FILES@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SQLITE_CFLAGS = @SQLITE_CFLAGS@ +SQLITE_LIBS = @SQLITE_LIBS@ +SQL_CFLAGS = @SQL_CFLAGS@ +SQL_LIBS = @SQL_LIBS@ +SSL_CFLAGS = @SSL_CFLAGS@ +SSL_LIBS = @SSL_LIBS@ +STRIP = @STRIP@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +ZSTD_CFLAGS = @ZSTD_CFLAGS@ +ZSTD_LIBS = @ZSTD_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dict_drivers = @dict_drivers@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +moduledir = @moduledir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +rundir = @rundir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sql_drivers = @sql_drivers@ +srcdir = @srcdir@ +ssldir = @ssldir@ +statedir = @statedir@ +sysconfdir = @sysconfdir@ +systemdservicetype = @systemdservicetype@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = libimap.la +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-charset \ + -I$(top_srcdir)/src/lib-mail + +libimap_la_SOURCES = \ + imap-arg.c \ + imap-base-subject.c \ + imap-bodystructure.c \ + imap-date.c \ + imap-envelope.c \ + imap-id.c \ + imap-keepalive.c \ + imap-match.c \ + imap-parser.c \ + imap-quote.c \ + imap-url.c \ + imap-seqset.c \ + imap-utf7.c \ + imap-util.c + +headers = \ + imap-arg.h \ + imap-base-subject.h \ + imap-bodystructure.h \ + imap-date.h \ + imap-envelope.h \ + imap-id.h \ + imap-keepalive.h \ + imap-match.h \ + imap-parser.h \ + imap-resp-code.h \ + imap-quote.h \ + imap-url.h \ + imap-seqset.h \ + imap-utf7.h \ + imap-util.h + +pkginc_libdir = $(pkgincludedir) +pkginc_lib_HEADERS = $(headers) +test_programs = \ + test-imap-bodystructure \ + test-imap-envelope \ + test-imap-match \ + test-imap-parser \ + test-imap-quote \ + test-imap-url \ + test-imap-utf7 \ + test-imap-util + +test_libs = \ + ../lib-test/libtest.la \ + ../lib/liblib.la + +test_deps = $(noinst_LTLIBRARIES) $(test_libs) +test_imap_bodystructure_SOURCES = test-imap-bodystructure.c +test_imap_bodystructure_LDADD = imap-bodystructure.lo imap-envelope.lo imap-quote.lo imap-parser.lo imap-arg.lo ../lib-mail/libmail.la $(test_libs) +test_imap_bodystructure_DEPENDENCIES = $(test_deps) ../lib-mail/libmail.la +test_imap_envelope_SOURCES = test-imap-envelope.c +test_imap_envelope_LDADD = imap-envelope.lo imap-quote.lo imap-parser.lo imap-arg.lo ../lib-mail/libmail.la $(test_libs) +test_imap_envelope_DEPENDENCIES = $(test_deps) ../lib-mail/libmail.la +test_imap_match_SOURCES = test-imap-match.c +test_imap_match_LDADD = imap-match.lo $(test_libs) +test_imap_match_DEPENDENCIES = $(test_deps) +test_imap_parser_SOURCES = test-imap-parser.c +test_imap_parser_LDADD = imap-parser.lo imap-arg.lo $(test_libs) +test_imap_parser_DEPENDENCIES = $(test_deps) +test_imap_quote_SOURCES = test-imap-quote.c +test_imap_quote_LDADD = imap-quote.lo $(test_libs) +test_imap_quote_DEPENDENCIES = $(test_deps) +test_imap_url_SOURCES = test-imap-url.c +test_imap_url_LDADD = imap-url.lo $(test_libs) +test_imap_url_DEPENDENCIES = $(test_deps) +test_imap_utf7_SOURCES = test-imap-utf7.c +test_imap_utf7_LDADD = imap-utf7.lo $(test_libs) +test_imap_utf7_DEPENDENCIES = $(test_deps) +test_imap_util_SOURCES = test-imap-util.c +test_imap_util_LDADD = imap-util.lo imap-arg.lo $(test_libs) +test_imap_util_DEPENDENCIES = $(test_deps) +@USE_FUZZER_TRUE@nodist_EXTRA_fuzz_imap_utf7_SOURCES = force-cxx-linking.cxx +@USE_FUZZER_TRUE@fuzz_imap_utf7_SOURCES = fuzz-imap-utf7.c +@USE_FUZZER_TRUE@fuzz_imap_utf7_CPPFLAGS = $(FUZZER_CPPFLAGS) +@USE_FUZZER_TRUE@fuzz_imap_utf7_LDFLAGS = $(FUZZER_LDFLAGS) +@USE_FUZZER_TRUE@fuzz_imap_utf7_LDADD = libimap.la $(test_libs) +@USE_FUZZER_TRUE@fuzz_imap_utf7_DEPENDENCIES = libimap.la $(test_deps) +@USE_FUZZER_TRUE@nodist_EXTRA_fuzz_imap_bodystructure_SOURCES = force-cxx-linking.cxx +@USE_FUZZER_TRUE@fuzz_imap_bodystructure_SOURCES = fuzz-imap-bodystructure.c +@USE_FUZZER_TRUE@fuzz_imap_bodystructure_CPPFLAGS = $(FUZZER_CPPFLAGS) +@USE_FUZZER_TRUE@fuzz_imap_bodystructure_LDFLAGS = $(FUZZER_LDFLAGS) +@USE_FUZZER_TRUE@fuzz_imap_bodystructure_LDADD = libimap.la $(test_libs) +@USE_FUZZER_TRUE@fuzz_imap_bodystructure_DEPENDENCIES = libimap.la $(test_deps) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .cxx .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-imap/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib-imap/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libimap.la: $(libimap_la_OBJECTS) $(libimap_la_DEPENDENCIES) $(EXTRA_libimap_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libimap_la_OBJECTS) $(libimap_la_LIBADD) $(LIBS) + +fuzz-imap-bodystructure$(EXEEXT): $(fuzz_imap_bodystructure_OBJECTS) $(fuzz_imap_bodystructure_DEPENDENCIES) $(EXTRA_fuzz_imap_bodystructure_DEPENDENCIES) + @rm -f fuzz-imap-bodystructure$(EXEEXT) + $(AM_V_CXXLD)$(fuzz_imap_bodystructure_LINK) $(fuzz_imap_bodystructure_OBJECTS) $(fuzz_imap_bodystructure_LDADD) $(LIBS) + +fuzz-imap-utf7$(EXEEXT): $(fuzz_imap_utf7_OBJECTS) $(fuzz_imap_utf7_DEPENDENCIES) $(EXTRA_fuzz_imap_utf7_DEPENDENCIES) + @rm -f fuzz-imap-utf7$(EXEEXT) + $(AM_V_CXXLD)$(fuzz_imap_utf7_LINK) $(fuzz_imap_utf7_OBJECTS) $(fuzz_imap_utf7_LDADD) $(LIBS) + +test-imap-bodystructure$(EXEEXT): $(test_imap_bodystructure_OBJECTS) $(test_imap_bodystructure_DEPENDENCIES) $(EXTRA_test_imap_bodystructure_DEPENDENCIES) + @rm -f test-imap-bodystructure$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_bodystructure_OBJECTS) $(test_imap_bodystructure_LDADD) $(LIBS) + +test-imap-envelope$(EXEEXT): $(test_imap_envelope_OBJECTS) $(test_imap_envelope_DEPENDENCIES) $(EXTRA_test_imap_envelope_DEPENDENCIES) + @rm -f test-imap-envelope$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_envelope_OBJECTS) $(test_imap_envelope_LDADD) $(LIBS) + +test-imap-match$(EXEEXT): $(test_imap_match_OBJECTS) $(test_imap_match_DEPENDENCIES) $(EXTRA_test_imap_match_DEPENDENCIES) + @rm -f test-imap-match$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_match_OBJECTS) $(test_imap_match_LDADD) $(LIBS) + +test-imap-parser$(EXEEXT): $(test_imap_parser_OBJECTS) $(test_imap_parser_DEPENDENCIES) $(EXTRA_test_imap_parser_DEPENDENCIES) + @rm -f test-imap-parser$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_parser_OBJECTS) $(test_imap_parser_LDADD) $(LIBS) + +test-imap-quote$(EXEEXT): $(test_imap_quote_OBJECTS) $(test_imap_quote_DEPENDENCIES) $(EXTRA_test_imap_quote_DEPENDENCIES) + @rm -f test-imap-quote$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_quote_OBJECTS) $(test_imap_quote_LDADD) $(LIBS) + +test-imap-url$(EXEEXT): $(test_imap_url_OBJECTS) $(test_imap_url_DEPENDENCIES) $(EXTRA_test_imap_url_DEPENDENCIES) + @rm -f test-imap-url$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_url_OBJECTS) $(test_imap_url_LDADD) $(LIBS) + +test-imap-utf7$(EXEEXT): $(test_imap_utf7_OBJECTS) $(test_imap_utf7_DEPENDENCIES) $(EXTRA_test_imap_utf7_DEPENDENCIES) + @rm -f test-imap-utf7$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_utf7_OBJECTS) $(test_imap_utf7_LDADD) $(LIBS) + +test-imap-util$(EXEEXT): $(test_imap_util_OBJECTS) $(test_imap_util_DEPENDENCIES) $(EXTRA_test_imap_util_DEPENDENCIES) + @rm -f test-imap-util$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(test_imap_util_OBJECTS) $(test_imap_util_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-arg.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-base-subject.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-bodystructure.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-date.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-envelope.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-id.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-keepalive.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-match.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-quote.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-seqset.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-url.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-utf7.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap-util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-bodystructure.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-envelope.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-match.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-parser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-quote.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-url.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-utf7.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-imap-util.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +fuzz_imap_bodystructure-fuzz-imap-bodystructure.o: fuzz-imap-bodystructure.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_bodystructure-fuzz-imap-bodystructure.o -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.o `test -f 'fuzz-imap-bodystructure.c' || echo '$(srcdir)/'`fuzz-imap-bodystructure.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-bodystructure.c' object='fuzz_imap_bodystructure-fuzz-imap-bodystructure.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.o `test -f 'fuzz-imap-bodystructure.c' || echo '$(srcdir)/'`fuzz-imap-bodystructure.c + +fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj: fuzz-imap-bodystructure.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj `if test -f 'fuzz-imap-bodystructure.c'; then $(CYGPATH_W) 'fuzz-imap-bodystructure.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-bodystructure.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Tpo $(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-bodystructure.c' object='fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_bodystructure-fuzz-imap-bodystructure.obj `if test -f 'fuzz-imap-bodystructure.c'; then $(CYGPATH_W) 'fuzz-imap-bodystructure.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-bodystructure.c'; fi` + +fuzz_imap_utf7-fuzz-imap-utf7.o: fuzz-imap-utf7.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_utf7-fuzz-imap-utf7.o -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo -c -o fuzz_imap_utf7-fuzz-imap-utf7.o `test -f 'fuzz-imap-utf7.c' || echo '$(srcdir)/'`fuzz-imap-utf7.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-utf7.c' object='fuzz_imap_utf7-fuzz-imap-utf7.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_utf7-fuzz-imap-utf7.o `test -f 'fuzz-imap-utf7.c' || echo '$(srcdir)/'`fuzz-imap-utf7.c + +fuzz_imap_utf7-fuzz-imap-utf7.obj: fuzz-imap-utf7.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT fuzz_imap_utf7-fuzz-imap-utf7.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo -c -o fuzz_imap_utf7-fuzz-imap-utf7.obj `if test -f 'fuzz-imap-utf7.c'; then $(CYGPATH_W) 'fuzz-imap-utf7.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-utf7.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Tpo $(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='fuzz-imap-utf7.c' object='fuzz_imap_utf7-fuzz-imap-utf7.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o fuzz_imap_utf7-fuzz-imap-utf7.obj `if test -f 'fuzz-imap-utf7.c'; then $(CYGPATH_W) 'fuzz-imap-utf7.c'; else $(CYGPATH_W) '$(srcdir)/fuzz-imap-utf7.c'; fi` + +.cxx.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cxx.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cxx.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +fuzz_imap_bodystructure-force-cxx-linking.o: force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_bodystructure-force-cxx-linking.o -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo -c -o fuzz_imap_bodystructure-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_bodystructure-force-cxx-linking.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_bodystructure-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx + +fuzz_imap_bodystructure-force-cxx-linking.obj: force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_bodystructure-force-cxx-linking.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo -c -o fuzz_imap_bodystructure-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_bodystructure-force-cxx-linking.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_bodystructure_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_bodystructure-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi` + +fuzz_imap_utf7-force-cxx-linking.o: force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_utf7-force-cxx-linking.o -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo -c -o fuzz_imap_utf7-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_utf7-force-cxx-linking.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_utf7-force-cxx-linking.o `test -f 'force-cxx-linking.cxx' || echo '$(srcdir)/'`force-cxx-linking.cxx + +fuzz_imap_utf7-force-cxx-linking.obj: force-cxx-linking.cxx +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT fuzz_imap_utf7-force-cxx-linking.obj -MD -MP -MF $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo -c -o fuzz_imap_utf7-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Tpo $(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='force-cxx-linking.cxx' object='fuzz_imap_utf7-force-cxx-linking.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(fuzz_imap_utf7_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o fuzz_imap_utf7-force-cxx-linking.obj `if test -f 'force-cxx-linking.cxx'; then $(CYGPATH_W) 'force-cxx-linking.cxx'; else $(CYGPATH_W) '$(srcdir)/force-cxx-linking.cxx'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_libHEADERS: $(pkginc_lib_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \ + done + +uninstall-pkginc_libHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkginc_libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po + -rm -f ./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po + -rm -f ./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po + -rm -f ./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po + -rm -f ./$(DEPDIR)/imap-arg.Plo + -rm -f ./$(DEPDIR)/imap-base-subject.Plo + -rm -f ./$(DEPDIR)/imap-bodystructure.Plo + -rm -f ./$(DEPDIR)/imap-date.Plo + -rm -f ./$(DEPDIR)/imap-envelope.Plo + -rm -f ./$(DEPDIR)/imap-id.Plo + -rm -f ./$(DEPDIR)/imap-keepalive.Plo + -rm -f ./$(DEPDIR)/imap-match.Plo + -rm -f ./$(DEPDIR)/imap-parser.Plo + -rm -f ./$(DEPDIR)/imap-quote.Plo + -rm -f ./$(DEPDIR)/imap-seqset.Plo + -rm -f ./$(DEPDIR)/imap-url.Plo + -rm -f ./$(DEPDIR)/imap-utf7.Plo + -rm -f ./$(DEPDIR)/imap-util.Plo + -rm -f ./$(DEPDIR)/test-imap-bodystructure.Po + -rm -f ./$(DEPDIR)/test-imap-envelope.Po + -rm -f ./$(DEPDIR)/test-imap-match.Po + -rm -f ./$(DEPDIR)/test-imap-parser.Po + -rm -f ./$(DEPDIR)/test-imap-quote.Po + -rm -f ./$(DEPDIR)/test-imap-url.Po + -rm -f ./$(DEPDIR)/test-imap-utf7.Po + -rm -f ./$(DEPDIR)/test-imap-util.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pkginc_libHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/fuzz_imap_bodystructure-force-cxx-linking.Po + -rm -f ./$(DEPDIR)/fuzz_imap_bodystructure-fuzz-imap-bodystructure.Po + -rm -f ./$(DEPDIR)/fuzz_imap_utf7-force-cxx-linking.Po + -rm -f ./$(DEPDIR)/fuzz_imap_utf7-fuzz-imap-utf7.Po + -rm -f ./$(DEPDIR)/imap-arg.Plo + -rm -f ./$(DEPDIR)/imap-base-subject.Plo + -rm -f ./$(DEPDIR)/imap-bodystructure.Plo + -rm -f ./$(DEPDIR)/imap-date.Plo + -rm -f ./$(DEPDIR)/imap-envelope.Plo + -rm -f ./$(DEPDIR)/imap-id.Plo + -rm -f ./$(DEPDIR)/imap-keepalive.Plo + -rm -f ./$(DEPDIR)/imap-match.Plo + -rm -f ./$(DEPDIR)/imap-parser.Plo + -rm -f ./$(DEPDIR)/imap-quote.Plo + -rm -f ./$(DEPDIR)/imap-seqset.Plo + -rm -f ./$(DEPDIR)/imap-url.Plo + -rm -f ./$(DEPDIR)/imap-utf7.Plo + -rm -f ./$(DEPDIR)/imap-util.Plo + -rm -f ./$(DEPDIR)/test-imap-bodystructure.Po + -rm -f ./$(DEPDIR)/test-imap-envelope.Po + -rm -f ./$(DEPDIR)/test-imap-match.Po + -rm -f ./$(DEPDIR)/test-imap-parser.Po + -rm -f ./$(DEPDIR)/test-imap-quote.Po + -rm -f ./$(DEPDIR)/test-imap-url.Po + -rm -f ./$(DEPDIR)/test-imap-utf7.Po + -rm -f ./$(DEPDIR)/test-imap-util.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkginc_libHEADERS + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am \ + check-local clean clean-generic clean-libtool \ + clean-noinstLTLIBRARIES clean-noinstPROGRAMS cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-pkginc_libHEADERS install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-pkginc_libHEADERS + +.PRECIOUS: Makefile + + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib-imap/fuzz-imap-bodystructure.c b/src/lib-imap/fuzz-imap-bodystructure.c new file mode 100644 index 0000000..a9d4499 --- /dev/null +++ b/src/lib-imap/fuzz-imap-bodystructure.c @@ -0,0 +1,52 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "fuzzer.h" +#include "imap-bodystructure.c" +#include <ctype.h> + +static const char *str_sanitize_binary(const char *input) +{ + string_t *dest = t_str_new(strlen(input)); + for (;*input != '\0';input++) { + if (i_isprint(*input) == 0) + str_printfa(dest, "<%02x>", (unsigned char)*input); + else + str_append_c(dest, *input); + } + return str_c(dest); +} + +FUZZ_BEGIN_STR(const char *str) +{ + pool_t pool = + pool_alloconly_create(MEMPOOL_GROWING"fuzz bodystructure", 1024); + struct message_part parts; + string_t *dest = str_new(pool, 32); + const char *error ATTR_UNUSED; + i_zero(&parts); + + if (imap_bodystructure_parse(str, pool, &parts, &error) == 0) { + if (imap_bodystructure_write(&parts, dest, TRUE, &error) < 0) + i_panic("Failed to write bodystructure: %s", error); + /* The written bodystructure must be parseable *and* + it must come out exactly the same again */ + if (imap_bodystructure_parse(str_c(dest), pool, &parts, &error) != 0) { + i_panic("Failed to reparse bodystructure '%s'", + str_sanitize_binary(str_c(dest))); + } else { + const char *new_str = t_strdup(str_c(dest)); + str_truncate(dest, 0); + if (imap_bodystructure_write(&parts, dest, TRUE, &error) < 0) + i_panic("Failed to write reparsed bodystructure: %s", error); + if (strcmp(str_c(dest), new_str) != 0) { + i_panic("Parsed bodystructure '%s' does not match '%s'", + str_sanitize_binary(new_str), + str_sanitize_binary(str_c(dest))); + } + } + } + pool_unref(&pool); +} +FUZZ_END diff --git a/src/lib-imap/fuzz-imap-utf7.c b/src/lib-imap/fuzz-imap-utf7.c new file mode 100644 index 0000000..2a561f1 --- /dev/null +++ b/src/lib-imap/fuzz-imap-utf7.c @@ -0,0 +1,15 @@ +/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "fuzzer.h" +#include "imap-utf7.h" + +FUZZ_BEGIN_STR(const char *str) +{ + string_t *dest = t_str_new(32); + + imap_utf8_to_utf7(str, dest); + imap_utf7_to_utf8(str, dest); +} +FUZZ_END diff --git a/src/lib-imap/imap-arg.c b/src/lib-imap/imap-arg.c new file mode 100644 index 0000000..0b947ce --- /dev/null +++ b/src/lib-imap/imap-arg.c @@ -0,0 +1,137 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "imap-arg.h" + +bool imap_arg_get_atom(const struct imap_arg *arg, const char **str_r) +{ + if (arg->type != IMAP_ARG_ATOM && arg->type != IMAP_ARG_NIL) + return FALSE; + + *str_r = arg->_data.str; + return TRUE; +} + +bool imap_arg_get_quoted(const struct imap_arg *arg, const char **str_r) +{ + if (arg->type != IMAP_ARG_STRING) + return FALSE; + + *str_r = arg->_data.str; + return TRUE; +} + +bool imap_arg_get_string(const struct imap_arg *arg, const char **str_r) +{ + if (arg->type != IMAP_ARG_STRING && arg->type != IMAP_ARG_LITERAL) + return FALSE; + + *str_r = arg->_data.str; + return TRUE; +} + +bool imap_arg_get_astring(const struct imap_arg *arg, const char **str_r) +{ + /* RFC 3501 4.5. specifies that NIL is the same as "NIL" when + reading astring. */ + if (!IMAP_ARG_IS_ASTRING(arg) && arg->type != IMAP_ARG_NIL) + return FALSE; + + *str_r = arg->_data.str; + return TRUE; +} + +bool imap_arg_get_nstring(const struct imap_arg *arg, const char **str_r) +{ + if (arg->type == IMAP_ARG_NIL) { + *str_r = NULL; + return TRUE; + } + return imap_arg_get_string(arg, str_r); +} + +bool imap_arg_get_literal_size(const struct imap_arg *arg, uoff_t *size_r) +{ + if (arg->type != IMAP_ARG_LITERAL_SIZE && + arg->type != IMAP_ARG_LITERAL_SIZE_NONSYNC) + return FALSE; + + *size_r = arg->_data.literal_size; + return TRUE; +} + +bool imap_arg_get_list(const struct imap_arg *arg, + const struct imap_arg **list_r) +{ + unsigned int count; + + return imap_arg_get_list_full(arg, list_r, &count); +} + +bool imap_arg_get_list_full(const struct imap_arg *arg, + const struct imap_arg **list_r, + unsigned int *list_count_r) +{ + unsigned int count; + + if (arg->type != IMAP_ARG_LIST) + return FALSE; + + *list_r = array_get(&arg->_data.list, &count); + + if (count > 0 && (*list_r)[count-1].type == IMAP_ARG_EOL) + count--; + else { + /* imap-parser stopped early (e.g. due to reading literal size). + The IMAP_ARG_EOL was added to the list only temporarily. */ + i_assert((*list_r)[count].type == IMAP_ARG_EOL); + } + *list_count_r = count; + return TRUE; +} + +const char *imap_arg_as_astring(const struct imap_arg *arg) +{ + const char *str; + + if (!imap_arg_get_astring(arg, &str)) + i_unreached(); + return str; +} + +const char *imap_arg_as_nstring(const struct imap_arg *arg) +{ + const char *str; + + if (!imap_arg_get_nstring(arg, &str)) + i_unreached(); + return str; +} + +uoff_t imap_arg_as_literal_size(const struct imap_arg *arg) +{ + uoff_t size; + + if (!imap_arg_get_literal_size(arg, &size)) + i_unreached(); + return size; +} + +const struct imap_arg * +imap_arg_as_list(const struct imap_arg *arg) +{ + const struct imap_arg *ret; + + if (!imap_arg_get_list(arg, &ret)) + i_unreached(); + return ret; +} + +bool imap_arg_atom_equals(const struct imap_arg *arg, const char *str) +{ + const char *value; + + if (!imap_arg_get_atom(arg, &value)) + return FALSE; + return strcasecmp(value, str) == 0; +} diff --git a/src/lib-imap/imap-arg.h b/src/lib-imap/imap-arg.h new file mode 100644 index 0000000..d55ee15 --- /dev/null +++ b/src/lib-imap/imap-arg.h @@ -0,0 +1,108 @@ +#ifndef IMAP_ARG_H +#define IMAP_ARG_H + +#include "array.h" + +/* ABNF: + + CHAR = %x01-7F + CTL = %x00-1F / %x7F + SP = %x20 + DQUOTE = %x22 */ + +/* ASTRING-CHAR = ATOM-CHAR / resp-specials */ +#define IS_ASTRING_CHAR(c) (IS_ATOM_CHAR(c) || IS_RESP_SPECIAL(c)) +/* ATOM-CHAR = <any CHAR except atom-specials> */ +#define IS_ATOM_CHAR(c) (!IS_ATOM_SPECIAL(c)) +/* atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / + quoted-specials / resp-specials + Since atoms are only 7bit, we'll also optimize a bit by assuming 8bit chars + are also atom-specials. */ +#define IS_ATOM_SPECIAL(c) \ + ((unsigned char)(c) <= 0x20 || (unsigned char)(c) >= 0x7f || \ + (c) == '(' || (c) == ')' || (c) == '{' || IS_LIST_WILDCARD(c) || \ + IS_QUOTED_SPECIAL(c) || IS_RESP_SPECIAL(c)) + +/* list-wildcards = "%" / "*" */ +#define IS_LIST_WILDCARD(c) ((c) == '%' || (c) == '*') +/* quoted-specials = DQUOTE / "\" */ +#define IS_QUOTED_SPECIAL(c) ((c) == '\"' || (c) == '\\') +/* resp-specials = "]" */ +#define IS_RESP_SPECIAL(c) ((c) == ']') + +enum imap_arg_type { + IMAP_ARG_NIL = 0, + IMAP_ARG_ATOM, + IMAP_ARG_STRING, + IMAP_ARG_LIST, + + /* literals are returned as IMAP_ARG_STRING by default */ + IMAP_ARG_LITERAL, + IMAP_ARG_LITERAL_SIZE, + IMAP_ARG_LITERAL_SIZE_NONSYNC, + + IMAP_ARG_EOL /* end of argument list */ +}; + +ARRAY_DEFINE_TYPE(imap_arg_list, struct imap_arg); +struct imap_arg { + enum imap_arg_type type; + struct imap_arg *parent; /* always of type IMAP_ARG_LIST */ + + /* Set when _data.str is set */ + size_t str_len; + + union { + const char *str; + uoff_t literal_size; + ARRAY_TYPE(imap_arg_list) list; + } _data; + bool literal8:1; /* BINARY literal8 used */ +}; + +/* RFC 3501's astring type. Note that this doesn't return TRUE for + IMAP_ARG_NIL, although it should be treated the same as "NIL" string when + reading an astring. */ +#define IMAP_ARG_TYPE_IS_ASTRING(type) \ + ((type) == IMAP_ARG_ATOM || \ + (type) == IMAP_ARG_STRING || \ + (type) == IMAP_ARG_LITERAL) +#define IMAP_ARG_IS_ASTRING(arg) \ + IMAP_ARG_TYPE_IS_ASTRING((arg)->type) +#define IMAP_ARG_IS_NSTRING(arg) \ + (IMAP_ARG_IS_ASTRING(arg) || (arg)->type == IMAP_ARG_NIL) +#define IMAP_ARG_IS_EOL(arg) \ + ((arg)->type == IMAP_ARG_EOL) + +bool imap_arg_get_atom(const struct imap_arg *arg, const char **str_r) + ATTR_WARN_UNUSED_RESULT; +bool imap_arg_get_quoted(const struct imap_arg *arg, const char **str_r) + ATTR_WARN_UNUSED_RESULT; +bool imap_arg_get_string(const struct imap_arg *arg, const char **str_r) + ATTR_WARN_UNUSED_RESULT; +bool imap_arg_get_astring(const struct imap_arg *arg, const char **str_r) + ATTR_WARN_UNUSED_RESULT; +/* str is set to NULL for NIL. */ +bool imap_arg_get_nstring(const struct imap_arg *arg, const char **str_r) + ATTR_WARN_UNUSED_RESULT; + +bool imap_arg_get_literal_size(const struct imap_arg *arg, uoff_t *size_r) + ATTR_WARN_UNUSED_RESULT; + +bool imap_arg_get_list(const struct imap_arg *arg, + const struct imap_arg **list_r) + ATTR_WARN_UNUSED_RESULT; +bool imap_arg_get_list_full(const struct imap_arg *arg, + const struct imap_arg **list_r, + unsigned int *list_count_r) ATTR_WARN_UNUSED_RESULT; + +/* Similar to above, but assumes that arg is already of correct type. */ +const char *imap_arg_as_astring(const struct imap_arg *arg); +const char *imap_arg_as_nstring(const struct imap_arg *arg); +uoff_t imap_arg_as_literal_size(const struct imap_arg *arg); +const struct imap_arg *imap_arg_as_list(const struct imap_arg *arg); + +/* Returns TRUE if arg is atom and case-insensitively matches str */ +bool imap_arg_atom_equals(const struct imap_arg *arg, const char *str); + +#endif diff --git a/src/lib-imap/imap-base-subject.c b/src/lib-imap/imap-base-subject.c new file mode 100644 index 0000000..02469d3 --- /dev/null +++ b/src/lib-imap/imap-base-subject.c @@ -0,0 +1,248 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +/* Implemented against draft-ietf-imapext-sort-10 and + draft-ietf-imapext-thread-12 */ + +#include "lib.h" +#include "buffer.h" +#include "charset-utf8.h" +#include "message-header-decode.h" +#include "imap-base-subject.h" + +static void pack_whitespace(buffer_t *buf) +{ + char *data, *dest; + bool last_lwsp; + + data = buffer_get_modifiable_data(buf, NULL); + + /* check if we need to do anything */ + while (*data != '\0') { + if (*data == '\t' || *data == '\n' || *data == '\r' || + (*data == ' ' && (data[1] == ' ' || data[1] == '\t'))) + break; + data++; + } + + if (*data == '\0') + return; + + /* @UNSAFE: convert/pack the whitespace */ + dest = data; last_lwsp = FALSE; + while (*data != '\0') { + if (*data == '\t' || *data == ' ' || + *data == '\r' || *data == '\n') { + if (!last_lwsp) { + *dest++ = ' '; + last_lwsp = TRUE; + } + } else { + *dest++ = *data; + last_lwsp = FALSE; + } + data++; + } + *dest = '\0'; + + data = buffer_get_modifiable_data(buf, NULL); + buffer_set_used_size(buf, (size_t) (dest - data)+1); +} + +static void remove_subj_trailers(buffer_t *buf, size_t start_pos, + bool *is_reply_or_forward_r) +{ + const char *data; + size_t orig_size, size; + + /* subj-trailer = "(fwd)" / WSP */ + data = buffer_get_data(buf, &orig_size); + + if (orig_size < 1) /* size includes trailing \0 */ + return; + + for (size = orig_size-1; size > start_pos; ) { + if (data[size-1] == ' ') + size--; + else if (size >= 5 && + memcmp(data + size - 5, "(FWD)", 5) == 0) { + *is_reply_or_forward_r = TRUE; + size -= 5; + } else { + break; + } + } + + if (size != orig_size-1) { + buffer_set_used_size(buf, size); + buffer_append_c(buf, '\0'); + } +} + +static bool remove_blob(const char **datap) +{ + const char *data = *datap; + + if (*data != '[') + return FALSE; + + data++; + while (*data != '\0' && *data != '[' && *data != ']') + data++; + + if (*data != ']') + return FALSE; + + data++; + if (*data == ' ') + data++; + + *datap = data; + return TRUE; +} + +static bool remove_subj_leader(buffer_t *buf, size_t *start_pos, + bool *is_reply_or_forward_r) +{ + const char *data, *orig_data; + bool ret = FALSE; + + /* subj-leader = (*subj-blob subj-refwd) / WSP + + subj-blob = "[" *BLOBCHAR "]" *WSP + subj-refwd = ("re" / ("fw" ["d"])) *WSP [subj-blob] ":" + + BLOBCHAR = %x01-5a / %x5c / %x5e-7f + ; any CHAR except '[' and ']' */ + orig_data = buf->data; + orig_data += *start_pos; + data = orig_data; + + if (*data == ' ') { + /* independent from checks below - always removed */ + data++; orig_data++; + *start_pos += 1; + ret = TRUE; + } + + while (*data == '[') { + if (!remove_blob(&data)) + return ret; + } + + if (str_begins(data, "RE")) + data += 2; + else if (str_begins(data, "FWD")) + data += 3; + else if (str_begins(data, "FW")) + data += 2; + else + return ret; + + if (*data == ' ') + data++; + + if (*data == '[' && !remove_blob(&data)) + return ret; + + if (*data != ':') + return ret; + + data++; + *start_pos += (size_t)(data - orig_data); + *is_reply_or_forward_r = TRUE; + return TRUE; +} + +static bool remove_blob_when_nonempty(buffer_t *buf, size_t *start_pos) +{ + const char *data, *orig_data; + + orig_data = buf->data; + orig_data += *start_pos; + data = orig_data; + if (*data == '[' && remove_blob(&data) && *data != '\0') { + *start_pos += (size_t)(data - orig_data); + return TRUE; + } + + return FALSE; +} + +static bool remove_subj_fwd_hdr(buffer_t *buf, size_t *start_pos, + bool *is_reply_or_forward_r) +{ + const char *data = buf->data; + size_t size = buf->used; + + /* subj-fwd = subj-fwd-hdr subject subj-fwd-trl + subj-fwd-hdr = "[fwd:" + subj-fwd-trl = "]" */ + + if (!str_begins(data + *start_pos, "[FWD:")) + return FALSE; + + if (data[size-2] != ']') + return FALSE; + + *is_reply_or_forward_r = TRUE; + + buffer_set_used_size(buf, size-2); + buffer_append_c(buf, '\0'); + + *start_pos += 5; + return TRUE; +} + +const char *imap_get_base_subject_cased(pool_t pool, const char *subject, + bool *is_reply_or_forward_r) +{ + buffer_t *buf; + size_t start_pos, subject_len; + bool found; + + *is_reply_or_forward_r = FALSE; + + subject_len = strlen(subject); + buf = buffer_create_dynamic(pool, subject_len); + + /* (1) Convert any RFC 2047 encoded-words in the subject to + UTF-8. Convert all tabs and continuations to space. + Convert all multiple spaces to a single space. */ + message_header_decode_utf8((const unsigned char *)subject, subject_len, + buf, uni_utf8_to_decomposed_titlecase); + buffer_append_c(buf, '\0'); + + pack_whitespace(buf); + + start_pos = 0; + do { + /* (2) Remove all trailing text of the subject that matches + the subj-trailer ABNF, repeat until no more matches are + possible. */ + remove_subj_trailers(buf, start_pos, is_reply_or_forward_r); + + do { + /* (3) Remove all prefix text of the subject that + matches the subj-leader ABNF. */ + found = remove_subj_leader(buf, &start_pos, + is_reply_or_forward_r); + + /* (4) If there is prefix text of the subject that + matches the subj-blob ABNF, and removing that prefix + leaves a non-empty subj-base, then remove the prefix + text. */ + found = remove_blob_when_nonempty(buf, &start_pos) || + found; + + /* (5) Repeat (3) and (4) until no matches remain. */ + } while (found); + + /* (6) If the resulting text begins with the subj-fwd-hdr ABNF + and ends with the subj-fwd-trl ABNF, remove the + subj-fwd-hdr and subj-fwd-trl and repeat from step (2). */ + } while (remove_subj_fwd_hdr(buf, &start_pos, is_reply_or_forward_r)); + + /* (7) The resulting text is the "base subject" used in the + SORT. */ + return (const char *)buf->data + start_pos; +} diff --git a/src/lib-imap/imap-base-subject.h b/src/lib-imap/imap-base-subject.h new file mode 100644 index 0000000..f086284 --- /dev/null +++ b/src/lib-imap/imap-base-subject.h @@ -0,0 +1,13 @@ +#ifndef IMAP_BASE_SUBJECT_H +#define IMAP_BASE_SUBJECT_H + +/* Returns the base subject of the given string, according to + draft-ietf-imapext-sort-10. String is returned so that it's suitable for + strcmp() comparing with another base subject. + + is_reply_or_forward is set to TRUE if message looks like reply or forward, + according to draft-ietf-imapext-thread-12 rules. */ +const char *imap_get_base_subject_cased(pool_t pool, const char *subject, + bool *is_reply_or_forward_r); + +#endif diff --git a/src/lib-imap/imap-bodystructure.c b/src/lib-imap/imap-bodystructure.c new file mode 100644 index 0000000..522c250 --- /dev/null +++ b/src/lib-imap/imap-bodystructure.c @@ -0,0 +1,956 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "buffer.h" +#include "istream.h" +#include "str.h" +#include "message-part-data.h" +#include "message-parser.h" +#include "rfc822-parser.h" +#include "rfc2231-parser.h" +#include "imap-parser.h" +#include "imap-quote.h" +#include "imap-envelope.h" +#include "imap-bodystructure.h" + +#define EMPTY_BODY "(\"text\" \"plain\" " \ + "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0)" +#define EMPTY_BODYSTRUCTURE "(\"text\" \"plain\" " \ + "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0 " \ + "NIL NIL NIL NIL)" + +/* + * IMAP BODY/BODYSTRUCTURE write + */ + +static void +params_write(const struct message_part_param *params, + unsigned int params_count, string_t *str, + bool default_charset) +{ + unsigned int i; + bool seen_charset; + + if (!default_charset && params_count == 0) { + str_append(str, "NIL"); + return; + } + str_append_c(str, '('); + + seen_charset = FALSE; + for (i = 0; i < params_count; i++) { + i_assert(params[i].name != NULL); + i_assert(params[i].value != NULL); + + if (i > 0) + str_append_c(str, ' '); + if (default_charset && + strcasecmp(params[i].name, "charset") == 0) + seen_charset = TRUE; + imap_append_string(str, params[i].name); + str_append_c(str, ' '); + imap_append_string(str, params[i].value); + } + if (default_charset && !seen_charset) { + if (i > 0) + str_append_c(str, ' '); + str_append(str, "\"charset\" " + "\""MESSAGE_PART_DEFAULT_CHARSET"\""); + } + str_append_c(str, ')'); +} + +static int +part_write_bodystructure_siblings(const struct message_part *part, + string_t *dest, bool extended, + const char **error_r) +{ + for (; part != NULL; part = part->next) { + str_append_c(dest, '('); + if (imap_bodystructure_write(part, dest, extended, error_r) < 0) + return -1; + str_append_c(dest, ')'); + } + return 0; +} + +static void +part_write_bodystructure_common(const struct message_part_data *data, + string_t *str) +{ + str_append_c(str, ' '); + if (data->content_disposition == NULL) + str_append(str, "NIL"); + else { + str_append_c(str, '('); + imap_append_string(str, data->content_disposition); + + str_append_c(str, ' '); + params_write(data->content_disposition_params, + data->content_disposition_params_count, str, FALSE); + + str_append_c(str, ')'); + } + + str_append_c(str, ' '); + if (data->content_language == NULL) + str_append(str, "NIL"); + else { + const char *const *lang = data->content_language; + + i_assert(*lang != NULL); + str_append_c(str, '('); + imap_append_string(str, *lang); + lang++; + while (*lang != NULL) { + str_append_c(str, ' '); + imap_append_string(str, *lang); + lang++; + } + str_append_c(str, ')'); + } + + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->content_location); +} + +static int part_write_body_multipart(const struct message_part *part, + string_t *str, bool extended, + const char **error_r) +{ + const struct message_part_data *data = part->data; + + i_assert(part->data != NULL); + + if (part->children != NULL) { + if (part_write_bodystructure_siblings(part->children, str, + extended, error_r) < 0) + return -1; + } else { + /* no parts in multipart message, + that's not allowed. write a single + 0-length text/plain structure */ + if (!extended) + str_append(str, EMPTY_BODY); + else + str_append(str, EMPTY_BODYSTRUCTURE); + } + + str_append_c(str, ' '); + imap_append_string(str, data->content_subtype); + + if (!extended) + return 0; + + /* BODYSTRUCTURE data */ + + str_append_c(str, ' '); + params_write(data->content_type_params, + data->content_type_params_count, str, FALSE); + + part_write_bodystructure_common(data, str); + return 0; +} + +static bool part_is_truncated(const struct message_part *part) +{ + const struct message_part_data *data = part->data; + + i_assert((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0); + i_assert((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0); + + if (data->content_type != NULL) { + if (strcasecmp(data->content_type, "message") == 0 && + strcasecmp(data->content_subtype, "rfc822") == 0) { + /* It's message/rfc822, but without + MESSAGE_PART_FLAG_MESSAGE_RFC822. */ + return TRUE; + } + if (strcasecmp(data->content_type, "multipart") == 0) { + /* It's multipart/, but without + MESSAGE_PART_FLAG_MULTIPART. */ + return TRUE; + } + } else { + /* No Content-Type */ + if (part->parent != NULL && + (part->parent->flags & MESSAGE_PART_FLAG_MULTIPART_DIGEST) != 0) { + /* Parent is MESSAGE_PART_FLAG_MULTIPART_DIGEST + (so this should have been message/rfc822). */ + return TRUE; + } + } + return FALSE; +} + +static int part_write_body(const struct message_part *part, + string_t *str, bool extended, const char **error_r) +{ + const struct message_part_data *data = part->data; + bool text; + + i_assert(part->data != NULL); + + if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { + str_append(str, "\"message\" \"rfc822\""); + text = FALSE; + } else if (part_is_truncated(part)) { + /* Maximum MIME part count was reached while parsing the mail. + Write this part out as application/octet-stream instead. + We're not using text/plain, because it would require + message-parser to use MESSAGE_PART_FLAG_TEXT for this part + to avoid losing line count in message_part serialization. */ + str_append(str, "\"application\" \"octet-stream\""); + text = FALSE; + } else { + /* "content type" "subtype" */ + if (data->content_type == NULL) { + text = TRUE; + str_append(str, "\"text\" \"plain\""); + } else { + text = (strcasecmp(data->content_type, "text") == 0); + imap_append_string(str, data->content_type); + str_append_c(str, ' '); + imap_append_string(str, data->content_subtype); + } + bool part_is_text = (part->flags & MESSAGE_PART_FLAG_TEXT) != 0; + if (text != part_is_text) { + *error_r = "text flag mismatch"; + return -1; + } + } + + /* ("content type param key" "value" ...) */ + str_append_c(str, ' '); + params_write(data->content_type_params, + data->content_type_params_count, str, text); + + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->content_id); + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->content_description); + str_append_c(str, ' '); + if (data->content_transfer_encoding != NULL) + imap_append_string(str, data->content_transfer_encoding); + else + str_append(str, "\"7bit\""); + str_printfa(str, " %"PRIuUOFF_T, part->body_size.virtual_size); + + if (text) { + /* text/.. contains line count */ + str_printfa(str, " %u", part->body_size.lines); + } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { + /* message/rfc822 contains envelope + body + line count */ + const struct message_part_data *child_data; + + i_assert(part->children != NULL); + i_assert(part->children->next == NULL); + + child_data = part->children->data; + + str_append(str, " ("); + imap_envelope_write(child_data->envelope, str); + str_append(str, ") "); + + if (part_write_bodystructure_siblings(part->children, str, + extended, error_r) < 0) + return -1; + str_printfa(str, " %u", part->body_size.lines); + } + + if (!extended) + return 0; + + /* BODYSTRUCTURE data */ + + /* "md5" ("content disposition" ("disposition" "params")) + ("body" "language" "params") "location" */ + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->content_md5); + part_write_bodystructure_common(data, str); + return 0; +} + +int imap_bodystructure_write(const struct message_part *part, + string_t *dest, bool extended, + const char **error_r) +{ + if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) + return part_write_body_multipart(part, dest, extended, error_r); + else + return part_write_body(part, dest, extended, error_r); +} + +/* + * IMAP BODYSTRUCTURE parsing + */ + +static int +imap_bodystructure_strlist_parse(const struct imap_arg *arg, + pool_t pool, const char *const **list_r) +{ + const char *item, **list; + const struct imap_arg *list_args; + unsigned int list_count, i; + + if (arg->type == IMAP_ARG_NIL) { + *list_r = NULL; + return 0; + } + if (imap_arg_get_nstring(arg, &item)) { + list = p_new(pool, const char *, 2); + list[0] = p_strdup(pool, item); + } else { + if (!imap_arg_get_list_full(arg, &list_args, &list_count)) + return -1; + if (list_count == 0) + return -1; + + list = p_new(pool, const char *, list_count+1); + for (i = 0; i < list_count; i++) { + if (!imap_arg_get_string(&list_args[i], &item)) + return -1; + list[i] = p_strdup(pool, item); + } + } + *list_r = list; + return 0; +} + +static int +imap_bodystructure_params_parse(const struct imap_arg *arg, + pool_t pool, const struct message_part_param **params_r, + unsigned int *count_r) +{ + struct message_part_param *params; + const struct imap_arg *list_args; + unsigned int list_count, params_count, i; + + if (arg->type == IMAP_ARG_NIL) { + *params_r = NULL; + return 0; + } + if (!imap_arg_get_list_full(arg, &list_args, &list_count)) + return -1; + if ((list_count % 2) != 0) + return -1; + if (list_count == 0) + return -1; + + params_count = list_count/2; + params = p_new(pool, struct message_part_param, params_count+1); + for (i = 0; i < params_count; i++) { + const char *name, *value; + + if (!imap_arg_get_string(&list_args[i*2+0], &name)) + return -1; + if (!imap_arg_get_string(&list_args[i*2+1], &value)) + return -1; + params[i].name = p_strdup(pool, name); + params[i].value = p_strdup(pool, value); + } + *params_r = params; + *count_r = params_count; + return 0; +} + +static int +imap_bodystructure_parse_args_common(struct message_part *part, + pool_t pool, const struct imap_arg *args, + const char **error_r) +{ + struct message_part_data *data = part->data; + const struct imap_arg *list_args; + + if (args->type == IMAP_ARG_EOL) + return 0; + if (args->type == IMAP_ARG_NIL) + args++; + else if (!imap_arg_get_list(args, &list_args)) { + *error_r = "Invalid content-disposition list"; + return -1; + } else { + if (!imap_arg_get_string + (list_args++, &data->content_disposition)) { + *error_r = "Invalid content-disposition"; + return -1; + } + data->content_disposition = p_strdup(pool, data->content_disposition); + if (imap_bodystructure_params_parse(list_args, pool, + &data->content_disposition_params, + &data->content_disposition_params_count) < 0) { + *error_r = "Invalid content-disposition params"; + return -1; + } + args++; + } + if (args->type == IMAP_ARG_EOL) + return 0; + if (imap_bodystructure_strlist_parse + (args++, pool, &data->content_language) < 0) { + *error_r = "Invalid content-language"; + return -1; + } + if (args->type == IMAP_ARG_EOL) + return 0; + if (!imap_arg_get_nstring + (args++, &data->content_location)) { + *error_r = "Invalid content-location"; + return -1; + } + data->content_location = p_strdup(pool, data->content_location); + return 0; +} + +int +imap_bodystructure_parse_args(const struct imap_arg *args, pool_t pool, + struct message_part **_part, + const char **error_r) +{ + struct message_part *part = *_part, *child_part;; + struct message_part **child_part_p; + struct message_part_data *data; + const struct imap_arg *list_args; + const char *value, *content_type, *subtype, *error; + bool multipart, text, message_rfc822, parsing_tree, has_lines; + unsigned int lines; + uoff_t vsize; + + if (part != NULL) { + /* parsing with pre-existing message_part tree */ + parsing_tree = FALSE; + } else { + /* parsing message_part tree from BODYSTRUCTURE as well */ + part = *_part = p_new(pool, struct message_part, 1); + parsing_tree = TRUE; + } + part->data = data = p_new(pool, struct message_part_data, 1); + + multipart = FALSE; + if (!parsing_tree) { + if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && + part->children == NULL) { + struct message_part_data dummy_part_data = { + .content_type = "text", + .content_subtype = "plain", + .content_transfer_encoding = "7bit" + }; + struct message_part dummy_part = { + .parent = part, + .data = &dummy_part_data, + .flags = MESSAGE_PART_FLAG_TEXT + }; + struct message_part *dummy_partp = &dummy_part; + + /* no parts in multipart message, + that's not allowed. expect a single + 0-length text/plain structure */ + if (args->type != IMAP_ARG_LIST || + (args+1)->type == IMAP_ARG_LIST) { + *error_r = "message_part hierarchy " + "doesn't match BODYSTRUCTURE"; + return -1; + } + + list_args = imap_arg_as_list(args); + if (imap_bodystructure_parse_args(list_args, pool, + &dummy_partp, error_r) < 0) + return -1; + child_part = NULL; + + multipart = TRUE; + args++; + + } else { + child_part = part->children; + while (args->type == IMAP_ARG_LIST) { + if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 || + child_part == NULL) { + *error_r = "message_part hierarchy " + "doesn't match BODYSTRUCTURE"; + return -1; + } + + list_args = imap_arg_as_list(args); + if (imap_bodystructure_parse_args(list_args, pool, + &child_part, error_r) < 0) + return -1; + child_part = child_part->next; + + multipart = TRUE; + args++; + } + } + if (multipart) { + if (child_part != NULL) { + *error_r = "message_part hierarchy " + "doesn't match BODYSTRUCTURE"; + return -1; + } + } else if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) { + *error_r = "message_part multipart flag " + "doesn't match BODYSTRUCTURE"; + return -1; + } + } else { + child_part_p = &part->children; + while (args->type == IMAP_ARG_LIST) { + list_args = imap_arg_as_list(args); + if (imap_bodystructure_parse_args(list_args, pool, + child_part_p, error_r) < 0) + return -1; + (*child_part_p)->parent = part; + child_part_p = &(*child_part_p)->next; + + multipart = TRUE; + args++; + } + if (multipart) { + part->flags |= MESSAGE_PART_FLAG_MULTIPART; + } + } + + if (multipart) { + data->content_type = "multipart"; + if (!imap_arg_get_string(args++, &data->content_subtype)) { + *error_r = "Invalid multipart content-type"; + return -1; + } + data->content_subtype = p_strdup(pool, data->content_subtype); + if (args->type == IMAP_ARG_EOL) + return 0; + if (imap_bodystructure_params_parse(args++, pool, + &data->content_type_params, + &data->content_type_params_count) < 0) { + *error_r = "Invalid content params"; + return -1; + } + return imap_bodystructure_parse_args_common + (part, pool, args, error_r); + } + + /* "content type" "subtype" */ + if (!imap_arg_get_string(&args[0], &content_type) || + !imap_arg_get_string(&args[1], &subtype)) { + *error_r = "Invalid content-type"; + return -1; + } + data->content_type = p_strdup(pool, content_type); + data->content_subtype = p_strdup(pool, subtype); + args += 2; + + text = strcasecmp(content_type, "text") == 0; + message_rfc822 = strcasecmp(content_type, "message") == 0 && + strcasecmp(subtype, "rfc822") == 0; + + if (!parsing_tree) { +#if 0 + /* Disabled for now. Earlier Dovecot versions handled broken + Content-Type headers by writing them as "text" "plain" to + BODYSTRUCTURE reply, but the message_part didn't have + MESSAGE_PART_FLAG_TEXT. */ + if (text != ((part->flags & MESSAGE_PART_FLAG_TEXT) != 0)) { + *error_r = "message_part text flag " + "doesn't match BODYSTRUCTURE"; + return -1; + } +#endif + if (message_rfc822 != + ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0)) { + *error_r = "message_part message/rfc822 flag " + "doesn't match BODYSTRUCTURE"; + return -1; + } + } else { + if (text) + part->flags |= MESSAGE_PART_FLAG_TEXT; + if (message_rfc822) + part->flags |= MESSAGE_PART_FLAG_MESSAGE_RFC822; + } + + /* ("content type param key" "value" ...) | NIL */ + if (imap_bodystructure_params_parse(args++, pool, + &data->content_type_params, + &data->content_type_params_count) < 0) { + *error_r = "Invalid content params"; + return -1; + } + + /* "content id" "content description" "transfer encoding" size */ + if (!imap_arg_get_nstring(args++, &data->content_id)) { + *error_r = "Invalid content-id"; + return -1; + } + if (!imap_arg_get_nstring(args++, &data->content_description)) { + *error_r = "Invalid content-description"; + return -1; + } + if (!imap_arg_get_string(args++, &data->content_transfer_encoding)) { + *error_r = "Invalid content-transfer-encoding"; + return -1; + } + data->content_id = p_strdup(pool, data->content_id); + data->content_description = p_strdup(pool, data->content_description); + data->content_transfer_encoding = + p_strdup(pool, data->content_transfer_encoding); + if (!imap_arg_get_atom(args++, &value) || + str_to_uoff(value, &vsize) < 0) { + *error_r = "Invalid size field"; + return -1; + } + if (!parsing_tree) { + if (vsize != part->body_size.virtual_size) { + *error_r = "message_part virtual_size doesn't match " + "size in BODYSTRUCTURE"; + return -1; + } + } else { + part->body_size.virtual_size = vsize; + } + + if (text) { + /* text/xxx - text lines */ + if (!imap_arg_get_atom(args++, &value) || + str_to_uint(value, &lines) < 0) { + *error_r = "Invalid lines field"; + return -1; + } + i_assert(part->children == NULL); + has_lines = TRUE; + } else if (message_rfc822) { + /* message/rfc822 - envelope + bodystructure + text lines */ + + if (!parsing_tree) { + i_assert(part->children != NULL && + part->children->next == NULL); + } + + if (!imap_arg_get_list(&args[1], &list_args)) { + *error_r = "Child bodystructure list expected"; + return -1; + } + if (imap_bodystructure_parse_args + (list_args, pool, &part->children, error_r) < 0) + return -1; + if (parsing_tree) { + i_assert(part->children != NULL && + part->children->next == NULL); + part->children->parent = part; + } + + if (!imap_arg_get_list(&args[0], &list_args)) { + *error_r = "Envelope list expected"; + return -1; + } + if (!imap_envelope_parse_args(list_args, pool, + &part->children->data->envelope, &error)) { + *error_r = t_strdup_printf + ("Invalid envelope list: %s", error); + return -1; + } + args += 2; + if (!imap_arg_get_atom(args++, &value) || + str_to_uint(value, &lines) < 0) { + *error_r = "Invalid lines field"; + return -1; + } + has_lines = TRUE; + } else { + i_assert(part->children == NULL); + lines = 0; + has_lines = FALSE; + } + if (!parsing_tree) { + if (has_lines && lines != part->body_size.lines) { + *error_r = "message_part lines " + "doesn't match lines in BODYSTRUCTURE"; + return -1; + } + } else { + part->body_size.lines = lines; + } + if (args->type == IMAP_ARG_EOL) + return 0; + if (!imap_arg_get_nstring(args++, &data->content_md5)) { + *error_r = "Invalid content-md5"; + return -1; + } + data->content_md5 = p_strdup(pool, data->content_md5); + return imap_bodystructure_parse_args_common + (part, pool, args, error_r); +} + +int imap_bodystructure_parse_full(const char *bodystructure, + pool_t pool, struct message_part **parts, + const char **error_r) +{ + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + int ret; + + i_assert(*parts == NULL || (*parts)->next == NULL); + + input = i_stream_create_from_data(bodystructure, strlen(bodystructure)); + (void)i_stream_read(input); + + parser = imap_parser_create(input, NULL, SIZE_MAX); + ret = imap_parser_finish_line(parser, 0, + IMAP_PARSE_FLAG_LITERAL_TYPE, &args); + if (ret < 0) { + *error_r = t_strdup_printf("IMAP parser failed: %s", + imap_parser_get_error(parser, NULL)); + } else if (ret == 0) { + *error_r = "Empty bodystructure"; + ret = -1; + } else { + T_BEGIN { + ret = imap_bodystructure_parse_args + (args, pool, parts, error_r); + } T_END_PASS_STR_IF(ret < 0, error_r); + } + + imap_parser_unref(&parser); + i_stream_destroy(&input); + return ret; +} + +int imap_bodystructure_parse(const char *bodystructure, + pool_t pool, struct message_part *parts, + const char **error_r) +{ + i_assert(parts != NULL); + + return imap_bodystructure_parse_full(bodystructure, + pool, &parts, error_r); +} + +/* + * IMAP BODYSTRUCTURE to BODY conversion + */ + +static bool str_append_nstring(string_t *str, const struct imap_arg *arg) +{ + const char *cstr; + + if (!imap_arg_get_nstring(arg, &cstr)) + return FALSE; + + switch (arg->type) { + case IMAP_ARG_NIL: + str_append(str, "NIL"); + break; + case IMAP_ARG_STRING: + str_append_c(str, '"'); + /* NOTE: we're parsing with no-unescape flag, + so don't double-escape it here */ + str_append(str, cstr); + str_append_c(str, '"'); + break; + case IMAP_ARG_LITERAL: { + str_printfa(str, "{%zu}\r\n", strlen(cstr)); + str_append(str, cstr); + break; + } + default: + i_unreached(); + return FALSE; + } + return TRUE; +} + +static void +imap_write_envelope_list(const struct imap_arg *args, string_t *str, + bool toplevel) +{ + const struct imap_arg *children; + + /* don't do any typechecking, just write it out */ + while (!IMAP_ARG_IS_EOL(args)) { + bool list = FALSE; + + if (!str_append_nstring(str, args)) { + if (!imap_arg_get_list(args, &children)) { + /* everything is either nstring or list */ + i_unreached(); + } + + str_append_c(str, '('); + imap_write_envelope_list(children, str, FALSE); + str_append_c(str, ')'); + + list = TRUE; + } + args++; + + if ((toplevel || !list) && !IMAP_ARG_IS_EOL(args)) + str_append_c(str, ' '); + } +} + +static void +imap_write_envelope(const struct imap_arg *args, string_t *str) +{ + imap_write_envelope_list(args, str, TRUE); +} + +static int imap_parse_bodystructure_args(const struct imap_arg *args, + string_t *str, const char **error_r) +{ + const struct imap_arg *subargs; + const struct imap_arg *list_args; + const char *value, *content_type, *subtype; + bool multipart, text, message_rfc822; + int i; + + multipart = FALSE; + while (args->type == IMAP_ARG_LIST) { + str_append_c(str, '('); + list_args = imap_arg_as_list(args); + if (imap_parse_bodystructure_args(list_args, str, error_r) < 0) + return -1; + str_append_c(str, ')'); + + multipart = TRUE; + args++; + } + + if (multipart) { + /* next is subtype of Content-Type. rest is skipped. */ + str_append_c(str, ' '); + if (!str_append_nstring(str, args)) { + *error_r = "Invalid multipart content-type"; + return -1; + } + return 0; + } + + /* "content type" "subtype" */ + if (!imap_arg_get_string(&args[0], &content_type) || + !imap_arg_get_string(&args[1], &subtype)) { + *error_r = "Invalid content-type"; + return -1; + } + + if (!str_append_nstring(str, &args[0])) + i_unreached(); + str_append_c(str, ' '); + if (!str_append_nstring(str, &args[1])) + i_unreached(); + + text = strcasecmp(content_type, "text") == 0; + message_rfc822 = strcasecmp(content_type, "message") == 0 && + strcasecmp(subtype, "rfc822") == 0; + + args += 2; + + /* ("content type param key" "value" ...) | NIL */ + if (imap_arg_get_list(args, &subargs)) { + str_append(str, " ("); + while (!IMAP_ARG_IS_EOL(subargs)) { + if (!str_append_nstring(str, &subargs[0])) { + *error_r = "Invalid content param key"; + return -1; + } + str_append_c(str, ' '); + if (!str_append_nstring(str, &subargs[1])) { + *error_r = "Invalid content param value"; + return -1; + } + + subargs += 2; + if (IMAP_ARG_IS_EOL(subargs)) + break; + str_append_c(str, ' '); + } + str_append(str, ")"); + } else if (args->type == IMAP_ARG_NIL) { + str_append(str, " NIL"); + } else { + *error_r = "list/NIL expected"; + return -1; + } + args++; + + /* "content id" "content description" "transfer encoding" size */ + for (i = 0; i < 3; i++, args++) { + str_append_c(str, ' '); + + if (!str_append_nstring(str, args)) { + *error_r = "nstring expected"; + return -1; + } + } + if (!imap_arg_get_atom(args, &value)) { + *error_r = "atom expected for size"; + return -1; + } + str_printfa(str, " %s", value); + args++; + + if (text) { + /* text/xxx - text lines */ + if (!imap_arg_get_atom(args, &value)) { + *error_r = "Text lines expected"; + return -1; + } + + str_append_c(str, ' '); + str_append(str, value); + } else if (message_rfc822) { + /* message/rfc822 - envelope + bodystructure + text lines */ + str_append_c(str, ' '); + + if (!imap_arg_get_list(&args[0], &list_args)) { + *error_r = "Envelope list expected"; + return -1; + } + str_append_c(str, '('); + imap_write_envelope(list_args, str); + str_append(str, ") ("); + + if (!imap_arg_get_list(&args[1], &list_args)) { + *error_r = "Child bodystructure list expected"; + return -1; + } + if (imap_parse_bodystructure_args(list_args, str, error_r) < 0) + return -1; + + str_append(str, ") "); + if (!imap_arg_get_atom(&args[2], &value)) { + *error_r = "Text lines expected"; + return -1; + } + str_append(str, value); + } + return 0; +} + +int imap_body_parse_from_bodystructure(const char *bodystructure, + string_t *dest, const char **error_r) +{ + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + int ret; + + input = i_stream_create_from_data(bodystructure, strlen(bodystructure)); + (void)i_stream_read(input); + + parser = imap_parser_create(input, NULL, SIZE_MAX); + ret = imap_parser_finish_line(parser, 0, IMAP_PARSE_FLAG_NO_UNESCAPE | + IMAP_PARSE_FLAG_LITERAL_TYPE, &args); + if (ret < 0) { + *error_r = t_strdup_printf("IMAP parser failed: %s", + imap_parser_get_error(parser, NULL)); + } else if (ret == 0) { + *error_r = "Empty bodystructure"; + ret = -1; + } else { + ret = imap_parse_bodystructure_args(args, dest, error_r); + } + + imap_parser_unref(&parser); + i_stream_destroy(&input); + return ret; +} diff --git a/src/lib-imap/imap-bodystructure.h b/src/lib-imap/imap-bodystructure.h new file mode 100644 index 0000000..a7cc6cd --- /dev/null +++ b/src/lib-imap/imap-bodystructure.h @@ -0,0 +1,39 @@ +#ifndef IMAP_BODYSTRUCTURE_H +#define IMAP_BODYSTRUCTURE_H + +struct message_part; +struct message_header_line; +struct imap_arg; + +/* Write a BODY/BODYSTRUCTURE from given message_part. The message_part->data + field must be set. part->body_size.virtual_size and .lines are also used + for writing it. Returns 0 on success, -1 if parts don't internally match + (e.g. broken cached mime.parts mixed with parsed message). */ +int imap_bodystructure_write(const struct message_part *part, + string_t *dest, bool extended, + const char **error_r); + +/* Parse BODYSTRUCTURE and save the contents to message_part->data for each + message tree node. If the parts argument points to NULL, the message_part + tree is created from the BODYSTRUCTURE. Otherwise, existing tree is used. + Returns 0 if ok, -1 if bodystructure wasn't valid. */ +int imap_bodystructure_parse_full(const char *bodystructure, pool_t pool, + struct message_part **parts, const char **error_r); + +/* Same as imap_bodystructure_parse_full(), but read the input from imap_args + instead of a string. */ +int imap_bodystructure_parse_args(const struct imap_arg *args, pool_t pool, + struct message_part **parts, const char **error_r); + +/* Parse BODYSTRUCTURE and save the contents to message_part->data for each + message tree node. The parts argument must point to an existing message_part + tree. Returns 0 if ok, -1 if bodystructure wasn't valid. */ +int imap_bodystructure_parse(const char *bodystructure, pool_t pool, + struct message_part *parts, const char **error_r); + +/* Get BODY part from BODYSTRUCTURE and write it to dest. + Returns 0 if ok, -1 if bodystructure wasn't valid. */ +int imap_body_parse_from_bodystructure(const char *bodystructure, + string_t *dest, const char **error_r); + +#endif diff --git a/src/lib-imap/imap-date.c b/src/lib-imap/imap-date.c new file mode 100644 index 0000000..2e5569a --- /dev/null +++ b/src/lib-imap/imap-date.c @@ -0,0 +1,247 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "utc-offset.h" +#include "utc-mktime.h" +#include "imap-date.h" + +#include <ctype.h> + +static const char *month_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static int parse_timezone(const char *str) +{ + int offset; + + /* +|-zone */ + if ((*str != '+' && *str != '-') || + !i_isdigit(str[1]) || !i_isdigit(str[2]) || + !i_isdigit(str[3]) || !i_isdigit(str[4])) + return 0; + + offset = (str[1]-'0') * 10*60 + (str[2]-'0') * 60 + + (str[3]-'0') * 10 + (str[4]-'0'); + return *str == '+' ? offset : -offset; +} + +static const char *imap_parse_date_internal(const char *str, struct tm *tm) +{ + int i; + + if (str == NULL || *str == '\0') + return NULL; + + i_zero(tm); + + /* "dd-mon-yyyy [hh:mi:ss +|-zone]" + dd is 1-2 digits and may be prefixed with space or zero. */ + + if (str[0] == ' ') { + /* " d-..." */ + str++; + } + + if (!(i_isdigit(str[0]) && (str[1] == '-' || + (i_isdigit(str[1]) && str[2] == '-')))) + return NULL; + + tm->tm_mday = (str[0]-'0'); + if (str[1] == '-') + str += 2; + else { + tm->tm_mday = (tm->tm_mday * 10) + (str[1]-'0'); + str += 3; + } + + /* month name */ + for (i = 0; i < 12; i++) { + if (strncasecmp(month_names[i], str, 3) == 0) { + tm->tm_mon = i; + break; + } + } + if (i == 12 || str[3] != '-') + return NULL; + str += 4; + + /* yyyy */ + if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || + !i_isdigit(str[2]) || !i_isdigit(str[3])) + return NULL; + + tm->tm_year = (str[0]-'0') * 1000 + (str[1]-'0') * 100 + + (str[2]-'0') * 10 + (str[3]-'0') - 1900; + + str += 4; + return str; +} + +static bool imap_mktime(struct tm *tm, time_t *time_r) +{ + *time_r = utc_mktime(tm); + if (*time_r != (time_t)-1) + return TRUE; + + /* the date is outside valid range for time_t. it might still be + technically valid though, so try to handle this case. + with 64bit time_t the full 0..9999 year range is valid. */ + if (tm->tm_year <= 100) { + /* too old. time_t can be signed or unsigned, handle + both cases. */ + *time_r = (time_t)-1 < (int)0 ? INT_MIN : 0; + } else { + /* too high. return the highest allowed value. + we shouldn't get here with 64bit time_t, + but handle that anyway. */ +#if (TIME_T_MAX_BITS == 32 || TIME_T_MAX_BITS == 64) + *time_r = (1UL << (TIME_T_MAX_BITS-1)) - 1; +#else + *time_r = (1UL << TIME_T_MAX_BITS) - 1; +#endif + } + return FALSE; +} + +bool imap_parse_date(const char *str, time_t *timestamp_r) +{ + struct tm tm; + + str = imap_parse_date_internal(str, &tm); + if (str == NULL || str[0] != '\0') + return FALSE; + + tm.tm_isdst = -1; + (void)imap_mktime(&tm, timestamp_r); + return TRUE; +} + +bool imap_parse_datetime(const char *str, time_t *timestamp_r, + int *timezone_offset_r) +{ + struct tm tm; + + str = imap_parse_date_internal(str, &tm); + if (str == NULL) + return FALSE; + + if (str[0] != ' ') + return FALSE; + str++; + + /* hh: */ + if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ':') + return FALSE; + tm.tm_hour = (str[0]-'0') * 10 + (str[1]-'0'); + str += 3; + + /* mm: */ + if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ':') + return FALSE; + tm.tm_min = (str[0]-'0') * 10 + (str[1]-'0'); + str += 3; + + /* ss */ + if (!i_isdigit(str[0]) || !i_isdigit(str[1]) || str[2] != ' ') + return FALSE; + tm.tm_sec = (str[0]-'0') * 10 + (str[1]-'0'); + str += 3; + + /* timezone */ + *timezone_offset_r = parse_timezone(str); + + tm.tm_isdst = -1; + if (imap_mktime(&tm, timestamp_r)) + *timestamp_r -= *timezone_offset_r * 60; + return TRUE; +} + +static void imap_to_date_tm(char buf[11], const struct tm *tm) +{ + int year; + + /* dd-mon- */ + buf[0] = (tm->tm_mday / 10) + '0'; + buf[1] = (tm->tm_mday % 10) + '0'; + buf[2] = '-'; + memcpy(buf+3, month_names[tm->tm_mon], 3); + buf[6] = '-'; + + /* yyyy */ + year = tm->tm_year + 1900; + buf[7] = (year / 1000) + '0'; + buf[8] = ((year / 100) % 10) + '0'; + buf[9] = ((year / 10) % 10) + '0'; + buf[10] = (year % 10) + '0'; +} + +static const char * +imap_to_datetime_tm(const struct tm *tm, int timezone_offset) +{ + char *buf; + + /* @UNSAFE: but faster than t_strdup_printf() call.. */ + buf = t_malloc0(27); + imap_to_date_tm(buf, tm); + buf[11] = ' '; + + /* hh:mi:ss */ + buf[12] = (tm->tm_hour / 10) + '0'; + buf[13] = (tm->tm_hour % 10) + '0'; + buf[14] = ':'; + buf[15] = (tm->tm_min / 10) + '0'; + buf[16] = (tm->tm_min % 10) + '0'; + buf[17] = ':'; + buf[18] = (tm->tm_sec / 10) + '0'; + buf[19] = (tm->tm_sec % 10) + '0'; + buf[20] = ' '; + + /* timezone */ + if (timezone_offset >= 0) + buf[21] = '+'; + else { + buf[21] = '-'; + timezone_offset = -timezone_offset; + } + buf[22] = (timezone_offset / 600) + '0'; + buf[23] = ((timezone_offset / 60) % 10) + '0'; + buf[24] = ((timezone_offset % 60) / 10) + '0'; + buf[25] = (timezone_offset % 10) + '0'; + buf[26] = '\0'; + + return buf; +} + +const char *imap_to_datetime(time_t timestamp) +{ + struct tm *tm; + int timezone_offset; + + tm = localtime(×tamp); + 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(×tamp); + + buf = t_malloc0(12); + imap_to_date_tm(buf, tm); + *str_r = buf; + return tm->tm_hour == 0 && tm->tm_min == 0 && tm->tm_sec == 0; +} diff --git a/src/lib-imap/imap-date.h b/src/lib-imap/imap-date.h new file mode 100644 index 0000000..b8ea982 --- /dev/null +++ b/src/lib-imap/imap-date.h @@ -0,0 +1,25 @@ +#ifndef IMAP_DATE_H +#define IMAP_DATE_H + +/* Parses IMAP date/time string and returns TRUE if the string is valid. + time_t is filled with UTC date. timezone_offset is filled with parsed + timezone. If no timezone is given, local timezone is assumed. + + If date is too low or too high to fit to time_t, it's set to lowest/highest + allowed value. This allows you to use the value directly for comparing + timestamps. */ +bool imap_parse_date(const char *str, time_t *timestamp_r); +bool imap_parse_datetime(const char *str, time_t *timestamp_r, + int *timezone_offset_r); + +/* Returns given UTC timestamp as IMAP date-time string in local timezone. */ +const char *imap_to_datetime(time_t timestamp); +/* Returns given UTC timestamp as IMAP date-time string in given timezone. */ +const char *imap_to_datetime_tz(time_t timestamp, int timezone_offset); + +/* Returns TRUE if timestamp was successfully converted to a date, + FALSE if time would have been required as well (but the string is still + returned). */ +bool imap_to_date(time_t timestamp, const char **str_r); + +#endif diff --git a/src/lib-imap/imap-envelope.c b/src/lib-imap/imap-envelope.c new file mode 100644 index 0000000..87297f4 --- /dev/null +++ b/src/lib-imap/imap-envelope.c @@ -0,0 +1,248 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "str.h" +#include "message-address.h" +#include "message-part-data.h" +#include "message-parser.h" +#include "imap-parser.h" +#include "imap-envelope.h" +#include "imap-quote.h" + +/* + * Envelope write + */ + +static void imap_write_address(string_t *str, struct message_address *addr) +{ + if (addr == NULL) { + str_append(str, "NIL"); + return; + } + + str_append_c(str, '('); + while (addr != NULL) { + str_append_c(str, '('); + if (addr->name == NULL) + str_append(str, "NIL"); + else { + imap_append_string_for_humans(str, + (const void *)addr->name, strlen(addr->name)); + } + str_append_c(str, ' '); + imap_append_nstring(str, addr->route); + str_append_c(str, ' '); + imap_append_nstring(str, addr->mailbox); + str_append_c(str, ' '); + imap_append_nstring(str, addr->domain); + str_append_c(str, ')'); + + addr = addr->next; + } + str_append_c(str, ')'); +} + +void imap_envelope_write(struct message_part_envelope *data, + string_t *str) +{ +#define NVL(str, nullstr) ((str) != NULL ? (str) : (nullstr)) + static const char *empty_envelope = + "NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL"; + + if (data == NULL) { + str_append(str, empty_envelope); + return; + } + + imap_append_nstring_nolf(str, data->date); + str_append_c(str, ' '); + if (data->subject == NULL) + str_append(str, "NIL"); + else { + imap_append_string_for_humans(str, + (const unsigned char *)data->subject, + strlen(data->subject)); + } + + str_append_c(str, ' '); + imap_write_address(str, data->from); + str_append_c(str, ' '); + imap_write_address(str, NVL(data->sender, data->from)); + str_append_c(str, ' '); + imap_write_address(str, NVL(data->reply_to, data->from)); + str_append_c(str, ' '); + imap_write_address(str, data->to); + str_append_c(str, ' '); + imap_write_address(str, data->cc); + str_append_c(str, ' '); + imap_write_address(str, data->bcc); + + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->in_reply_to); + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->message_id); +} + +/* + * ENVELOPE parsing + */ + +static bool +imap_envelope_parse_address(const struct imap_arg *arg, + pool_t pool, struct message_address **addr_r) +{ + struct message_address *addr; + const struct imap_arg *list_args; + const char *name, *route, *mailbox, *domain; + unsigned int list_count; + + if (!imap_arg_get_list_full(arg, &list_args, &list_count)) + return FALSE; + + /* we require 4 arguments, strings or NILs */ + if (list_count < 4) + return FALSE; + + if (!imap_arg_get_nstring(&list_args[0], &name)) + return FALSE; + if (!imap_arg_get_nstring(&list_args[1], &route)) + return FALSE; + if (!imap_arg_get_nstring(&list_args[2], &mailbox)) + return FALSE; + if (!imap_arg_get_nstring(&list_args[3], &domain)) + return FALSE; + + addr = p_new(pool, struct message_address, 1); + addr->name = p_strdup(pool, name); + addr->route = p_strdup(pool, route); + addr->mailbox = p_strdup(pool, mailbox); + addr->domain = p_strdup(pool, domain); + + *addr_r = addr; + return TRUE; +} + +static bool +imap_envelope_parse_addresses(const struct imap_arg *arg, + pool_t pool, struct message_address **addrs_r) +{ + struct message_address *first, *addr, *prev; + const struct imap_arg *list_args; + + if (arg->type == IMAP_ARG_NIL) { + *addrs_r = NULL; + return TRUE; + } + + if (!imap_arg_get_list(arg, &list_args)) + return FALSE; + + first = addr = prev = NULL; + for (; !IMAP_ARG_IS_EOL(list_args); list_args++) { + if (!imap_envelope_parse_address + (list_args, pool, &addr)) + return FALSE; + if (first == NULL) + first = addr; + if (prev != NULL) + prev->next = addr; + prev = addr; + } + + *addrs_r = first; + return TRUE; +} + +bool imap_envelope_parse_args(const struct imap_arg *args, + pool_t pool, struct message_part_envelope **envlp_r, + const char **error_r) +{ + struct message_part_envelope *envlp; + + envlp = p_new(pool, struct message_part_envelope, 1); + + if (!imap_arg_get_nstring(args++, &envlp->date)) { + *error_r = "Invalid date field"; + return FALSE; + } + envlp->date = p_strdup(pool, envlp->date); + + if (!imap_arg_get_nstring(args++, &envlp->subject)) { + *error_r = "Invalid subject field"; + return FALSE; + } + envlp->subject = p_strdup(pool, envlp->subject); + + if (!imap_envelope_parse_addresses(args++, pool, &envlp->from)) { + *error_r = "Invalid from field"; + return FALSE; + } + if (!imap_envelope_parse_addresses(args++, pool, &envlp->sender)) { + *error_r = "Invalid sender field"; + return FALSE; + } + if (!imap_envelope_parse_addresses(args++, pool, &envlp->reply_to)) { + *error_r = "Invalid reply_to field"; + return FALSE; + } + if (!imap_envelope_parse_addresses(args++, pool, &envlp->to)) { + *error_r = "Invalid to field"; + return FALSE; + } + if (!imap_envelope_parse_addresses(args++, pool, &envlp->cc)) { + *error_r = "Invalid cc field"; + return FALSE; + } + if (!imap_envelope_parse_addresses(args++, pool, &envlp->bcc)) { + *error_r = "Invalid bcc field"; + return FALSE; + } + + if (!imap_arg_get_nstring(args++, &envlp->in_reply_to)) { + *error_r = "Invalid in_reply_to field"; + return FALSE; + } + envlp->in_reply_to = p_strdup(pool, envlp->in_reply_to); + + if (!imap_arg_get_nstring(args++, &envlp->message_id)) { + *error_r = "Invalid message_id field"; + return FALSE; + } + envlp->message_id = p_strdup(pool, envlp->message_id); + + *envlp_r = envlp; + return TRUE; +} + +bool imap_envelope_parse(const char *envelope, + pool_t pool, struct message_part_envelope **envlp_r, + const char **error_r) +{ + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + int ret; + bool success; + + input = i_stream_create_from_data(envelope, strlen(envelope)); + (void)i_stream_read(input); + + parser = imap_parser_create(input, NULL, SIZE_MAX); + ret = imap_parser_finish_line(parser, 0, + IMAP_PARSE_FLAG_LITERAL_TYPE, &args); + if (ret < 0) { + *error_r = t_strdup_printf("IMAP parser failed: %s", + imap_parser_get_error(parser, NULL)); + success = FALSE; + } else if (ret == 0) { + *error_r = "Empty envelope"; + success = FALSE; + } else { + success = imap_envelope_parse_args(args, pool, envlp_r, error_r); + } + + imap_parser_unref(&parser); + i_stream_destroy(&input); + return success; +} diff --git a/src/lib-imap/imap-envelope.h b/src/lib-imap/imap-envelope.h new file mode 100644 index 0000000..f9e4c0b --- /dev/null +++ b/src/lib-imap/imap-envelope.h @@ -0,0 +1,20 @@ +#ifndef IMAP_ENVELOPE_H +#define IMAP_ENVELOPE_H + +struct imap_arg; +struct message_part_envelope; + +/* Write envelope to given string */ +void imap_envelope_write(struct message_part_envelope *data, + string_t *str); + +/* Parse envelope from arguments */ +bool imap_envelope_parse_args(const struct imap_arg *args, + pool_t pool, struct message_part_envelope **envlp_r, + const char **error_r); +/* Parse envelope from string */ +bool imap_envelope_parse(const char *envelope, + pool_t pool, struct message_part_envelope **envlp_r, + const char **error_r); + +#endif diff --git a/src/lib-imap/imap-id.c b/src/lib-imap/imap-id.c new file mode 100644 index 0000000..f873c57 --- /dev/null +++ b/src/lib-imap/imap-id.c @@ -0,0 +1,173 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "str-sanitize.h" +#include "istream.h" +#include "imap-parser.h" +#include "imap-quote.h" +#include "imap-id.h" +#include "dovecot-version.h" + +#ifdef HAVE_SYS_UTSNAME_H +# include <sys/utsname.h> +#endif + +#ifdef HAVE_UNAME +static struct utsname utsname_result; +static bool utsname_set = FALSE; + +static const char *imap_id_get_uname(const char *key) +{ + if (!utsname_set) { + utsname_set = TRUE; + if (uname(&utsname_result) < 0) { + i_error("uname() failed: %m"); + i_zero(&utsname_result); + } + } + + if (strcasecmp(key, "os") == 0) + return utsname_result.sysname; + if (strcasecmp(key, "os-version") == 0) + return utsname_result.release; + return NULL; +} +#endif + +static const char *imap_id_get_default(const char *key) +{ + if (strcasecmp(key, "name") == 0) + return PACKAGE_NAME; + if (strcasecmp(key, "version") == 0) + return PACKAGE_VERSION; + if (strcasecmp(key, "revision") == 0) + return DOVECOT_REVISION; + if (strcasecmp(key, "support-url") == 0) + return PACKAGE_WEBPAGE; + if (strcasecmp(key, "support-email") == 0) + return PACKAGE_BUGREPORT; +#ifdef HAVE_UNAME + return imap_id_get_uname(key); +#endif +} + +static const char * +imap_id_reply_generate_from_imap_args(const struct imap_arg *args) +{ + string_t *str; + const char *key, *value; + + if (IMAP_ARG_IS_EOL(args)) + return "NIL"; + + str = t_str_new(256); + str_append_c(str, '('); + for (; !IMAP_ARG_IS_EOL(args); args++) { + if (!imap_arg_get_astring(args, &key)) { + /* broken input */ + if (IMAP_ARG_IS_EOL(&args[1])) + break; + args++; + } else { + /* key */ + if (str_len(str) > 1) + str_append_c(str, ' '); + imap_append_quoted(str, key); + str_append_c(str, ' '); + /* value */ + if (IMAP_ARG_IS_EOL(&args[1])) { + str_append(str, "NIL"); + break; + } + args++; + if (!imap_arg_get_astring(args, &value)) + value = NULL; + else { + if (strcmp(value, "*") == 0) + value = imap_id_get_default(key); + } + imap_append_nstring(str, value); + } + } + if (str_len(str) == 1) { + /* broken */ + return "NIL"; + } + str_append_c(str, ')'); + return str_c(str); +} + +const char *imap_id_reply_generate(const char *settings) +{ + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + const char *ret; + + if (settings == NULL) + return "NIL"; + + input = i_stream_create_from_data(settings, strlen(settings)); + (void)i_stream_read(input); + + parser = imap_parser_create(input, NULL, SIZE_MAX); + if (imap_parser_finish_line(parser, 0, 0, &args) <= 0) + ret = "NIL"; + else + ret = imap_id_reply_generate_from_imap_args(args); + + imap_parser_unref(&parser); + i_stream_destroy(&input); + return ret; +} + +void imap_id_log_reply_append(string_t *reply, const char *key, + const char *value) +{ + if (str_len(reply) > 0) + str_append(reply, ", "); + str_append(reply, str_sanitize(key, IMAP_ID_KEY_MAX_LEN)); + str_append_c(reply, '='); + str_append(reply, value == NULL ? "NIL" : str_sanitize(value, 80)); +} + +const char *imap_id_args_get_log_reply(const struct imap_arg *args, + const char *settings) +{ + const char *const *keys, *key, *value; + string_t *reply; + bool log_all; + + if (settings == NULL || *settings == '\0') + return NULL; + if (!imap_arg_get_list(args, &args)) + return NULL; + + log_all = strcmp(settings, "*") == 0; + reply = t_str_new(256); + keys = t_strsplit_spaces(settings, " "); + while (!IMAP_ARG_IS_EOL(&args[0]) && + !IMAP_ARG_IS_EOL(&args[1])) { + if (!imap_arg_get_string(args, &key)) { + /* broken input */ + args += 2; + continue; + } + args++; + if (strlen(key) > 30) { + /* broken: ID spec requires fields to be max. 30 + octets */ + args++; + continue; + } + + if (log_all || str_array_icase_find(keys, key)) { + if (!imap_arg_get_nstring(args, &value)) + value = ""; + imap_id_log_reply_append(reply, key, value); + } + args++; + } + return str_len(reply) == 0 ? NULL : str_c(reply); +} diff --git a/src/lib-imap/imap-id.h b/src/lib-imap/imap-id.h new file mode 100644 index 0000000..853153a --- /dev/null +++ b/src/lib-imap/imap-id.h @@ -0,0 +1,19 @@ +#ifndef IMAP_ID_H +#define IMAP_ID_H + +struct imap_arg; + +/* RFC 2971 says keys are max. 30 octets */ +#define IMAP_ID_KEY_MAX_LEN 30 + +/* Return ID reply based on given settings. */ +const char *imap_id_reply_generate(const char *settings); +/* Return a line that should be logged based on given args and settings. + Returns NULL if nothing should be logged. */ +const char *imap_id_args_get_log_reply(const struct imap_arg *args, + const char *settings); +/* Append [, ]key=value to the reply sanitized. */ +void imap_id_log_reply_append(string_t *reply, const char *key, + const char *value); + +#endif diff --git a/src/lib-imap/imap-keepalive.c b/src/lib-imap/imap-keepalive.c new file mode 100644 index 0000000..0756ce8 --- /dev/null +++ b/src/lib-imap/imap-keepalive.c @@ -0,0 +1,47 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "crc32.h" +#include "net.h" +#include "imap-keepalive.h" + +#include <time.h> + +static bool imap_remote_ip_is_usable(const struct ip_addr *ip) +{ + unsigned int addr; + + if (ip->family == 0) + return FALSE; + if (ip->family == AF_INET) { +#define IP4(a,b,c,d) ((unsigned)(a)<<24|(unsigned)(b)<<16|(unsigned)(c)<<8|(unsigned)(d)) + addr = ip->u.ip4.s_addr; + if (addr >= IP4(10,0,0,0) && addr <= IP4(10,255,255,255)) + return FALSE; /* 10/8 */ + if (addr >= IP4(192,168,0,0) && addr <= IP4(192,168,255,255)) + return FALSE; /* 192.168/16 */ + if (addr >= IP4(172,16,0,0) && addr <= IP4(172,31,255,255)) + return FALSE; /* 172.16/12 */ + if (addr >= IP4(127,0,0,0) && addr <= IP4(127,255,255,255)) + return FALSE; /* 127/8 */ +#undef IP4 + } + else if (ip->family == AF_INET6) { + addr = ip->u.ip6.s6_addr[0]; + if (addr == 0xfc || addr == 0xfd) + return FALSE; /* fc00::/7 */ + } + return TRUE; +} + +unsigned int +imap_keepalive_interval_msecs(const char *username, const struct ip_addr *ip, + unsigned int interval_secs) +{ + unsigned int client_hash; + + client_hash = ip != NULL && imap_remote_ip_is_usable(ip) ? + net_ip_hash(ip) : crc32_str(username); + interval_secs -= (time(NULL) + client_hash) % interval_secs; + return interval_secs * 1000; +} diff --git a/src/lib-imap/imap-keepalive.h b/src/lib-imap/imap-keepalive.h new file mode 100644 index 0000000..238cdbd --- /dev/null +++ b/src/lib-imap/imap-keepalive.h @@ -0,0 +1,24 @@ +#ifndef IMAP_KEEPALIVE_H +#define IMAP_KEEPALIVE_H + +/* This function can be used to set IMAP IDLE keepalive notification timeout + interval so that the client gets the keepalive notifications at exactly the + same time for all the IMAP connections. This helps to reduce battery usage + in mobile devices. + + One problem with this is that we don't really want to send the notifications + to everyone at the same time, because it would cause huge peaks of activity. + Basing the notifications on the username works well for one account, but + basing it on the IP address allows the client to get all of the + notifications at the same time for multiple accounts as well (of course + assuming Dovecot is running on all the servers :) + + One potential downside to using IP is that if a proxy hides the client's IP + address, the notifications are sent to everyone at the same time. This can + be avoided by using a properly configured Dovecot proxy, but we'll also try + to avoid this by not doing it for the commonly used intranet IP ranges. */ +unsigned int +imap_keepalive_interval_msecs(const char *username, const struct ip_addr *ip, + unsigned int interval_secs); + +#endif diff --git a/src/lib-imap/imap-match.c b/src/lib-imap/imap-match.c new file mode 100644 index 0000000..8603e00 --- /dev/null +++ b/src/lib-imap/imap-match.c @@ -0,0 +1,382 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +/* imap_match_init() logic originates from Cyrus, but the code is fully + rewritten. */ + +#include "lib.h" +#include "array.h" +#include "imap-match.h" + +#include <ctype.h> + +struct imap_match_pattern { + const char *pattern; + bool inboxcase; +}; + +struct imap_match_glob { + pool_t pool; + + struct imap_match_pattern *patterns; + + char sep; + char patterns_data[FLEXIBLE_ARRAY_MEMBER]; +}; + +struct imap_match_context { + const char *inboxcase_end; + + char sep; + bool inboxcase; +}; + +/* name of "INBOX" - must not have repeated substrings */ +static const char inbox[] = "INBOX"; +#define INBOXLEN (sizeof(inbox) - 1) + +struct imap_match_glob * +imap_match_init(pool_t pool, const char *pattern, + bool inboxcase, char separator) +{ + const char *patterns[2]; + + patterns[0] = pattern; + patterns[1] = NULL; + return imap_match_init_multiple(pool, patterns, inboxcase, separator); +} + +static const char *pattern_compress(const char *pattern) +{ + char *dest, *ret; + + dest = ret = t_strdup_noconst(pattern); + + /* @UNSAFE: compress the pattern */ + while (*pattern != '\0') { + if (*pattern == '*' || *pattern == '%') { + /* remove duplicate hierarchy wildcards */ + while (*pattern == '%') pattern++; + + /* "%*" -> "*" */ + if (*pattern == '*') { + /* remove duplicate wildcards */ + while (*pattern == '*' || *pattern == '%') + pattern++; + *dest++ = '*'; + } else { + *dest++ = '%'; + } + } else { + *dest++ = *pattern++; + } + } + *dest = '\0'; + return ret; +} + +static bool pattern_is_inboxcase(const char *pattern, char separator) +{ + const char *p = pattern, *inboxp = inbox; + + /* skip over exact matches */ + while (*inboxp == i_toupper(*p) && *p != '\0') { + inboxp++; p++; + } + if (*p != '%') { + return *p == '*' || *p == separator || + (*inboxp == '\0' && *p == '\0'); + } + + /* handle 'I%B%X' style checks */ + for (; *p != '\0' && *p != '*' && *p != separator; p++) { + if (*p != '%') { + inboxp = strchr(inboxp, i_toupper(*p)); + if (inboxp == NULL) + return FALSE; + + if (*++inboxp == '\0') { + /* now check that it doesn't end with + any invalid chars */ + if (*++p == '%') p++; + if (*p != '\0' && *p != '*' && + *p != separator) + return FALSE; + break; + } + } + } + return TRUE; +} + +static struct imap_match_glob * +imap_match_init_multiple_real(pool_t pool, const char *const *patterns, + bool inboxcase, char separator) +{ + struct imap_match_glob *glob; + struct imap_match_pattern *match_patterns; + unsigned int i, patterns_count; + size_t len, pos, patterns_data_len = 0; + + patterns_count = str_array_length(patterns); + match_patterns = p_new(pool, struct imap_match_pattern, + patterns_count + 1); + + /* compress the patterns */ + for (i = 0; i < patterns_count; i++) { + match_patterns[i].pattern = pattern_compress(patterns[i]); + match_patterns[i].inboxcase = inboxcase && + pattern_is_inboxcase(match_patterns[i].pattern, + separator); + + patterns_data_len += strlen(match_patterns[i].pattern) + 1; + } + patterns_count = i; + + /* now we know how much memory we need */ + glob = p_malloc(pool, sizeof(struct imap_match_glob) + + patterns_data_len); + glob->pool = pool; + glob->sep = separator; + + /* copy pattern strings to our allocated memory */ + for (i = 0, pos = 0; i < patterns_count; i++) { + len = strlen(match_patterns[i].pattern) + 1; + i_assert(pos + len <= patterns_data_len); + + /* @UNSAFE */ + memcpy(glob->patterns_data + pos, + match_patterns[i].pattern, len); + match_patterns[i].pattern = glob->patterns_data + pos; + pos += len; + } + glob->patterns = match_patterns; + return glob; +} + +struct imap_match_glob * +imap_match_init_multiple(pool_t pool, const char *const *patterns, + bool inboxcase, char separator) +{ + struct imap_match_glob *glob; + + if (pool->datastack_pool) { + return imap_match_init_multiple_real(pool, patterns, + inboxcase, separator); + } + T_BEGIN { + glob = imap_match_init_multiple_real(pool, patterns, + inboxcase, separator); + } T_END; + return glob; +} + +void imap_match_deinit(struct imap_match_glob **glob) +{ + if (glob == NULL || *glob == NULL) + return; + p_free((*glob)->pool, (*glob)->patterns); + p_free((*glob)->pool, *glob); + *glob = NULL; +} + +static struct imap_match_glob * +imap_match_dup_real(pool_t pool, const struct imap_match_glob *glob) +{ + ARRAY_TYPE(const_string) patterns; + const struct imap_match_pattern *p; + bool inboxcase = FALSE; + + t_array_init(&patterns, 8); + for (p = glob->patterns; p->pattern != NULL; p++) { + if (p->inboxcase) + inboxcase = TRUE; + array_push_back(&patterns, &p->pattern); + } + array_append_zero(&patterns); + return imap_match_init_multiple_real(pool, array_front(&patterns), + inboxcase, glob->sep); +} + +struct imap_match_glob * +imap_match_dup(pool_t pool, const struct imap_match_glob *glob) +{ + struct imap_match_glob *new_glob; + + if (pool->datastack_pool) { + return imap_match_dup_real(pool, glob); + } else { + T_BEGIN { + new_glob = imap_match_dup_real(pool, glob); + } T_END; + return new_glob; + } +} + +bool imap_match_globs_equal(const struct imap_match_glob *glob1, + const struct imap_match_glob *glob2) +{ + const struct imap_match_pattern *p1 = glob1->patterns; + const struct imap_match_pattern *p2 = glob2->patterns; + + if (glob1->sep != glob2->sep) + return FALSE; + + for (; p1->pattern != NULL && p2->pattern != NULL; p1++, p2++) { + if (strcmp(p1->pattern, p2->pattern) != 0) + return FALSE; + if (p1->inboxcase != p2->inboxcase) + return FALSE; + } + return p1->pattern == p2->pattern; +} + +#define CMP_CUR_CHR(ctx, data, pattern) \ + (*(data) == *(pattern) || \ + (i_toupper(*(data)) == i_toupper(*(pattern)) && \ + (data) < (ctx)->inboxcase_end)) + +static enum imap_match_result +match_sub(struct imap_match_context *ctx, const char **data_p, + const char **pattern_p) +{ + enum imap_match_result ret, match; + unsigned int i; + const char *data = *data_p, *pattern = *pattern_p; + + /* match all non-wildcards */ + i = 0; + while (pattern[i] != '\0' && pattern[i] != '*' && pattern[i] != '%') { + if (!CMP_CUR_CHR(ctx, data+i, pattern+i)) { + if (data[i] != '\0') + return IMAP_MATCH_NO; + if (pattern[i] == ctx->sep) + return IMAP_MATCH_CHILDREN; + if (i > 0 && pattern[i-1] == ctx->sep) { + /* data="foo/" pattern = "foo/bar/%" */ + return IMAP_MATCH_CHILDREN; + } + return IMAP_MATCH_NO; + } + i++; + } + data += i; + pattern += i; + + if (*data == '\0' && *data_p != data && data[-1] == ctx->sep && + *pattern != '\0') { + /* data="/" pattern="/%..." */ + match = IMAP_MATCH_CHILDREN; + } else { + match = IMAP_MATCH_NO; + } + while (*pattern == '%') { + pattern++; + + if (*pattern == '\0') { + /* match, if this is the last hierarchy */ + while (*data != '\0' && *data != ctx->sep) + data++; + break; + } + + /* skip over this hierarchy */ + while (*data != '\0') { + if (CMP_CUR_CHR(ctx, data, pattern)) { + ret = match_sub(ctx, &data, &pattern); + if (ret == IMAP_MATCH_YES) + break; + + match |= ret; + } + + if (*data == ctx->sep) + break; + + data++; + } + } + + if (*pattern != '*') { + if (*data == '\0' && *pattern != '\0') { + if (*pattern == ctx->sep) + match |= IMAP_MATCH_CHILDREN; + return match; + } + + if (*data != '\0') { + if (*pattern == '\0' && *data == ctx->sep) + match |= IMAP_MATCH_PARENT; + return match; + } + } + + *data_p = data; + *pattern_p = pattern; + return IMAP_MATCH_YES; +} + +static enum imap_match_result +imap_match_pattern(struct imap_match_context *ctx, + const char *data, const char *pattern) +{ + enum imap_match_result ret, match; + + ctx->inboxcase_end = data; + if (ctx->inboxcase && strncasecmp(data, inbox, INBOXLEN) == 0 && + (data[INBOXLEN] == '\0' || data[INBOXLEN] == ctx->sep)) { + /* data begins with INBOX/, use case-insensitive comparison + for it */ + ctx->inboxcase_end += INBOXLEN; + } + + if (*pattern != '*') { + /* handle the pattern up to the first '*' */ + ret = match_sub(ctx, &data, &pattern); + if (ret != IMAP_MATCH_YES || *pattern == '\0') + return ret; + } + + match = IMAP_MATCH_CHILDREN; + while (*pattern == '*') { + pattern++; + + if (*pattern == '\0') + return IMAP_MATCH_YES; + + while (*data != '\0') { + if (CMP_CUR_CHR(ctx, data, pattern)) { + ret = match_sub(ctx, &data, &pattern); + if (ret == IMAP_MATCH_YES) + break; + match |= ret; + } + + data++; + } + } + + return *data == '\0' && *pattern == '\0' ? + IMAP_MATCH_YES : match; +} + +enum imap_match_result +imap_match(struct imap_match_glob *glob, const char *data) +{ + struct imap_match_context ctx; + unsigned int i; + enum imap_match_result ret, match; + + match = IMAP_MATCH_NO; + ctx.sep = glob->sep; + for (i = 0; glob->patterns[i].pattern != NULL; i++) { + ctx.inboxcase = glob->patterns[i].inboxcase; + + ret = imap_match_pattern(&ctx, data, glob->patterns[i].pattern); + if (ret == IMAP_MATCH_YES) + return IMAP_MATCH_YES; + + match |= ret; + } + + return match; +} diff --git a/src/lib-imap/imap-match.h b/src/lib-imap/imap-match.h new file mode 100644 index 0000000..f2e5b51 --- /dev/null +++ b/src/lib-imap/imap-match.h @@ -0,0 +1,42 @@ +#ifndef IMAP_MATCH_H +#define IMAP_MATCH_H + +enum imap_match_result { + IMAP_MATCH_NO = 0x00, /* definite non-match */ + IMAP_MATCH_YES = 0x01, /* match */ + + /* YES and NO are returned alone, but CHILDREN and PARENT may be + returned both (eg. "foo*bar" vs. "foobar/baz") */ + + /* non-match, but its children could match (eg. "box" vs "box/%") */ + IMAP_MATCH_CHILDREN = 0x02, + /* non-match, but one of its parents does match. This should often be + handled with YES matches, because when listing for "%" and "box/foo" + exists but "box" doesn't, you should still list "box" as + (Nonexistent HasChildren) mailbox. */ + IMAP_MATCH_PARENT = 0x04 +}; + +struct imap_match_glob; + +/* If inboxcase is TRUE, the "INBOX" string at the beginning of line is + compared case-insensitively */ +struct imap_match_glob * +imap_match_init(pool_t pool, const char *pattern, + bool inboxcase, char separator); +struct imap_match_glob * +imap_match_init_multiple(pool_t pool, const char *const *patterns, + bool inboxcase, char separator); +void imap_match_deinit(struct imap_match_glob **glob); + +struct imap_match_glob * +imap_match_dup(pool_t pool, const struct imap_match_glob *glob); +/* Returns TRUE if two globs were created with same init() parameters + (but inboxcase is ignored if no patterns can match INBOX) */ +bool imap_match_globs_equal(const struct imap_match_glob *glob1, + const struct imap_match_glob *glob2); + +enum imap_match_result +imap_match(struct imap_match_glob *glob, const char *data); + +#endif diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c new file mode 100644 index 0000000..2deb75f --- /dev/null +++ b/src/lib-imap/imap-parser.c @@ -0,0 +1,1023 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "ostream.h" +#include "strescape.h" +#include "imap-parser.h" + +/* We use this macro to read atoms from input. It should probably contain + everything some day, but for now we can't handle some input otherwise: + + ']' is required for parsing section (FETCH BODY[]) + '%', '*' and ']' are valid list-chars for LIST patterns + '\' is used in flags */ +#define IS_ATOM_PARSER_INPUT(c) \ + ((c) == '(' || (c) == ')' || (c) == '{' || \ + (c) == '"' || (c) <= 32 || (c) == 0x7f) + +#define is_linebreak(c) \ + ((c) == '\r' || (c) == '\n') + +#define LIST_INIT_COUNT 7 + +enum arg_parse_type { + ARG_PARSE_NONE = 0, + ARG_PARSE_ATOM, + ARG_PARSE_STRING, + ARG_PARSE_LITERAL, + ARG_PARSE_LITERAL8, + ARG_PARSE_LITERAL_DATA, + ARG_PARSE_LITERAL_DATA_FORCED, + ARG_PARSE_TEXT +}; + +struct imap_parser { + /* permanent */ + int refcount; + pool_t pool; + struct istream *input; + struct ostream *output; + size_t max_line_size; + enum imap_parser_flags flags; + + /* reset by imap_parser_reset(): */ + size_t line_size; + ARRAY_TYPE(imap_arg_list) root_list; + ARRAY_TYPE(imap_arg_list) *cur_list; + struct imap_arg *list_arg; + + enum arg_parse_type cur_type; + size_t cur_pos; /* parser position in input buffer */ + bool cur_resp_text; /* we're parsing [resp-text-code] */ + + int str_first_escape; /* ARG_PARSE_STRING: index to first '\' */ + uoff_t literal_size; /* ARG_PARSE_LITERAL: string size */ + + enum imap_parser_error error; + const char *error_msg; + + bool literal_minus:1; + bool literal_skip_crlf:1; + bool literal_nonsync:1; + bool literal8:1; + bool literal_size_return:1; + bool eol:1; + bool args_added_extra_eol:1; + bool fatal_error:1; +}; + +struct imap_parser * +imap_parser_create(struct istream *input, struct ostream *output, + size_t max_line_size) +{ + struct imap_parser *parser; + + parser = i_new(struct imap_parser, 1); + parser->refcount = 1; + parser->pool = pool_alloconly_create(MEMPOOL_GROWING"IMAP parser", + 1024); + parser->input = input; + parser->output = output; + parser->max_line_size = max_line_size; + + p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT); + parser->cur_list = &parser->root_list; + return parser; +} + +void imap_parser_ref(struct imap_parser *parser) +{ + i_assert(parser->refcount > 0); + + parser->refcount++; +} + +void imap_parser_unref(struct imap_parser **_parser) +{ + struct imap_parser *parser = *_parser; + + *_parser = NULL; + + i_assert(parser->refcount > 0); + if (--parser->refcount > 0) + return; + + pool_unref(&parser->pool); + i_free(parser); +} + +void imap_parser_enable_literal_minus(struct imap_parser *parser) +{ + parser->literal_minus = TRUE; +} + +void imap_parser_reset(struct imap_parser *parser) +{ + p_clear(parser->pool); + + parser->line_size = 0; + + p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT); + parser->cur_list = &parser->root_list; + parser->list_arg = NULL; + + parser->cur_type = ARG_PARSE_NONE; + parser->cur_pos = 0; + parser->cur_resp_text = FALSE; + + parser->str_first_escape = 0; + parser->literal_size = 0; + + parser->error = IMAP_PARSE_ERROR_NONE; + parser->error_msg = NULL; + + parser->literal_skip_crlf = FALSE; + parser->eol = FALSE; + parser->args_added_extra_eol = FALSE; + parser->literal_size_return = FALSE; +} + +void imap_parser_set_streams(struct imap_parser *parser, struct istream *input, + struct ostream *output) +{ + parser->input = input; + parser->output = output; +} + +const char *imap_parser_get_error(struct imap_parser *parser, + enum imap_parser_error *error_r) +{ + if (error_r != NULL) + *error_r = parser->error; + return parser->error_msg; +} + +/* skip over everything parsed so far, plus the following whitespace */ +static bool imap_parser_skip_to_next(struct imap_parser *parser, + const unsigned char **data, + size_t *data_size) +{ + size_t i; + + for (i = parser->cur_pos; i < *data_size; i++) { + if ((*data)[i] != ' ') + break; + } + + parser->line_size += i; + i_stream_skip(parser->input, i); + parser->cur_pos = 0; + + *data += i; + *data_size -= i; + return *data_size > 0; +} + +static struct imap_arg *imap_arg_create(struct imap_parser *parser) +{ + struct imap_arg *arg; + + arg = array_append_space(parser->cur_list); + arg->parent = parser->list_arg; + return arg; +} + +static void imap_parser_open_list(struct imap_parser *parser) +{ + parser->list_arg = imap_arg_create(parser); + parser->list_arg->type = IMAP_ARG_LIST; + p_array_init(&parser->list_arg->_data.list, parser->pool, + LIST_INIT_COUNT); + parser->cur_list = &parser->list_arg->_data.list; + + parser->cur_type = ARG_PARSE_NONE; +} + +static bool imap_parser_close_list(struct imap_parser *parser) +{ + struct imap_arg *arg; + + if (parser->list_arg == NULL) { + /* we're not inside list */ + if ((parser->flags & IMAP_PARSE_FLAG_INSIDE_LIST) != 0) { + parser->eol = TRUE; + parser->cur_type = ARG_PARSE_NONE; + return TRUE; + } + parser->error_msg = "Unexpected ')'"; + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + return FALSE; + } + + arg = imap_arg_create(parser); + arg->type = IMAP_ARG_EOL; + + parser->list_arg = parser->list_arg->parent; + if (parser->list_arg == NULL) { + parser->cur_list = &parser->root_list; + } else { + parser->cur_list = &parser->list_arg->_data.list; + } + + parser->cur_type = ARG_PARSE_NONE; + return TRUE; +} + +static char * +imap_parser_strdup(struct imap_parser *parser, + const void *data, size_t len) +{ + char *ret; + + ret = p_malloc(parser->pool, len + 1); + memcpy(ret, data, len); + return ret; +} + +static void imap_parser_save_arg(struct imap_parser *parser, + const unsigned char *data, size_t size) +{ + struct imap_arg *arg; + char *str; + + arg = imap_arg_create(parser); + + switch (parser->cur_type) { + case ARG_PARSE_ATOM: + case ARG_PARSE_TEXT: + if (size == 3 && i_memcasecmp(data, "NIL", 3) == 0) { + /* NIL argument. it might be an actual NIL, but if + we're reading astring, it's an atom and we can't + lose its case. */ + arg->type = IMAP_ARG_NIL; + } else { + /* simply save the string */ + arg->type = IMAP_ARG_ATOM; + } + arg->_data.str = imap_parser_strdup(parser, data, size); + arg->str_len = size; + break; + case ARG_PARSE_STRING: + /* data is quoted and may contain escapes. */ + i_assert(size > 0); + + arg->type = IMAP_ARG_STRING; + str = p_strndup(parser->pool, data+1, size-1); + + /* remove the escapes */ + if (parser->str_first_escape >= 0 && + (parser->flags & IMAP_PARSE_FLAG_NO_UNESCAPE) == 0) + (void)str_unescape(str); + arg->_data.str = str; + arg->str_len = strlen(str); + break; + case ARG_PARSE_LITERAL_DATA: + if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) != 0) { + /* save literal size */ + arg->type = parser->literal_nonsync ? + IMAP_ARG_LITERAL_SIZE_NONSYNC : + IMAP_ARG_LITERAL_SIZE; + arg->_data.literal_size = parser->literal_size; + arg->literal8 = parser->literal8; + break; + } + /* fall through */ + case ARG_PARSE_LITERAL_DATA_FORCED: + if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_TYPE) != 0) + arg->type = IMAP_ARG_LITERAL; + else + arg->type = IMAP_ARG_STRING; + arg->_data.str = imap_parser_strdup(parser, data, size); + arg->literal8 = parser->literal8; + arg->str_len = size; + break; + default: + i_unreached(); + } + + parser->cur_type = ARG_PARSE_NONE; +} + +static bool is_valid_atom_char(struct imap_parser *parser, char chr) +{ + const char *error_msg; + + if (IS_ATOM_PARSER_INPUT((unsigned char)chr)) + error_msg = "Invalid characters in atom"; + else if ((((unsigned char)chr) & 0x80) != 0) + error_msg = "8bit data in atom"; + else + return TRUE; + + if ((parser->flags & IMAP_PARSE_FLAG_ATOM_ALLCHARS) != 0) + return TRUE; + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = error_msg; + return FALSE; +} + +static bool imap_parser_read_atom(struct imap_parser *parser, + const unsigned char *data, size_t data_size) +{ + size_t i; + + /* read until we've found space, CR or LF. */ + for (i = parser->cur_pos; i < data_size; i++) { + if (data[i] == ' ' || is_linebreak(data[i])) { + imap_parser_save_arg(parser, data, i); + break; + } else if (data[i] == ')') { + if (parser->list_arg != NULL || + (parser->flags & IMAP_PARSE_FLAG_INSIDE_LIST) != 0) { + imap_parser_save_arg(parser, data, i); + break; + } else if ((parser->flags & + IMAP_PARSE_FLAG_ATOM_ALLCHARS) == 0) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Unexpected ')'"; + return FALSE; + } + /* assume it's part of the atom */ + } else if (!is_valid_atom_char(parser, data[i])) + return FALSE; + } + + parser->cur_pos = i; + return parser->cur_type == ARG_PARSE_NONE; +} + +static bool imap_parser_read_string(struct imap_parser *parser, + const unsigned char *data, size_t data_size) +{ + size_t i; + + /* read until we've found non-escaped ", CR or LF */ + for (i = parser->cur_pos; i < data_size; i++) { + if (data[i] == '"') { + imap_parser_save_arg(parser, data, i); + + i++; /* skip the trailing '"' too */ + break; + } + + if (data[i] == '\0') { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "NULs not allowed in strings"; + return FALSE; + } + + if (data[i] == '\\') { + if (i+1 == data_size) { + /* known data ends with '\' - leave it to + next time as well if it happens to be \" */ + break; + } + + /* save the first escaped char */ + if (parser->str_first_escape < 0) + parser->str_first_escape = i; + + /* skip the escaped char */ + i++; + } + + /* check linebreaks here, so escaping CR/LF isn't possible. + string always ends with '"', so it's an error if we found + a linebreak.. */ + if (is_linebreak(data[i]) && + (parser->flags & IMAP_PARSE_FLAG_MULTILINE_STR) == 0) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Missing '\"'"; + return FALSE; + } + } + + parser->cur_pos = i; + return parser->cur_type == ARG_PARSE_NONE; +} + +static bool imap_parser_literal_end(struct imap_parser *parser) +{ + if (parser->literal_minus && parser->literal_nonsync && + parser->literal_size > 4096) { + parser->error_msg = "Non-synchronizing literal size too large"; + parser->error = IMAP_PARSE_ERROR_LITERAL_TOO_BIG; + return FALSE; + } + + if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) == 0) { + if (parser->line_size >= parser->max_line_size || + parser->literal_size > + parser->max_line_size - parser->line_size) { + /* too long string, abort. */ + parser->error = IMAP_PARSE_ERROR_LITERAL_TOO_BIG; + parser->error_msg = "Literal size too large"; + return FALSE; + } + + if (parser->output != NULL && !parser->literal_nonsync) { + o_stream_nsend(parser->output, "+ OK\r\n", 6); + if (o_stream_is_corked(parser->output)) { + /* make sure this continuation is sent to the + client as soon as possible */ + o_stream_uncork(parser->output); + o_stream_cork(parser->output); + } + } + } + + parser->cur_type = ARG_PARSE_LITERAL_DATA; + parser->literal_skip_crlf = TRUE; + + parser->cur_pos = 0; + return TRUE; +} + +static bool imap_parser_read_literal(struct imap_parser *parser, + const unsigned char *data, + size_t data_size) +{ + size_t i; + + /* expecting digits + "}" */ + for (i = parser->cur_pos; i < data_size; i++) { + if (data[i] == '}') { + parser->line_size += i+1; + i_stream_skip(parser->input, i+1); + return imap_parser_literal_end(parser); + } + + if (parser->literal_nonsync) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Expecting '}' after '+'"; + return FALSE; + } + + if (data[i] == '+') { + parser->literal_nonsync = TRUE; + continue; + } + + if (data[i] < '0' || data[i] > '9') { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Invalid literal size"; + return FALSE; + } + + if (parser->literal_size >= ((uoff_t)-1 / 10)) { + if (parser->literal_size > ((uoff_t)-1 / 10) || + (uoff_t)(data[i] - '0') > ((uoff_t)-1 % 10)) { + parser->error = IMAP_PARSE_ERROR_LITERAL_TOO_BIG; + parser->error_msg = "Literal size too large"; + return FALSE; + } + } + parser->literal_size = parser->literal_size * 10 + + (data[i] - '0'); + } + + parser->cur_pos = i; + return FALSE; +} + +static bool imap_parser_read_literal_data(struct imap_parser *parser, + const unsigned char *data, + size_t data_size) +{ + if (parser->literal_skip_crlf) { + /* skip \r\n or \n, anything else gives an error */ + if (data_size == 0) + return FALSE; + + if (*data == '\r') { + parser->line_size++; + data++; data_size--; + i_stream_skip(parser->input, 1); + + if (data_size == 0) + return FALSE; + } + + if (*data != '\n') { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Missing LF after literal size"; + return FALSE; + } + + parser->line_size++; + data++; data_size--; + i_stream_skip(parser->input, 1); + + parser->literal_skip_crlf = FALSE; + + i_assert(parser->cur_pos == 0); + } + + if ((parser->flags & IMAP_PARSE_FLAG_LITERAL_SIZE) == 0 || + parser->cur_type == ARG_PARSE_LITERAL_DATA_FORCED) { + /* now we just wait until we've read enough data */ + if (data_size < parser->literal_size) + return FALSE; + else { + imap_parser_save_arg(parser, data, + (size_t)parser->literal_size); + parser->cur_pos = (size_t)parser->literal_size; + return TRUE; + } + } else { + /* we want to save only literal size, not the literal itself. */ + parser->literal_size_return = TRUE; + imap_parser_save_arg(parser, uchar_empty_ptr, 0); + return FALSE; + } +} + +static bool imap_parser_is_next_resp_text(struct imap_parser *parser) +{ + const struct imap_arg *arg; + + if (parser->cur_list != &parser->root_list || + array_count(parser->cur_list) != 1) + return FALSE; + + arg = array_front(&parser->root_list); + if (arg->type != IMAP_ARG_ATOM) + return FALSE; + + return strcasecmp(arg->_data.str, "OK") == 0 || + strcasecmp(arg->_data.str, "NO") == 0 || + strcasecmp(arg->_data.str, "BAD") == 0 || + strcasecmp(arg->_data.str, "BYE") == 0; +} + +static bool imap_parser_is_next_text(struct imap_parser *parser) +{ + const struct imap_arg *arg; + size_t len; + + if (parser->cur_list != &parser->root_list) + return FALSE; + + arg = array_back(&parser->root_list); + if (arg->type != IMAP_ARG_ATOM) + return FALSE; + + len = strlen(arg->_data.str); + return len > 0 && arg->_data.str[len-1] == ']'; +} + +static bool imap_parser_read_text(struct imap_parser *parser, + const unsigned char *data, size_t data_size) +{ + size_t i; + + /* read until end of line */ + for (i = parser->cur_pos; i < data_size; i++) { + if (is_linebreak(data[i])) { + imap_parser_save_arg(parser, data, i); + break; + } + } + parser->cur_pos = i; + return parser->cur_type == ARG_PARSE_NONE; +} + +/* Returns TRUE if argument was fully processed. Also returns TRUE if + an argument inside a list was processed. */ +static bool imap_parser_read_arg(struct imap_parser *parser) +{ + const unsigned char *data; + size_t data_size; + + data = i_stream_get_data(parser->input, &data_size); + if (data_size == 0) + return FALSE; + + while (parser->cur_type == ARG_PARSE_NONE) { + /* we haven't started parsing yet */ + if (!imap_parser_skip_to_next(parser, &data, &data_size)) + return FALSE; + i_assert(parser->cur_pos == 0); + + if (parser->cur_resp_text && + imap_parser_is_next_text(parser)) { + /* we just parsed [resp-text-code] */ + parser->cur_type = ARG_PARSE_TEXT; + break; + } + + switch (data[0]) { + case '\r': + if (data_size == 1) { + /* wait for LF */ + return FALSE; + } + if (data[1] != '\n') { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "CR sent without LF"; + return FALSE; + } + /* fall through */ + case '\n': + /* unexpected end of line */ + if ((parser->flags & IMAP_PARSE_FLAG_INSIDE_LIST) != 0) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Missing ')'"; + return FALSE; + } + parser->eol = TRUE; + return FALSE; + case '"': + parser->cur_type = ARG_PARSE_STRING; + parser->str_first_escape = -1; + break; + case '~': + /* This could be either literal8 or atom */ + if (data_size == 1) { + /* wait for the next char */ + return FALSE; + } + if (data[1] != '{') { + parser->cur_type = ARG_PARSE_ATOM; + break; + } + if ((parser->flags & IMAP_PARSE_FLAG_LITERAL8) == 0) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "literal8 not allowed here"; + return FALSE; + } + parser->cur_type = ARG_PARSE_LITERAL8; + parser->literal_size = 0; + parser->literal_nonsync = FALSE; + parser->literal8 = TRUE; + break; + case '{': + parser->cur_type = ARG_PARSE_LITERAL; + parser->literal_size = 0; + parser->literal_nonsync = FALSE; + parser->literal8 = FALSE; + break; + case '(': + imap_parser_open_list(parser); + if ((parser->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) != 0) { + i_stream_skip(parser->input, 1); + return FALSE; + } + break; + case ')': + if (!imap_parser_close_list(parser)) + return FALSE; + + if (parser->list_arg == NULL) { + /* end of argument */ + parser->cur_pos++; + return TRUE; + } + break; + default: + if (!is_valid_atom_char(parser, data[0])) + return FALSE; + parser->cur_type = ARG_PARSE_ATOM; + break; + } + + parser->cur_pos++; + } + + i_assert(data_size > 0); + + switch (parser->cur_type) { + case ARG_PARSE_ATOM: + if (!imap_parser_read_atom(parser, data, data_size)) + return FALSE; + if ((parser->flags & IMAP_PARSE_FLAG_SERVER_TEXT) == 0) + break; + + if (imap_parser_is_next_resp_text(parser)) { + /* we just parsed OK/NO/BAD/BYE. after parsing the + [resp-text-code] the rest of the message can contain + pretty much any random text, which we can't parse + as if it was valid IMAP input */ + parser->cur_resp_text = TRUE; + } + break; + case ARG_PARSE_STRING: + if (!imap_parser_read_string(parser, data, data_size)) + return FALSE; + break; + case ARG_PARSE_LITERAL8: + if (parser->cur_pos == data_size) + return FALSE; + if (data[parser->cur_pos] != '{') { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Expected '{'"; + return FALSE; + } + parser->cur_type = ARG_PARSE_LITERAL; + parser->cur_pos++; + /* fall through */ + case ARG_PARSE_LITERAL: + if (!imap_parser_read_literal(parser, data, data_size)) + return FALSE; + + /* pass through to parsing data. since input->skip was + modified, we need to get the data start position again. */ + data = i_stream_get_data(parser->input, &data_size); + + /* fall through */ + case ARG_PARSE_LITERAL_DATA: + case ARG_PARSE_LITERAL_DATA_FORCED: + if (!imap_parser_read_literal_data(parser, data, data_size)) + return FALSE; + break; + case ARG_PARSE_TEXT: + if (!imap_parser_read_text(parser, data, data_size)) + return FALSE; + break; + default: + i_unreached(); + } + + i_assert(parser->cur_type == ARG_PARSE_NONE); + return TRUE; +} + +static void list_add_ghost_eol(struct imap_arg *list_arg) +{ + struct imap_arg *arg; + + i_assert(list_arg->type == IMAP_ARG_LIST); + + arg = array_append_space(&list_arg->_data.list); + arg->type = IMAP_ARG_EOL; + array_pop_back(&list_arg->_data.list); + + if (list_arg->parent != NULL) + list_add_ghost_eol(list_arg->parent); +} + +/* ARG_PARSE_NONE checks that last argument isn't only partially parsed. */ +#define IS_UNFINISHED(parser) \ + ((parser)->cur_type != ARG_PARSE_NONE || \ + ((parser)->cur_list != &parser->root_list && \ + ((parser)->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) == 0)) + +static int finish_line(struct imap_parser *parser, unsigned int count, + const struct imap_arg **args_r) +{ + struct imap_arg *arg; + int ret = array_count(&parser->root_list); + + parser->line_size += parser->cur_pos; + i_stream_skip(parser->input, parser->cur_pos); + parser->cur_pos = 0; + parser->cur_resp_text = FALSE; + + if (parser->list_arg == NULL) { + /* no open list */ + } else if (!parser->literal_size_return && + (parser->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) == 0) { + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + parser->error_msg = "Missing ')'"; + *args_r = NULL; + return -1; + } else { + list_add_ghost_eol(parser->list_arg); + } + + arg = array_append_space(&parser->root_list); + arg->type = IMAP_ARG_EOL; + parser->args_added_extra_eol = TRUE; + + *args_r = array_get(&parser->root_list, &count); + return ret; +} + +static void imap_parser_delete_extra_eol(struct imap_parser *parser) +{ + array_pop_back(&parser->root_list); + parser->args_added_extra_eol = FALSE; +} + +int imap_parser_read_args(struct imap_parser *parser, unsigned int count, + enum imap_parser_flags flags, + const struct imap_arg **args_r) +{ + parser->flags = flags; + + if (parser->args_added_extra_eol) { + /* delete EOL */ + imap_parser_delete_extra_eol(parser); + parser->literal_size_return = FALSE; + } + + while (!parser->eol && (count == 0 || IS_UNFINISHED(parser) || + array_count(&parser->root_list) < count)) { + if (!imap_parser_read_arg(parser)) + break; + + if (parser->line_size > parser->max_line_size) { + parser->error = IMAP_PARSE_ERROR_LINE_TOO_LONG; + parser->error_msg = "IMAP command line too large"; + break; + } + } + + if (parser->error != IMAP_PARSE_ERROR_NONE) { + /* error, abort */ + parser->line_size += parser->cur_pos; + i_stream_skip(parser->input, parser->cur_pos); + parser->cur_pos = 0; + *args_r = NULL; + return -1; + } else if ((!IS_UNFINISHED(parser) && count > 0 && + array_count(&parser->root_list) >= count) || + parser->eol || parser->literal_size_return) { + /* all arguments read / end of line. */ + return finish_line(parser, count, args_r); + } else { + /* need more data */ + *args_r = NULL; + return -2; + } +} + +static struct imap_arg * +imap_parser_get_last_literal_size(struct imap_parser *parser, + ARRAY_TYPE(imap_arg_list) **list_r) +{ + ARRAY_TYPE(imap_arg_list) *list; + struct imap_arg *args; + unsigned int count; + + list = &parser->root_list; + args = array_get_modifiable(&parser->root_list, &count); + i_assert(count > 1 && args[count-1].type == IMAP_ARG_EOL); + count--; + + while (args[count-1].type != IMAP_ARG_LITERAL_SIZE && + args[count-1].type != IMAP_ARG_LITERAL_SIZE_NONSYNC) { + if (args[count-1].type != IMAP_ARG_LIST) + return NULL; + + /* maybe the list ends with literal size */ + list = &args[count-1]._data.list; + args = array_get_modifiable(list, &count); + if (count == 0) + return NULL; + } + + *list_r = list; + return &args[count-1]; +} + +bool imap_parser_get_literal_size(struct imap_parser *parser, uoff_t *size_r) +{ + ARRAY_TYPE(imap_arg_list) *list; + struct imap_arg *last_arg; + + last_arg = imap_parser_get_last_literal_size(parser, &list); + if (last_arg == NULL) + return FALSE; + + return imap_arg_get_literal_size(last_arg, size_r); +} + +void imap_parser_read_last_literal(struct imap_parser *parser) +{ + ARRAY_TYPE(imap_arg_list) *list; + struct imap_arg *last_arg; + + i_assert(parser->literal_size_return); + i_assert(parser->args_added_extra_eol); + + last_arg = imap_parser_get_last_literal_size(parser, &list); + i_assert(last_arg != NULL); + + parser->cur_type = ARG_PARSE_LITERAL_DATA_FORCED; + i_assert(parser->literal_size == last_arg->_data.literal_size); + + /* delete EOL */ + imap_parser_delete_extra_eol(parser); + + /* delete literal size */ + array_pop_back(list); + parser->literal_size_return = FALSE; +} + +int imap_parser_finish_line(struct imap_parser *parser, unsigned int count, + enum imap_parser_flags flags, + const struct imap_arg **args_r) +{ + const unsigned char *data; + size_t data_size; + int ret; + + ret = imap_parser_read_args(parser, count, flags, args_r); + if (ret == -1) + return -1; + if (ret == -2) { + /* we should have noticed end of everything except atom */ + if (parser->cur_type == ARG_PARSE_ATOM) { + data = i_stream_get_data(parser->input, &data_size); + imap_parser_save_arg(parser, data, data_size); + } + } + return finish_line(parser, count, args_r); +} + +const char *imap_parser_read_word(struct imap_parser *parser) +{ + const unsigned char *data; + size_t i, data_size; + + data = i_stream_get_data(parser->input, &data_size); + + for (i = 0; i < data_size; i++) { + if (data[i] == ' ' || data[i] == '\r' || data[i] == '\n') + break; + } + + if (i < data_size) { + data_size = i + (data[i] == ' ' ? 1 : 0); + parser->line_size += data_size; + i_stream_skip(parser->input, data_size); + return p_strndup(parser->pool, data, i); + } else { + return NULL; + } +} + +static int +imap_parser_read_next_atom(struct imap_parser *parser, bool parsing_tag, + const char **atom_r) +{ + const unsigned char *data; + size_t i, data_size; + + data = i_stream_get_data(parser->input, &data_size); + + /* + tag = 1*<any ASTRING-CHAR except "+"> + ASTRING-CHAR = ATOM-CHAR / resp-specials + ATOM-CHAR = <any CHAR except atom-specials> + + x-command = "X" atom <experimental command arguments> + atom = 1*ATOM-CHAR + */ + for (i = 0; i < data_size; i++) { + /* explicitly check for atom-specials, because + IS_ATOM_PARSER_INPUT() allows some atom-specials */ + switch (data[i]) { + case ' ': + case '\r': + case '\n': + data_size = i + (data[i] == ' ' ? 1 : 0); + parser->line_size += data_size; + i_stream_skip(parser->input, data_size); + *atom_r = p_strndup(parser->pool, data, i); + /* don't allow empty string */ + return i == 0 ? -1 : 1; + /* atom-specials: */ + case '(': + case ')': + case '{': + /* list-wildcards: */ + case '%': + case '*': + /* quoted-specials: */ + case '"': + case '\\': + /* resp-specials: */ + case ']': + return -1; + case '+': + if (parsing_tag) + return -1; + break; + default: + if ((unsigned char)data[i] < ' ' || + (unsigned char)data[i] >= 0x80) + return -1; + } + } + return 0; +} + +int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r) +{ + return imap_parser_read_next_atom(parser, TRUE, tag_r); +} + +int imap_parser_read_command_name(struct imap_parser *parser, + const char **name_r) +{ + return imap_parser_read_next_atom(parser, FALSE, name_r); +} + +int imap_parser_client_read_tag(struct imap_parser *parser, + const char **tag_r) +{ + return imap_parser_read_next_atom(parser, FALSE, tag_r); +} diff --git a/src/lib-imap/imap-parser.h b/src/lib-imap/imap-parser.h new file mode 100644 index 0000000..cd3748c --- /dev/null +++ b/src/lib-imap/imap-parser.h @@ -0,0 +1,117 @@ +#ifndef IMAP_PARSER_H +#define IMAP_PARSER_H + +#include "imap-arg.h" + +enum imap_parser_flags { + /* Set this flag if you wish to read only size of literal argument + and not convert literal into string. Useful when you need to deal + with large literal sizes. The literal must be the last read + parameter. */ + IMAP_PARSE_FLAG_LITERAL_SIZE = 0x01, + /* Don't remove '\' chars from string arguments */ + IMAP_PARSE_FLAG_NO_UNESCAPE = 0x02, + /* Return literals as IMAP_ARG_LITERAL instead of IMAP_ARG_STRING */ + IMAP_PARSE_FLAG_LITERAL_TYPE = 0x04, + /* Don't check if atom contains invalid characters */ + IMAP_PARSE_FLAG_ATOM_ALLCHARS = 0x08, + /* Allow strings to contain CRLFs */ + IMAP_PARSE_FLAG_MULTILINE_STR = 0x10, + /* Parse in list context; ')' parses as EOL */ + IMAP_PARSE_FLAG_INSIDE_LIST = 0x20, + /* Parse literal8 and set it as flag to imap_arg. */ + IMAP_PARSE_FLAG_LITERAL8 = 0x40, + /* We're parsing IMAP server replies. Parse the "text" after + OK/NO/BAD/BYE replies as a single atom. We assume that the initial + "*" or tag was already skipped over. */ + IMAP_PARSE_FLAG_SERVER_TEXT = 0x80, + /* Parse until '(' and return it as an empty list */ + IMAP_PARSE_FLAG_STOP_AT_LIST = 0x100 +}; + +enum imap_parser_error { + /* not fatal */ + IMAP_PARSE_ERROR_NONE = 0, + IMAP_PARSE_ERROR_BAD_SYNTAX, + IMAP_PARSE_ERROR_LINE_TOO_LONG, + /* fatal */ + IMAP_PARSE_ERROR_LITERAL_TOO_BIG +}; + +struct imap_parser; + +/* Create new IMAP argument parser. output is used for sending command + continuation requests for literals. + + max_line_size can be used to approximately limit the maximum amount of + memory that gets allocated when parsing a line. Input buffer size limits + the maximum size of each parsed token. + + Usually the largest lines are large only because they have a one huge + message set token, so you'll probably want to keep input buffer size the + same as max_line_size. That means the maximum memory usage is around + 2 * max_line_size. */ +struct imap_parser * +imap_parser_create(struct istream *input, struct ostream *output, + size_t max_line_size) ATTR_NULL(2); +void imap_parser_ref(struct imap_parser *parser); +void imap_parser_unref(struct imap_parser **parser); + +/* Enable LITERAL- parser semantics: non-synchronizing literals must not + exceed 4096 bytes */ +void imap_parser_enable_literal_minus(struct imap_parser *parser); + +/* Reset the parser to initial state. */ +void imap_parser_reset(struct imap_parser *parser); + +/* Change parser's input and output streams */ +void imap_parser_set_streams(struct imap_parser *parser, struct istream *input, + struct ostream *output) ATTR_NULL(3); + +/* Return the last error in parser. fatal is set to TRUE if there's no way to + continue parsing, currently only if too large non-sync literal size was + given. */ +const char *imap_parser_get_error(struct imap_parser *parser, + enum imap_parser_error *error_r) ATTR_NULL(2); + +/* Read a number of arguments. This function doesn't call i_stream_read(), you + need to do that. Returns number of arguments read (may be less than count + in case of EOL), -2 if more data is needed or -1 if error occurred. + + count-sized array of arguments are stored into args when return value is + 0 or larger. If all arguments weren't read, they're set to NIL. count + can be set to 0 to read all arguments in the line. Last element in + args is always of type IMAP_ARG_EOL. */ +int imap_parser_read_args(struct imap_parser *parser, unsigned int count, + enum imap_parser_flags flags, + const struct imap_arg **args_r); +/* If parsing ended with literal size, return it. */ +bool imap_parser_get_literal_size(struct imap_parser *parser, uoff_t *size_r); +/* IMAP_PARSE_FLAG_LITERAL_SIZE is set and last read argument was a literal. + Calling this function causes the literal size to be replaced with the actual + literal data when continuing argument parsing. */ +void imap_parser_read_last_literal(struct imap_parser *parser); + +/* just like imap_parser_read_args(), but assume \n at end of data in + input stream. */ +int imap_parser_finish_line(struct imap_parser *parser, unsigned int count, + enum imap_parser_flags flags, + const struct imap_arg **args_r); + +/* Read one word - used for reading tag and command name. + Returns NULL if more data is needed. */ +const char *imap_parser_read_word(struct imap_parser *parser); +/* Read command tag. Returns 1 if tag was returned, 0 if more data is needed, + -1 if input isn't a valid tag. */ +int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r); +/* Read command name. Returns 1 if command name was returned, 0 if more data is + needed, -1 if input isn't a valid command name string. */ +int imap_parser_read_command_name(struct imap_parser *parser, + const char **name_r); +/* For IMAP clients: Read the command tag, which could also be "+" or "*". + Returns 1 if tag was returned, 0 if more data is needed, -1 if input isn't + valid. */ +int imap_parser_client_read_tag(struct imap_parser *parser, + const char **tag_r); + +#endif diff --git a/src/lib-imap/imap-quote.c b/src/lib-imap/imap-quote.c new file mode 100644 index 0000000..622e21c --- /dev/null +++ b/src/lib-imap/imap-quote.c @@ -0,0 +1,239 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "imap-arg.h" +#include "imap-quote.h" + +/* If we have quoted-specials (<">, <\>) in a string, the minimum quoted-string + overhead is 3 bytes ("\") while the minimum literal overhead is 5 bytes + ("{n}\r\n"). But the literal overhead also depends on the string size. If + the string length is less than 10, literal catches up to quoted-string after + 3 quoted-specials. If the string length is 10..99, it catches up after 4 + quoted-specials, and so on. We'll assume that the string lengths are usually + in double digits, so we'll switch to literals after seeing 4 + quoted-specials. */ +#define QUOTED_MAX_ESCAPE_CHARS 4 + +void imap_append_string(string_t *dest, const char *src) +{ + i_assert(src != NULL); + + imap_append_nstring(dest, src); +} + +void imap_append_astring(string_t *dest, const char *src) +{ + unsigned int i; + + i_assert(src != NULL); + + for (i = 0; src[i] != '\0'; i++) { + if (!IS_ASTRING_CHAR(src[i])) { + imap_append_string(dest, src); + return; + } + } + /* don't mix up NIL and "NIL"! */ + if (i == 0 || strcasecmp(src, "NIL") == 0) + imap_append_string(dest, src); + else + str_append(dest, src); +} + +static void +imap_append_literal(string_t *dest, const char *src, unsigned int pos) +{ + size_t full_len = pos + strlen(src+pos); + + str_printfa(dest, "{%zu}\r\n", full_len); + buffer_append(dest, src, full_len); +} + +void imap_append_nstring(string_t *dest, const char *src) +{ + unsigned int escape_count = 0; + size_t i; + + if (src == NULL) { + str_append(dest, "NIL"); + return; + } + + /* first check if we can (or want to) write this as quoted or + as literal. + + quoted-specials = DQUOTE / "\" + QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> / + "\" quoted-specials + TEXT-CHAR = <any CHAR except CR and LF> + */ + for (i = 0; src[i] != '\0'; i++) { + switch (src[i]) { + case '"': + case '\\': + if (escape_count++ < QUOTED_MAX_ESCAPE_CHARS) + break; + /* fall through */ + case 13: + case 10: + imap_append_literal(dest, src, i); + return; + default: + if ((unsigned char)src[i] >= 0x80) { + imap_append_literal(dest, src, i); + return; + } + break; + } + } + imap_append_quoted(dest, src); +} + +static void remove_newlines_and_append(string_t *dest, const char *src) +{ + size_t src_len; + string_t *src_nolf; + src_len = strlen(src); + src_nolf = t_str_new(src_len + 1); + for (size_t i = 0; i < src_len; ++i) { + if (src[i] != '\r' && src[i] != '\n') { + str_append_c(src_nolf, src[i]); + } else if (src[i+1] != ' ' && + src[i+1] != '\t' && + src[i+1] != '\r' && + src[i+1] != '\n' && + src[i+1] != '\0') { + /* ensure whitespace between lines if new line doesn't start with whitespace */ + str_append_c(src_nolf, ' '); + } + } + imap_append_nstring(dest, str_c(src_nolf)); +} + +void imap_append_nstring_nolf(string_t *dest, const char *src) +{ + if (src == NULL || strpbrk(src, "\r\n") == NULL) + imap_append_nstring(dest, src); + else if (buffer_get_pool(dest)->datastack_pool) + remove_newlines_and_append(dest, src); + else T_BEGIN { + remove_newlines_and_append(dest, src); + } T_END; +} + +void imap_append_quoted(string_t *dest, const char *src) +{ + str_append_c(dest, '"'); + for (; *src != '\0'; src++) { + switch (*src) { + case 13: + case 10: + /* not allowed */ + break; + case '"': + case '\\': + str_append_c(dest, '\\'); + str_append_c(dest, *src); + break; + default: + if ((unsigned char)*src >= 0x80) { + /* 8bit input not allowed in dquotes */ + break; + } + + str_append_c(dest, *src); + break; + } + } + str_append_c(dest, '"'); +} + +void imap_append_string_for_humans(string_t *dest, + const unsigned char *src, size_t size) +{ + size_t i, pos, remove_count = 0; + bool whitespace_prefix = TRUE, last_lwsp = TRUE, modify = FALSE; + + /* first check if there is anything to change */ + for (i = 0; i < size; i++) { + switch (src[i]) { + case 0: + /* convert NUL to #0x80 */ + last_lwsp = FALSE; + modify = TRUE; + break; + case 13: + case 10: + case '\t': + modify = TRUE; + /* fall through */ + case ' ': + if (last_lwsp) { + modify = TRUE; + remove_count++; + } + last_lwsp = TRUE; + break; + case '"': + case '\\': + modify = TRUE; + last_lwsp = FALSE; + break; + default: + if ((src[i] & 0x80) != 0) + modify = TRUE; + last_lwsp = FALSE; + break; + } + if (!last_lwsp) + whitespace_prefix = FALSE; + } + if (last_lwsp && i > 0 && !whitespace_prefix) { + modify = TRUE; + remove_count++; + } + if (!modify) { + /* fast path: we can simply write it as quoted string + without any escaping */ + str_append_c(dest, '"'); + str_append_data(dest, src, size); + str_append_c(dest, '"'); + return; + } + if (size == remove_count) { + /* contained only whitespace */ + str_append(dest, "\"\""); + return; + } + + str_printfa(dest, "{%zu}\r\n", size - remove_count); + pos = str_len(dest); + + last_lwsp = TRUE; whitespace_prefix = TRUE; + for (i = 0; i < size; i++) { + switch (src[i]) { + case 0: + str_append_c(dest, 128); + last_lwsp = FALSE; + break; + case 13: + case 10: + case '\t': + case ' ': + if (!last_lwsp) + str_append_c(dest, ' '); + last_lwsp = TRUE; + break; + default: + last_lwsp = FALSE; + str_append_c(dest, src[i]); + break; + } + if (!last_lwsp) + whitespace_prefix = FALSE; + } + if (last_lwsp && i > 0 && !whitespace_prefix) + str_truncate(dest, str_len(dest)-1); + i_assert(str_len(dest) - pos == size - remove_count); +} diff --git a/src/lib-imap/imap-quote.h b/src/lib-imap/imap-quote.h new file mode 100644 index 0000000..a397ec3 --- /dev/null +++ b/src/lib-imap/imap-quote.h @@ -0,0 +1,21 @@ +#ifndef IMAP_QUOTE_H +#define IMAP_QUOTE_H + +/* Append "quoted" or literal. */ +void imap_append_string(string_t *dest, const char *src); +/* Append atom, "quoted" or literal. */ +void imap_append_astring(string_t *dest, const char *src); +/* Append NIL, "quoted" or literal. */ +void imap_append_nstring(string_t *dest, const char *src); +/* Append NIL, "quoted" or literal, CRs and LFs skipped. */ +void imap_append_nstring_nolf(string_t *dest, const char *src); +/* Append "quoted". If src has 8bit chars, skip over them. */ +void imap_append_quoted(string_t *dest, const char *src); + +/* Otherwise the same as imap_append_string(), but cleanup the input data + so that it's more readable by humans. This includes converting TABs to + spaces, multiple spaces into a single space and NULs to #0x80. */ +void imap_append_string_for_humans(string_t *dest, + const unsigned char *src, size_t size); + +#endif diff --git a/src/lib-imap/imap-resp-code.h b/src/lib-imap/imap-resp-code.h new file mode 100644 index 0000000..a7a4de8 --- /dev/null +++ b/src/lib-imap/imap-resp-code.h @@ -0,0 +1,28 @@ +#ifndef IMAP_RESP_CODE_H +#define IMAP_RESP_CODE_H + +/* IMAP response codes (RFC 5530) */ +#define IMAP_RESP_CODE_UNAVAILABLE "UNAVAILABLE" +#define IMAP_RESP_CODE_AUTHFAILED "AUTHENTICATIONFAILED" +#define IMAP_RESP_CODE_AUTHZFAILED "AUTHORIZATIONFAILED" +#define IMAP_RESP_CODE_EXPIRED "EXPIRED" +#define IMAP_RESP_CODE_PRIVACYREQUIRED "PRIVACYREQUIRED" +#define IMAP_RESP_CODE_CONTACTADMIN "CONTACTADMIN" +#define IMAP_RESP_CODE_NOPERM "NOPERM" +#define IMAP_RESP_CODE_INUSE "INUSE" +#define IMAP_RESP_CODE_EXPUNGEISSUED "EXPUNGEISSUED" +#define IMAP_RESP_CODE_CORRUPTION "CORRUPTION" +#define IMAP_RESP_CODE_SERVERBUG "SERVERBUG" +#define IMAP_RESP_CODE_CLIENTBUG "CLIENTBUG" +#define IMAP_RESP_CODE_CANNOT "CANNOT" +#define IMAP_RESP_CODE_LIMIT "LIMIT" +#define IMAP_RESP_CODE_OVERQUOTA "OVERQUOTA" +#define IMAP_RESP_CODE_ALREADYEXISTS "ALREADYEXISTS" +#define IMAP_RESP_CODE_NONEXISTENT "NONEXISTENT" + +#define IMAP_RESP_CODE_UNKNOWN_CTE "UNKNOWN-CTE" /* BINARY */ + +/* IMAP standard (RFC 3501) */ +#define IMAP_RESP_CODE_PARSE "PARSE" + +#endif diff --git a/src/lib-imap/imap-seqset.c b/src/lib-imap/imap-seqset.c new file mode 100644 index 0000000..5e7ea21 --- /dev/null +++ b/src/lib-imap/imap-seqset.c @@ -0,0 +1,105 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "imap-seqset.h" + +static uint32_t get_next_number(const char **str) +{ + uint32_t num; + + num = 0; + while (**str != '\0') { + if (**str < '0' || **str > '9') + break; + + num = num*10 + (**str - '0'); + (*str)++; + } + + if (num == (uint32_t)-1) { + /* FIXME: ugly hack, we're using this number to mean the + last existing message. In reality UIDs should never get + this high, so we can quite safely just drop this one down. */ + num--; + } + + return num; +} + +static int +get_next_seq_range(const char **str, uint32_t *seq1_r, uint32_t *seq2_r) +{ + uint32_t seq1, seq2; + + if (**str == '*') { + /* last message */ + seq1 = (uint32_t)-1; + *str += 1; + } else { + seq1 = get_next_number(str); + if (seq1 == 0) + return -1; + } + + if (**str != ':') + seq2 = seq1; + else { + /* first:last range */ + *str += 1; + + if (**str == '*') { + seq2 = (uint32_t)-1; + *str += 1; + } else { + seq2 = get_next_number(str); + if (seq2 == 0) + return -1; + } + } + + if (seq1 > seq2) { + /* swap, as specified by RFC-3501 */ + *seq1_r = seq2; + *seq2_r = seq1; + } else { + *seq1_r = seq1; + *seq2_r = seq2; + } + return 0; +} + +int imap_seq_set_parse(const char *str, ARRAY_TYPE(seq_range) *dest) +{ + uint32_t seq1, seq2; + + while (*str != '\0') { + if (get_next_seq_range(&str, &seq1, &seq2) < 0) + return -1; + seq_range_array_add_range(dest, seq1, seq2); + + if (*str == ',') + str++; + else if (*str != '\0') + return -1; + } + return 0; +} + +int imap_seq_set_nostar_parse(const char *str, ARRAY_TYPE(seq_range) *dest) +{ + if (imap_seq_set_parse(str, dest) < 0) + return -1; + + if (seq_range_exists(dest, (uint32_t)-1)) { + /* '*' used */ + return -1; + } + return 0; +} + +int imap_seq_range_parse(const char *str, uint32_t *seq1_r, uint32_t *seq2_r) +{ + if (get_next_seq_range(&str, seq1_r, seq2_r) < 0) + return -1; + return *str == '\0' ? 0 : -1; +} diff --git a/src/lib-imap/imap-seqset.h b/src/lib-imap/imap-seqset.h new file mode 100644 index 0000000..a7e1ffd --- /dev/null +++ b/src/lib-imap/imap-seqset.h @@ -0,0 +1,15 @@ +#ifndef IMAP_SEQSET_H +#define IMAP_SEQSET_H + +#include "seq-range-array.h" + +/* Parse IMAP sequence-set and store the result in dest. '*' is stored as + (uint32_t)-1. Returns 0 if successful, -1 if input is invalid. */ +int imap_seq_set_parse(const char *str, ARRAY_TYPE(seq_range) *dest); +/* Like imap_seq_set_parse(), but fail if '*' is used. */ +int imap_seq_set_nostar_parse(const char *str, ARRAY_TYPE(seq_range) *dest); + +/* Parse IMAP seq-number / seq-range. */ +int imap_seq_range_parse(const char *str, uint32_t *seq1_r, uint32_t *seq2_r); + +#endif diff --git a/src/lib-imap/imap-url.c b/src/lib-imap/imap-url.c new file mode 100644 index 0000000..6da6e21 --- /dev/null +++ b/src/lib-imap/imap-url.c @@ -0,0 +1,1009 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strfuncs.h" +#include "str-sanitize.h" +#include "hex-binary.h" +#include "net.h" +#include "iso8601-date.h" +#include "uri-util.h" + +#include "imap-url.h" + +#include <ctype.h> + +/* + * IMAP URL parsing + */ + +/* +IMAP URL Grammar overview + +RFC5092 Section 11: + +imapurl = "imap://" iserver ipath-query + ; Defines an absolute IMAP URL +iserver = [iuserinfo "@"] host [ ":" port ] + ; This is the same as "authority" defined in [URI-GEN]. +iuserinfo = enc-user [iauth] / [enc-user] iauth + ; conforms to the generic syntax of "userinfo" as + ; defined in [URI-GEN]. +enc-user = 1*achar + ; %-encoded version of [IMAP4] authorization identity or + ; "userid". +iauth = ";AUTH=" ( "*" / enc-auth-type ) +enc-auth-type = 1*achar + ; %-encoded version of [IMAP4] "auth-type" +ipath-query = ["/" [ icommand ]] + ; Corresponds to "path-abempty [ "?" query ]" in + ; [URI-GEN] +icommand = imessagelist / + imessagepart [iurlauth] +imessagelist = imailbox-ref [ "?" enc-search ] + ; "enc-search" is [URI-GEN] "query". +imessagepart = imailbox-ref iuid [isection] [ipartial] +imailbox-ref = enc-mailbox [uidvalidity] +uidvalidity = ";UIDVALIDITY=" nz-number + ; See [IMAP4] for "nz-number" definition +iuid = "/" iuid-only +iuid-only = ";UID=" nz-number + ; See [IMAP4] for "nz-number" definition +isection = "/" isection-only +isection-only = ";SECTION=" enc-section +ipartial = "/" ipartial-only +ipartial-only = ";PARTIAL=" partial-range +enc-search = 1*bchar + ; %-encoded version of [IMAPABNF] + ; "search-program". Note that IMAP4 + ; literals may not be used in + ; a "search-program", i.e., only + ; quoted or non-synchronizing + ; literals (if the server supports + ; LITERAL+ [LITERAL+]) are allowed. +enc-mailbox = 1*bchar + ; %-encoded version of [IMAP4] "mailbox" +enc-section = 1*bchar + ; %-encoded version of [IMAP4] "section-spec" +partial-range = number ["." nz-number] + ; partial FETCH. The first number is + ; the offset of the first byte, + ; the second number is the length of + ; the fragment. +bchar = achar / ":" / "@" / "/" +achar = uchar / "&" / "=" + ;; Same as [URI-GEN] 'unreserved / sub-delims / + ;; pct-encoded', but ";" is disallowed. +uchar = unreserved / sub-delims-sh / pct-encoded +sub-delims-sh = "!" / "$" / "'" / "(" / ")" / + "*" / "+" / "," + ;; Same as [URI-GEN] sub-delims, + ;; but without ";", "&" and "=". + +The following rules are only used in the presence of the IMAP +[URLAUTH] extension: + +authimapurl = "imap://" iserver "/" imessagepart + ; Same as "imapurl" when "[icommand]" is + ; "imessagepart" +authimapurlfull = authimapurl iurlauth + ; Same as "imapurl" when "[icommand]" is + ; "imessagepart iurlauth" +authimapurlrump = authimapurl iurlauth-rump + +iurlauth = iurlauth-rump iua-verifier +enc-urlauth = 32*HEXDIG +iua-verifier = ":" uauth-mechanism ":" enc-urlauth +iurlauth-rump = [expire] ";URLAUTH=" access +access = ("submit+" enc-user) / ("user+" enc-user) / + "authuser" / "anonymous" +expire = ";EXPIRE=" date-time + ; date-time is defined in [DATETIME] +uauth-mechanism = "INTERNAL" / 1*(ALPHA / DIGIT / "-" / ".") + ; Case-insensitive. + +[URI-GEN] RFC3986 Appendix A: + +Implemented in src/lib/uri-util.c + +*/ + +/* + * Imap URL parser + */ + +struct imap_url_parser { + struct uri_parser parser; + + enum imap_url_parse_flags flags; + + struct imap_url *url; + const struct imap_url *base; + + bool relative:1; +}; + +static int +imap_url_parse_number(struct uri_parser *parser, const char *data, + uint32_t *number_r) +{ + /* [IMAP4] RFC3501, Section 9 + * + * number = 1*DIGIT + * ; Unsigned 32-bit integer + * ; (0 <= n < 4,294,967,296) + */ + + if (i_isdigit(*data)) { + if (str_to_uint32(data, number_r) == 0) + return 1; + parser->error = "IMAP number is too high"; + return -1; + } + + parser->error = t_strdup_printf( + "Value '%s' is not a valid IMAP number", data); + return -1; +} + +static int +imap_url_parse_offset(struct uri_parser *parser, const char *data, + uoff_t *number_r) +{ + /* Syntax for big (uoff_t) numbers. Not strictly IMAP syntax, but this + is handled similarly for Dovecot IMAP FETCH BODY partial <.> + implementation. */ + if (i_isdigit(*data)) { + if (str_to_uoff(data, number_r) == 0) + return 1; + parser->error = "IMAP number is too high"; + return -1; + } + + parser->error = t_strdup_printf( + "Value '%s' is not a valid IMAP number", data); + return -1; +} + +static int imap_url_parse_iserver(struct imap_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + struct uri_authority auth; + struct imap_url *url = url_parser->url; + const char *data; + int ret = 0; + + /* imapurl = "imap://" iserver {...} + * inetwork-path = "//" iserver {...} + * iserver = [iuserinfo "@"] host [":" port] + * ; This is the same as "authority" defined + * ; in [URI-GEN]. + * iuserinfo = enc-user [iauth] / [enc-user] iauth + * ; conforms to the generic syntax of "userinfo" as + * ; defined in [URI-GEN]. + * enc-user = 1*achar + * ; %-encoded version of [IMAP4] authorization identity or + * ; "userid". + * iauth = ";AUTH=" ( "*" / enc-auth-type ) + * enc-auth-type = 1*achar + * ; %-encoded version of [IMAP4] "auth-type" + */ + + /* "//" iserver */ + if ((ret = uri_parse_slashslash_host_authority + (parser, &auth)) <= 0) + return ret; + if (auth.host.name == NULL || *auth.host.name == '\0') { + /* This situation is not documented anywhere, but it is not + currently useful either and potentially problematic if not + handled explicitly everywhere. So, it is denied hier for now. + */ + parser->error = "IMAP URL does not allow empty host identifier"; + return -1; + } + /* iuserinfo = enc-user [iauth] / [enc-user] iauth */ + if (auth.enc_userinfo != NULL) { + const char *p, *uend; + + /* Scan for ";AUTH=" */ + for (p = auth.enc_userinfo; *p != '\0'; p++) { + if (*p == ';') + break; + /* check for unallowed userinfo characters */ + if (*p == ':') { + parser->error = t_strdup_printf( + "Stray ':' in userinfo `%s'", auth.enc_userinfo); + return -1; + } + } + + uend = p; + + if (*p == ';') { + if (strncasecmp(p, ";AUTH=", 6) != 0) { + parser->error = t_strdup_printf( + "Stray ';' in userinfo `%s'", + auth.enc_userinfo); + return -1; + } + + for (p += 6; *p != '\0'; p++) { + if (*p == ';' || *p == ':') { + parser->error = t_strdup_printf( + "Stray '%c' in userinfo `%s'", *p, auth.enc_userinfo); + return -1; + } + } + } + + /* enc-user */ + if (url != NULL && uend > auth.enc_userinfo) { + if (!uri_data_decode(parser, auth.enc_userinfo, uend, &data)) + return -1; + url->userid = p_strdup(parser->pool, data); + } + + /* ( "*" / enc-auth-type ) */ + if (*uend == ';') { + p = uend + 6; + if (*p == '\0') { + parser->error = "Empty auth-type value after ';AUTH='"; + return -1; + } + if (url != NULL) { + if (!uri_data_decode(parser, p, NULL, &data)) + return -1; + url->auth_type = p_strdup(parser->pool, data); + } + } + } + + if (url != NULL) { + url->host = auth.host; + url->port = auth.port; + } + return 1; +} + +static int +imap_url_parse_urlauth(struct imap_url_parser *url_parser, const char *urlext) +{ + struct uri_parser *parser = &url_parser->parser; + struct imap_url *url = url_parser->url; + const char *p, *q, *data; + buffer_t *uauth_token; + time_t expire = (time_t)-1; + int tz; + + /* iurlauth = iurlauth-rump iua-verifier + * enc-urlauth = 32*HEXDIG + * iua-verifier = ":" uauth-mechanism ":" enc-urlauth + * iurlauth-rump = [expire] ";URLAUTH=" access + * access = ("submit+" enc-user) / ("user+" enc-user) / + * "authuser" / "anonymous" + * expire = ";EXPIRE=" date-time + * ; date-time is defined in [DATETIME] + * uauth-mechanism = "INTERNAL" / 1*(ALPHA / DIGIT / "-" / ".") + * ; Case-insensitive. + */ + + /* ";EXPIRE=" date-time */ + if (strncasecmp(urlext, ";EXPIRE=", 8) == 0) { + if ((url_parser->flags & IMAP_URL_PARSE_ALLOW_URLAUTH) == 0) { + parser->error = "`;EXPIRE=' is not allowed in this context"; + return -1; + } + + if ((p = strchr(urlext+8, ';')) != NULL) { + if (!iso8601_date_parse((const unsigned char *)urlext+8, + p-urlext-8, &expire, &tz)) { + parser->error = "invalid date-time for `;EXPIRE='"; + return -1; + } + urlext = p; + } + } + + /* ";URLAUTH=" access */ + if (strncasecmp(urlext, ";URLAUTH=", 9) != 0) { + if (expire != (time_t)-1) { + parser->error = "`;EXPIRE=' without `;URLAUTH='"; + return -1; + } + return 0; + } + urlext += 9; + + if (url != NULL) + url->uauth_expire = expire; + + if ((url_parser->flags & IMAP_URL_PARSE_ALLOW_URLAUTH) == 0) { + parser->error = "`;URLAUTH=' is not allowed in this context"; + return -1; + } + + if (url_parser->relative) { + parser->error = "IMAP URLAUTH requires absolute URL"; + return -1; + } + + if ((p = strchr(urlext, ':')) == NULL) { + size_t len = strlen(urlext); + if (len == 0) { + parser->error = "Missing URLAUTH access specifier"; + return -1; + } + p = urlext+len; + } else if (p == urlext) { + parser->error = "Empty URLAUTH access specifier"; + return -1; + } + + /* parse access */ + if ((q = strchr(urlext, '+')) == NULL) { + /* application */ + if (url != NULL) { + url->uauth_access_application = + p_strdup_until(parser->pool, urlext, p); + } + } else { + /* application "+" enc-user */ + if (urlext == q) { + parser->error = "Empty URLAUTH access application"; + return -1; + } + if (q+1 == p) { + parser->error = t_strdup_printf( + "Empty URLAUTH access user for `%s' application", + t_strdup_until(urlext, q)); + return -1; + } + if (!uri_data_decode(parser, q+1, p, &data)) + return -1; + if (url != NULL) { + url->uauth_access_application = + p_strdup_until(parser->pool, urlext, q); + url->uauth_access_user = p_strdup(parser->pool, data); + } + } + + if (url != NULL) { + /* get rump url */ + if ((url_parser->flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0) { + url->uauth_rumpurl = p_strdup_until(parser->pool, + parser->begin, parser->end-strlen(p)); + } else { + url->uauth_rumpurl = p_strconcat(parser->pool, "imap:", + t_strdup_until(parser->begin, parser->end-strlen(p)), + NULL); + } + } + + if (*p == '\0') { + /* rump url; caller should check whether this is appropriate */ + return 1; + } + + /* iua-verifier = ":" uauth-mechanism ":" enc-urlauth */ + + q = p + 1; + if (*q == '\0') { + parser->error = "Missing URLAUTH verifier"; + return -1; + } + if ((p = strchr(q, ':')) == NULL || p[1] == '\0') { + parser->error = "Missing URLAUTH token"; + return -1; + } + if (p == q) { + parser->error = "Missing URLAUTH mechanism"; + return -1; + } + if (url != NULL) { + /* get mechanism */ + url->uauth_mechanism = p_strdup_until(parser->pool, q, p); + } + + /* enc-urlauth = 32*HEXDIG */ + + q = p+1; + if (strlen(q) < 32) { + parser->error = "Too short URLAUTH token"; + return -1; + } + + uauth_token = t_buffer_create(64); + if (hex_to_binary(q, uauth_token) < 0) { + parser->error = "Invalid URLAUTH token"; + return -1; + } + + if (url != NULL) { + url->uauth_token = uauth_token->data; + url->uauth_token_size = uauth_token->used; + } + return 1; +} + +static int +imap_url_parse_path(struct imap_url_parser *url_parser, + const char *const *path, int relative, + bool *is_messagelist_r) +{ + struct uri_parser *parser = &url_parser->parser; + struct imap_url *url = url_parser->url; + const char *const *segment; + string_t *mailbox, *section = NULL; + uint32_t uid = 0, uidvalidity = 0; + uoff_t partial_offset = 0, partial_size = 0; + bool have_partial = FALSE; + const char *p, *value, *urlext = NULL; + bool mailbox_endslash = FALSE, section_endslash = FALSE; + int ret; + + /* icommand = imessagelist / + * imessagepart [iurlauth] + * imessagelist = imailbox-ref [ "?" enc-search ] + * ; "enc-search" is [URI-GEN] "query". + * imessagepart = imailbox-ref iuid [isection] [ipartial] + * imailbox-ref = enc-mailbox [uidvalidity] + * uidvalidity = ";UIDVALIDITY=" nz-number + * iuid = "/" iuid-only + * iuid-only = ";UID=" nz-number + * ; See [IMAP4] for "nz-number" definition + * isection = "/" isection-only + * isection-only = ";SECTION=" enc-section + * ipartial = "/" ipartial-only + * ipartial-only = ";PARTIAL=" partial-range + * enc-mailbox = 1*bchar + * ; %-encoded version of [IMAP4] "mailbox" + * enc-section = 1*bchar + * ; %-encoded version of [IMAP4] "section-spec" + * partial-range = number ["." nz-number] + * ; partial FETCH. The first number is + * ; the offset of the first byte, + * ; the second number is the length of + * ; the fragment. + */ + + /* IMAP URL syntax is quite horrible to parse. It relies upon the + generic URI path resolution, but the icommand syntax also relies on + ';' separators. We use the generic URI path parse functions to + adhere to the URI path resolution rules and glue back together path + segments when these are part of the same (mailbox or section) value. + */ + + mailbox = t_str_new(256); + segment = path; + + /* Resolve relative URI path; determine what to copy from the base URI */ + if (url != NULL && url_parser->base != NULL && relative > 0) { + const struct imap_url *base = url_parser->base; + int rel = relative; + + /* /;PARTIAL= */ + if (base->have_partial && --rel <= 0) { + have_partial = base->have_partial; + partial_offset = base->partial_offset; + partial_size = base->partial_size; + } + /* /;SECTION= */ + if (base->section != NULL) { + p = base->section + strlen(base->section); + /* determine what to retain from base section path */ + for (; p > base->section && rel > 0; p--) { + if (*p =='/' && --rel <= 0) break; + } + if (--rel <= 0 && p > base->section) { + if (p[-1] == '/') section_endslash = TRUE; + if (section == NULL) + section = t_str_new(256); + str_append_data(section, base->section, p-base->section); + } + } + /* /;UID= */ + if (base->uid > 0 && --rel <= 0) { + uid = base->uid; + } + /* /mail/box;UIDVALIDITY= */ + if (base->mailbox != NULL) { + uidvalidity = base->uidvalidity; + p = base->mailbox + strlen(base->mailbox); + /* mailbox has implicit trailing '/' */ + if (p[-1] != '/' && base->uid == 0 && rel > 0) + rel--; + /* determine what to retain from base mailbox path */ + for (; p > base->mailbox && rel > 0; p--) { + if (*p =='/') { + uidvalidity = 0; + if (--rel <= 0) + break; + } + } + if (--rel <= 0 && p > base->mailbox) { + if (p[-1] == '/') + mailbox_endslash = TRUE; + str_append_data(mailbox, base->mailbox, + p - base->mailbox); + } + } + } + + /* Scan for last mailbox-ref segment */ + if (segment != NULL) { + if (relative == 0 || (!have_partial && section == NULL)) { + p = NULL; + while (*segment != NULL) { + /* ';' must be pct-encoded; if it is not, this is + either the last mailbox-ref path segment containing + ';UIDVALIDITY=' or the subsequent iuid ';UID=' path + segment */ + if ((p = strchr(*segment, ';')) != NULL) + break; + + if (**segment != '\0') { + if (segment > path || + (!mailbox_endslash && str_len(mailbox) > 0)) + str_append_c(mailbox, '/'); + if (!uri_data_decode(parser, *segment, NULL, &value)) + return -1; + str_append(mailbox, value); + mailbox_endslash = FALSE; + } + segment++; + } + + /* Handle ';' */ + if (p != NULL) { + /* [uidvalidity] */ + if (strncasecmp(p, ";UIDVALIDITY=", 13) == 0) { + /* append last bit of mailbox */ + if (*segment != p) { + if (segment > path || + (!mailbox_endslash && str_len(mailbox) > 0)) + str_append_c(mailbox, '/'); + if (!uri_data_decode(parser, *segment, p, &value)) + return -1; + str_append(mailbox, value); + } + + /* ";UIDVALIDITY=" nz-number */ + if (strchr(p+13, ';') != NULL) { + parser->error = "Encountered stray ';' after UIDVALIDITY"; + return -1; + } + + /* nz-number */ + if (p[13] == '\0') { + parser->error = "Empty UIDVALIDITY value"; + return -1; + } + if (imap_url_parse_number(parser, p+13, &uidvalidity) <= 0) + return -1; + if (uidvalidity == 0) { + parser->error = "UIDVALIDITY cannot be zero"; + return -1; + } + segment++; + } else if (p != *segment) { + parser->error = "Encountered stray ';' in mailbox reference"; + return -1; + } + } + + /* iuid */ + if (*segment != NULL && strncasecmp(*segment, ";UID=", 5) == 0) { + /* ";UID=" nz-number */ + value = (*segment)+5; + if ((p = strchr(value,';')) != NULL) { + if (segment[1] != NULL ) { + /* not the last segment, so it cannot be extension like iurlauth */ + parser->error = "Encountered stray ';' in UID path segment"; + return -1; + } + urlext = p; + value = t_strdup_until(value, p); + } + /* nz-number */ + if (*value == '\0') { + parser->error = "Empty UID value"; + return -1; + } + if (imap_url_parse_number(parser, value, &uid) <= 0) + return -1; + if (uid == 0) { + parser->error = "UID cannot be zero"; + return -1; + } + segment++; + } + } + + /* [isection] [ipartial] */ + if (*segment != NULL && uid > 0) { + /* [isection] */ + if (section != NULL || + strncasecmp(*segment, ";SECTION=", 9) == 0) { + /* ";SECTION=" enc-section */ + if (section == NULL) { + section = t_str_new(256); + value = (*segment) + 9; + } else { + value = *segment; + } + + /* enc-section can contain slashes, so we merge path segments until one + contains ';' */ + while ((p = strchr(value,';')) == NULL) { + if (!section_endslash && str_len(section) > 0) + str_append_c(section, '/'); + if (*value != '\0') { + if (!uri_data_decode(parser, value, NULL, &value)) + return -1; + str_append(section, value); + section_endslash = FALSE; + } + + segment++; + if (*segment == NULL) + break; + value = *segment; + } + + if (p != NULL) { + /* found ';' */ + if (p != value) { + /* it is not at the beginning of the path segment */ + if (segment[1] != NULL) { + /* not the last segment, so it cannot be extension like iurlauth */ + parser->error = "Encountered stray ';' in SECTION path segment"; + return -1; + } + urlext = p; + value = t_strdup_until(value, p); + if (!section_endslash && str_len(section) > 0) + str_append_c(section, '/'); + if (!uri_data_decode(parser, value, NULL, &value)) + return -1; + str_append(section, value); + segment++; + } + } + + if (str_len(section) == 0) { + parser->error = "Empty SECTION value"; + return -1; + } + } + + /* [ipartial] */ + if (*segment != NULL && + strncasecmp(*segment, ";PARTIAL=", 9) == 0) { + have_partial = TRUE; + + /* ";PARTIAL=" partial-range */ + value = (*segment) + 9; + if ((p = strchr(value,';')) != NULL) { + urlext = p; + value = t_strdup_until(value, p); + } + if (*value == '\0') { + parser->error = "Empty PARTIAL value"; + return -1; + } + /* partial-range = number ["." nz-number] */ + if ((p = strchr(value,'.')) != NULL) { + if (p[1] == '\0') { + parser->error = "Empty PARTIAL size"; + return -1; + } + if (imap_url_parse_offset(parser, p+1, &partial_size) <= 0) + return -1; + if (partial_size == 0) { + parser->error = "PARTIAL size cannot be zero"; + return -1; + } + value = t_strdup_until(value, p); + if (*value == '\0') { + parser->error = "Empty PARTIAL offset"; + return -1; + } + } + if (imap_url_parse_offset(parser,value, &partial_offset) <= 0) + return -1; + segment++; + } + } + + if (*segment != NULL) { + if (urlext != NULL || **segment != '\0' || *(segment+1) != NULL ) { + parser->error = t_strdup_printf( + "Unexpected IMAP URL path segment: `%s'", + str_sanitize(*segment, 80)); + return -1; + } + } + } + + /* ";" {...} at end of URL */ + if (urlext != NULL) { + /* [iurlauth] */ + if ((ret = imap_url_parse_urlauth(url_parser, urlext)) < 0) + return ret; + else if (ret == 0) { + /* something else */ + parser->error = t_strdup_printf( + "Unrecognized IMAP URL extension: %s", + str_sanitize(urlext, 80)); + return -1; + } + } + + if (is_messagelist_r != NULL) + *is_messagelist_r = (uid == 0); + + if (url != NULL) { + if (str_len(mailbox) > 0) + url->mailbox = p_strdup(parser->pool, str_c(mailbox)); + url->uidvalidity = uidvalidity; + url->uid = uid; + if (section != NULL) + url->section = p_strdup(parser->pool, str_c(section)); + url->have_partial = have_partial; + url->partial_offset = partial_offset; + url->partial_size = partial_size; + } + return 1; +} + +static bool imap_url_do_parse(struct imap_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + const char *const *path; + bool is_messagelist = FALSE; + bool have_scheme = FALSE; + int relative; + const char *query; + int ret, sret; + + /* + * imapurl = "imap://" iserver ipath-query + * ; Defines an absolute IMAP URL + * iserver = [iuserinfo "@"] host [":" port] + * ; This is the same as "authority" defined + * ; in [URI-GEN]. + * ipath-query = ["/" [ icommand ]] + * ; Corresponds to "path-abempty [ "?" query ]" in + * ; [URI-GEN] + * icommand = imessagelist / + * imessagepart [iurlauth] + * imessagelist = imailbox-ref [ "?" enc-search ] + * ; "enc-search" is [URI-GEN] "query". + * imessagepart = imailbox-ref iuid [isection] [ipartial] + * enc-search = 1*bchar + * ; %-encoded version of [IMAPABNF] + * ; "search-program". Note that IMAP4 + * ; literals may not be used in + * ; a "search-program", i.e., only + * ; quoted or non-synchronizing + * ; literals (if the server supports + * ; LITERAL+ [LITERAL+]) are allowed. + */ + + /* "imap:" */ + if ((url_parser->flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0) { + const char *scheme; + + if (uri_parse_scheme(parser, &scheme) <= 0) { + parser->cur = parser->begin; + } else { + if (strcasecmp(scheme, "imap") != 0) { + parser->error = "Not an IMAP URL"; + return FALSE; + } + have_scheme = TRUE; + } + } else { + have_scheme = TRUE; + } + + /* "//" iserver */ + if ((sret = imap_url_parse_iserver(url_parser)) < 0) + return FALSE; + + if (have_scheme && sret == 0) { + parser->error = "Absolute IMAP URL requires `//' after `imap:'"; + return FALSE; + } + + if (sret > 0 && + (url_parser->flags & IMAP_URL_PARSE_REQUIRE_RELATIVE) != 0) { + parser->error = "Relative URL required"; + return FALSE; + } + + /* ipath-query = ["/" [ icommand ]] ; excludes `[ "?" enc-search ]` */ + if ((ret = uri_parse_path(parser, &relative, &path)) < 0) + return FALSE; + + /* Relative urls are only valid when we have a base url */ + if (sret == 0) { + if (url_parser->base == NULL) { + parser->error = "Relative URL not allowed"; + return FALSE; + } else if (url_parser->url != NULL) { + struct imap_url *url = url_parser->url; + const struct imap_url *base = url_parser->base; + + uri_host_copy(parser->pool, &url->host, &base->host); + url->port = base->port; + url->userid = p_strdup_empty(parser->pool, base->userid); + url->auth_type = p_strdup_empty(parser->pool, base->auth_type); + } + + url_parser->relative = TRUE; + } + + /* Parse path, i.e. `[ icommand ]` from `*( "/" segment )` */ + if (ret > 0 || url_parser->relative) { + if (imap_url_parse_path(url_parser, path, relative, + &is_messagelist) < 0) + return FALSE; + } + + /* [ "?" enc-search ] */ + if ((ret = uri_parse_query(parser, &query)) != 0) { + if (ret < 0) + return FALSE; + + if (!is_messagelist) { + parser->error = + "Search query part only valid for messagelist-type IMAP URL"; + return FALSE; + } else if (*query == '\0') { + parser->error = "Empty IMAP URL search query not allowed"; + return FALSE; + } + + if (url_parser->url != NULL) { + if (!uri_data_decode(parser, query, NULL, &query)) + return FALSE; + url_parser->url->search_program = + p_strdup(parser->pool, query); + } + } + + /* IMAP URL has no fragment */ + if ((ret = uri_parse_fragment(parser, &query)) != 0) { + if (ret == 1) + parser->error = "Fragment component not allowed in IMAP URL"; + return FALSE; + } + + /* must be at end of URL now */ + i_assert(parser->cur == parser->end); + + return TRUE; +} + +/* Public API */ + +int imap_url_parse(const char *url, const struct imap_url *base, + enum imap_url_parse_flags flags, + struct imap_url **url_r, const char **error_r) +{ + struct imap_url_parser url_parser; + + /* base != NULL indicates whether relative URLs are allowed. However, certain + flags may also dictate whether relative URLs are allowed/required. */ + i_assert((flags & IMAP_URL_PARSE_REQUIRE_RELATIVE) == 0 || base != NULL); + i_assert((flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0 || base == NULL); + + i_zero(&url_parser); + uri_parser_init(&url_parser.parser, pool_datastack_create(), url); + + url_parser.url = t_new(struct imap_url, 1); + url_parser.url->uauth_expire = (time_t)-1; + url_parser.base = base; + url_parser.flags = flags; + + if (!imap_url_do_parse(&url_parser)) { + *error_r = url_parser.parser.error; + return -1; + } + *url_r = url_parser.url; + return 0; +} + +/* + * IMAP URL construction + */ + +static void +imap_url_append_mailbox(const struct imap_url *url, string_t *urlstr) +{ + uri_append_path_data(urlstr, ";", url->mailbox); + if (url->uidvalidity != 0) + str_printfa(urlstr, ";UIDVALIDITY=%u", url->uidvalidity); + if (url->uid == 0) { + /* message list */ + if (url->search_program != NULL) { + str_append_c(urlstr, '?'); + uri_append_query_data(urlstr, ";", url->search_program); + } + } else { + /* message part */ + str_printfa(urlstr, "/;UID=%u", url->uid); + if (url->section != NULL) { + str_append(urlstr, "/;SECTION="); + uri_append_path_data(urlstr, ";", url->section); + } + if (url->have_partial) { + str_append(urlstr, "/;PARTIAL="); + if (url->partial_size == 0) { + str_printfa(urlstr, "%"PRIuUOFF_T, + url->partial_offset); + } else { + str_printfa(urlstr, "%"PRIuUOFF_T".%"PRIuUOFF_T, + url->partial_offset, + url->partial_size); + } + } + + /* urlauth */ + if (url->uauth_access_application != NULL) { + if (url->uauth_expire != (time_t)-1) { + str_append(urlstr, ";EXPIRE="); + str_append(urlstr, iso8601_date_create(url->uauth_expire)); + } + str_append(urlstr, ";URLAUTH="); + str_append(urlstr, url->uauth_access_application); + if (url->uauth_access_user != NULL) { + str_append_c(urlstr, '+'); + uri_append_user_data(urlstr, ";", + url->uauth_access_user); + } + } + } +} + +const char *imap_url_create(const struct imap_url *url) +{ + string_t *urlstr = t_str_new(512); + + /* scheme */ + uri_append_scheme(urlstr, "imap"); + str_append(urlstr, "//"); + + /* user */ + if (url->userid != NULL || url->auth_type != NULL) { + if (url->userid != NULL) + uri_append_user_data(urlstr, ";:", url->userid); + if (url->auth_type != NULL) { + str_append(urlstr, ";AUTH="); + uri_append_user_data(urlstr, ";:", url->auth_type); + } + str_append_c(urlstr, '@'); + } + + /* server */ + uri_append_host(urlstr, &url->host); + uri_append_port(urlstr, url->port); + + /* Older syntax (RFC 2192) requires this slash at all times */ + str_append_c(urlstr, '/'); + + /* mailbox */ + if (url->mailbox != NULL) + imap_url_append_mailbox(url, urlstr); + return str_c(urlstr); +} + +const char * +imap_url_add_urlauth(const char *rumpurl, const char *mechanism, + const unsigned char *token, size_t token_len) +{ + return t_strconcat(rumpurl, ":", t_str_lcase(mechanism), ":", + binary_to_hex(token, token_len), NULL); +} diff --git a/src/lib-imap/imap-url.h b/src/lib-imap/imap-url.h new file mode 100644 index 0000000..9f0b2aa --- /dev/null +++ b/src/lib-imap/imap-url.h @@ -0,0 +1,71 @@ +#ifndef IMAP_URL_H +#define IMAP_URL_H + +#include "uri-util.h" + +struct imap_url { + /* server */ + struct uri_host host; + in_port_t port; + + /* user */ + const char *userid; + const char *auth_type; + + /* mailbox */ + const char *mailbox; + uint32_t uidvalidity; /* 0 if not set */ + + /* message part */ + uint32_t uid; + const char *section; + uoff_t partial_offset; + uoff_t partial_size; /* 0 if not set */ + + /* message list (uid == 0) */ + const char *search_program; + + /* urlauth */ + const char *uauth_rumpurl; + const char *uauth_access_application; + const char *uauth_access_user; + const char *uauth_mechanism; + const unsigned char *uauth_token; + size_t uauth_token_size; + time_t uauth_expire; /* (time_t)-1 if not set */ + + bool have_partial:1; +}; + +/* + * IMAP URL parsing + */ + +enum imap_url_parse_flags { + /* Scheme part 'imap:' is already parsed externally. This implies that + this is an absolute IMAP URL. */ + IMAP_URL_PARSE_SCHEME_EXTERNAL = 0x01, + /* Require relative URL (omitting _both_ scheme and authority), e.g. + /MAILBOX/;UID=uid or even ;UID=uid. This flag means that an absolute + URL makes no sense in this context. Relative URLs are allowed once a + base URL is provided to the parser. */ + IMAP_URL_PARSE_REQUIRE_RELATIVE = 0x02, + /* Allow URLAUTH URL */ + IMAP_URL_PARSE_ALLOW_URLAUTH = 0x04 +}; + +/* Parses full IMAP URL. The returned URL is allocated from data stack. */ +int imap_url_parse(const char *url, const struct imap_url *base, + enum imap_url_parse_flags flags, + struct imap_url **url_r, const char **error_r); + +/* + * IMAP URL construction + */ + +const char *imap_url_create(const struct imap_url *url); + +const char *imap_url_add_urlauth(const char *rumpurl, const char *mechanism, + const unsigned char *token, size_t token_len); + +#endif diff --git a/src/lib-imap/imap-utf7.c b/src/lib-imap/imap-utf7.c new file mode 100644 index 0000000..7ea53f5 --- /dev/null +++ b/src/lib-imap/imap-utf7.c @@ -0,0 +1,380 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "unichar.h" +#include "imap-utf7.h" + +static const char imap_b64enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; + +#define XX 0xff +static const unsigned char imap_b64dec[256] = { + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, 63,XX,XX,XX, + 52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX, + XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX, + XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX +}; + +static void +mbase64_encode(string_t *dest, const unsigned char *in, size_t len) +{ + str_append_c(dest, '&'); + while (len >= 3) { + str_append_c(dest, imap_b64enc[in[0] >> 2]); + str_append_c(dest, imap_b64enc[((in[0] & 3) << 4) | + (in[1] >> 4)]); + str_append_c(dest, imap_b64enc[((in[1] & 0x0f) << 2) | + ((in[2] & 0xc0) >> 6)]); + str_append_c(dest, imap_b64enc[in[2] & 0x3f]); + in += 3; + len -= 3; + } + if (len > 0) { + str_append_c(dest, imap_b64enc[in[0] >> 2]); + if (len == 1) + str_append_c(dest, imap_b64enc[(in[0] & 0x03) << 4]); + else { + str_append_c(dest, imap_b64enc[((in[0] & 0x03) << 4) | + (in[1] >> 4)]); + str_append_c(dest, imap_b64enc[(in[1] & 0x0f) << 2]); + } + } + str_append_c(dest, '-'); +} + +static const char * +imap_utf8_first_encode_char(const char *str, char escape_char) +{ + const char *p; + + for (p = str; *p != '\0'; p++) { + if (*p == '&' || *p < 0x20 || *p >= 0x7f || *p == escape_char) + return p; + } + return NULL; +} + +int imap_escaped_utf8_hex_to_char(const char *str, unsigned char *chr_r) +{ + unsigned int i = 0; + unsigned char c = 0; + + /* NOTE: Only lowercase hex characters are allowed so the output is + reversible. */ + for (;;) { + if (str[i] >= '0' && str[i] <= '9') + c += str[i] - '0'; + else if (str[i] >= 'a' && str[i] <= 'f') + c += str[i] - 'a' + 10; + else + return -1; + if (++i == 2) + break; + c *= 0x10; + } + *chr_r = c; + return 0; +} + +static int +imap_utf8_to_utf7_int(const char *src, char escape_char, string_t *dest) +{ + const char *p; + unichar_t chr; + uint8_t *utf16, *u; + uint16_t u16; + unsigned char c; + + p = imap_utf8_first_encode_char(src, escape_char); + if (p == NULL) { + /* no characters that need to be encoded */ + str_append(dest, src); + return 0; + } + + /* at least one encoded character */ + str_append_data(dest, src, p-src); + utf16 = t_malloc0(MALLOC_MULTIPLY(strlen(p), 2)); + while (*p != '\0') { + if (*p == escape_char && + imap_escaped_utf8_hex_to_char(p+1, &c) == 0) { + str_append_c(dest, c); + p += 3; + continue; + } + if (*p == '&') { + str_append(dest, "&-"); + p++; + continue; + } + if (*p >= 0x20 && *p < 0x7f) { + str_append_c(dest, *p); + p++; + continue; + } + + u = utf16; + while (*p != '\0' && (*p < 0x20 || *p >= 0x7f)) { + if (uni_utf8_get_char(p, &chr) <= 0) + return -1; + /* @UNSAFE */ + if (chr < UTF16_SURROGATE_BASE) { + *u++ = chr >> 8; + *u++ = chr & 0xff; + } else { + u16 = UTF16_SURROGATE_HIGH(chr); + *u++ = u16 >> 8; + *u++ = u16 & 0xff; + u16 = UTF16_SURROGATE_LOW(chr); + *u++ = u16 >> 8; + *u++ = u16 & 0xff; + } + p += uni_utf8_char_bytes((unsigned char)*p); + } + mbase64_encode(dest, utf16, u-utf16); + } + return 0; +} + +int imap_utf8_to_utf7(const char *src, string_t *dest) +{ + return imap_utf8_to_utf7_int(src, '\0', dest); +} + +int imap_escaped_utf8_to_utf7(const char *src, char escape_char, string_t *dest) +{ + i_assert(escape_char != '&'); + + return imap_utf8_to_utf7_int(src, escape_char, dest); +} + +int t_imap_utf8_to_utf7(const char *src, const char **dest_r) +{ + string_t *str; + int ret; + + if (imap_utf8_first_encode_char(src, '\0') == NULL) { + *dest_r = src; + return 0; + } + + str = t_str_new(64); + ret = imap_utf8_to_utf7(src, str); + *dest_r = str_c(str); + return ret; +} + +static int utf16buf_to_utf8(string_t *dest, const unsigned char output[4], + unsigned int *_pos, unsigned int len) +{ + unsigned int pos = *_pos; + uint16_t high, low; + unichar_t chr; + + if (len % 2 != 0) + return -1; + + high = (output[pos % 4] << 8) | output[(pos+1) % 4]; + if (high < UTF16_SURROGATE_HIGH_FIRST || + high > UTF16_SURROGATE_HIGH_MAX) { + /* single byte */ + size_t oldlen = str_len(dest); + + if (high == 0) { + /* Encoded NUL isn't going to work in Dovecot code, + even though it's technically valid. Return failure + so the callers don't even get a chance to handle the + NUL in the string inconsistently. */ + return -1; + } + uni_ucs4_to_utf8_c(high, dest); + if (str_len(dest) - oldlen == 1) { + unsigned char last = str_data(dest)[oldlen]; + if (last >= 0x20 && last < 0x7f) + return -1; + } + *_pos = (pos + 2) % 4; + return 0; + } + + if (high > UTF16_SURROGATE_HIGH_LAST) + return -1; + if (len != 4) { + /* missing the second character */ + return -1; + } + + low = (output[(pos+2)%4] << 8) | output[(pos+3) % 4]; + if (low < UTF16_SURROGATE_LOW_FIRST || low > UTF16_SURROGATE_LOW_LAST) + return -1; + + chr = UTF16_SURROGATE_BASE + + (((high & UTF16_SURROGATE_MASK) << UTF16_SURROGATE_SHIFT) | + (low & UTF16_SURROGATE_MASK)); + uni_ucs4_to_utf8_c(chr, dest); + return 0; +} + +static int mbase64_decode_to_utf8(string_t *dest, const char **_src) +{ + const char *src = *_src; + unsigned char input[4], output[4]; + unsigned int outstart = 0, outpos = 0; + + while (*src != '-') { + input[0] = imap_b64dec[(uint8_t)src[0]]; + if (input[0] == 0xff) + return -1; + input[1] = imap_b64dec[(uint8_t)src[1]]; + if (input[1] == 0xff) + return -1; + + output[outpos % 4] = (input[0] << 2) | (input[1] >> 4); + if (++outpos % 4 == outstart) { + if (utf16buf_to_utf8(dest, output, &outstart, 4) < 0) + return -1; + } + + input[2] = imap_b64dec[(uint8_t)src[2]]; + if (input[2] == 0xff) { + if (src[2] != '-') + return -1; + + src += 2; + break; + } + + output[outpos % 4] = ((input[1] << 4) & 0xff) | (input[2] >> 2); + if (++outpos % 4 == outstart) { + if (utf16buf_to_utf8(dest, output, &outstart, 4) < 0) + return -1; + } + + input[3] = imap_b64dec[(uint8_t)src[3]]; + if (input[3] == 0xff) { + if (src[3] != '-') + return -1; + + src += 3; + break; + } + + output[outpos % 4] = ((input[2] << 6) & 0xc0) | input[3]; + if (++outpos % 4 == outstart) { + if (utf16buf_to_utf8(dest, output, &outstart, 4) < 0) + return -1; + } + + src += 4; + } + if (outstart != outpos % 4) { + if (utf16buf_to_utf8(dest, output, &outstart, + (4 + outpos - outstart) % 4) < 0) + return -1; + } + + /* Found the ending '-'. Make sure it's not followed by unnecessary + shift. Note that '&' is always escaped as "&-" so it's not an + unnecessary shift. */ + if (src[1] == '&' && src[2] != '-') + return -1; + + *_src = src + 1; + return 0; +} + +static int +imap_utf7_to_utf8_int(const char *src, const char *escape_chars, string_t *dest) +{ + const char *p; + + for (p = src; *p != '\0'; p++) { + if (*p < 0x20 || *p >= 0x7f) { + if (escape_chars[0] == '\0') + return -1; + break; + } + if (*p == '&' || strchr(escape_chars, *p) != NULL) + break; + } + if (*p == '\0') { + /* no IMAP-UTF-7 encoded characters */ + str_append(dest, src); + return 0; + } + + /* at least one encoded character */ + str_append_data(dest, src, p-src); + while (*p != '\0') { + if (strchr(escape_chars, *p) != NULL || + *p < 0x20 || *p >= 0x7f) { + str_printfa(dest, "%c%02x", escape_chars[0], + (unsigned char)*p); + p++; + } else if (*p == '&') { + if (*++p == '-') { + str_append_c(dest, '&'); + p++; + } else { + size_t orig_size = str_len(dest); + if (mbase64_decode_to_utf8(dest, &p) < 0) { + if (escape_chars[0] == '\0') + return -1; + str_truncate(dest, orig_size); + str_printfa(dest, "%c26", escape_chars[0]); + } + } + } else { + str_append_c(dest, *p++); + } + } + return 0; +} + +int imap_utf7_to_utf8(const char *src, string_t *dest) +{ + return imap_utf7_to_utf8_int(src, "", dest); +} + +void imap_utf7_to_utf8_escaped(const char *src, const char *escape_chars, + string_t *dest) +{ + i_assert(escape_chars[0] != '&'); + + if (imap_utf7_to_utf8_int(src, escape_chars, dest) < 0) + i_unreached(); +} + +bool imap_utf7_is_valid(const char *src) +{ + const char *p; + int ret; + + for (p = src; *p != '\0'; p++) { + if (*p < 0x20 || *p >= 0x7f) + return FALSE; + if (*p == '&') { + /* slow scan */ + T_BEGIN { + string_t *tmp = t_str_new(128); + ret = imap_utf7_to_utf8(p, tmp); + } T_END; + if (ret < 0) + return FALSE; + } + } + return TRUE; +} diff --git a/src/lib-imap/imap-utf7.h b/src/lib-imap/imap-utf7.h new file mode 100644 index 0000000..d7ae306 --- /dev/null +++ b/src/lib-imap/imap-utf7.h @@ -0,0 +1,28 @@ +#ifndef IMAP_UTF7_H +#define IMAP_UTF7_H + +/* Convert an UTF-8 string to IMAP-UTF-7. Returns 0 if ok, -1 if src isn't + valid UTF-8. */ +int imap_utf8_to_utf7(const char *src, string_t *dest); +int t_imap_utf8_to_utf7(const char *src, const char **dest_r); +/* Like imap_utf8_to_utf7(), but decode all <escape_char><hex> instances. + Returns -1 if src isn't valid UTF-8. Note that invalid <escape_char> content + isn't treated as an error - it's simply passed through. */ +int imap_escaped_utf8_to_utf7(const char *src, char escape_char, string_t *dest); +/* For manually parsing the <hex> after <escape_char>. Returns 0 on success, + -1 if str doesn't point to valid <hex>. */ +int imap_escaped_utf8_hex_to_char(const char *str, unsigned char *chr_r); + +/* Convert IMAP-UTF-7 string to UTF-8. Returns 0 if ok, -1 if src isn't + valid IMAP-UTF-7. */ +int imap_utf7_to_utf8(const char *src, string_t *dest); +/* Like imap_utf7_to_utf8(), but write invalid input as <escape_chars[0]><hex>. + All the characters in escape_chars[] are escaped in the same way. This + allows converting the escaped output back to the original (broken) + IMAP-UTF-7 input. */ +void imap_utf7_to_utf8_escaped(const char *src, const char *escape_chars, + string_t *dest); +/* Returns TRUE if the string is valid IMAP-UTF-7 string. */ +bool imap_utf7_is_valid(const char *src); + +#endif diff --git a/src/lib-imap/imap-util.c b/src/lib-imap/imap-util.c new file mode 100644 index 0000000..dc1ae24 --- /dev/null +++ b/src/lib-imap/imap-util.c @@ -0,0 +1,202 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strescape.h" +#include "unichar.h" +#include "mail-types.h" +#include "imap-parser.h" +#include "imap-util.h" + +void imap_write_flags(string_t *dest, enum mail_flags flags, + const char *const *keywords) +{ + size_t size; + + size = str_len(dest); + if ((flags & MAIL_ANSWERED) != 0) + str_append(dest, "\\Answered "); + if ((flags & MAIL_FLAGGED) != 0) + str_append(dest, "\\Flagged "); + if ((flags & MAIL_DELETED) != 0) + str_append(dest, "\\Deleted "); + if ((flags & MAIL_SEEN) != 0) + str_append(dest, "\\Seen "); + if ((flags & MAIL_DRAFT) != 0) + str_append(dest, "\\Draft "); + if ((flags & MAIL_RECENT) != 0) + str_append(dest, "\\Recent "); + + if (keywords != NULL) { + /* we have keywords too */ + while (*keywords != NULL) { + str_append(dest, *keywords); + str_append_c(dest, ' '); + keywords++; + } + } + + if (str_len(dest) != size) + str_truncate(dest, str_len(dest)-1); +} + +enum mail_flags imap_parse_system_flag(const char *str) +{ + if (strcasecmp(str, "\\Answered") == 0) + return MAIL_ANSWERED; + else if (strcasecmp(str, "\\Flagged") == 0) + return MAIL_FLAGGED; + else if (strcasecmp(str, "\\Deleted") == 0) + return MAIL_DELETED; + else if (strcasecmp(str, "\\Seen") == 0) + return MAIL_SEEN; + else if (strcasecmp(str, "\\Draft") == 0) + return MAIL_DRAFT; + else if (strcasecmp(str, "\\Recent") == 0) + return MAIL_RECENT; + else + return 0; +} + +void imap_write_seq_range(string_t *dest, const ARRAY_TYPE(seq_range) *array) +{ + const struct seq_range *range; + unsigned int i, count; + + range = array_get(array, &count); + for (i = 0; i < count; i++) { + if (i > 0) + str_append_c(dest, ','); + str_printfa(dest, "%u", range[i].seq1); + if (range[i].seq1 != range[i].seq2) + str_printfa(dest, ":%u", range[i].seq2); + } +} + +void imap_write_arg(string_t *dest, const struct imap_arg *arg) +{ + switch (arg->type) { + case IMAP_ARG_NIL: + str_append(dest, "NIL"); + break; + case IMAP_ARG_ATOM: + str_append(dest, imap_arg_as_astring(arg)); + break; + case IMAP_ARG_STRING: { + const char *strarg = imap_arg_as_astring(arg); + str_append_c(dest, '"'); + str_append_escaped(dest, strarg, strlen(strarg)); + str_append_c(dest, '"'); + break; + } + case IMAP_ARG_LITERAL: { + const char *strarg = imap_arg_as_astring(arg); + str_printfa(dest, "{%zu}\r\n", + strlen(strarg)); + str_append(dest, strarg); + break; + } + case IMAP_ARG_LIST: + str_append_c(dest, '('); + imap_write_args(dest, imap_arg_as_list(arg)); + str_append_c(dest, ')'); + break; + case IMAP_ARG_LITERAL_SIZE: + case IMAP_ARG_LITERAL_SIZE_NONSYNC: + str_printfa(dest, "<%"PRIuUOFF_T" byte literal>", + imap_arg_as_literal_size(arg)); + break; + case IMAP_ARG_EOL: + i_unreached(); + } +} + +void imap_write_args(string_t *dest, const struct imap_arg *args) +{ + bool first = TRUE; + + for (; !IMAP_ARG_IS_EOL(args); args++) { + if (first) + first = FALSE; + else + str_append_c(dest, ' '); + imap_write_arg(dest, args); + } +} + +static void imap_human_args_fix_control_chars(char *str) +{ + size_t i; + + for (i = 0; str[i] != '\0'; i++) { + if (str[i] < 0x20 || str[i] == 0x7f) + str[i] = '?'; + } +} + +void imap_write_args_for_human(string_t *dest, const struct imap_arg *args) +{ + bool first = TRUE; + + for (; !IMAP_ARG_IS_EOL(args); args++) { + if (first) + first = FALSE; + else + str_append_c(dest, ' '); + + switch (args->type) { + case IMAP_ARG_NIL: + str_append(dest, "NIL"); + break; + case IMAP_ARG_ATOM: + /* atom has only printable us-ascii chars */ + str_append(dest, imap_arg_as_astring(args)); + break; + case IMAP_ARG_STRING: + case IMAP_ARG_LITERAL: { + const char *strarg = imap_arg_as_astring(args); + + if (strpbrk(strarg, "\r\n") != NULL) { + str_printfa(dest, "<%zu byte multi-line literal>", + strlen(strarg)); + break; + } + strarg = str_escape(strarg); + + str_append_c(dest, '"'); + size_t start_pos = str_len(dest); + /* append only valid UTF-8 chars */ + if (uni_utf8_get_valid_data((const unsigned char *)strarg, + strlen(strarg), dest)) + str_append(dest, strarg); + /* replace all control chars */ + imap_human_args_fix_control_chars( + str_c_modifiable(dest) + start_pos); + str_append_c(dest, '"'); + break; + } + case IMAP_ARG_LIST: + str_append_c(dest, '('); + imap_write_args_for_human(dest, imap_arg_as_list(args)); + str_append_c(dest, ')'); + break; + case IMAP_ARG_LITERAL_SIZE: + case IMAP_ARG_LITERAL_SIZE_NONSYNC: + str_printfa(dest, "<%"PRIuUOFF_T" byte literal>", + imap_arg_as_literal_size(args)); + break; + case IMAP_ARG_EOL: + i_unreached(); + } + } +} + +const char *imap_args_to_str(const struct imap_arg *args) +{ + string_t *str; + + str = t_str_new(128); + imap_write_args(str, args); + return str_c(str); +} diff --git a/src/lib-imap/imap-util.h b/src/lib-imap/imap-util.h new file mode 100644 index 0000000..47b7d2c --- /dev/null +++ b/src/lib-imap/imap-util.h @@ -0,0 +1,29 @@ +#ifndef IMAP_UTIL_H +#define IMAP_UTIL_H + +#include "seq-range-array.h" +#include "mail-types.h" + +struct imap_arg; + +/* Write flags as a space separated string. */ +void imap_write_flags(string_t *dest, enum mail_flags flags, + const char *const *keywords) ATTR_NULL(3); +/* Parse system flag from a string, or return 0 if it's invalid. */ +enum mail_flags imap_parse_system_flag(const char *str); + +/* Write sequence range as IMAP sequence-set */ +void imap_write_seq_range(string_t *dest, const ARRAY_TYPE(seq_range) *array); +/* Write IMAP arg to the given string. Because IMAP_ARG_LITERAL_SIZE* have no + content, they're written as "{size}\r\n<too large>". */ +void imap_write_arg(string_t *dest, const struct imap_arg *arg); +/* Same as imap_write_arg(), but write all the args until EOL. */ +void imap_write_args(string_t *dest, const struct imap_arg *args); +/* Write IMAP args in a human-readable format to given string (e.g. for + logging). The output is a single valid UTF-8 line without control + characters. Multi-line literals are replaced with a generic placeholder. */ +void imap_write_args_for_human(string_t *dest, const struct imap_arg *args); +/* Like imap_write_args(), but return the string allocated from data stack. */ +const char *imap_args_to_str(const struct imap_arg *args); + +#endif diff --git a/src/lib-imap/test-imap-bodystructure.c b/src/lib-imap/test-imap-bodystructure.c new file mode 100644 index 0000000..6035118 --- /dev/null +++ b/src/lib-imap/test-imap-bodystructure.c @@ -0,0 +1,733 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "str.h" +#include "message-part-data.h" +#include "message-part-serialize.h" +#include "message-parser.h" +#include "imap-bodystructure.h" +#include "test-common.h" + +struct parse_test { + const char *message; + const char *body; + const char *bodystructure; +}; + +struct parse_test parse_tests[] = { + { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "body\n", + .bodystructure = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL", + .body = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=utf-8\n" + "Content-Transfer-Encoding: 8bit\n" + "\n" + "body\n" + "\n", + .bodystructure = + "\"text\" \"plain\" (\"charset\" \"utf-8\") NIL NIL \"8bit\" 8 2 NIL NIL NIL NIL", + .body = + "\"text\" \"plain\" (\"charset\" \"utf-8\") NIL NIL \"8bit\" 8 2" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2007 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo\n" + " bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/x-myown; charset=us-ascii\n" + "\n" + "hello\n" + "\n" + "--foo bar--\n" + "\n", + .bodystructure = + "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 7 1 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL", + .body = + "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 7 1) \"mixed\"" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "See attached...\n" + "\n" + "--foo bar\n" + "Content-Type: message/rfc822\n" + "\n" + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "body\n" + "\n" + "--foo bar--\n" + "\n", + .bodystructure = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL NIL)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL) 6 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL", + .body = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1) 6) \"mixed\"" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "Content-ID: <A.frop.example.com>\n" + "Content-Description: Container message\n" + "\n" + "See attached...\n" + "\n" + "--foo bar\n" + "Content-Type: message/rfc822\n" + "Content-ID: <B.frop.example.com>\n" + "Content-Description: Forwarded\n" + "\n" + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "body\n" + "\n" + "--foo bar--\n", + .bodystructure = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") \"<A.frop.example.com>\" \"Container message\" \"7bit\" 17 1 NIL NIL NIL NIL)(\"message\" \"rfc822\" NIL \"<B.frop.example.com>\" \"Forwarded\" \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL) 6 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL", + .body = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") \"<A.frop.example.com>\" \"Container message\" \"7bit\" 17 1)(\"message\" \"rfc822\" NIL \"<B.frop.example.com>\" \"Forwarded\" \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1) 6) \"mixed\"" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii; format=\"flowed\";\n" + " delsp=\"no\"\n" + "Content-Language: la\n" + "\n" + "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo\n" + "ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis\n" + "parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec,\n" + "pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec\n" + "pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo,\n" + "rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede\n" + "mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper\n" + "nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu,\n" + "consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra\n" + "quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet.\n" + "Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur\n" + "ullamcorper ultricies nisi. Nam eget dui.\n" + "\n" + "--foo bar\n" + "Content-Type: image/png\n" + "Content-Transfer-Encoding: base64\n" + "Content-Disposition: attachment; filename=\"pigeon.png\"\n" + "\n" + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n" + "AAAGJwAABicBTVTYxwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAN2SURB\n" + "VEiJ7ZfdK7tvHMffZuWpKTlR1my3bTEPU0hJcsABh0QrKYUTJ5KyHCzlyPwBkpSyg1mUpB3ILYna\n" + "lEYKcbADm4cDD7NpM2bv75HF1/az8l3fX/1+77pO7vu6rtf1/jx0X3caSeIvSfK3wP/DPykcDsNs\n" + "NqOqqgpdXV3Y2NhIGVz6+4PJyUlcXFxAFEV4vV709fVBrVZDpVL9eTo/aGVlhXl5efT5fDw/P2c4\n" + "HKbdbmd3dzdTIZDk6+srh4eHqdPpqNfrSZJGo5H7+/sMBoNUKpUpgUsAQCqVQq1WIxgMfgp/dXU1\n" + "srKykJ+fD6/X+8ejHiu4wcFB2Gy2uJPq6uqwt7eXOjgAaLXahHCn05laeCKlyvmXVosnrVaLQCCA\n" + "4uJiyOVyCIIQGyqVCoIgoKCgIDXwtLQ0HBwc4O3tDR6PB263G263G8fHx1hbW4Pb7cbNzQ1yc3Nj\n" + "hxEEAdXV1WhoaEi88cfSf3h4iLXaR01MTFChUFChULCjo4M7OztxW8fn89HlcnF5eZlTU1Nsb29n\n" + "W1sb/X5/3PlJwd/19vbG7e1tlpWVcXV19ftGJjk0NESbzRb33aeCy8zMhM/nSxgliUSCxsZGiKKI\n" + "ra2tb9N1e3uLSCSC8fFxGAwGDAwMfC7c309TWFjI09PTpFwlis76+jo7OzspCAJNJhN3d3fpcDho\n" + "tVqpUCgYCoVIkl8Krr6+Hna7HSUlJd86+yiPx4P5+XlYLBZUVFSgv78fVqsV6enpAIC7uzt4vV5E\n" + "IhFIJJL4zp1OJ2traxmJRL51+fLywuXlZba2trK0tJRms5mXl5c8PT3l4uIix8bG2NbWRqVSyfLy\n" + "cvb09PDw8DC2Po38eocbGRlBIBDA9PQ0pNKv3Xh2doa5uTksLCwgJycHNTU1yM3NxdHREa6vr6HR\n" + "aKDX62NDp9MhIyPjyz5x4SQxOjoKURTR29sLmUyG7Oxs+P1+WCwW7O7uQiKRQKvVQq/Xo7KyMgaS\n" + "y+VJpyou/F0nJydYWlqCy+XC1dUV7u/vYTQa8fT0BIfDgaWlpaRBcZVsFc/OznJsbIwkaTKZODMz\n" + "k+zShEr6Arm5uYnm5mYAgCiKaGlp+ZnrZJ1Ho1EWFRXx+fmZj4+P1Gg0P3ZNJqj2dxkMhth3PBAI\n" + "QCaTIRqNIhQKoamp6cc5/0d4qvXv+mn4z8B/AV1UVu6zi+zUAAAAAElFTkSuQmCC\n" + "--foo bar--\n", + .bodystructure = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\" \"format\" \"flowed\" \"delsp\" \"no\") NIL NIL \"7bit\" 881 12 NIL NIL (\"la\") NIL)(\"image\" \"png\" NIL NIL NIL \"base64\" 1390 NIL (\"attachment\" (\"filename\" \"pigeon.png\")) NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL", + .body = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\" \"format\" \"flowed\" \"delsp\" \"no\") NIL NIL \"7bit\" 881 12)(\"image\" \"png\" NIL NIL NIL \"base64\" 1390) \"mixed\"" + },{ + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2007 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo\n" + " bar\"\n" + "\n" + "Root MIME prologue\n" + "\n" + "--foo bar\n" + "Content-Type: text/x-myown; charset=us-ascii; foo=\"quoted\\\"string\"\n" + "Content-ID: <foo@example.com>\n" + "Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==\n" + "Content-Disposition: inline; foo=bar\n" + "Content-Description: hellodescription\n" + "Content-Language: en, fi, se\n" + "Content-Location: http://example.com/test.txt\n" + "\n" + "hello\n" + "\n" + "--foo bar\n" + "Content-Type: message/rfc822\n" + "\n" + "From: sub@domain.org\n" + "To: sub-to1@domain.org, sub-to2@domain.org\n" + "Date: Sun, 12 Aug 2012 12:34:56 +0300\n" + "Subject: submsg\n" + "Content-Type: multipart/alternative; boundary=\"sub1\"\n" + "\n" + "Sub MIME prologue\n" + "--sub1\n" + "Content-Type: text/html\n" + "Content-Transfer-Encoding: 8bit\n" + "\n" + "<p>Hello world</p>\n" + "\n" + "--sub1\n" + "Content-Type: text/plain\n" + "Content-Transfer-Encoding: ?invalid\n" + "\n" + "Hello another world\n" + "\n" + "--sub1--\n" + "Sub MIME epilogue\n" + "\n" + "--foo bar--\n" + "Root MIME epilogue\n", + .bodystructure = + "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\" \"foo\" \"quoted\\\"string\") \"<foo@example.com>\" \"hellodescription\" \"7bit\" 7 1 \"Q2hlY2sgSW50ZWdyaXR5IQ==\" (\"inline\" (\"foo\" \"bar\")) (\"en\" \"fi\" \"se\") \"http://example.com/test.txt\")(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 412 (\"Sun, 12 Aug 2012 12:34:56 +0300\" \"submsg\" ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub-to1\" \"domain.org\")(NIL NIL \"sub-to2\" \"domain.org\")) NIL NIL NIL NIL) ((\"text\" \"html\" (\"charset\" \"us-ascii\") NIL NIL \"8bit\" 20 1 NIL NIL NIL NIL)(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 21 1 NIL NIL NIL NIL) \"alternative\" (\"boundary\" \"sub1\") NIL NIL NIL) 21 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL", + .body = + "(\"text\" \"x-myown\" (\"charset\" \"us-ascii\" \"foo\" \"quoted\\\"string\") \"<foo@example.com>\" \"hellodescription\" \"7bit\" 7 1)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 412 (\"Sun, 12 Aug 2012 12:34:56 +0300\" \"submsg\" ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub\" \"domain.org\")) ((NIL NIL \"sub-to1\" \"domain.org\")(NIL NIL \"sub-to2\" \"domain.org\")) NIL NIL NIL NIL) ((\"text\" \"html\" (\"charset\" \"us-ascii\") NIL NIL \"8bit\" 20 1)(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 21 1) \"alternative\") 21) \"mixed\"" + },{ + .message = + "Content-Type: multipart/mixed; boundary=\"foo\"\n" + "\n", + .bodystructure = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo\") NIL NIL NIL", + .body = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0) \"mixed\"" + + } +}; + +static const unsigned int parse_tests_count = N_ELEMENTS(parse_tests); + +struct normalize_test { + const char *message; + const char *input; + const char *output; +}; + +struct normalize_test normalize_tests[] = { + { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "body\n", + .input = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1", + .output = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL", + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "Content-MD5: ae6ba5b4c6eb1efd4a9fac3708046cbe\n" + "\n" + "body\n", + .input = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 \"ae6ba5b4c6eb1efd4a9fac3708046cbe\"", + .output = + "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 \"ae6ba5b4c6eb1efd4a9fac3708046cbe\" NIL NIL NIL", + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "See attached...\n" + "\n" + "--foo bar\n" + "Content-Type: message/rfc822\n" + "\n" + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "body\n" + "\n" + "--foo bar--\n" + "\n", + .input = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1) 6) \"mixed\"", + .output = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL NIL)(\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 133 (\"Sat, 24 Mar 2017 23:00:00 +0200\" NIL ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) ((NIL NIL \"user\" \"domain.org\")) NIL NIL NIL NIL NIL) (\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 6 1 NIL NIL NIL NIL) 6 NIL NIL NIL NIL) \"mixed\" NIL NIL NIL NIL" + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "\n" + "See attached...\n" + "\n" + "--foo bar--\n" + "\n", + .input = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1) \"mixed\" (\"boundary\" \"foo bar\")", + .output = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL" + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "Content-MD5: 6537bae18ed07779c9dc25f24635b0f3\n" + "\n" + "See attached...\n" + "\n" + "--foo bar--\n" + "\n", + .input = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 \"6537bae18ed07779c9dc25f24635b0f3\") \"mixed\" (\"boundary\" \"foo bar\")", + .output = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 \"6537bae18ed07779c9dc25f24635b0f3\" NIL NIL NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL" + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "Content-Language: en\n" + "\n" + "See attached...\n" + "\n" + "--foo bar--\n" + "\n", + .input = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL \"en\") \"mixed\" (\"boundary\" \"foo bar\")", + .output = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL (\"en\") NIL) \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL" + }, { + .message = + "From: user@domain.org\n" + "Date: Sat, 24 Mar 2017 23:00:00 +0200\n" + "Mime-Version: 1.0\n" + "Content-Type: multipart/mixed; boundary=\"foo bar\"\n" + "\n" + "--foo bar\n" + "Content-Type: text/plain; charset=us-ascii\n" + "Content-Location: http://www.example.com/frop.txt\n" + "\n" + "See attached...\n" + "\n" + "--foo bar--\n" + "\n", + .input = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL \"http://www.example.com/frop.txt\") \"mixed\" (\"boundary\" \"foo bar\")", + .output = + "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 17 1 NIL NIL NIL \"http://www.example.com/frop.txt\") \"mixed\" (\"boundary\" \"foo bar\") NIL NIL NIL" + } +}; + +static const unsigned int normalize_tests_count = N_ELEMENTS(normalize_tests); + +static struct message_part * +msg_parse(pool_t pool, const char *message, unsigned int max_nested_mime_parts, + unsigned int max_total_mime_parts, bool parse_bodystructure) +{ + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | + MESSAGE_HEADER_PARSER_FLAG_DROP_CR, + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, + .max_nested_mime_parts = max_nested_mime_parts, + .max_total_mime_parts = max_total_mime_parts, + }; + struct message_parser_ctx *parser; + struct istream *input; + struct message_block block; + struct message_part *parts; + int ret; + + input = i_stream_create_from_data(message, strlen(message)); + parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { + if (parse_bodystructure) { + message_part_data_parse_from_header(pool, block.part, + block.hdr); + } + } + test_assert(ret < 0); + + message_parser_deinit(&parser, &parts); + i_stream_unref(&input); + return parts; +} + +static void test_imap_bodystructure_write(void) +{ + struct message_part *parts; + const char *error; + unsigned int i; + + for (i = 0; i < parse_tests_count; i++) T_BEGIN { + struct parse_test *test = &parse_tests[i]; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap bodystructure write", 1024); + + test_begin(t_strdup_printf("imap bodystructure write [%u]", i)); + parts = msg_parse(pool, test->message, 0, 0, TRUE); + + test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0); + test_assert(strcmp(str_c(str), test->bodystructure) == 0); + + str_truncate(str, 0); + test_assert(imap_bodystructure_write(parts, str, FALSE, &error) == 0); + test_assert(strcmp(str_c(str), test->body) == 0); + + pool_unref(&pool); + test_end(); + } T_END; + + T_BEGIN { + test_begin("imap bodystructure write - corrupted"); + pool_t pool = pool_alloconly_create("imap bodystructure write", 1024); + + parts = msg_parse(pool, "Subject: hello world", 0, 0, TRUE); + i_assert((parts->flags & MESSAGE_PART_FLAG_TEXT) != 0); + parts->flags &= ENUM_NEGATE(MESSAGE_PART_FLAG_TEXT); + + string_t *str = t_str_new(128); + test_assert(imap_bodystructure_write(parts, str, FALSE, &error) < 0); + test_assert_strcmp(error, "text flag mismatch"); + pool_unref(&pool); + test_end(); + } T_END; +} + +static void test_imap_bodystructure_parse(void) +{ + struct message_part *parts; + const char *error; + unsigned int i; + int ret; + + for (i = 0; i < parse_tests_count; i++) T_BEGIN { + struct parse_test *test = &parse_tests[i]; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + test_begin(t_strdup_printf("imap bodystructure parser [%u]", i)); + parts = msg_parse(pool, test->message, 0, 0, FALSE); + + test_assert(imap_body_parse_from_bodystructure(test->bodystructure, + str, &error) == 0); + test_assert(strcmp(str_c(str), test->body) == 0); + + ret = imap_bodystructure_parse(test->bodystructure, + pool, parts, &error); + test_assert(ret == 0); + + if (ret == 0) { + str_truncate(str, 0); + test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0); + test_assert(strcmp(str_c(str), test->bodystructure) == 0); + } else { + i_error("Invalid BODYSTRUCTURE: %s", error); + } + + pool_unref(&pool); + test_end(); + } T_END; +} + +static void test_imap_bodystructure_parse_invalid(void) +{ + static const struct parse_test_invalid { + const char *message; + const char *bodystructure; + const char *error; + } invalid_bodystructure_tests[] = { + /* Make sure NILs aren't allowed where strings are expected */ + { "foo", "NIL \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" }, + { "foo", "\"text\" NIL (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" }, + { "foo", "\"text\" \"plain\" (NIL \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" }, + { "foo", "\"text\" \"plain\" (\"charset\" NIL) NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL NIL 0 0 NIL NIL NIL NIL", "Invalid content-transfer-encoding" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" NIL 0 NIL NIL NIL NIL", "Invalid size field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 NIL NIL NIL NIL NIL", "Invalid lines field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (NIL (\"foo\" \"bar\")) NIL NIL", "Invalid content-disposition" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (NIL \"bar\")) NIL NIL", "Invalid content-disposition params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (\"foo\" NIL)) NIL NIL", "Invalid content-disposition params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (NIL \"bar\") NIL", "Invalid content-language" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (\"foo\" NIL) NIL", "Invalid content-language" }, + + /* Make sure atoms aren't allowed anywhere */ + { "foo", "ATOM \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" }, + { "foo", "\"text\" ATOM (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-type" }, + { "foo", "\"text\" \"plain\" (ATOM \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" }, + { "foo", "\"text\" \"plain\" (\"charset\" ATOM) NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") ATOM NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-id" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL ATOM \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-description" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL ATOM 0 0 NIL NIL NIL NIL", "Invalid content-transfer-encoding" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" ATOM 0 NIL NIL NIL NIL", "Invalid size field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 ATOM NIL NIL NIL NIL", "Invalid lines field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 ATOM NIL NIL NIL", "Invalid content-md5" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL ATOM NIL NIL", "Invalid content-disposition list" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (ATOM (\"foo\" \"bar\")) NIL NIL", "Invalid content-disposition" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (ATOM \"bar\")) NIL NIL", "Invalid content-disposition params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL (\"inline\" (\"foo\" ATOM)) NIL NIL", "Invalid content-disposition params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL ATOM NIL", "Invalid content-language" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (ATOM \"bar\") NIL", "Invalid content-language" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL (\"foo\" ATOM) NIL", "Invalid content-language" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL ATOM", "Invalid content-location" }, + + /* Make sure empty lists aren't allowed anywhere */ + { "foo", "\"text\" \"plain\" () NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content params" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") () NIL \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-id" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL () \"7bit\" 0 0 NIL NIL NIL NIL", "Invalid content-description" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL () 0 0 NIL NIL NIL NIL", "Invalid content-transfer-encoding" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" () 0 NIL NIL NIL NIL", "Invalid size field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 () NIL NIL NIL NIL", "Invalid lines field" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 () NIL NIL NIL", "Invalid content-md5" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL () NIL NIL", "Invalid content-disposition" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL () NIL", "Invalid content-language" }, + { "foo", "\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 0 0 NIL NIL NIL ()", "Invalid content-location" }, + }; + struct message_part *parts; + const char *error; + unsigned int i; + + test_begin("imap bodystructure parser invalid"); + for (i = 0; i < N_ELEMENTS(invalid_bodystructure_tests); i++) T_BEGIN { + const struct parse_test_invalid *test = + &invalid_bodystructure_tests[i]; + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + parts = msg_parse(pool, test->message, 0, 0, FALSE); + test_assert_idx(imap_bodystructure_parse(test->bodystructure, + pool, parts, &error) == -1, i); + test_assert_strcmp_idx(error, test->error, i); + pool_unref(&pool); + } T_END; + test_end(); +} + +static void test_imap_bodystructure_parse_full(void) +{ + const char *error; + unsigned int i; + int ret; + + for (i = 0; i < parse_tests_count; i++) T_BEGIN { + struct parse_test *test = &parse_tests[i]; + struct message_part *parts = NULL; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap bodystructure parse full", 1024); + + test_begin(t_strdup_printf("imap bodystructure parser full [%u]", i)); + + ret = imap_bodystructure_parse_full(test->bodystructure, + pool, &parts, &error); + test_assert(ret == 0); + + if (ret == 0) { + str_truncate(str, 0); + test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0); + test_assert(strcmp(str_c(str), test->bodystructure) == 0); + } else { + i_error("Invalid BODYSTRUCTURE: %s", error); + } + + pool_unref(&pool); + test_end(); + } T_END; +} + +static void test_imap_bodystructure_normalize(void) +{ + struct message_part *parts; + const char *error; + unsigned int i; + int ret; + + for (i = 0; i < normalize_tests_count; i++) T_BEGIN { + struct normalize_test *test = &normalize_tests[i]; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + test_begin(t_strdup_printf("imap bodystructure normalize [%u]", i)); + parts = msg_parse(pool, test->message, 0, 0, FALSE); + + ret = imap_bodystructure_parse(test->input, + pool, parts, &error); + test_assert(ret == 0); + + if (ret == 0) { + str_truncate(str, 0); + test_assert(imap_bodystructure_write(parts, str, TRUE, &error) == 0); + test_assert(strcmp(str_c(str), test->output) == 0); + } else { + i_error("Invalid BODYSTRUCTURE: %s", error); + } + + pool_unref(&pool); + test_end(); + } T_END; +} + +static const struct { + const char *input; + const char *bodystructure; + unsigned int max_depth; + unsigned int max_total; +} truncation_tests[] = { + { + .input = "Content-Type: message/rfc822\n" + "\n" + "Content-Type: message/rfc822\n" + "Header2: value2\n" + "\n" + "Subject: hello world\n" + "Header2: value2\n" + "Header3: value3\n" + "\n" + "body line 1\n" + "body line 2\n" + "body line 4\n" + "body line 3\n", + .bodystructure = "\"message\" \"rfc822\" NIL NIL NIL \"7bit\" 159 (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"application\" \"octet-stream\" NIL NIL NIL \"7bit\" 110 NIL NIL NIL NIL) 11 NIL NIL NIL NIL", + .max_depth = 2, + }, + { + .input = "Content-Type: multipart/mixed; boundary=1\n" + "\n" + "--1\n" + "Content-Type: multipart/mixed; boundary=2\n" + "\n" + "--2\n" + "Content-Type: multipart/mixed; boundary=3\n" + "\n" + "--3\n" + "\n" + "body\n", + .bodystructure = "(\"application\" \"octet-stream\" (\"boundary\" \"2\") NIL NIL \"7bit\" 63 NIL NIL NIL NIL) \"mixed\" (\"boundary\" \"1\") NIL NIL NIL", + .max_depth = 2, + }, + { + .input = "Content-Type: multipart/digest; boundary=1\n" + "\n" + "--1\n" + "\n" + "Subject: hdr1\n" + "\n" + "body1\n" + "--1\n" + "\n" + "Subject: hdr2\n" + "\n" + "body2\n", + .bodystructure = "(\"application\" \"octet-stream\" NIL NIL NIL \"7bit\" 55 NIL NIL NIL NIL) \"digest\" (\"boundary\" \"1\") NIL NIL NIL", + .max_total = 2, + }, + +}; + +static void test_imap_bodystructure_truncation(void) +{ + struct message_part *parts; + const char *error; + string_t *str_body = t_str_new(128); + string_t *str_parts = t_str_new(128); + pool_t pool = pool_alloconly_create("imap bodystructure parse", 1024); + + test_begin("imap bodystructure truncation"); + + for (unsigned int i = 0; i < N_ELEMENTS(truncation_tests); i++) { + p_clear(pool); + str_truncate(str_body, 0); + str_truncate(str_parts, 0); + + parts = msg_parse(pool, truncation_tests[i].input, + truncation_tests[i].max_depth, + truncation_tests[i].max_total, + TRUE); + + /* write out BODYSTRUCTURE and serialize message_parts */ + test_assert(imap_bodystructure_write(parts, str_body, TRUE, &error) == 0); + message_part_serialize(parts, str_parts); + + /* now deserialize message_parts and make sure they can be used + to parse BODYSTRUCTURE */ + parts = message_part_deserialize(pool, str_data(str_parts), + str_len(str_parts), &error); + test_assert(parts != NULL); + test_assert(imap_bodystructure_parse(str_c(str_body), pool, + parts, &error) == 0); + test_assert_strcmp(str_c(str_body), + truncation_tests[i].bodystructure); + } + pool_unref(&pool); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_bodystructure_write, + test_imap_bodystructure_parse, + test_imap_bodystructure_parse_invalid, + test_imap_bodystructure_normalize, + test_imap_bodystructure_parse_full, + test_imap_bodystructure_truncation, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-envelope.c b/src/lib-imap/test-imap-envelope.c new file mode 100644 index 0000000..1f295e5 --- /dev/null +++ b/src/lib-imap/test-imap-envelope.c @@ -0,0 +1,205 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "str.h" +#include "message-part-data.h" +#include "message-parser.h" +#include "imap-envelope.h" +#include "test-common.h" + +struct parse_test { + const char *message; + const char *envelope; +}; + +struct parse_test parse_tests[] = { + /* Tests copied from imaptest */ + { + .message = + "Message-ID: <msg@id>\n" + "In-Reply-To: <reply@to.id>\n" + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "Subject: subject header\n" + "From: From Real <fromuser@fromdomain.org>\n" + "To: To Real <touser@todomain.org>\n" + "Cc: Cc Real <ccuser@ccdomain.org>\n" + "Bcc: Bcc Real <bccuser@bccdomain.org>\n" + "Sender: Sender Real <senderuser@senderdomain.org>\n" + "Reply-To: ReplyTo Real <replytouser@replytodomain.org>\n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" " + "\"subject header\" " + "((\"From Real\" NIL \"fromuser\" \"fromdomain.org\")) " + "((\"Sender Real\" NIL \"senderuser\" \"senderdomain.org\")) " + "((\"ReplyTo Real\" NIL \"replytouser\" \"replytodomain.org\")) " + "((\"To Real\" NIL \"touser\" \"todomain.org\")) " + "((\"Cc Real\" NIL \"ccuser\" \"ccdomain.org\")) " + "((\"Bcc Real\" NIL \"bccuser\" \"bccdomain.org\")) " + "\"<reply@to.id>\" \"<msg@id>\"" + }, { + .message = + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "From: user@domain\n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL " + "((NIL NIL \"user\" \"domain\")) " + "((NIL NIL \"user\" \"domain\")) " + "((NIL NIL \"user\" \"domain\")) NIL NIL NIL NIL NIL" + }, { + .message = + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "From: user@domain\n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL " + "((NIL NIL \"user\" \"domain\")) " + "((NIL NIL \"user\" \"domain\")) " + "((NIL NIL \"user\" \"domain\")) NIL NIL NIL NIL NIL" + }, { + .message = + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "From: user@domain (Real Name)\n" + "To: group: g1@d1.org, g2@d2.org;, group2: g3@d3.org;\n" + "Cc: group:;, group2: (foo) ;\n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "((NIL NIL \"group\" NIL)" + "(NIL NIL \"g1\" \"d1.org\")" + "(NIL NIL \"g2\" \"d2.org\")" + "(NIL NIL NIL NIL)" + "(NIL NIL \"group2\" NIL)" + "(NIL NIL \"g3\" \"d3.org\")" + "(NIL NIL NIL NIL)) " + "((NIL NIL \"group\" NIL)(NIL NIL NIL NIL)" + "(NIL NIL \"group2\" NIL)(NIL NIL NIL NIL)) " + "NIL NIL NIL" + }, { + .message = + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "From: user@domain (Real Name)\n" + "Sender: \n" + "Reply-To: \n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "((\"Real Name\" NIL \"user\" \"domain\")) " + "NIL NIL NIL NIL NIL" + }, { + .message = + "Date: Thu, 15 Feb 2007 01:02:03 +0200\n" + "From: <@route:user@domain>\n" + "\n" + "body\n", + .envelope = + "\"Thu, 15 Feb 2007 01:02:03 +0200\" NIL " + "((NIL \"@route\" \"user\" \"domain\")) " + "((NIL \"@route\" \"user\" \"domain\")) " + "((NIL \"@route\" \"user\" \"domain\")) " + "NIL NIL NIL NIL NIL" + } +}; + +static const unsigned int parse_tests_count = N_ELEMENTS(parse_tests); + +static struct message_part_envelope * +msg_parse(pool_t pool, const char *message) +{ + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | + MESSAGE_HEADER_PARSER_FLAG_DROP_CR, + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, + }; + struct message_parser_ctx *parser; + struct message_part_envelope *envlp = NULL; + struct istream *input; + struct message_block block; + struct message_part *parts; + int ret; + + input = i_stream_create_from_data(message, strlen(message)); + parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { + i_assert(block.part->parent == NULL); + message_part_envelope_parse_from_header(pool, &envlp, block.hdr); + } + test_assert(ret < 0); + + message_parser_deinit(&parser, &parts); + i_stream_unref(&input); + return envlp; +} + +static void test_imap_envelope_write(void) +{ + struct message_part_envelope *envlp; + unsigned int i; + + for (i = 0; i < parse_tests_count; i++) T_BEGIN { + struct parse_test *test = &parse_tests[i]; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap envelope write", 1024); + + test_begin(t_strdup_printf("imap envelope write [%u]", i)); + envlp = msg_parse(pool, test->message); + + imap_envelope_write(envlp, str); + test_assert(strcmp(str_c(str), test->envelope) == 0); + + pool_unref(&pool); + test_end(); + } T_END; +} + +static void test_imap_envelope_parse(void) +{ + struct message_part_envelope *envlp; + const char *error; + unsigned int i; + bool ret; + + for (i = 0; i < parse_tests_count; i++) T_BEGIN { + struct parse_test *test = &parse_tests[i]; + string_t *str = t_str_new(128); + pool_t pool = pool_alloconly_create("imap envelope parse", 1024); + + test_begin(t_strdup_printf("imap envelope parser [%u]", i)); + + ret = imap_envelope_parse(test->envelope, pool, &envlp, &error); + test_assert(ret); + + if (ret) { + str_truncate(str, 0); + imap_envelope_write(envlp, str); + test_assert(strcmp(str_c(str), test->envelope) == 0); + } else { + i_error("Invalid envelope: %s", error); + } + + pool_unref(&pool); + test_end(); + } T_END; +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_envelope_write, + test_imap_envelope_parse, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-match.c b/src/lib-imap/test-imap-match.c new file mode 100644 index 0000000..df911da --- /dev/null +++ b/src/lib-imap/test-imap-match.c @@ -0,0 +1,127 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "imap-match.h" +#include "test-common.h" + +struct test_imap_match { + const char *pattern; + const char *input; + enum imap_match_result result; +}; + +static void test_imap_match(void) +{ + struct test_imap_match test[] = { + { "", "", IMAP_MATCH_YES }, + { "a", "b", IMAP_MATCH_NO }, + { "foo", "foo", IMAP_MATCH_YES }, + { "foo", "foo/", IMAP_MATCH_PARENT }, + { "%", "", IMAP_MATCH_YES }, + { "%", "foo", IMAP_MATCH_YES }, + { "%", "foo/", IMAP_MATCH_PARENT }, + { "%/", "foo/", IMAP_MATCH_YES }, + { "%", "foo/bar", IMAP_MATCH_PARENT }, + { "%/%", "foo", IMAP_MATCH_CHILDREN }, + { "%/%", "foo/", IMAP_MATCH_YES }, + { "foo/bar/%", "foo", IMAP_MATCH_CHILDREN }, + { "foo/bar/%", "foo/", IMAP_MATCH_CHILDREN }, + { "foo*", "foo", IMAP_MATCH_YES }, + { "foo*", "foo/", IMAP_MATCH_YES }, + { "foo*", "fobo", IMAP_MATCH_NO }, + { "*foo*", "bar/foo/", IMAP_MATCH_YES }, + { "*foo*", "fobo", IMAP_MATCH_CHILDREN }, + { "foo*bar", "foobar/baz", IMAP_MATCH_CHILDREN | IMAP_MATCH_PARENT }, + { "*foo*", "fobo", IMAP_MATCH_CHILDREN }, + { "%/%/%", "foo/", IMAP_MATCH_CHILDREN }, + { "%/%o/%", "foo/", IMAP_MATCH_CHILDREN }, + { "%/%o/%", "foo", IMAP_MATCH_CHILDREN }, + { "inbox", "inbox", IMAP_MATCH_YES }, + { "inbox", "INBOX", IMAP_MATCH_NO } + }; + struct test_imap_match inbox_test[] = { + { "inbox", "inbox", IMAP_MATCH_YES }, + { "inbox", "iNbOx", IMAP_MATCH_YES }, + { "i%X", "iNbOx", IMAP_MATCH_YES }, + { "%I%N%B%O%X%", "inbox", IMAP_MATCH_YES }, + { "i%X/foo", "iNbOx/foo", IMAP_MATCH_YES }, + { "%I%N%B%O%X%/foo", "inbox/foo", IMAP_MATCH_YES }, + { "i%X/foo", "inbx/foo", IMAP_MATCH_NO } + }; + struct imap_match_glob *glob, *glob2; + unsigned int i; + pool_t pool; + + pool = pool_alloconly_create("imap match", 1024); + + /* first try tests without inboxcasing */ + test_begin("imap match"); + for (i = 0; i < N_ELEMENTS(test); i++) { + glob = imap_match_init(pool, test[i].pattern, + FALSE, '/'); + test_assert(imap_match(glob, test[i].input) == test[i].result); + + glob2 = imap_match_dup(default_pool, glob); + test_assert(imap_match_globs_equal(glob, glob2)); + p_clear(pool); + + /* test the dup after clearing first one's memory */ + test_assert(imap_match(glob2, test[i].input) == test[i].result); + imap_match_deinit(&glob2); + } + + /* inboxcasing tests */ + for (i = 0; i < N_ELEMENTS(inbox_test); i++) { + glob = imap_match_init(pool, inbox_test[i].pattern, + TRUE, '/'); + test_assert(imap_match(glob, inbox_test[i].input) == inbox_test[i].result); + + glob2 = imap_match_dup(default_pool, glob); + test_assert(imap_match_globs_equal(glob, glob2)); + p_clear(pool); + + /* test the dup after clearing first one's memory */ + test_assert(imap_match(glob2, inbox_test[i].input) == inbox_test[i].result); + imap_match_deinit(&glob2); + } + pool_unref(&pool); + test_end(); +} + +static void test_imap_match_globs_equal(void) +{ + struct imap_match_glob *glob; + pool_t pool; + + pool = pool_alloconly_create("imap match globs equal", 1024); + test_begin("imap match globs equal"); + + glob = imap_match_init(pool, "1", FALSE, '/'); + test_assert(imap_match_globs_equal(glob, + imap_match_init(pool, "1", FALSE, '/'))); + test_assert(imap_match_globs_equal(glob, + imap_match_init(pool, "1", TRUE, '/'))); + test_assert(!imap_match_globs_equal(glob, + imap_match_init(pool, "1", FALSE, '.'))); + test_assert(!imap_match_globs_equal(glob, + imap_match_init(pool, "11", FALSE, '/'))); + + glob = imap_match_init(pool, "in%", TRUE, '/'); + test_assert(!imap_match_globs_equal(glob, + imap_match_init(pool, "in%", FALSE, '/'))); + test_assert(!imap_match_globs_equal(glob, + imap_match_init(pool, "In%", TRUE, '/'))); + + pool_unref(&pool); + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_match, + test_imap_match_globs_equal, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-parser.c b/src/lib-imap/test-imap-parser.c new file mode 100644 index 0000000..3ca4e34 --- /dev/null +++ b/src/lib-imap/test-imap-parser.c @@ -0,0 +1,157 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "imap-parser.h" +#include "test-common.h" + +static void test_imap_parser_crlf(void) +{ + static const char *test_input = "foo\r\nx\ry\n"; + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + unsigned int i; + enum imap_parser_error parse_error; + + test_begin("imap parser crlf handling"); + input = test_istream_create(test_input); + parser = imap_parser_create(input, NULL, 1024); + + /* must return -2 until LF is read */ + for (i = 0; test_input[i] != '\n'; i++) { + test_istream_set_size(input, i+1); + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, 0, &args) == -2); + } + test_istream_set_size(input, i+1); + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, 0, &args) == 1); + test_assert(args[0].type == IMAP_ARG_ATOM); + test_assert(args[1].type == IMAP_ARG_EOL); + + /* CR without LF should fail with error */ + imap_parser_reset(parser); + i_stream_seek(input, ++i); + test_istream_set_size(input, ++i); + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, 0, &args) == -2); + test_istream_set_size(input, ++i); + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, 0, &args) == -2); + test_istream_set_size(input, ++i); + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, 0, &args) == -1); + test_assert(strcmp(imap_parser_get_error + (parser, &parse_error), "CR sent without LF") == 0 && + parse_error == IMAP_PARSE_ERROR_BAD_SYNTAX); + + imap_parser_unref(&parser); + i_stream_destroy(&input); + test_end(); +} + +static void test_imap_parser_partial_list(void) +{ + static const char *test_input = "((((foo {1000000}\r\n"; + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args, *sub_list; + + test_begin("imap parser partial list"); + input = test_istream_create(test_input); + parser = imap_parser_create(input, NULL, 1024); + + (void)i_stream_read(input); + test_assert(imap_parser_read_args(parser, 0, + IMAP_PARSE_FLAG_LITERAL_SIZE, &args) == 1); + for (unsigned int i = 0; i < 4; i++) { + sub_list = imap_arg_as_list(&args[0]); + test_assert(IMAP_ARG_IS_EOL(&args[1])); + args = sub_list; + } + test_assert(imap_arg_atom_equals(&args[0], "foo")); + test_assert(args[1].type == IMAP_ARG_LITERAL_SIZE); + test_assert(IMAP_ARG_IS_EOL(&args[2])); + + imap_parser_unref(&parser); + i_stream_destroy(&input); + test_end(); +} + +static void test_imap_parser_read_tag_cmd(void) +{ + enum read_type { + BOTH, + TAG, + COMMAND + }; + struct { + const char *input; + const char *tag; + int ret; + enum read_type type; + } tests[] = { + { "tag foo", "tag", 1, BOTH }, + { "tag\r", "tag", 1, BOTH }, + { "tag\rfoo", "tag", 1, BOTH }, + { "tag\nfoo", "tag", 1, BOTH }, + { "tag\r\nfoo", "tag", 1, BOTH }, + { "\n", NULL, -1, BOTH }, + { "tag", NULL, 0, BOTH }, + { "tag\t", NULL, -1, BOTH }, + { "tag\001", NULL, -1, BOTH }, + { "tag\x80", NULL, -1, BOTH }, + { "tag(", NULL, -1, BOTH }, + { "tag)", NULL, -1, BOTH }, + { "tag{", NULL, -1, BOTH }, + { "tag/ ", "tag/", 1, BOTH }, + { "tag%", NULL, -1, BOTH }, + { "tag*", NULL, -1, BOTH }, + { "tag\"", NULL, -1, BOTH }, + { "tag\\", NULL, -1, BOTH }, + { "tag+", NULL, -1, TAG }, + { "tag+ ", "tag+", 1, COMMAND }, + }; + struct istream *input; + struct imap_parser *parser; + const char *atom; + int ret; + + test_begin("imap_parser_read_tag and imap_parser_read_command_name"); + for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { + if (tests[i].type != COMMAND) { + input = test_istream_create(tests[i].input); + test_assert(i_stream_read(input) > 0); + parser = imap_parser_create(input, NULL, 1024); + ret = imap_parser_read_tag(parser, &atom); + test_assert_idx(ret == tests[i].ret, i); + test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i); + imap_parser_unref(&parser); + i_stream_destroy(&input); + } + + if (tests[i].type != TAG) { + input = test_istream_create(tests[i].input); + test_assert(i_stream_read(input) > 0); + parser = imap_parser_create(input, NULL, 1024); + ret = imap_parser_read_command_name(parser, &atom); + test_assert_idx(ret == tests[i].ret, i); + test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i); + imap_parser_unref(&parser); + i_stream_destroy(&input); + } + } + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_parser_crlf, + test_imap_parser_partial_list, + test_imap_parser_read_tag_cmd, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-quote.c b/src/lib-imap/test-imap-quote.c new file mode 100644 index 0000000..77b46b3 --- /dev/null +++ b/src/lib-imap/test-imap-quote.c @@ -0,0 +1,171 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "imap-quote.h" +#include "test-common.h" + +static void test_imap_append_string_for_humans(void) +{ + static const struct { + const char *input, *output; + } tests[] = { + { "", "\"\"" }, + { " ", "\"\"" }, + { " ", "\"\"" }, + { "\t", "\"\"" }, + { " \t", "\"\"" }, + { " \t ", "\"\"" }, + { " foo", "{3}\r\nfoo" }, + { "\tfoo", "{3}\r\nfoo" }, + { "\t \tfoo", "{3}\r\nfoo" }, + { " foo ", "{3}\r\nfoo" }, + { " foo ", "{3}\r\nfoo" }, + { " foo \t \t", "{3}\r\nfoo" }, + { "hello\"world", "{11}\r\nhello\"world" }, + { "hello\\world", "{11}\r\nhello\\world" }, + { "hello\rworld", "{11}\r\nhello world" }, + { "hello\nworld", "{11}\r\nhello world" }, + { "hello\r\nworld", "{11}\r\nhello world" }, + { "hello\r\n world", "{11}\r\nhello world" }, + { "hello \r\n world", "{11}\r\nhello world" }, + }; + string_t *str = t_str_new(128); + unsigned int i; + + test_begin("imap_append_string_for_humans()"); + + for (i = 0; i < N_ELEMENTS(tests); i++) { + str_truncate(str, 0); + imap_append_string_for_humans(str, (const void *)tests[i].input, + strlen(tests[i].input)); + test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); + } + test_end(); +} + +static void test_imap_append_astring(void) +{ + static const struct { + const char *input, *output; + } tests[] = { + { "", "\"\"" }, + { "NIL", "\"NIL\"" }, + { "niL", "\"niL\"" }, + { "ni", "ni" }, + { "\\", "\"\\\\\"" }, + { "\\\\", "\"\\\\\\\\\"" }, + { "\\\\\\", "\"\\\\\\\\\\\\\"" }, + { "\\\\\\\\", "\"\\\\\\\\\\\\\\\\\"" }, + { "\\\\\\\\\\", "{5}\r\n\\\\\\\\\\" }, + { "\\\\\\\\\\\\", "{6}\r\n\\\\\\\\\\\\" }, + { "\"", "\"\\\"\"" }, + { "\"\"", "\"\\\"\\\"\"" }, + { "\"\"\"", "\"\\\"\\\"\\\"\"" }, + { "\"\"\"\"", "\"\\\"\\\"\\\"\\\"\"" }, + { "\"\"\"\"\"", "{5}\r\n\"\"\"\"\"" }, + { "\"\"\"\"\"\"", "{6}\r\n\"\"\"\"\"\"" }, + { "\r", "{1}\r\n\r" }, + { "\n", "{1}\r\n\n" }, + { "\r\n", "{2}\r\n\r\n" }, + { "\x7f", "\"\x7f\"" }, + { "\x80", "{1}\r\n\x80" }, + { "\xff", "{1}\r\n\xff" }, + }; + string_t *str = t_str_new(128); + unsigned int i; + + test_begin("test_imap_append_astring()"); + + for (i = 0; i < N_ELEMENTS(tests); i++) { + str_truncate(str, 0); + imap_append_astring(str, tests[i].input); + test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); + } + test_end(); +} + +static void test_imap_append_nstring(void) +{ + static const struct { + const char *input, *output; + } tests[] = { + { "", "\"\"" }, + { NULL, "NIL" }, + { "NIL", "\"NIL\"" }, + { "\"America N.\"", "\"\\\"America N.\\\"\"" }, + { "\"America N.\", \"America S.\"", "\"\\\"America N.\\\", \\\"America S.\\\"\"" }, + { "\"America N.\", \"America S.\", \"Africa\"", "{36}\r\n\"America N.\", \"America S.\", \"Africa\"" }, + { "Antarctica\n Australia", "{21}\r\nAntarctica\n Australia" }, + { "ni", "\"ni\"" } + }; + string_t *str = t_str_new(128); + unsigned int i; + + test_begin("test_imap_append_nstring()"); + + for (i = 0; i < N_ELEMENTS(tests); i++) { + str_truncate(str, 0); + imap_append_nstring(str, tests[i].input); + test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); + } + test_end(); +} + +static void test_imap_append_nstring_nolf(void) +{ + static const struct { + const char *input, *output; + } tests[] = { + { "", "\"\"" }, + { NULL, "NIL" }, + { "NIL", "\"NIL\"" }, + { "ni", "\"ni\"" }, + { "\"NIL\n foo", "\"\\\"NIL foo\"" }, + { "\"America N.\", \"America S.\", \"Africa\"", "{36}\r\n\"America N.\", \"America S.\", \"Africa\"" }, + { "foo\nbar", "\"foo bar\"" }, + { "foo\r\nbar", "\"foo bar\"" }, + { "foo\rbar", "\"foo bar\"" }, + { "foo\n bar", "\"foo bar\"" }, + { "foo\r\n bar", "\"foo bar\"" }, + { "foo\r bar", "\"foo bar\"" }, + { "foo\n\tbar", "\"foo\tbar\"" }, + { "foo\r\n\tbar", "\"foo\tbar\"" }, + { "foo\r\tbar", "\"foo\tbar\"" }, + { "foo\n bar", "\"foo bar\"" }, + { "foo\r\n bar", "\"foo bar\"" }, + { "foo\r bar", "\"foo bar\"" }, + { "\nfoo\r bar\r\n", "\" foo bar\"" } + }; + unsigned int i; + + test_begin("test_imap_append_nstring_nolf()"); + + for (i = 0; i < N_ELEMENTS(tests); i++) T_BEGIN { + string_t *str = t_str_new(1); + string_t *str2 = str_new(default_pool, 1); + + str_truncate(str, 0); + imap_append_nstring_nolf(str, tests[i].input); + test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i); + + str_truncate(str2, 0); + imap_append_nstring_nolf(str2, tests[i].input); + test_assert_idx(strcmp(tests[i].output, str_c(str2)) == 0, i); + + str_free(&str2); + } T_END; + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_append_string_for_humans, + test_imap_append_astring, + test_imap_append_nstring, + test_imap_append_nstring_nolf, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-url.c b/src/lib-imap/test-imap-url.c new file mode 100644 index 0000000..a63ca33 --- /dev/null +++ b/src/lib-imap/test-imap-url.c @@ -0,0 +1,1029 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "imap-url.h" +#include "test-common.h" + +struct valid_imap_url_test { + const char *url; + enum imap_url_parse_flags flags; + struct imap_url url_base; + + struct imap_url url_parsed; +}; + +/* Valid IMAP URL tests */ +static const struct valid_imap_url_test valid_url_tests[] = { + { + .url = "imap://localhost", + .url_parsed = { + .host = { .name = "localhost" } } + },{ + .url = "imap://user@localhost", + .url_parsed = { + .host = { .name = "localhost" }, + .userid = "user" } + },{ + .url = "imap://user;AUTH=PLAIN@localhost", + .url_parsed = { + .host = { .name = "localhost" }, + .userid = "user", + .auth_type = "PLAIN" } + },{ + .url = "imap://;AUTH=PLAIN@localhost", + .url_parsed = { + .host = { .name = "localhost" }, + .auth_type = "PLAIN" } + },{ + .url = "imap://%68endri%6B;AUTH=GSS%41PI@%65%78%61%6d%70%6c%65.com", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "hendrik", + .auth_type = "GSSAPI" } + },{ + .url = "imap://user@localhost:993", + .url_parsed = { + .host = { .name = "localhost" }, + .userid = "user", + .port = 993 } + },{ + .url = "imap://user@127.0.0.1", + .url_parsed = { + .host = { + .name = "127.0.0.1", + .ip = { .family = AF_INET } }, + .userid = "user" } + },{ + .url = "imap://user@[::1]", + .url_parsed = { + .host = { + .name = "[::1]", + .ip = { .family = AF_INET6 } }, + .userid = "user" } + },{ + .url = "imap://user@4example.com:423", + .url_parsed = { + .host = { .name = "4example.com" }, + .userid = "user", + .port = 423 } + },{ + .url = "imap://beelzebub@666.4example.com:999", + .url_parsed = { + .host = { .name = "666.4example.com" }, + .userid = "beelzebub", + .port = 999 } + },{ + .url = "imap://user@example.com/", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = NULL } + },{ + .url = "imap://user@example.com/./", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = NULL } + },{ + .url = "imap://user@example.com/INBOX", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX" } + },{ + .url = "imap://user@example.com/INBOX/", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX" } + },{ + .url = "imap://user@example.com//", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user"} + },{ + .url = "imap://user@example.com/INBOX/Trash", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash" } + },{ + .url = "imap://user@example.com/INBOX/Trash/..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX" } + },{ + .url = "imap://user@example.com/INBOX/Trash/../", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX" } + },{ + .url = "imap://user@example.com/INBOX/Trash/../..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = NULL } + },{ + .url = "imap://user@example.com/INBOX.Trash", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX.Trash" } + },{ + .url = "imap://user@example.com/INBOX%3BTrash", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX;Trash" } + },{ + .url = "imap://user@example.com/INBOX;UIDVALIDITY=1341", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", .uidvalidity = 1341 } + },{ + .url = "imap://user@example.com/INBOX/;UIDVALIDITY=23423", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", .uidvalidity = 23423 } + },{ + .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=6567", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", .uidvalidity = 6567 } + },{ + .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=788/;UID=16", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", .uidvalidity = 788, + .uid = 16 } + },{ + .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=788/;UID=16/..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", .uidvalidity = 788, + .uid = 0 } + },{ + .url = "imap://user@example.com/INBOX/Drafts;UIDVALIDITY=788/;UID=16/../..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", .uidvalidity = 0, + .uid = 0 } + },{ + .url = "imap://user@example.com/INBOX/Junk;UIDVALIDITY=27667/" + ";UID=434/;SECTION=HEADER", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Junk", .uidvalidity = 27667, + .uid = 434, .section = "HEADER" } + },{ + .url = "imap://user@example.com/INBOX/Important/" + ";UID=437/;SECTION=1.2.MIME", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 437, .section = "1.2.MIME" } + },{ + .url = "imap://user@example.com/INBOX/Important/;UID=56/;SECTION=AA/BB", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 56, .section = "AA/BB" } + },{ + .url = "imap://user@example.com/INBOX/Important/;UID=56/;SECTION=AA/BB/..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 56, .section = "AA/" } + },{ + .url = "imap://user@example.com/INBOX/Important/;UID=56/" + ";SECTION=AA/BB/../..", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 56, .section = NULL } + },{ + .url = "imap://user@example.com/INBOX/Important/;UID=234/" + ";SECTION=HEADER.FIELDS%20(%22To%22%20%22From%22)", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 234, .section = "HEADER.FIELDS (\"To\" \"From\")" } + },{ + .url = "imap://user@example.com/INBOX/Important/;UID=234/" + ";PARTIAL=10.250", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Important", + .uid = 234, .section = NULL, .partial_offset = 10, .partial_size = 250 } + },{ + .url = "imap://hendrik@example.com/INBOX/Important/;UID=34534/" + ";SECTION=1.3.TEXT/;PARTIAL=0.34254", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "hendrik", + .mailbox = "INBOX/Important", + .uid = 34534, .section = "1.3.TEXT", + .partial_offset = 0, .partial_size = 34254 } + },{ + .url = "imap://hendrik@example.com/INBOX/Sent" + ";UIDVALIDITY=534?SUBJECT%20%22Frop?%22", + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "hendrik", + .mailbox = "INBOX/Sent", .uidvalidity = 534, + .search_program = "SUBJECT \"Frop?\"" } + },{ + .url = "//hendrik@example.org/INBOX/Trash", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user" }, + .url_parsed = { + .host = { .name = "example.org" }, + .userid = "hendrik", + .mailbox = "INBOX/Trash" } + },{ + .url = "/INBOX/Trash", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash" } + },{ + .url = "user@example.com", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Accounts" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Accounts/user@example.com" } + },{ + .url = "Drafts", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts" } + },{ + .url = "../Drafts", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts" } + },{ + .url = "../Junk", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Junk", + .uidvalidity = 0 } + },{ + .url = "../Junk;UIDVALIDITY=23", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Junk", + .uidvalidity = 23 } + },{ + .url = "../../%23shared;UIDVALIDITY=23452", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 764 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "#shared", + .uidvalidity = 23452 } + },{ + .url = "../../%23news;UIDVALIDITY=546/;UID=456", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "#news", + .uidvalidity = 546, + .uid = 456 } + },{ + .url = "", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452 } + },{ + .url = "", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65 } + },{ + .url = "", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65, + .section = "AA/BB", + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65, + .section = "AA/BB", + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 } + },{ + .url = "", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65, + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65, + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 } + },{ + .url = ";UID=4767", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 4767 } + },{ + .url = ";UID=4767", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452}, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 4767 } + },{ + .url = "../;UID=4767", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", + .uidvalidity = 0, + .uid = 4767 } + },{ + .url = "../;UID=4767/;SECTION=TEXT", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 65, + .section = "1.2.3.MIME" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Trash", + .uidvalidity = 23452, + .uid = 4767, + .section = "TEXT" } + },{ + .url = ";SECTION=TEXT", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "1.2.3.MIME" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "TEXT" } + },{ + .url = "..", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/BB" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43 } + },{ + .url = "../;SECTION=CC", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/BB" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "CC" } + },{ + .url = "CC", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/BB" }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/CC" } + },{ + .url = ";PARTIAL=1024.1024", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .have_partial = TRUE, .partial_offset = 0, .partial_size = 1024 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 } + },{ + .url = "../CC/;PARTIAL=0.512", + .url_base = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/BB", + .have_partial = TRUE, .partial_offset = 1024, .partial_size = 1024 }, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX/Drafts", + .uidvalidity = 769, + .uid = 43, + .section = "AA/CC", + .have_partial = TRUE, .partial_offset = 0, .partial_size = 512 } + },{ + .url = "imap://user@example.com/INBOX/;UID=377;URLAUTH=anonymous", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", + .uid = 377, + .uauth_rumpurl = "imap://user@example.com/INBOX/;UID=377" + ";URLAUTH=anonymous", + .uauth_access_application = "anonymous"} + },{ + .url = "imap://user@example.com/INBOX/;UID=377" + ";URLAUTH=anonymous:internal:4142434445464748494A4B4C4D4E4F5051525354", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", + .uid = 377, + .uauth_rumpurl = "imap://user@example.com/INBOX/;UID=377" + ";URLAUTH=anonymous", + .uauth_access_application = "anonymous", + .uauth_mechanism = "internal", + .uauth_token = (const unsigned char *)"ABCDEFGHIJKLMNOPQRST", + .uauth_token_size = 20} + },{ + .url = "imap://user@example.com/INBOX/;UID=377" + ";EXPIRE=2011-02-12T12:45:14+01:00" + ";URLAUTH=user+frop:internal:4142434445464748494A4B4C4D4E4F5051525354", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH, + .url_parsed = { + .host = { .name = "example.com" }, + .userid = "user", + .mailbox = "INBOX", + .uid = 377, + .uauth_rumpurl = "imap://user@example.com/INBOX/;UID=377" + ";EXPIRE=2011-02-12T12:45:14+01:00;URLAUTH=user+frop", + .uauth_access_application = "user", + .uauth_access_user = "frop", + .uauth_mechanism = "internal", + .uauth_token = (const unsigned char *)"ABCDEFGHIJKLMNOPQRST", + .uauth_token_size = 20} + } +}; + +static const unsigned int valid_url_test_count = N_ELEMENTS(valid_url_tests); + +static void test_imap_url_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_url_test_count; i++) T_BEGIN { + const char *url = valid_url_tests[i].url; + enum imap_url_parse_flags flags = valid_url_tests[i].flags; + const struct imap_url *urlt = &valid_url_tests[i].url_parsed; + const struct imap_url *urlb = &valid_url_tests[i].url_base; + struct imap_url *urlp; + const char *error = NULL; + + test_begin(t_strdup_printf("imap url valid [%d]", i)); + + if (urlb->host.name == NULL) urlb = NULL; + if (imap_url_parse(url, urlb, flags, &urlp, &error) < 0) + urlp = NULL; + + test_out_reason(t_strdup_printf("imap_url_parse(%s)", + valid_url_tests[i].url), urlp != NULL, error); + if (urlp != NULL) { + if (urlp->host.name == NULL || urlt->host.name == NULL) { + test_out_quiet(t_strdup_printf("url->host.name = %s", urlp->host.name), + urlp->host.name == urlt->host.name); + } else { + test_out_quiet(t_strdup_printf("url->host.name = %s", urlp->host.name), + strcmp(urlp->host.name, urlt->host.name) == 0); + } + if (urlp->userid == NULL || urlt->userid == NULL) { + test_out_quiet(t_strdup_printf("url->userid = %s", urlp->userid), + urlp->userid == urlt->userid); + } else { + test_out_quiet(t_strdup_printf("url->userid = %s", urlp->userid), + strcmp(urlp->userid, urlt->userid) == 0); + } + if (urlp->auth_type == NULL || urlt->auth_type == NULL) { + test_out_quiet(t_strdup_printf("url->auth_type = %s", urlp->auth_type), + urlp->auth_type == urlt->auth_type); + } else { + test_out_quiet(t_strdup_printf("url->auth_type = %s", urlp->auth_type), + strcmp(urlp->auth_type, urlt->auth_type) == 0); + } + if (urlp->port == 0) { + test_out_quiet("url->port = (unspecified)", + urlp->port == urlt->port); + } else { + test_out_quiet(t_strdup_printf("url->port = %u", urlp->port), + urlp->port == urlt->port); + } + if (urlp->host.ip.family == 0) { + test_out_quiet("url->host.ip = (unspecified)", + urlp->host.ip.family == urlt->host.ip.family); + } else { + test_out_quiet("url->host.ip = (valid)", + urlp->host.ip.family == urlt->host.ip.family); + } + if (urlp->mailbox == NULL || urlt->mailbox == NULL) { + test_out_quiet(t_strdup_printf("url->mailbox = %s", urlp->mailbox), + urlp->mailbox == urlt->mailbox); + } else { + test_out_quiet(t_strdup_printf("url->mailbox = %s", urlp->mailbox), + strcmp(urlp->mailbox, urlt->mailbox) == 0); + } + test_out_quiet(t_strdup_printf("url->uidvalidity = %u", urlp->uidvalidity), + urlp->uidvalidity == urlt->uidvalidity); + test_out_quiet(t_strdup_printf("url->uid = %u", urlp->uid), + urlp->uid == urlt->uid); + if (urlp->section == NULL || urlt->section == NULL) { + test_out_quiet(t_strdup_printf("url->section = %s", urlp->section), + urlp->section == urlt->section); + } else { + test_out_quiet(t_strdup_printf("url->section = %s", urlp->section), + strcmp(urlp->section, urlt->section) == 0); + } + test_out_quiet(t_strdup_printf("url->partial = %"PRIuUOFF_T".%"PRIuUOFF_T, + urlp->partial_offset, urlp->partial_size), + urlp->partial_offset == urlt->partial_offset && + urlp->partial_size == urlt->partial_size); + if (urlp->search_program == NULL || urlt->search_program == NULL) { + test_out_quiet(t_strdup_printf( + "url->search_program = %s", urlp->search_program), + urlp->search_program == urlt->search_program); + } else { + test_out_quiet(t_strdup_printf( + "url->search_program = %s", urlp->search_program), + strcmp(urlp->search_program, urlt->search_program) == 0); + } + if (urlt->uauth_rumpurl != NULL) { + if (urlp->uauth_rumpurl == NULL) { + test_out_quiet("url->uauth_rumpurl = NULL", FALSE); + } else { + test_out_quiet(t_strdup_printf( + "url->uauth_rumpurl = %s", urlp->uauth_rumpurl), + strcmp(urlp->uauth_rumpurl, urlt->uauth_rumpurl) == 0); + } + if (urlp->uauth_access_application == NULL || + urlt->uauth_access_application == NULL) { + test_out_quiet(t_strdup_printf("url->uauth_access_application = %s", + urlp->uauth_access_application), + urlp->uauth_access_application == urlt->uauth_access_application); + } else { + test_out_quiet(t_strdup_printf("url->uauth_access_application = %s", + urlp->uauth_access_application), + strcmp(urlp->uauth_access_application, + urlt->uauth_access_application) == 0); + } + if (urlp->uauth_access_user == NULL || + urlt->uauth_access_user == NULL) { + test_out_quiet(t_strdup_printf("url->uauth_access_user = %s", + urlp->uauth_access_user), + urlp->uauth_access_user == urlt->uauth_access_user); + } else { + test_out_quiet(t_strdup_printf("url->uauth_access_user = %s", + urlp->uauth_access_user), + strcmp(urlp->uauth_access_user, + urlt->uauth_access_user) == 0); + } + if (urlp->uauth_mechanism == NULL || urlt->uauth_mechanism == NULL) { + test_out_quiet(t_strdup_printf( + "url->uauth_mechanism = %s", urlp->uauth_mechanism), + urlp->uauth_mechanism == urlt->uauth_mechanism); + } else { + test_out_quiet(t_strdup_printf( + "url->uauth_mechanism = %s", urlp->uauth_mechanism), + strcmp(urlp->uauth_mechanism, urlt->uauth_mechanism) == 0); + } + if (urlp->uauth_token == NULL || urlt->uauth_token == NULL) { + test_out_quiet(t_strdup_printf( + "url->uauth_token = %s", urlp->uauth_token), + urlp->uauth_token == urlt->uauth_token); + } else { + bool equal = urlp->uauth_token_size == urlt->uauth_token_size; + size_t i; + test_out_quiet(t_strdup_printf( + "url->uauth_token_size = %zu", urlp->uauth_token_size), + equal); + + if (equal) { + for (i = 0; i < urlp->uauth_token_size; i++) { + if (urlp->uauth_token[i] != urlt->uauth_token[i]) { + equal = FALSE; + break; + } + } + test_out_quiet(t_strdup_printf("url->uauth_token [index=%d]", (int)i), + equal); + } + } + } + } + + test_end(); + } T_END; +} + +struct invalid_imap_url_test { + const char *url; + enum imap_url_parse_flags flags; + struct imap_url url_base; +}; + +static const struct invalid_imap_url_test invalid_url_tests[] = { + { + .url = "http://www.dovecot.org" + },{ + .url = "imap:/INBOX" + },{ + .url = "imap://user@example.com/INBOX", + .flags = IMAP_URL_PARSE_REQUIRE_RELATIVE, + .url_base = { + .host = { .name = "example.com" }, + .userid = "user" } + },{ + .url = "" + },{ + .url = "/INBOX/;UID=377" + },{ + .url = "imap://user@example.com/INBOX/;UID=377/;SECTION=TEXT?ALL" + },{ + .url = "imap://user@example.com/INBOX/?" + },{ + .url = "imap://user@example.com/INBOX/#Fragment" + },{ + .url = "imap://user@example.com/INBOX/\"" + },{ + .url = "imap:///INBOX" + },{ + .url = "imap://[]/INBOX" + },{ + .url = "imap://[v08.234:232:234:234:2221]/INBOX" + },{ + .url = "imap://[1::34a:34:234::6]/INBOX" + },{ + .url = "imap://example%a.com/INBOX" + },{ + .url = "imap://example.com%/INBOX" + },{ + .url = "imap://example%00.com/INBOX" + },{ + .url = "imap://example.com:65539/INBOX" + },{ + .url = "imap://user;ATH=frop@example.com" + },{ + .url = "imap://user;AUTH=frop;friep@example.com" + },{ + .url = "imap://user;AUTH=@example.com" + },{ + .url = "imap://user:password@example.com" + },{ + .url = "imap://user;AUTH=A:B@example.com" + },{ + .url = "imap://user%@example.com" + },{ + .url = "imap://user%00@example.com" + },{ + .url = "imap://user%ar;AUTH=*@example.com" + },{ + .url = "imap://;AUTH=FR%etD@example.com" + },{ + .url = "imap://user;AUTH=%@example.com" + },{ + .url = "imap://user;AUTH=%00@example.com" + },{ + .url = "imap://example.com/INBOX/%00/" + },{ + .url = "imap://example.com/INBOX/%0r/" + },{ + .url = "imap://example.com/INBOX/Trash/%/" + },{ + .url = "imap://example.com/INBOX;UIDVALIDITY=23423;FROP=friep/" + },{ + .url = "imap://example.com/INBOX;UIDVALIDITY=0/;UID=377" + },{ + .url = "imap://example.com/INBOX;UIDVALIDITY=/" + },{ + .url = "imap://example.com/INBOX;UIDVALIDITY=33a/" + },{ + .url = "imap://example.com/INBOX;FROP=friep/" + },{ + .url = "imap://example.com/INBOX/;UID=377;FROP=friep/" + },{ + .url = "imap://example.com/INBOX/;UID=0/" + },{ + .url = "imap://example.com/INBOX/;UID=/" + },{ + .url = "imap://example.com/INBOX/;UID=5e6/" + },{ + .url = "imap://example.com/INBOX/;UID=35/;SECTION=ALL;FROP=43/" + },{ + .url = "imap://example.com/INBOX/;UID=35/;SECTION=/" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0." + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0.e10" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=.3" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=5t4.3" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0.0" + },{ + .url = "imap://example.com/INBOX/;UID=34/;PARTIAL=0.23409823409820938409823" + },{ + .url = "imap://example.com/INBOX/;UID=377/;FROP=34" + },{ + .url = "imap://example.com/INBOX/;UID=377;FROP=34" + },{ + .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2010-02-02T12:00:12Z" + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523f45aadf2afe" + },{ + .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2011-15-02T00:00:00Z" + ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2011-10-02T00:00:00Z", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "/INBOX/;UID=377;EXPIRE=2011-10-02T00:00:00Z" + ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377;URLAUTH=", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=:internal:0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=user+:internal:0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=+frop:internal:0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377;URLAUTH=anonymous:", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=anonymous::0ad89fafd79f54afe4523f45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377;URLAUTH=anonymous:internal:", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377" + ";URLAUTH=anonymous:internal:fd79f54afe4523", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + },{ + .url = "imap://example.com/INBOX/;UID=377;EXPIRE=2011-10-02T00:00:00Z" + ";URLAUTH=anonymous:internal:0ad89fafd79f54afe4523q45aadf2afe", + .flags = IMAP_URL_PARSE_ALLOW_URLAUTH + }, +}; + +static const unsigned int invalid_url_test_count = N_ELEMENTS(invalid_url_tests); + +static void test_imap_url_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_url_test_count; i++) T_BEGIN { + const char *url = invalid_url_tests[i].url; + enum imap_url_parse_flags flags = invalid_url_tests[i].flags; + const struct imap_url *urlb = &invalid_url_tests[i].url_base; + struct imap_url *urlp; + const char *error = NULL; + + if (urlb->host.name == NULL) + urlb = NULL; + + test_begin(t_strdup_printf("imap url invalid [%d]", i)); + + if (imap_url_parse(url, urlb, flags, &urlp, &error) < 0) + urlp = NULL; + test_out_reason(t_strdup_printf("parse %s", url), urlp == NULL, error); + + test_end(); + } T_END; + +} + +static const char *parse_create_url_tests[] = { + "imap://host.example.com/", + "imap://10.0.0.1/", + "imap://[::1]/", + "imap://user@host.example.com/", + "imap://user@host.example.com:993/", + "imap://su%3auser@host.example.com/", + "imap://user;AUTH=PLAIN@host.example.com/", + "imap://user;AUTH=PLAIN@host.example.com/INBOX", + "imap://user;AUTH=PLAIN@host.example.com/INBOX/;UID=5", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT/;PARTIAL=1", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT/;PARTIAL=1.14", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT/;PARTIAL=1.14;URLAUTH=anonymous", + "imap://user;AUTH=PLAIN@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT/;PARTIAL=1.14;URLAUTH=user+username", + "imap://user;AUTH=PLAIN@host.example.com/INBOX?SUBJECT%20%22Frop?%22", + "imap://user%3ba@host.example.com/", + "imap://user%40example.com@host.example.com/", + "imap://user%40example.com;AUTH=STR%23ANGE@host.example.com/", + "imap://user;AUTH=PLAIN@host.example.com/INBOX/Important%3bWork", + "imap://user@host.example.com/%23shared/news", + "imap://user@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=HEADER.FIELDS%20(DATE%20FROM)", + "imap://user@host.example.com/INBOX;UIDVALIDITY=15/;UID=5" + "/;SECTION=TEXT/;PARTIAL=1.14;URLAUTH=user+user%3bname", +}; + +static const unsigned int parse_create_url_test_count = N_ELEMENTS(parse_create_url_tests); + +static void test_imap_url_parse_create(void) +{ + unsigned int i; + + for (i = 0; i < parse_create_url_test_count; i++) T_BEGIN { + const char *url = parse_create_url_tests[i]; + struct imap_url *urlp; + const char *error = NULL; + + test_begin(t_strdup_printf("imap url parse/create [%d]", i)); + + if (imap_url_parse + (url, NULL, IMAP_URL_PARSE_ALLOW_URLAUTH, &urlp, &error) < 0) + urlp = NULL; + test_out_reason(t_strdup_printf("parse %s", url), urlp != NULL, error); + if (urlp != NULL) { + const char *urlnew = imap_url_create(urlp); + test_out(t_strdup_printf + ("create %s", urlnew), strcmp(url, urlnew) == 0); + } + + test_end(); + } T_END; + +} + + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_url_valid, + test_imap_url_invalid, + test_imap_url_parse_create, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-utf7.c b/src/lib-imap/test-imap-utf7.c new file mode 100644 index 0000000..216eebf --- /dev/null +++ b/src/lib-imap/test-imap-utf7.c @@ -0,0 +1,216 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "unichar.h" +#include "imap-utf7.h" +#include "test-common.h" + +static void test_imap_utf7_by_example(void) +{ + static const struct test { + const char *utf8; + const char *mutf7; + } tests[] = { + { "&&x&&", "&-&-x&-&-" }, + { "~peter/mail/\xe5\x8f\xb0\xe5\x8c\x97/\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e", + "~peter/mail/&U,BTFw-/&ZeVnLIqe-" }, + { "tiet\xc3\xa4&j\xc3\xa4&", "tiet&AOQ-&-j&AOQ-&-" }, /* & is always encoded as &- */ + { "p\xe4\xe4", NULL }, + { NULL, "&" }, + { NULL, "&Jjo" }, + { NULL, "&Jjo!" }, + { NULL, "&U,BTFw-&ZeVnLIqe-" } /* unnecessary shift */ + }; + string_t *dest, *dest2; + unsigned int i; + + dest = t_str_new(256); + dest2 = t_str_new(256); + + test_begin("imap mutf7 examples"); + for (i = 0; i < N_ELEMENTS(tests); i++) { + str_truncate(dest, 0); + if (tests[i].utf8 != NULL) { + if (imap_utf8_to_utf7(tests[i].utf8, dest) < 0) + test_assert_idx(tests[i].mutf7 == NULL, i); + else + test_assert_idx(null_strcmp(tests[i].mutf7, str_c(dest)) == 0, i); + } else { + /* invalid mUTF-7 - test that escaping works */ + str_truncate(dest2, 0); + imap_utf7_to_utf8_escaped(tests[i].mutf7, "%", dest); + imap_escaped_utf8_to_utf7(str_c(dest), '%', dest2); + test_assert_idx(strcmp(tests[i].mutf7, str_c(dest2)) == 0, i); + } + if (tests[i].mutf7 != NULL) { + str_truncate(dest, 0); + if (imap_utf7_to_utf8(tests[i].mutf7, dest) < 0) + test_assert_idx(tests[i].utf8 == NULL, i); + else + test_assert_idx(null_strcmp(tests[i].utf8, str_c(dest)) == 0, i); + test_assert_idx(imap_utf7_is_valid(tests[i].mutf7) != (tests[i].utf8 == NULL), i); + } + } + + str_truncate(dest, 0); + imap_utf7_to_utf8_escaped(".foo%", "%.", dest); + test_assert_strcmp(str_c(dest), "%2efoo%25"); + + str_truncate(dest, 0); + test_assert(imap_escaped_utf8_to_utf7("%foo%2ebar", '%', dest) == 0); + test_assert_strcmp(str_c(dest), "%foo.bar"); + + test_end(); +} + +static void test_imap_utf7_ucs4_cases(void) +{ + string_t *src, *dest; + const char *orig_src; + unsigned int i, j; + unichar_t chr; + + src = t_str_new(256); + dest = t_str_new(256); + + test_begin("imap mutf7 ucs4 cases"); + for (chr = 0xffff; chr <= 0x10010; chr++) T_BEGIN { + for (i = 1; i <= 10; i++) { + str_truncate(src, 0); + str_truncate(dest, 0); + for (j = 0; j < i; j++) { + if (j % 3 == 0) + str_append_c(src, 'x'); + if (j % 5 == 0) + str_append_c(src, '&'); + uni_ucs4_to_utf8_c(chr, src); + } + + orig_src = t_strdup(str_c(src)); + str_truncate(src, 0); + + test_assert_idx(imap_utf8_to_utf7(orig_src, dest) == 0, chr*100+i); + test_assert_idx(imap_utf7_to_utf8(str_c(dest), src) == 0, chr*100+i); + test_assert_idx(strcmp(str_c(src), orig_src) == 0, chr+100+i); + } + } T_END; + test_end(); +} + +static const char mb64[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; +static void test_imap_utf7_non_utf16(void) +{ + string_t *dest, *dest2; + unsigned int i; + + test_begin("imap mutf7 non-utf16"); + dest = t_str_new(32); + dest2 = t_str_new(32); + for (i = 0; i <= 255; ++i) { + /* Invalid, code a single 8-bit octet */ + const char csrc[] = { + '&', + mb64[i >> 2], + mb64[(i & 3) << 4], + '-', + '\0' + }; + test_assert_idx(!imap_utf7_is_valid(csrc), i); + + /* escaping can reverse the original string */ + str_truncate(dest, 0); + str_truncate(dest2, 0); + imap_utf7_to_utf8_escaped(csrc, "%", dest); + imap_escaped_utf8_to_utf7(str_c(dest), '%', dest2); + test_assert_idx(strcmp(csrc, str_c(dest2)) == 0, i); + } + for (i = 0; i <= 255; ++i) { + /* Invalid, U+00E4 followed by a single octet */ + const char csrc[] = { + '&', + mb64[ (0x00 >> 2)], + mb64[((0x00 & 0x03) << 4) | (0xe4 >> 4)], + mb64[((0xe4 & 0x0f) << 2) | ( i >> 6)], + mb64[ i & 0x3f ], + '-', + '\0' + }; + test_assert_idx(!imap_utf7_is_valid(csrc), i); + + /* escaping can reverse the original string */ + str_truncate(dest, 0); + str_truncate(dest2, 0); + imap_utf7_to_utf8_escaped(csrc, "%", dest); + imap_escaped_utf8_to_utf7(str_c(dest), '%', dest2); + test_assert_idx(strcmp(csrc, str_c(dest2)) == 0, i); + } + test_end(); +} + +static void test_imap_utf7_bad_ascii(void) +{ + string_t *dest; + char csrc[1+1]; + unsigned int i; + + dest = t_str_new(256); + + test_begin("imap mutf7 bad ascii"); + for (i = 1; i <= 0x7f; ++i) { + if (i == ' ') + i = 0x7f; + csrc[0] = i; + csrc[1] = '\0'; + test_assert_idx(!imap_utf7_is_valid(csrc), i); + str_truncate(dest, 0); + test_assert_idx(imap_utf7_to_utf8(csrc, dest) < 0, i); + } + test_end(); +} + +static void test_imap_utf7_unnecessary(void) +{ + string_t *dest; + char csrc[1+3+1+1]; + unsigned int i; + + dest = t_str_new(256); + + test_begin("imap mutf7 unnecessary"); + for (i = 0x20; i < 0x7f; ++i) { + /* Create an invalid escaped encoding of a simple char or '&' */ + csrc[0] = '&'; + csrc[1] = mb64[ (0x00 >> 2)]; + csrc[2] = mb64[((0x00 & 0x03) << 4) | ( i >> 4)]; + csrc[3] = mb64[(( i & 0x0f) << 2) | 0 ]; + csrc[4] = '-'; + csrc[5] = '\0'; + test_assert_idx(!imap_utf7_is_valid(csrc), i); + str_truncate(dest, 0); + test_assert_idx(imap_utf7_to_utf8(csrc, dest) < 0, i); + + /* All self-coding characters must self-code */ + if (i == '&') + continue; + csrc[0] = i; + csrc[1] = '\0'; + str_truncate(dest, 0); + test_assert_idx(imap_utf8_to_utf7(csrc, dest) >= 0, i); + test_assert_idx(strcmp(csrc, str_c(dest)) == 0, i); + } + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_utf7_by_example, + test_imap_utf7_ucs4_cases, + test_imap_utf7_non_utf16, + test_imap_utf7_bad_ascii, + test_imap_utf7_unnecessary, + NULL + }; + return test_run(test_functions); +} diff --git a/src/lib-imap/test-imap-util.c b/src/lib-imap/test-imap-util.c new file mode 100644 index 0000000..90f59a1 --- /dev/null +++ b/src/lib-imap/test-imap-util.c @@ -0,0 +1,79 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "mail-types.h" +#include "imap-arg.h" +#include "imap-util.h" +#include "test-common.h" + +static void test_imap_parse_system_flag(void) +{ + test_begin("imap_parse_system_flag"); + test_assert(imap_parse_system_flag("\\aNswered") == MAIL_ANSWERED); + test_assert(imap_parse_system_flag("\\fLagged") == MAIL_FLAGGED); + test_assert(imap_parse_system_flag("\\dEleted") == MAIL_DELETED); + test_assert(imap_parse_system_flag("\\sEen") == MAIL_SEEN); + test_assert(imap_parse_system_flag("\\dRaft") == MAIL_DRAFT); + test_assert(imap_parse_system_flag("\\rEcent") == MAIL_RECENT); + test_assert(imap_parse_system_flag("answered") == 0); + test_assert(imap_parse_system_flag("\\broken") == 0); + test_assert(imap_parse_system_flag("\\") == 0); + test_assert(imap_parse_system_flag("") == 0); + test_end(); +} + +static void test_imap_write_arg(void) +{ + ARRAY_TYPE(imap_arg_list) list_root, list_sub; + struct imap_arg *arg; + + t_array_init(&list_sub, 2); + arg = array_append_space(&list_sub); + arg->type = IMAP_ARG_ATOM; + arg->_data.str = "foo"; + arg = array_append_space(&list_sub); + arg->type = IMAP_ARG_EOL; + + t_array_init(&list_root, 2); + arg = array_append_space(&list_root); + arg->type = IMAP_ARG_LIST; + arg->_data.list = list_sub; + arg = array_append_space(&list_root); + arg->type = IMAP_ARG_STRING; + arg->_data.str = "bar"; + arg = array_append_space(&list_root); + arg->type = IMAP_ARG_EOL; + + const struct { + struct imap_arg input; + const char *output; + } tests[] = { + { { .type = IMAP_ARG_NIL }, "NIL" }, + { { .type = IMAP_ARG_ATOM, ._data.str = "atom" }, "atom" }, + { { .type = IMAP_ARG_STRING, ._data.str = "s\\t\"ring" }, "\"s\\\\t\\\"ring\"" }, + { { .type = IMAP_ARG_LITERAL, ._data.str = "l\\i\"t\r\neral" }, "{11}\r\nl\\i\"t\r\neral" }, + { { .type = IMAP_ARG_LITERAL_SIZE, ._data.literal_size = 12345678 }, "<12345678 byte literal>" }, + { { .type = IMAP_ARG_LITERAL_SIZE_NONSYNC, ._data.literal_size = 12345678 }, "<12345678 byte literal>" }, + { { .type = IMAP_ARG_LIST, ._data.list = list_root }, "((foo) \"bar\")" }, + }; + string_t *str = t_str_new(100); + + test_begin("imap_write_arg"); + for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) { + str_truncate(str, 0); + imap_write_arg(str, &tests[i].input); + test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i); + } + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_parse_system_flag, + test_imap_write_arg, + NULL + }; + return test_run(test_functions); +} |