Projects
Kolab:3.4
cyrus-imapd
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 62
View file
cyrus-imapd-2.5.tar.gz/.gitignore
Changed
@@ -11,7 +11,6 @@ *.gcno *.gcda .deps -.libs .dirstamp .libs Makefile.in @@ -31,13 +30,11 @@ config.status.lineno config.sub configure -debian/ com_err/et/compile_et coverage.xml cscope.out depcomp imap/arbitron -imap/ctl_conversationsdb imap/chk_cyrus imap/ctl_cyrusdb imap/ctl_deliver @@ -50,18 +47,12 @@ imap/cyr_expire imap/cyr_info imap/cyr_sequence -imap/cyr_sphinxmgr imap/cyr_synclog imap/cyr_userseen imap/cyrdump -imap/dav_reconstruct imap/deliver imap/fetchnews imap/fud -imap/http_err.c -imap/http_err.h -imap/httpd -imap/hammer_cyrusdb imap/idled imap/imap_err.c imap/imap_err.h @@ -73,7 +64,6 @@ imap/mbexamine imap/mbpath imap/mbtool -imap/message_test imap/mupdate_err.c imap/mupdate_err.h imap/mupdate @@ -85,10 +75,8 @@ imap/pushstats.h imap/quota imap/reconstruct -imap/search_test imap/smmapd imap/squatter -imap/squat_dump imap/sync_client imap/sync_reset imap/sync_server
View file
cyrus-imapd-2.5.tar.gz/Makefile.am
Changed
@@ -55,7 +55,6 @@ imap/imap_err.c \ lib/imapopts.c DISTCLEANFILES = imap/imap_err.c imap/imap_err.h imap/mupdate_err.c imap/mupdate_err.h imap/nntp_err.c imap/nntp_err.h \ - imap/http_err.c imap/http_err.h \ com_err/et/compile_et \ perl/annotator/Makefile perl/annotator/Makefile.PL perl/imap/Makefile perl/imap/Makefile.PL perl/sieve/managesieve/Makefile perl/sieve/managesieve/Makefile.PL perl/sieve/scripts/installsieve perl/sieve/scripts/sieveshell \ sieve/sieve_err.c sieve/sieve_err.h @@ -90,8 +89,6 @@ imap/nntp_err.c \ imap/lmtpstats.c \ imap/pushstats.c \ - lib/htmlchar.c \ - lib/htmlchar.h \ imap/rfc822_header.c \ imap/rfc822_header.h lib_LTLIBRARIES += imap/libcyrus_imap.la @@ -103,7 +100,6 @@ user_PROGRAMS += \ imap/arbitron \ imap/chk_cyrus \ - imap/ctl_conversationsdb \ imap/ctl_cyrusdb \ imap/ctl_deliver \ imap/ctl_mboxlist \ @@ -118,22 +114,13 @@ imap/cyr_synclog \ imap/cyr_userseen \ imap/deliver \ - imap/hammer_cyrusdb \ imap/ipurge \ imap/mbexamine \ imap/mbpath \ imap/mbtool \ - imap/message_test \ imap/quota \ imap/reconstruct \ - imap/search_test -if USE_SQUAT -service_PROGRAMS += imap/squat_dump -endif -if SQUATTER -# Despite the name, the squatter program handles any search engine -service_PROGRAMS += imap/squatter -endif + imap/squatter if NNTPD user_PROGRAMS += imap/fetchnews endif @@ -147,37 +134,6 @@ if NNTPD service_PROGRAMS += imap/nntpd endif -if CALALARMD -user_PROGRAMS += imap/calalarmd -endif -if HTTPD -AM_CPPFLAGS += $(HTTP_CPPFLAGS) -AM_LDFLAGS += $(HTTP_LIBS) -BUILT_SOURCES += imap/http_err.c imap/http_err.h -service_PROGRAMS += imap/httpd -user_PROGRAMS += imap/dav_reconstruct -HTTP_SOURCES = imap/http_err.c \ - imap/http_proxy.c \ - imap/http_caldav.c \ - imap/http_carddav.c \ - imap/http_dav.c \ - imap/http_dblookup.c \ - imap/http_ischedule.c \ - imap/http_rss.c \ - imap/http_timezone.c \ - imap/zoneinfo_db.c \ - imap/caldav_db.c \ - imap/caldav_alarm.c \ - imap/proxy.c \ - imap/smtpclient.c \ - imap/spool.c \ - imap/carddav_db.c \ - imap/dav_db.c \ - imap/dav_util.c \ - imap/jcal.c \ - imap/xcal.c \ - imap/httpd.c -endif if REPLICATION user_PROGRAMS += imap/sync_client imap/sync_reset service_PROGRAMS += imap/sync_server @@ -295,7 +251,6 @@ lib/charset/windows-1252.t \ lib/charset/windows-1255.t \ lib/charset/windows-1256.t \ - lib/htmlchar.st \ lib/test/cyrusdb.c \ lib/test/cyrusdb.INPUT \ lib/test/cyrusdblong.INPUT \ @@ -521,11 +476,7 @@ cunit/syslog.c \ cunit/cunit-syslog.h \ cunit/timeout.c \ - cunit/timeout.h \ - cunit/timezones.c \ - cunit/timezones.h \ - cunit/timeofday.c \ - cunit/timeofday.h + cunit/timeout.h cunit_TESTS = \ cunit/annotate.testc \ @@ -535,7 +486,6 @@ cunit/buf.testc \ cunit/byteorder64.testc \ cunit/charset.testc \ - cunit/command.testc \ cunit/crc32.testc \ cunit/db.testc \ cunit/dlist.testc \ @@ -554,9 +504,7 @@ cunit/prot.testc \ cunit/ptrarray.testc \ cunit/quota.testc \ - cunit/rfc822tok.testc \ - cunit/search_expr.testc \ - cunit/seqset.testc + cunit/rfc822tok.testc if SIEVE cunit_TESTS += cunit/sieve.testc endif @@ -636,7 +584,6 @@ lib/lsort.h \ lib/map.h \ lib/mappedfile.h \ - lib/me.h \ lib/mkgmtime.h \ lib/mpool.h \ lib/nonblock.h \ @@ -672,9 +619,6 @@ imap_chk_cyrus_SOURCES = imap/chk_cyrus.c imap/cli_fatal.c imap/mutex_fake.c imap_chk_cyrus_LDADD = $(LD_UTILITY_ADD) -imap_ctl_conversationsdb_SOURCES = imap/ctl_conversationsdb.c imap/mutex_fake.c -imap_ctl_conversationsdb_LDADD = $(LD_UTILITY_ADD) - imap_ctl_cyrusdb_SOURCES = imap/cli_fatal.c imap/ctl_cyrusdb.c imap/mutex_fake.c imap_ctl_cyrusdb_LDADD = $(LD_UTILITY_ADD) @@ -732,15 +676,9 @@ imap_fud_SOURCES = imap/fud.c imap/mutex_fake.c master/service.c imap_fud_LDADD = $(LD_SERVER_ADD) -imap_hammer_cyrusdb_SOURCES = imap/cli_fatal.c imap/hammer_cyrusdb.c imap/mutex_fake.c -imap_hammer_cyrusdb_LDADD = $(LD_UTILITY_ADD) - imap_idled_SOURCES = imap/idled.c imap/mutex_fake.c imap_idled_LDADD = $(LD_UTILITY_ADD) -imap_calalarmd_SOURCES = imap/calalarmd.c imap/mutex_fake.c -imap_calalarmd_LDADD = $(LD_UTILITY_ADD) - imap_imapd_SOURCES = \ imap/imap_proxy.c \ imap/imap_proxy.h \ @@ -774,7 +712,6 @@ imap/append.h \ imap/backend.c \ imap/backend.h \ - imap/conversations.c \ imap/convert_code.c \ imap/convert_code.h \ imap/dlist.c \ @@ -788,7 +725,6 @@ imap/idlemsg.c \ imap/idlemsg.h \ imap/imapparse.c \ - imap/imapparse.h \ imap/index.c \ imap/index.h \ imap/mailbox.c \ @@ -823,15 +759,15 @@ imap/saslserver.c \ imap/search_engines.c \ imap/search_engines.h \ - imap/search_expr.c \ - imap/search_expr.h \ - imap/search_query.c \ - imap/search_query.h \ imap/seen.h \ imap/seen_db.c \ imap/sequence.c \ imap/sequence.h \ imap/setproctitle.c \ + imap/squat.c \ + imap/squat.h \ + imap/squat_internal.c \ + imap/squat_internal.h \ imap/statuscache.h \ imap/statuscache_db.c \ imap/sync_log.c \ @@ -847,43 +783,13 @@ imap/userdeny_db.c \ imap/userdeny.h \ imap/version.c \ - imap/version.h \ - imap/xstats.c \ - imap/xstats.h \ - imap/xstats_metrics.h - + imap/version.h imap_libcyrus_imap_la_LIBADD = $(COM_ERR_LIBS) $(LIB_UUID) lib/libcyrus_min.la lib/libcyrus.la imap_libcyrus_imap_la_CFLAGS = $(AM_CFLAGS) $(CFLAG_VISIBILITY) -imap_libcyrus_imap_la_CXXFLAGS = - -if USE_SQUAT -imap_libcyrus_imap_la_SOURCES += \ - imap/search_squat.c \ - imap/squat.c \ - imap/squat.h \ - imap/squat_build.c \ - imap/squat_internal.c \ - imap/squat_internal.h -endif -if USE_SPHINX -imap_libcyrus_imap_la_SOURCES += \ - imap/search_sphinx.c -endif if JANSSON imap_libcyrus_imap_la_LIBADD += $(JANSSON_LIBS) imap_libcyrus_imap_la_CFLAGS += $(JANSSON_CFLAGS) endif -if HTTPD -imap_libcyrus_imap_la_SOURCES += $(HTTP_SOURCES) -endif -if USE_XAPIAN -imap_libcyrus_imap_la_SOURCES += \ - imap/search_xapian.c \ - imap/xapian_wrap.h \ - imap/xapian_wrap.cpp -imap_libcyrus_imap_la_LIBADD += $(XAPIAN_LIBS) -imap_libcyrus_imap_la_CXXFLAGS += $(XAPIAN_CXXFLAGS) -endif imap_lmtpd_SOURCES = \ imap/autocreate.c \ @@ -920,9 +826,6 @@ imap_mbtool_SOURCES = imap/cli_fatal.c imap/mbtool.c imap/mutex_fake.c imap_mbtool_LDADD = $(LD_UTILITY_ADD) -imap_message_test_SOURCES = imap/message_test.c imap/mutex_fake.c -imap_message_test_LDADD = $(LD_UTILITY_ADD) - imap_mupdate_SOURCES = \ imap/mupdate.c \ imap/mupdate.h \ @@ -945,35 +848,6 @@ master/service.c imap_nntpd_LDADD = $(LD_SERVER_ADD) -nodist_imap_httpd_SOURCES = \ - imap/http_err.c \ - imap/http_err.h -imap_httpd_SOURCES = \ - imap/mutex_fake.c \ - imap/carddav_db.c \ - imap/caldav_db.c \ - imap/caldav_alarm.c \ - imap/http_proxy.c \ - imap/httpd.c \ - imap/proxy.c \ - imap/spool.c \ - imap/spool.h \ - imap/http_rss.c \ - imap/http_dav.c \ - imap/http_dblookup.c \ - imap/dav_db.c \ - imap/dav_util.c \ - imap/http_ischedule.c \ - imap/http_carddav.c \ - imap/http_caldav.c \ - imap/http_timezone.c \ - imap/zoneinfo_db.c \ - imap/smtpclient.c \ - imap/xcal.c \ - imap/jcal.c \ - master/service.c -imap_httpd_LDADD = $(LD_SERVER_ADD) - imap_pop3d_SOURCES = \ imap/autocreate.c \ imap/autocreate.h \ @@ -993,25 +867,17 @@ imap_reconstruct_SOURCES = imap/cli_fatal.c imap/mutex_fake.c imap/reconstruct.c imap_reconstruct_LDADD = $(LD_UTILITY_ADD) -imap_dav_reconstruct_SOURCES = imap/cli_fatal.c imap/mutex_fake.c imap/dav_reconstruct.c -imap_dav_reconstruct_LDADD = $(LD_UTILITY_ADD) - -imap_search_test_SOURCES = imap/search_test.c imap/mutex_fake.c -imap_search_test_LDADD = $(LD_UTILITY_ADD) - imap_smmapd_SOURCES = imap/mutex_fake.c imap/proxy.c imap/smmapd.c master/service.c imap_smmapd_LDADD = $(LD_SERVER_ADD) -imap_squatter_SOURCES = imap/cli_fatal.c imap/mutex_fake.c imap/squatter.c +imap_squatter_SOURCES = imap/cli_fatal.c imap/mutex_fake.c imap/squatter.c imap/squat_build.c imap_squatter_LDADD = $(LD_UTILITY_ADD) -imap_squat_dump_SOURCES = imap/cli_fatal.c imap/mutex_fake.c imap/squat_dump.c -imap_squat_dump_LDADD = $(LD_UTILITY_ADD) - -imap_sync_client_SOURCES = imap/mutex_fake.c imap/sync_client.c imap/sync_support.c imap/sync_support.h +imap_sync_client_SOURCES = imap/mutex_fake.c imap/sync_client.c \ + imap/sync_support.c imap/sync_support.h imap_sync_client_LDADD = $(LD_UTILITY_ADD) -imap_sync_reset_SOURCES = imap/mutex_fake.c imap/sync_reset.c imap/sync_support.c imap/sync_support.h +imap_sync_reset_SOURCES = imap/mutex_fake.c imap/sync_reset.c imap/sync_support.c imap_sync_reset_LDADD = $(LD_UTILITY_ADD) imap_sync_server_SOURCES = imap/mutex_fake.c imap/sync_server.c imap/sync_support.c master/service.c @@ -1032,21 +898,12 @@ imap/nntp_err.h imap/nntp_err.c: imap/nntp_err.et $(COMPILE_ET_DEP) cd imap && $(COMPILE_ET) ../$(top_srcdir)/imap/nntp_err.et -imap/http_err.h imap/http_err.c: imap/http_err.et $(COMPILE_ET_DEP) - cd imap && $(COMPILE_ET) ../$(top_srcdir)/imap/http_err.et - if MAINTAINER_MODE imap/rfc822_header.c: imap/rfc822_header.st ${top_srcdir}/tools/compile_st.pl -c $< > $@.NEW && mv $@.NEW $@ imap/rfc822_header.h: imap/rfc822_header.st ${top_srcdir}/tools/compile_st.pl -h $< > $@.NEW && mv $@.NEW $@ - -lib/htmlchar.c: lib/htmlchar.st - ${top_srcdir}/tools/compile_st.pl -c $< > $@.NEW && mv $@.NEW $@ - -lib/htmlchar.h: lib/htmlchar.st - ${top_srcdir}/tools/compile_st.pl -h $< > $@.NEW && mv $@.NEW $@ endif imtest_imtest_SOURCES = imtest/imtest.c @@ -1074,8 +931,6 @@ lib/cyrusdb_skiplist.c \ lib/cyrusdb_twoskip.c \ lib/glob.c \ - lib/htmlchar.c \ - lib/htmlchar.h \ lib/imapurl.c \ lib/imclient.c \ lib/imparse.c \ @@ -1120,7 +975,6 @@ lib/hashu64.c \ lib/imapopts.c \ lib/libconfig.c \ - lib/me.c \ lib/mpool.c \ lib/retry.c \ lib/strarray.c \ @@ -1212,28 +1066,15 @@ man/reconstruct.8 \ man/rmnews.8 \ man/smmapd.8 \ + man/squatter.8 \ man/syncnews.8 \ + man/sync_client.8 \ + man/sync_reset.8 \ + man/sync_server.8 \ man/timsieved.8 \ man/tls_prune.8 \ man/unexpunge.8 -if SQUATTER -dist_man_MANS += \ - man/squatter.8 -endif - -if HTTPD -dist_man_MANS += \ - man/httpd.8 -endif - -if REPLICATION -dist_man_MANS += \ - man/sync_client.8 \ - man/sync_reset.8 \ - man/sync_server.8 -endif - master_master_SOURCES = \ master/cyrusMasterMIB.c \ master/cyrusMasterMIB.h \ @@ -1509,7 +1350,3 @@ libtool: $(LIBTOOL_DEPS) $(SHELL) ./config.status libtool - -# Install for Cassandane -cassandane: - $(MAKE) DESTDIR=`cd ../inst ; /bin/pwd` install
View file
cyrus-imapd-2.5.tar.gz/README.ubuntu
Deleted
@@ -1,7 +0,0 @@ -# to install from Ubuntu you will need the following packages - -sudo apt-get -y install build-essential automake libtool \ - libdb5.3-dev libsasl2-dev zlib1g-dev libssl-dev libpcre3-dev \ - uuid-dev comerr-dev libcunit1-dev valgrind libsnmp-dev \ - bison flex libjansson-dev -
View file
cyrus-imapd-2.5.tar.gz/configure.ac
Changed
@@ -103,7 +103,6 @@ AC_DEFINE_UNQUOTED(SYSCONFDIR,"$sysconfdir",Config File Location) AC_PROG_CC -AC_PROG_CXX AM_PROG_CC_C_O AC_PROG_LN_S AC_PROG_MAKE_SET @@ -127,6 +126,7 @@ #define HIDDEN #endif) +LT_PREREQ(2.2.6) LT_INIT(disable-static) AC_SUBST(LIBTOOL_DEPS) @@ -389,60 +389,6 @@ AM_CONDITIONAL(USE_SQL, test $HAVE_SQL = 1) dnl End SQL DB Detection -dnl -dnl Search engines - SQUAT -dnl - -AC_ARG_ENABLE(squat, - --disable-squat disable Squat support) -if test "x$enable_squat" != xno ; then - AC_DEFINE(USE_SQUAT,,Build in Squat support?) -fi -AM_CONDITIONAL(USE_SQUAT, test "${enable_squat}" != "no") - -dnl -dnl Search engines - Sphinx -dnl - -AC_ARG_ENABLE(sphinx, - --enable-sphinx disable Sphinx support, - enable_sphinx=$enableval - , - enable_sphinx=no - ) -if test "x$enable_sphinx" != xno ; then - AC_DEFINE(USE_SPHINX,,Build in Sphinx support?) -fi -AM_CONDITIONAL(USE_SPHINX, test "${enable_sphinx}" != "no") - -dnl Note that Sphinx' searchd uses the MySQL protocol -dnl so we need to link with the MySQL client libraries. -if test "$enable_sphinx" = yes -a "$with_mysql" = no ; then - AC_MSG_ERROR(Sphinx requires MySQL but you have not used --with-mysql; disabling Sphinx) - enable_sphinx=no -fi - -dnl -dnl Search engines - Xapian -dnl - -AC_ARG_ENABLE(xapian, - --enable-xapian enable Xapian support, - enable_xapian=$enableval - , - enable_xapian=no - ) -if test "x$enable_xapian" != xno ; then - XO_LIB_XAPIAN( - AC_DEFINE(USE_XAPIAN,,Build in Xapian support?) - ) -fi -AM_CONDITIONAL(USE_XAPIAN, test "${enable_xapian}" != "no") - - -AM_CONDITIONAL(SQUATTER, - test "${enable_squat}" != "no" -o "${enable_sphinx}" != "no" -o "${enable_xapian}" != "no" ) - AC_ARG_ENABLE(sieve, AS_HELP_STRING(--disable-sieve, disable Sieve support),,enable_sieve="yes";) AC_ARG_ENABLE(pcre, @@ -1141,7 +1087,6 @@ AC_ARG_ENABLE(nntp, AS_HELP_STRING(--enable-nntp, enable NNTP support),,enable_nntp="no";) AM_CONDITIONAL(NNTPD, test "$enable_nntp" != "no") - dnl dnl see if we're compiling the Murder support programs dnl @@ -1154,54 +1099,6 @@ fi dnl -dnl see if we're compiling with calendar alarm support -dnl -AC_ARG_ENABLE(calalarmd, - AS_HELP_STRING(--enable-calalarmd, enable CalDAV alarm support),,enable_calalarmd="no";) -AM_CONDITIONAL(CALALARMD, test "$enable_calalarmd" != "no") - -dnl -dnl see if we're compiling with HTTP support -dnl -ENABLE_HTTP=no -AC_ARG_ENABLE(http, - AS_HELP_STRING(--enable-http, enable HTTP support),, enable_http="no";) -AM_CONDITIONAL(HTTPD, test "$enable_http" != "no") - -HTTP_CPPFLAGS= -HTTP_LIBS= -if test "$enable_http" != no; then -dnl -dnl make sure all the modules we need are present -dnl - ENABLE_RSS=yes - ENABLE_DAV=yes - PKG_CHECK_MODULES(XML2, libxml-2.0,,AC_MSG_ERROR(Need libxml-2.0 for http)) - PKG_CHECK_MODULES(ICAL, libical,,AC_MSG_ERROR(Need libical for http)) - PKG_CHECK_MODULES(SQLITE3, sqlite3,,AC_MSG_ERROR(Need sqlite3 for http)) - PKG_CHECK_MODULES(JSON, jansson,,AC_MSG_ERROR(Need jansson for http)) - - HTTP_LIBS="${XML2_LIBS} ${ICAL_LIBS} ${SQLITE3_LIBS} ${JSON_LIBS}" - HTTP_CPPFLAGS="${JSON_CFLAGS} ${SQLITE3_CFLAGS} ${ICAL_CFLAGS} ${XML2_CFLAGS}" - - AC_DEFINE(HAVE_XML2,,Build in libxml support?) - AC_DEFINE(HAVE_ICAL,,Build in ical support?) - AC_DEFINE(HAVE_SQLITE3,,Build in SQLite support?) - - AC_DEFINE(WITH_DAV,,Build DAV support into httpd?) - AC_DEFINE(WITH_JSON,,Build jCal/jCard support into httpd?) - - CYRUS_OPENDKIM_CHK() - if test "$opendkimlib" = "yes"; then - AC_SUBST(DKIM_CFLAGS) - AC_SUBST(DKIM_LIBS) - AC_DEFINE(WITH_DKIM,,Build DKIM support into iSchedule?) - fi -fi -AC_SUBST(HTTP_CPPFLAGS) -AC_SUBST(HTTP_LIBS) - -dnl dnl see if we're compiling replication support programs dnl AC_ARG_ENABLE(replication, @@ -1602,6 +1499,10 @@ # define HAVE_SS_FAMILY #endif /* !HAVE_STRUCT_SOCKADDR_STORAGE */ +#ifndef HAVE_SS_FAMILY +#define ss_family __ss_family +#endif + #ifndef AF_INET6 /* Define it to something that should never appear */ #define AF_INET6 AF_MAX @@ -1692,6 +1593,9 @@ /* should we do a fast TLS session shutdown? */ TLS_FAST_SHUTDOWN = 1, + /* should we use the SQUAT engine to accelerate SEARCH? */ + SQUAT_ENGINE = 1, + /* should we have long LMTP error messages? */ LMTP_LONG_ERROR_MSGS = 1 }; @@ -1723,11 +1627,10 @@ nttpd: $enable_nntp replication: $enable_replication sieve: $enable_sieve - calalarmd: $enable_calalarmd - http: $enable_http External dependencies: pcre: $cyrus_cv_pcre_utf8 + openssl: $with_ssl Database support: bdb: $use_berkeley
View file
cyrus-imapd-2.5.tar.gz/cunit/.gitignore
Changed
@@ -3,4 +3,3 @@ registers.h reports unit -.cunit-*.c
View file
cyrus-imapd-2.5.tar.gz/cunit/annotate.testc
Changed
@@ -5,7 +5,6 @@ #include "assert.h" #include "xmalloc.h" #include "retry.h" -#include "util.h" #include "imap/global.h" #include "libcyr_cfg.h" #include "imap/annotate.h" @@ -1909,7 +1908,7 @@ r = mailbox_create(MBOXNAME1_INT, /*mbtype*/0, PARTITION, ACL, /*uniqueid*/NULL, /*options*/0, /*uidvalidity*/0, - /*highestmodseq*/0, &mailbox); + &mailbox); if (r) return r; mailbox_close(&mailbox); @@ -1926,7 +1925,7 @@ r = mailbox_create(MBOXNAME2_INT, /*mbtype*/0, PARTITION, ACL, /*uniqueid*/NULL, /*options*/0, /*uidvalidity*/0, - /*highestmodseq*/0, &mailbox); + &mailbox); if (r) return r; mailbox_close(&mailbox);
View file
cyrus-imapd-2.5.tar.gz/cunit/bitvector.testc
Changed
@@ -2,7 +2,6 @@ #include "cunit/cunit.h" #include "bitvector.h" -#include "util.h" static void test_free(void) { @@ -184,27 +183,6 @@ bv_free(&b); } -static void test_oreq_empty(void) -{ - bitvector_t a = BV_INITIALIZER; - bitvector_t b = BV_INITIALIZER; - - bv_set(&b, 0); - bv_set(&b, 23); - CU_ASSERT_EQUAL(24, b.length); - CU_ASSERT_EQUAL(1, bv_isset(&b, 0)); - CU_ASSERT_EQUAL(1, bv_isset(&b, 23)); - - bv_oreq(&a, &b); - - CU_ASSERT_EQUAL(24, a.length); - CU_ASSERT_EQUAL(1, bv_isset(&a, 0)); - CU_ASSERT_EQUAL(1, bv_isset(&a, 23)); - - bv_free(&a); - bv_free(&b); -} - static void test_oreq_noexpand(void) { bitvector_t a = BV_INITIALIZER; @@ -390,127 +368,4 @@ bv_free(&bv); } -static void test_next_set(void) -{ -#define TESTCASE(...) \ - { \ - static const int _in = {__VA_ARGS__}; \ - bitvector_t bv = BV_INITIALIZER; \ - int bit = -1; \ - int i; \ - for (i = 0 ; i < (int)VECTOR_SIZE(_in) ; i++) \ - bv_set(&bv, _ini); \ - for (i = 0 ; i < (int)VECTOR_SIZE(_in) ; i++) { \ - bit = bv_next_set(&bv, bit+1); \ - CU_ASSERT_EQUAL(bit, _ini); \ - } \ - bit = bv_next_set(&bv, bit+1); \ - CU_ASSERT_EQUAL(bit, -1); \ - bv_free(&bv); \ - } - - /* empty vector never reports any set bits */ - TESTCASE(); - - /* vector with a single bit reports only that bit */ - TESTCASE(0); - TESTCASE(3); - TESTCASE(7); - TESTCASE(8); - TESTCASE(15); - TESTCASE(16); - TESTCASE(128); - - /* vector with several bits reports them all */ - TESTCASE(1,2,3,4,7,11,12,63,64,65); - - /* vector with all bits reports them all */ - TESTCASE(0,1,2,3,4,5,6,7); - TESTCASE(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); - TESTCASE(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23); - -#undef TESTCASE -} - -static void test_prev_set(void) -{ -#define TESTCASE(...) \ - { \ - static const int _in = {__VA_ARGS__}; \ - bitvector_t bv = BV_INITIALIZER; \ - int bit; \ - int i; \ - for (i = 0 ; i < (int)VECTOR_SIZE(_in) ; i++) \ - bv_set(&bv, _ini); \ - bit = bv.length; \ - for (i = (int)VECTOR_SIZE(_in)-1 ; i >= 0 ; i--) { \ - bit = bv_prev_set(&bv, bit-1); \ - CU_ASSERT_EQUAL(bit, _ini); \ - } \ - bit = bv_prev_set(&bv, bit-1); \ - CU_ASSERT_EQUAL(bit, -1); \ - bv_free(&bv); \ - } - - /* empty vector never reports any set bits */ - TESTCASE(); - - /* vector with a single bit reports only that bit */ - TESTCASE(0); - TESTCASE(3); - TESTCASE(7); - TESTCASE(8); - TESTCASE(15); - TESTCASE(16); - TESTCASE(128); - - /* vector with several bits reports them all */ - TESTCASE(1,2,3,4,7,11,12,63,64,65); - - /* vector with all bits reports them all */ - TESTCASE(0,1,2,3,4,5,6,7); - TESTCASE(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); - TESTCASE(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23); - -#undef TESTCASE -} - -static void test_count(void) -{ -#define TESTCASE(...) \ - { \ - static const int _in = {__VA_ARGS__}; \ - bitvector_t bv = BV_INITIALIZER; \ - int count; \ - int i; \ - for (i = 0 ; i < (int)VECTOR_SIZE(_in) ; i++) \ - bv_set(&bv, _ini); \ - count = bv_count(&bv); \ - CU_ASSERT_EQUAL(count, VECTOR_SIZE(_in)); \ - bv_free(&bv); \ - } - - /* empty vector */ - TESTCASE(); - - /* vector with a single bit */ - TESTCASE(0); - TESTCASE(3); - TESTCASE(7); - TESTCASE(8); - TESTCASE(15); - TESTCASE(16); - TESTCASE(128); - - /* vector with several bits */ - TESTCASE(1,2,3,4,7,11,12,63,64,65); - - /* vector with all bits */ - TESTCASE(0,1,2,3,4,5,6,7); - TESTCASE(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); - TESTCASE(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23); - -#undef TESTCASE -} - /* vim: set ft=c: */
View file
cyrus-imapd-2.5.tar.gz/cunit/buf.testc
Changed
@@ -5,7 +5,6 @@ #include "util.h" #include "retry.h" #include "map.h" -#include "util.h" #include <sys/mman.h> static void test_simple(void) @@ -1288,9 +1287,11 @@ * in case Valgrind is smart enough to detect that */ retry_write(fd, DATA0, sizeof(DATA0)); - buf_init_mmap(&b, 1, fd, fname, MAP_UNKNOWN_LEN, NULL); - base = b.s; - len = b.len; + map_refresh(fd, /*onceonly*/1, &base, &len, sizeof(DATA0), fname, NULL); + + CU_ASSERT(is_valid_mapping(base, len)); + + buf_init_mmap(&b, base, len); CU_ASSERT_EQUAL(b.len, sizeof(DATA0)); CU_ASSERT_EQUAL(b.alloc, 0); CU_ASSERT_EQUAL(buf_len(&b), b.len); @@ -1325,9 +1326,7 @@ CU_ASSERT(is_valid_mapping(base, len)); - buf_init_mmap(&b, 1, fd, fname, MAP_UNKNOWN_LEN, NULL); - base = b.s; - len = b.len; + buf_init_mmap(&b, base, len); CU_ASSERT_EQUAL(b.len, sizeof(DATA0)); CU_ASSERT_EQUAL(b.alloc, 0); CU_ASSERT_EQUAL(buf_len(&b), b.len);
View file
cyrus-imapd-2.5.tar.gz/cunit/charset.testc
Changed
@@ -3,12 +3,7 @@ #include "cunit/cunit.h" #include "charset.h" -extern int charset_debug; - -/* The Unicode Replacement character 0xfffd in UTF-8 encoding */ #define UTF8_REPLACEMENT "\357\277\275" -/* The Replacement char after search normalisation */ -#define SEARCH_REPLACEMENT "\377" static void test_lookupname(void) { @@ -95,48 +90,6 @@ free(s); } -static void test_qp(void) -{ - /* corner cases in Quoted-Printable */ -#define TESTCASE(in, cs, enc, exp) \ - { \ - static const char _in = (in); \ - static const char _exp = (exp); \ - int _cs = (cs); \ - int _enc = (enc); \ - char *s = charset_to_utf8(_in, sizeof(_in)-1, _cs, _enc); \ - CU_ASSERT_PTR_NOT_NULL(s); \ - CU_ASSERT_STRING_EQUAL(s, _exp); \ - free(s); \ - } - - /* encoding of SP */ - TESTCASE("ab=20xy", 0, ENCODING_QP, "ab xy"); - - /* encoding of '=' */ - TESTCASE("ab=3Dxy", 0, ENCODING_QP, "ab=xy"); - - /* lowercase also */ - TESTCASE("ab=3dxy", 0, ENCODING_QP, "ab=xy"); - - /* underscore is not special outside of headers */ - TESTCASE("ab_xy", 0, ENCODING_QP, "ab_xy"); - - /* invalid characters after = are passed through - * even if one of them is a valid hexchar */ - TESTCASE("ab=ZZxy", 0, ENCODING_QP, "ab=ZZxy"); - TESTCASE("ab=ZCxy", 0, ENCODING_QP, "ab=ZCxy"); - TESTCASE("ab=CZxy", 0, ENCODING_QP, "ab=CZxy"); - TESTCASE("ab=Zcxy", 0, ENCODING_QP, "ab=Zcxy"); - TESTCASE("ab=cZxy", 0, ENCODING_QP, "ab=cZxy"); - - /* soft line break */ - TESTCASE("ab=\r\nxy", 0, ENCODING_QP, "abxy"); - -#undef TESTCASE -} - - static void test_decode_mimeheader(void) { char *s; @@ -202,90 +155,6 @@ free(s); } -static void test_mimeheader_badcharset(void) -{ - /* when given an unknown charset, the entire word is - * replaced with a single Unicode replacement char */ - char *s; - static const char ASCII_1 = "A =?EUC-KR?B?wMzIo8Dn?= B"; - static const char SEARCH_1 = "A " UTF8_REPLACEMENT "B"; - int flags = CHARSET_SKIPDIACRIT | CHARSET_MERGESPACE; /* default */ - - s = charset_decode_mimeheader(ASCII_1, flags); - CU_ASSERT_PTR_NOT_NULL(s); - CU_ASSERT_STRING_EQUAL(s, SEARCH_1); - free(s); -} - -static void test_mime_unfold(void) -{ - char *s; - - /* Test unfolding and the 'keep' space option. Note that 'keep' is - * a bit of a misnomer, it actually converts whitespace characters - * to SP before keeping the same *number* of chars, which is - * actually quite unhelpful. - */ - s = charset_decode_mimeheader( -"From: foo@bar\r\n" -"To: baz@quux\r\n" -"Subject: this\r\n" -"\tline is continued\r\n" -"Keywords: and\r\n" -"\tso is\r\n" -" this one\r\n" -"\r\n", - CHARSET_SKIPDIACRIT); - CU_ASSERT_STRING_EQUAL(s, -"FROM: FOO@BAR " -"TO: BAZ@QUUX " -"SUBJECT: THIS LINE IS CONTINUED " -"KEYWORDS: AND SO IS THIS ONE " -" " - ); - free(s); - - /* test unfolding and the 'merge' space option which merges any - * amount of whitespace down to a single SP character */ - s = charset_decode_mimeheader( -"From: foo@bar\r\n" -"To: baz@quux\r\n" -"Subject: this\r\n" -"\tline is continued\r\n" -"Keywords: and\r\n" -"\tso is\r\n" -" this one\r\n" -"\r\n", - CHARSET_SKIPDIACRIT|CHARSET_MERGESPACE); - CU_ASSERT_STRING_EQUAL(s, -"FROM: FOO@BAR " -"TO: BAZ@QUUX " -"SUBJECT: THIS LINE IS CONTINUED " -"KEYWORDS: AND SO IS THIS ONE " - ); - free(s); - - /* test unfolding and the 'skip' space option which elides - * all whitespace. */ - s = charset_decode_mimeheader( -"From: foo@bar\r\n" -"To: baz@quux\r\n" -"Subject: this\r\n" -"\tline is continued\r\n" -"Keywords: and\r\n" -"\tso is\r\n" -" this one\r\n" -"\r\n", - CHARSET_SKIPDIACRIT|CHARSET_SKIPSPACE); - CU_ASSERT_STRING_EQUAL(s, -"FROM:FOO@BAR" -"TO:BAZ@QUUX" -"SUBJECT:THISLINEISCONTINUED" -"KEYWORDS:ANDSOISTHISONE" - ); - free(s); -} - static void test_search_mimeheader(void) { char *s; @@ -321,352 +190,4 @@ CU_ASSERT_STRING_EQUAL(s, RES_RFC5051); free(s); } - -struct text_rock { - int ncalls; - struct buf out; -}; - -static void append_text(const struct buf *text, void *rock) -{ - struct text_rock *tr = (struct text_rock *)rock; - - tr->ncalls++; - buf_append(&tr->out, text); -} - -#define TESTCASE(in, cs, enc, st, exp) \ - { \ - static const char _in = (in); \ - static int _cs = (cs); \ - int _enc = (enc); \ - static const char _st = (st); \ - static const char _exp = (exp); \ - struct buf bin = BUF_INITIALIZER; \ - struct text_rock tr; \ - int r; \ - \ - memset(&tr, 0, sizeof(tr)); \ - buf_init_ro(&bin, _in, sizeof(_in)-1); \ - \ - r = charset_extract(append_text, &tr, &bin, _cs, _enc, _st, flags); \ - CU_ASSERT_EQUAL(r, 1); \ - CU_ASSERT_EQUAL(tr.ncalls, 1); \ - CU_ASSERT_STRING_EQUAL(buf_cstring(&tr.out), _exp); \ - \ - buf_free(&bin); \ - buf_free(&tr.out); \ - } - -static void test_extract(void) -{ - int flags = CHARSET_SKIPDIACRIT | CHARSET_MERGESPACE; /* default */ - /* data thanks to hipsteripsum.me */ - - /* simplest case - no space, plain text is capitalised */ - TESTCASE("freegan", 0, ENCODING_NONE, "PLAIN", "FREEGAN"); - - /* capitalised text is still capitalised */ - TESTCASE("FANNY PACK", 0, ENCODING_NONE, "PLAIN", "FANNY PACK"); - - /* single spaces become single spaces */ - TESTCASE("before they sold out", - 0, ENCODING_NONE, "PLAIN", - "BEFORE THEY SOLD OUT"); - - /* multiple spaces are squashed to a single spaces */ - TESTCASE("you probably \t haven't\r\nheard\t\r\tof them", - 0, ENCODING_NONE, "PLAIN", - "YOU PROBABLY HAVEN'T HEARD OF THEM"); - - /* invalid UTF-8 bytes become the Replacement character */ - TESTCASE("a\300b", 0, ENCODING_NONE, "PLAIN", /* 0xC0 */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\301b", 0, ENCODING_NONE, "PLAIN", /* 0xC1 */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\365b", 0, ENCODING_NONE, "PLAIN", /* 0xF5 */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\366b", 0, ENCODING_NONE, "PLAIN", /* 0xF6 */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\367b", 0, ENCODING_NONE, "PLAIN", /* 0xF7 */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\370b", 0, ENCODING_NONE, "PLAIN", /* 0xF8 */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\371b", 0, ENCODING_NONE, "PLAIN", /* 0xF9 */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\372b", 0, ENCODING_NONE, "PLAIN", /* 0xFA */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\373b", 0, ENCODING_NONE, "PLAIN", /* 0xFB */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\374b", 0, ENCODING_NONE, "PLAIN", /* 0xFC */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\375b", 0, ENCODING_NONE, "PLAIN", /* 0xFD */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\376b", 0, ENCODING_NONE, "PLAIN", /* 0xFE */ - "A"UTF8_REPLACEMENT"B"); - TESTCASE("a\377b", 0, ENCODING_NONE, "PLAIN", /* 0xFF */ - "A"UTF8_REPLACEMENT"B"); - - /* ill-formed UTF-8 sequences become the Replacement character */ - - /* 2-byte sequence lead byte then a non-continuation byte */ - TESTCASE("a\302bcd", 0, ENCODING_NONE, "PLAIN", - "A"UTF8_REPLACEMENT"BCD"); - /* 3-byte sequence lead byte then a non-continuation byte */ - TESTCASE("a\340bcde", 0, ENCODING_NONE, "PLAIN", - "A"UTF8_REPLACEMENT"BCDE"); - /* 4-byte sequence lead byte then a non-continuation byte */ - TESTCASE("a\360bcdef", 0, ENCODING_NONE, "PLAIN", - "A"UTF8_REPLACEMENT"BCDEF"); - /* unexpected continuation byte */ - TESTCASE("a\240bc", 0, ENCODING_NONE, "PLAIN", - "A"UTF8_REPLACEMENT"BC"); - - /* HTML: correctly formed balanced tag pairs */ - TESTCASE("<b>Photo</b> <em>booth</em>", - 0, ENCODING_NONE, "HTML", - "PHOTO BOOTH"); - - /* HTML: unbalanced tags */ - TESTCASE("<b>American<b> <b>Apparel</b>", - 0, ENCODING_NONE, "HTML", - "AMERICAN APPAREL"); - - /* HTML: OMITTAG tags with and without end tags */ - TESTCASE("<hr>Terry<hr> <hr>Richardson</hr>", - 0, ENCODING_NONE, "HTML", - " TERRY RICHARDSON "); - - /* HTML: non-phrasing tags are replaced with whitespace */ - TESTCASE("hella<br>mlkshk", - 0, ENCODING_NONE, "HTML", - "HELLA MLKSHK"); - TESTCASE("godard<br/>synth", - 0, ENCODING_NONE, "HTML", - "GODARD SYNTH"); - TESTCASE("<div>vinyl</div><div>narwhal</div>", - 0, ENCODING_NONE, "HTML", - " VINYL NARWHAL "); - - /* HTML: quoted tag parameters */ - TESTCASE("<a href=\"foo.html\">leggings</a> <img src\"beer.jpg\">gastropub", - 0, ENCODING_NONE, "HTML", - "LEGGINGS GASTROPUB"); - - /* HTML: unquoted tag parameters */ - TESTCASE("<a href=foo.html>biodiesel</a> <img srcbeer.jpg>seitan", - 0, ENCODING_NONE, "HTML", - "BIODIESEL SEITAN"); - - /* HTML: contents of SCRIPT tag */ - TESTCASE("viral <script>bicycle rights</script>readymade", - 0, ENCODING_NONE, "HTML", - "VIRAL READYMADE"); - - /* HTML: HTML4 SCRIPT tag with no contents */ - TESTCASE("cardigan <script type=\"text/javascript\" " - "src=\"truffaut.js\"></script>williamsburg", - 0, ENCODING_NONE, "HTML", - "CARDIGAN WILLIAMSBURG"); - - /* HTML: XHTML SCRIPT empty-element-tag aka self-closing tag */ - TESTCASE("brunch <script type=\"text/javascript\" " - "src=\"cred.js\"/>shoreditch", - 0, ENCODING_NONE, "HTML", - "BRUNCH SHOREDITCH"); - - /* HTML: contents of STYLE tag */ - TESTCASE("pickled <style>whatever tumblr</style>stumptown", - 0, ENCODING_NONE, "HTML", - "PICKLED STUMPTOWN"); - - /* HTML: comments, correctly formed */ - TESTCASE("pinterest <!-- master cleanse -->forage", - 0, ENCODING_NONE, "HTML", - "PINTEREST FORAGE"); - - /* HTML: comments correctly formed with embedded -- */ - TESTCASE("polaroid <!-- food -- truck -->letterpress", - 0, ENCODING_NONE, "HTML", - "POLAROID LETTERPRESS"); - - /* HTML: comments correctly formed with embedded tags */ - TESTCASE("semiotics <!-- messenger <hr> bag -->scenester", - 0, ENCODING_NONE, "HTML", - "SEMIOTICS SCENESTER"); - - /* HTML: comments correctly formed with embedded -> */ - TESTCASE("butcher <!-- cosby -> sweater -->whatever", - 0, ENCODING_NONE, "HTML", - "BUTCHER WHATEVER"); - - /* HTML: comments correctly formed with ---> ending */ - TESTCASE("ennui <!-- art party --->keffiyeh", - 0, ENCODING_NONE, "HTML", - "ENNUI KEFFIYEH"); - - /* HTML: trivial comment */ - TESTCASE("street <!-->art", - 0, ENCODING_NONE, "HTML", - "STREET ART"); - - /* HTML: initial DOCTYPE is ignored */ - TESTCASE("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" " - "\"http://www.w3.org/TR/html4/strict.dtd\">ethnic sustainable", - 0, ENCODING_NONE, "HTML", - "ETHNIC SUSTAINABLE"); - - /* HTML: simple character references */ - TESTCASE(""Twee & Keytar" <dreamcatcher@umami.org>", - 0, ENCODING_NONE, "HTML", - "\"TWEE & KEYTAR\" <DREAMCATCHER@UMAMI.ORG>"); - - /* HTML: naked & is emitted */ - TESTCASE("gentrify&<b>sartorial</b>", - 0, ENCODING_NONE, "HTML", - "GENTRIFY&SARTORIAL"); - - /* HTML: non-zero length unterminated entities are emitted */ - TESTCASE("tattooed& locavore", - 0, ENCODING_NONE, "HTML", - "TATTOOED& LOCAVORE"); - - /* HTML: decimal Unicode entities: U+267B RECYCLE SYMBOL */ - TESTCASE("odd♻future", - 0, ENCODING_NONE, "HTML", - "ODD♻FUTURE"); - - /* HTML: hexadecimal Unicode entities: U+2704 SCISSORS */ - TESTCASE("odd✄future", - 0, ENCODING_NONE, "HTML", - "ODD✄FUTURE"); - - /* HTML: compatibility numerical character references */ - TESTCASE( - "A€BC‚Dƒ" - "E„F…G†H‡" - "IˆJ‰KŠL‹" - "MŒNOŽP" - "QR‘S’T“" - "U”V•W–X—" - "Y˜Z™AšB›" - "CœDEžFŸg", - 0, ENCODING_NONE, "HTML", - "A€BC‚DƑ" /* ƒ capitalised */ - "E„F...G†H‡" /* … normalised to ... */ - "IˆJ‰KSL‹" /* Š normalised to S */ - "MŒNOZP" /* Ž normalised to Z */ - "QR‘S’T“" - "U”V•W–X—" - "Y˜ZTMASB›" /* š capitalised then normalised to S, - * ™ normalised to TM */ - "CŒDEZFYG") /* œ capitalised to Œ, - ž capitalised then normalised to Z, - * Ÿ normalised to Y */ - - /* HTML: numerical character references to invalid Unicode - * codepoints and valid codepoints just adjacent to invalid - * ranges. HTML5 requires us to emit a Replacement char. */ - TESTCASE("A퟿B", 0, ENCODING_NONE, "HTML", "A\355\237\277B"); - TESTCASE("A�B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - TESTCASE("A�B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - TESTCASE("A�B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - TESTCASE("A�B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - TESTCASE("A쀀B", 0, ENCODING_NONE, "HTML", "A\354\200\200B"); - TESTCASE("A􏿽B", 0, ENCODING_NONE, "HTML", "A\364\217\277\275B"); - TESTCASE("A�B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - TESTCASE("A�B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - TESTCASE("A�B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - - /* HTML: zero numerical character reference. The HTML5 spec says - * to return a Replacement char. */ - TESTCASE("A�B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - - /* HTML: numerical character references whose codepoints the HTML5 - * spec says are a parse error. We just silently swallow these. */ - /* U+0001..U+0008 */ - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - /* U+000B */ - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - /* U+000E..U+001F */ - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - /* U+007F..U+009f, when not a compatibility codepoint */ - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - /* U+FDD0..U+FDEF */ - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - /* the last two codepoints in each plane */ - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - TESTCASE("AB", 0, ENCODING_NONE, "HTML", "AB"); - - /* HTML: some of the more obscure named character references. The - * tricky part is testing the case sensitivity and unusual character - * generation of the HTML character reference matching code, while - * the search normalisation code gets in the way. */ - - /* α and Α are both defined but both get normalised - * to GREEK CAPITAL LETTER ALPHA */ - TESTCASE("AαB", 0, ENCODING_NONE, "HTML", "AΑB"); - TESTCASE("AΑB", 0, ENCODING_NONE, "HTML", "AΑB"); - /* ♣ is defined, &Clubs is not */ - TESTCASE("A♣B", 0, ENCODING_NONE, "HTML", "A♣B"); - TESTCASE("A&Clubs;B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - /* fj is defined to emit a 2-codepoint sequence */ - TESTCASE("AfjB", 0, ENCODING_NONE, "HTML", "AFJB"); - /* ŷ emits a codepoint which is then normalised and capitalised */ - TESTCASE("AŷB", 0, ENCODING_NONE, "HTML", "AYB"); - /* ↑ and ↑ are both defined to the same codepoint, - * which survives normalisation intact, but neither &UParrow; nor - * &upARROW; are defined. &Uparrow is defined to a *different* - * codepoint which also survives normalisation. */ - TESTCASE("A↑B", 0, ENCODING_NONE, "HTML", "A↑B"); - TESTCASE("A⇑B", 0, ENCODING_NONE, "HTML", "A⇑B"); - TESTCASE("A↑B", 0, ENCODING_NONE, "HTML", "A↑B"); - TESTCASE("A&UParrow;B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - TESTCASE("A&upARROW;B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); - - /* &nonesuch; is most definitely not defined */ - TESTCASE("A&nonesuch;B", 0, ENCODING_NONE, "HTML", "A"UTF8_REPLACEMENT"B"); -} -#undef TESTCASE - /* vim: set ft=c: */
View file
cyrus-imapd-2.5.tar.gz/cunit/command.testc
Deleted
@@ -1,125 +0,0 @@ -#include "config.h" -#include <stdio.h> -#include "cunit/cunit.h" -#include <sys/stat.h> -#include "command.h" - -const char canary = "canary.txt"; - -static void test_run(void) -{ - int r; - struct stat sb; - - /* make sure the file isnt there */ - r = unlink(canary); - if (r < 0) r = errno; - if (r == ENOENT) r = 0; - CU_ASSERT_EQUAL_FATAL(r, 0); - - r = run_command("/usr/bin/touch", canary, NULL); - CU_ASSERT_EQUAL_FATAL(r, 0); - - r = stat(canary, &sb); - if (r < 0) r = errno; - CU_ASSERT_EQUAL_FATAL(r, 0); - unlink(canary); -} - -static void test_popen_r(void) -{ -#define WORD0 "dreamcatcher" - int r; - struct command *cmd = NULL; - char buf32; - - r = command_popen(&cmd, "r", "/bin/echo", WORD0, NULL); - CU_ASSERT_EQUAL_FATAL(r, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(cmd); - CU_ASSERT_PTR_NULL_FATAL(cmd->stdin_prot); - CU_ASSERT_PTR_NOT_NULL_FATAL(cmd->stdout_prot); - - memset(buf, 0, sizeof(buf)); - r = prot_read(cmd->stdout_prot, buf, sizeof(buf)); - CU_ASSERT_EQUAL(r, sizeof(WORD0)); - CU_ASSERT_STRING_EQUAL(buf, WORD0 "\n"); - - r = command_pclose(&cmd); - CU_ASSERT_EQUAL_FATAL(r, 0); - CU_ASSERT_PTR_NULL_FATAL(cmd); -#undef WORD0 -} - -static void test_popen_w(void) -{ -#define WORD0 "semiotics" - int r; - struct command *cmd = NULL; - FILE *fp; - char buf32; - - /* make sure the file isnt there */ - r = unlink(canary); - if (r < 0) r = errno; - if (r == ENOENT) r = 0; - CU_ASSERT_EQUAL_FATAL(r, 0); - - snprintf(buf, sizeof(buf), "cat > %s", canary); - r = command_popen(&cmd, "w", "/bin/sh", "-c", buf, NULL); - CU_ASSERT_EQUAL_FATAL(r, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(cmd); - CU_ASSERT_PTR_NOT_NULL_FATAL(cmd->stdin_prot); - CU_ASSERT_PTR_NULL_FATAL(cmd->stdout_prot); - - r = prot_write(cmd->stdin_prot, WORD0, sizeof(WORD0)-1); - CU_ASSERT_EQUAL(r, 0); - - r = command_pclose(&cmd); - CU_ASSERT_EQUAL_FATAL(r, 0); - CU_ASSERT_PTR_NULL_FATAL(cmd); - - fp = fopen(canary, "r"); - CU_ASSERT_PTR_NOT_NULL_FATAL(fp); - memset(buf, 0, sizeof(buf)); - r = fread(buf, 1, sizeof(buf), fp); - CU_ASSERT_EQUAL_FATAL(r, sizeof(WORD0)-1); - CU_ASSERT_STRING_EQUAL(buf, WORD0); - fclose(fp); - - unlink(canary); -#undef WORD0 -} - -static void test_popen_rw(void) -{ -#define WORD0 "cosby sweater" -#define WORD0_CAP "COSBY SWEATER" - int r; - struct command *cmd = NULL; - char buf32; - - r = command_popen(&cmd, "rw", "/usr/bin/tr", "a-z", "A-Z", NULL); - CU_ASSERT_EQUAL_FATAL(r, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(cmd); - CU_ASSERT_PTR_NOT_NULL_FATAL(cmd->stdin_prot); - CU_ASSERT_PTR_NOT_NULL_FATAL(cmd->stdout_prot); - - r = prot_write(cmd->stdin_prot, WORD0, sizeof(WORD0)-1); - CU_ASSERT_EQUAL(r, 0); - - r = command_done_stdin(cmd); - CU_ASSERT_EQUAL(r, 0); - - memset(buf, 0, sizeof(buf)); - r = prot_read(cmd->stdout_prot, buf, sizeof(buf)); - CU_ASSERT_EQUAL(r, sizeof(WORD0_CAP)-1); - CU_ASSERT_STRING_EQUAL(buf, WORD0_CAP); - - r = command_pclose(&cmd); - CU_ASSERT_EQUAL_FATAL(r, 0); - CU_ASSERT_PTR_NULL_FATAL(cmd); -#undef WORD0 -#undef WORD0_CAP -} - -/* vim: set ft=c: */
View file
cyrus-imapd-2.5.tar.gz/cunit/conversations.c
Deleted
@@ -1,1443 +0,0 @@ -#if HAVE_CONFIG_H -#include <config.h> -#endif -#include "cunit/cunit.h" -#include "conversations.h" -#include "global.h" -#include "strarray.h" -#include "cyrusdb.h" -#include "libcyr_cfg.h" -#include "message.h" /* for VECTOR_SIZE */ -#include "xmalloc.h" - -#define DBDIR "test-dbdir" -#define DBNAME "conversations.db" -#define DBNAME2 "conversations2.db" -#define DBNAME3 "conversations.db" - -static void test_open(void) -{ - int r; - struct conversations_state *state = NULL; - - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); -} - -static void test_getset(void) -{ - int r; - struct conversations_state *state = NULL; - static const char C_MSGID = "<0001.1288854309@example.com>"; - static const conversation_id_t C_CID = 0x12345689abcdef0ULL; - conversation_id_t cid; - - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* Database is empty, so get should succeed and report no results */ - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - /* set should succeed */ - r = conversations_set_msgid(state, C_MSGID, C_CID); - CU_ASSERT_EQUAL(r, 0); - - /* get should now succeed and report the value we gave it */ - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID); - - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* get should still succeed after the db is closed & reopened */ - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID); - - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); -} - -static void test_abort(void) -{ - int r; - struct conversations_state *state = NULL; - static const char C_MSGID = "<0002.1288854309@example.com>"; - static const conversation_id_t C_CID = 0x10345689abcdef2ULL; - conversation_id_t cid; - - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* Database is empty, so get should succeed and report no results */ - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - /* set should succeed */ - r = conversations_set_msgid(state, C_MSGID, C_CID); - CU_ASSERT_EQUAL(r, 0); - - /* get should now succeed and report the value we gave it */ - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID); - - /* abort the txn */ - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); - - /* open the db again */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* the set vanished with the txn abort, so get should - * succeed and report no results */ - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); -} - -static void test_prune(void) -{ - int r; - struct conversations_state *state = NULL; - static const char C_MSGID1 = "<0003.1288854309@example.com>"; - static const conversation_id_t C_CID1 = 0x1045689abcdef23ULL; - time_t stamp1; - static const char C_MSGID2 = "<0004.1288854309@example.com>"; - static const conversation_id_t C_CID2 = 0x105689abcdef234ULL; - time_t stamp2; - static const char C_MSGID3 = "<0005.1288854309@example.com>"; - static const conversation_id_t C_CID3 = 0x10689abcdef2345ULL; - time_t stamp3; - conversation_id_t cid; - unsigned int nseen = 0, ndeleted = 0; - - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* Add keys, with delays in between */ - /* TODO: CUnit needs a time warping system */ - - r = conversations_set_msgid(state, C_MSGID1, C_CID1); - CU_ASSERT_EQUAL(r, 0); - stamp1 = time(NULL); - - sleep(4); - - r = conversations_set_msgid(state, C_MSGID2, C_CID2); - CU_ASSERT_EQUAL(r, 0); - stamp2 = time(NULL); - - sleep(4); - - r = conversations_set_msgid(state, C_MSGID3, C_CID3); - CU_ASSERT_EQUAL(r, 0); - stamp3 = time(NULL); - - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* Should be able to get all 3 msgids */ - - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID1, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID1); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID2, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID2); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID3, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID3); - - /* Prune out the oldest two. Note we try to make this test - * stable with respect to timing artifacts, such as clock - * granularity, by careful choice of sleep times. */ - r = conversations_prune(state, stamp2+(stamp3-stamp2)/2, - &nseen, &ndeleted); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT(nseen >= 3); - CU_ASSERT(ndeleted >= 2); - CU_ASSERT(nseen - ndeleted >= 1); - - /* gets of the oldest two records should succeed - * but report no record, and a get of the newest - * record should succeed */ - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID1, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID2, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID3, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID3); - - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); -} - -/* Test whether it is possible to open two databases at - * the same time. */ -static void test_two(void) -{ - int r; - struct conversations_state *state1 = NULL; - struct conversations_state *state2 = NULL; - static const char C_MSGID1 = "<0006.1288854309@example.com>"; - static const conversation_id_t C_CID1 = 0x1089abcdef23456ULL; - static const char C_MSGID2 = "<0007.1288854309@example.com>"; - static const conversation_id_t C_CID2 = 0x109abcdef234567ULL; - conversation_id_t cid; - - r = conversations_open_path(DBNAME, &state1); - CU_ASSERT_EQUAL(r, 0); - - r = conversations_open_path(DBNAME2, &state2); - CU_ASSERT_EQUAL(r, 0); - - /* Databases are empty, so gets of either msgid from either db - * should succeed and report no results */ - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state1, C_MSGID1, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state1, C_MSGID2, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state2, C_MSGID2, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state2, C_MSGID2, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - /* set should succeed */ - r = conversations_set_msgid(state1, C_MSGID1, C_CID1); - CU_ASSERT_EQUAL(r, 0); - - r = conversations_set_msgid(state2, C_MSGID2, C_CID2); - CU_ASSERT_EQUAL(r, 0); - - /* get should now succeed and report the value we gave it - * and not the value in the other db */ - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state1, C_MSGID1, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID1); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state1, C_MSGID2, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state2, C_MSGID1, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, NULLCONVERSATION); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state2, C_MSGID2, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID2); - - r = conversations_abort(&state1); - CU_ASSERT_EQUAL(r, 0); - - r = conversations_abort(&state2); - CU_ASSERT_EQUAL(r, 0); -} - -/* test CID encoding */ -static void test_cid_encode(void) -{ - static const conversation_id_t CID1 = 0x01089abcdef23456ULL; - static const char STR1 = "01089abcdef23456"; - static const conversation_id_t CID2 = NULLCONVERSATION; - static const char STR2 = "NIL"; - const char *r; - - r = conversation_id_encode(CID1); - CU_ASSERT_STRING_EQUAL(r, STR1); - - r = conversation_id_encode(CID2); - CU_ASSERT_STRING_EQUAL(r, STR2); -} - -/* test CID decoding */ -static void test_cid_decode(void) -{ - static const char STR1 = "01089abcdef23456"; - static const conversation_id_t CID1 = 0x01089abcdef23456ULL; - static const char STR2 = "NIL"; - static const conversation_id_t CID2 = NULLCONVERSATION; - conversation_id_t cid; - int r; - - memset(&cid, 0x45, sizeof(cid)); - r = conversation_id_decode(&cid, STR1); - CU_ASSERT_EQUAL(r, 1); - CU_ASSERT_EQUAL(cid, CID1); - - memset(&cid, 0x45, sizeof(cid)); - r = conversation_id_decode(&cid, STR2); - CU_ASSERT_EQUAL(r, 1); - CU_ASSERT_EQUAL(cid, CID2); -} - -static int num_folders(conversation_t *conv) -{ - int n = 0; - conv_folder_t *folder; - - if (!conv) return 0; - - for (folder = conv->folders ; folder ; folder = folder->next) - n++; - - return n; -} - -static void test_cid_rename(void) -{ - int r; - struct conversations_state *state = NULL; - static const char FOLDER1 = "fnarp.com!user.smurf"; - static const char FOLDER2 = "fnarp.com!user.smurf.foo bar"; - static const char FOLDER3 = "fnarp.com!user.smurf.quux.foonly"; - static const char C_MSGID1 = "<0008.1288854309@example.com>"; - static const char C_MSGID2 = "<0009.1288854309@example.com>"; - static const char C_MSGID3 = "<0010.1288854309@example.com>"; - static const conversation_id_t C_CID1 = 0x10bcdef23456789aULL; - static const conversation_id_t C_CID2 = 0x10cdef23456789abULL; - conversation_id_t cid; - conversation_t *conv; - conv_folder_t *folder; - - /* XXX - need to fix conversations_rename_cid to have a real mailbox - * underneath! */ - return; - - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* setup the records we expect */ - r = conversations_set_msgid(state, C_MSGID1, C_CID1); - CU_ASSERT_EQUAL(r, 0); - r = conversations_set_msgid(state, C_MSGID2, C_CID1); - CU_ASSERT_EQUAL(r, 0); - r = conversations_set_msgid(state, C_MSGID3, C_CID1); - CU_ASSERT_EQUAL(r, 0); - - conv = conversation_new(state); - CU_ASSERT_PTR_NOT_NULL(conv); - - conversation_update(state, conv, FOLDER1, /*num_records*/3, - /*exists*/3, /*unseen*/0, /*counts*/NULL, - /*modseq*/1); - conversation_update(state, conv, FOLDER2, /*num_records*/3, - /*exists*/2, /*unseen*/0, /*counts*/NULL, - /*modseq*/8); - conversation_update(state, conv, FOLDER3, /*num_records*/13, - /*exists*/10, /*unseen*/0, /*counts*/NULL, - /*modseq*/5); - - r = conversation_save(state, C_CID1, conv); - CU_ASSERT_EQUAL(r, 0); - - /* commit & close */ - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - conversation_free(conv); - conv = NULL; - - /* open the db again */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* do a rename */ - conversations_rename_cid(state, C_CID1, C_CID2); - - /* commit & close */ - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* open the db again */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* - * The B records in the database are renamed immediately, so the - * counts should all be in CID2, and CID1 should be empty - */ - conv = NULL; - r = conversation_load(state, C_CID2, &conv); - CU_ASSERT_PTR_NOT_NULL_FATAL(conv); - CU_ASSERT_EQUAL(conv->modseq, 8); - CU_ASSERT_EQUAL(num_folders(conv), 3); - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - conversation_free(conv); - conv = NULL; - - conv = NULL; - r = conversation_load(state, C_CID1, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NULL(conv); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID1, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID2); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID2, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID2); - - memset(&cid, 0x45, sizeof(cid)); - r = conversations_get_msgid(state, C_MSGID3, &cid); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, C_CID2); - - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); -} - -static void test_folder_rename(void) -{ - int r; - struct conversations_state *state = NULL; - static const char FOLDER1 = "fnarp.com!user.smurf"; - static const char FOLDER2 = "fnarp.com!user.smurf.foo"; - static const char FOLDER3 = "fnarp.com!user.smurf.bar"; - static const char C_MSGID1 = "<0008.1288854309@example.com>"; - static const char C_MSGID2 = "<0009.1288854309@example.com>"; - static const char C_MSGID3 = "<0010.1288854309@example.com>"; - static const conversation_id_t C_CID = 0x10bcdef23456789aULL; - conversation_t *conv; - conv_folder_t *folder; - - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NOT_NULL(state); - - /* setup the records we expect */ - r = conversations_set_msgid(state, C_MSGID1, C_CID); - CU_ASSERT_EQUAL(r, 0); - r = conversations_set_msgid(state, C_MSGID2, C_CID); - CU_ASSERT_EQUAL(r, 0); - r = conversations_set_msgid(state, C_MSGID3, C_CID); - CU_ASSERT_EQUAL(r, 0); - - conv = conversation_new(state); - CU_ASSERT_PTR_NOT_NULL(conv); - - conversation_update(state, conv, FOLDER1, /*num_records*/3, - /*exists*/3, /*unseen*/0, /*counts*/NULL, - /*modseq*/1); - conversation_update(state, conv, FOLDER2, /*num_records*/3, - /*exists*/2, /*unseen*/0, /*counts*/NULL, - /*modseq*/8); - - r = conversation_save(state, C_CID, conv); - CU_ASSERT_EQUAL(r, 0); - - conversation_free(conv); - conv = NULL; - - /* commit & close */ - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* open the db again */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* do a rename */ - r = conversations_rename_folder(state, FOLDER2, FOLDER3); - CU_ASSERT_EQUAL(r, 0); - - /* commit & close */ - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* open the db again */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - conv = NULL; - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_PTR_NOT_NULL_FATAL(conv); - CU_ASSERT_EQUAL(conv->modseq, 8); - CU_ASSERT_EQUAL(num_folders(conv), 2); - CU_ASSERT_EQUAL(conv->exists, 5); - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->exists, 3); - /* no record for folder2 */ - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NULL_FATAL(folder); - /* have a record for folder3 */ - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->exists, 2); - conversation_free(conv); - conv = NULL; - - /* now "delete" the folder. NOTE - this doesn't actually - * change any counts, because we go through the CIDs - * individually on the delete codepath */ - r = conversations_rename_folder(state, FOLDER3, NULL); - CU_ASSERT_EQUAL(r, 0); - - conversation_free(conv); - conv = NULL; - - /* commit & close */ - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* open the db again */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_PTR_NOT_NULL_FATAL(conv); - /* got a record for folder1 */ - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->exists, 3); - /* no record for folder2 */ - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NULL_FATAL(folder); - /* no record for folder3 either */ - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NULL_FATAL(folder); - - conversation_free(conv); - conv = NULL; - - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); -} - -static void test_folders(void) -{ - int r; - struct conversations_state *state = NULL; - static const char FOLDER1 = "foobar.com!user.smurf"; - static const char FOLDER2 = "foobar.com!user.smurf.foo bar"; - static const char FOLDER3 = "foobar.com!user.smurf.quux.foonly"; - static const char FOLDER_N1 = "aaa not here"; - static const char FOLDER_N2 = "zzz not here"; - static const conversation_id_t C_CID = 0x10abcdef23456789ULL; - conversation_t *conv; - conv_folder_t *folder; - int *counts; - - /* hack to get this DB created with a counted_strings value */ - imapoptsIMAPOPT_CONVERSATIONS_COUNTED_FLAGS.val.s = "\\Draft $HasRandom"; - - r = conversations_open_path(DBNAME3, &state); - CU_ASSERT_EQUAL(r, 0); - - imapoptsIMAPOPT_CONVERSATIONS_COUNTED_FLAGS.val.s = NULL; - - CU_ASSERT_EQUAL(state->counted_flags->count, 2); - - counts = xzmalloc(sizeof(int) * state->counted_flags->count); - - /* Database is empty, so get should succeed and report no results */ - conv = NULL; - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NULL(conv); - - /* update should succeed */ - conv = conversation_new(state); - CU_ASSERT_PTR_NOT_NULL(conv); - CU_ASSERT_EQUAL(conv->dirty, 1); - - counts0 = 1; - counts1 = 0; - - conversation_update(state, conv, FOLDER1, /*num_records*/13, - /*exists*/7, /*unseen*/5, counts, - /*modseq*/4); - - /* make sure the data we just passed to conversation_update() - * is present in the structure */ - CU_ASSERT_EQUAL(conv->num_records, 13); - CU_ASSERT_EQUAL(conv->exists, 7); - CU_ASSERT_EQUAL(conv->unseen, 5); - CU_ASSERT_EQUAL(conv->counts0, 1); - CU_ASSERT_EQUAL(conv->counts1, 0); - CU_ASSERT_EQUAL(conv->modseq, 4); - CU_ASSERT_EQUAL(num_folders(conv), 1); - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 13); - CU_ASSERT_EQUAL(folder->exists, 7); - CU_ASSERT_EQUAL(folder->modseq, 4); - CU_ASSERT_EQUAL(conv->dirty, 1); - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N1); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N2); - CU_ASSERT_PTR_NULL_FATAL(folder); - - r = conversation_save(state, C_CID, conv); - CU_ASSERT_EQUAL(r, 0); - conversation_free(conv); - conv = NULL; - - /* get should now succeed and report the value we gave it */ - conv = NULL; - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_EQUAL(conv->dirty, 0); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NOT_NULL(conv); - CU_ASSERT_EQUAL(conv->num_records, 13); - CU_ASSERT_EQUAL(conv->exists, 7); - CU_ASSERT_EQUAL(conv->unseen, 5); - CU_ASSERT_EQUAL(conv->counts0, 1); - CU_ASSERT_EQUAL(conv->counts1, 0); - CU_ASSERT_EQUAL(conv->modseq, 4); - CU_ASSERT_EQUAL(num_folders(conv), 1); - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 13); - CU_ASSERT_EQUAL(folder->exists, 7); - CU_ASSERT_EQUAL(folder->modseq, 4); - CU_ASSERT_EQUAL(conv->dirty, 0); - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N1); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N2); - CU_ASSERT_PTR_NULL_FATAL(folder); - - counts1 = 2; - /* some more updates should succeed */ - conversation_update(state, conv, FOLDER2,/*num_records*/2, - /*exists*/1, /*unseen*/0, counts, - /*modseq*/7); - - CU_ASSERT_EQUAL(conv->dirty, 1); - CU_ASSERT_EQUAL(conv->num_records, 15); - CU_ASSERT_EQUAL(conv->exists, 8); - CU_ASSERT_EQUAL(conv->unseen, 5); - CU_ASSERT_EQUAL(conv->counts0, 2); - CU_ASSERT_EQUAL(conv->counts1, 2); - CU_ASSERT_EQUAL(conv->modseq, 7); - CU_ASSERT_EQUAL(num_folders(conv), 2); - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 13); - CU_ASSERT_EQUAL(folder->exists, 7); - CU_ASSERT_EQUAL(folder->modseq, 4); - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 2); - CU_ASSERT_EQUAL(folder->exists, 1); - CU_ASSERT_EQUAL(folder->modseq, 7); - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N1); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N2); - CU_ASSERT_PTR_NULL_FATAL(folder); - - - counts1 = 5; - conversation_update(state, conv, FOLDER3,/*num_records*/10, - /*exists*/10, /*unseen*/0, counts, - /*modseq*/55); - - CU_ASSERT_EQUAL(conv->dirty, 1); - CU_ASSERT_EQUAL(conv->num_records, 25); - CU_ASSERT_EQUAL(conv->exists, 18); - CU_ASSERT_EQUAL(conv->unseen, 5); - CU_ASSERT_EQUAL(conv->counts0, 3); - CU_ASSERT_EQUAL(conv->counts1, 7); - CU_ASSERT_EQUAL(conv->modseq, 55); - CU_ASSERT_EQUAL(num_folders(conv), 3); - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 13); - CU_ASSERT_EQUAL(folder->exists, 7); - CU_ASSERT_EQUAL(folder->modseq, 4); - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 2); - CU_ASSERT_EQUAL(folder->exists, 1); - CU_ASSERT_EQUAL(folder->modseq, 7); - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 10); - CU_ASSERT_EQUAL(folder->exists, 10); - CU_ASSERT_EQUAL(folder->modseq, 55); - folder = conversation_find_folder(state, conv, FOLDER_N1); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N2); - CU_ASSERT_PTR_NULL_FATAL(folder); - - r = conversation_save(state, C_CID, conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(conv->dirty, 0); - conversation_free(conv); - conv = NULL; - - /* get should now succeed and report all values we gave it */ - conv = NULL; - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NOT_NULL(conv); - CU_ASSERT_EQUAL(conv->num_records, 25); - CU_ASSERT_EQUAL(conv->exists, 18); - CU_ASSERT_EQUAL(conv->unseen, 5); - CU_ASSERT_EQUAL(conv->counts0, 3); - CU_ASSERT_EQUAL(conv->counts1, 7); - CU_ASSERT_EQUAL(conv->modseq, 55); - CU_ASSERT_EQUAL(num_folders(conv), 3); - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 13); - CU_ASSERT_EQUAL(folder->exists, 7); - CU_ASSERT_EQUAL(folder->modseq, 4); - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 2); - CU_ASSERT_EQUAL(folder->exists, 1); - CU_ASSERT_EQUAL(folder->modseq, 7); - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 10); - CU_ASSERT_EQUAL(folder->exists, 10); - CU_ASSERT_EQUAL(folder->modseq, 55); - folder = conversation_find_folder(state, conv, FOLDER_N1); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N2); - CU_ASSERT_PTR_NULL_FATAL(folder); - CU_ASSERT_EQUAL(conv->dirty, 0); - conversation_free(conv); - conv = NULL; - - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* open the db again */ - r = conversations_open_path(DBNAME3, &state); - CU_ASSERT_EQUAL(r, 0); - - /* get should still succeed and report all values we gave it */ - conv = NULL; - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NOT_NULL(conv); - CU_ASSERT_EQUAL(conv->num_records, 25); - CU_ASSERT_EQUAL(conv->exists, 18); - CU_ASSERT_EQUAL(conv->unseen, 5); - CU_ASSERT_EQUAL(conv->counts0, 3); - CU_ASSERT_EQUAL(conv->counts1, 7); - CU_ASSERT_EQUAL(conv->modseq, 55); - CU_ASSERT_EQUAL(num_folders(conv), 3); - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL(folder); - CU_ASSERT_EQUAL(folder->num_records, 13); - CU_ASSERT_EQUAL(folder->exists, 7); - CU_ASSERT_EQUAL(folder->modseq, 4); - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NOT_NULL(folder); - CU_ASSERT_EQUAL(folder->num_records, 2); - CU_ASSERT_EQUAL(folder->exists, 1); - CU_ASSERT_EQUAL(folder->modseq, 7); - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NOT_NULL(folder); - CU_ASSERT_EQUAL(folder->num_records, 10); - CU_ASSERT_EQUAL(folder->exists, 10); - CU_ASSERT_EQUAL(folder->modseq, 55); - folder = conversation_find_folder(state, conv, FOLDER_N1); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N2); - CU_ASSERT_PTR_NULL_FATAL(folder); - CU_ASSERT_EQUAL(conv->dirty, 0); - - /* decrementing a folder down to zero */ - counts0 = -1; - counts1 = 0; - conversation_update(state, conv, FOLDER1,/*num_records*/-13, - /*exists*/-7, /*unseen*/0, counts, - /*modseq*/56); - - CU_ASSERT_EQUAL(conv->num_records, 12); - CU_ASSERT_EQUAL(conv->exists, 11); - CU_ASSERT_EQUAL(conv->unseen, 5); - CU_ASSERT_EQUAL(conv->counts0, 2); - CU_ASSERT_EQUAL(conv->counts1, 7); - CU_ASSERT_EQUAL(conv->modseq, 56); - CU_ASSERT_EQUAL(num_folders(conv), 3); /* struct still in place - with all counters zero */ - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder); - CU_ASSERT_EQUAL(folder->num_records, 0); - CU_ASSERT_EQUAL(folder->exists, 0); - CU_ASSERT_EQUAL(folder->modseq, 56); - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NOT_NULL(folder); - CU_ASSERT_EQUAL(folder->num_records, 2); - CU_ASSERT_EQUAL(folder->exists, 1); - CU_ASSERT_EQUAL(folder->modseq, 7); - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NOT_NULL(folder); - CU_ASSERT_EQUAL(folder->num_records, 10); - CU_ASSERT_EQUAL(folder->exists, 10); - CU_ASSERT_EQUAL(folder->modseq, 55); - folder = conversation_find_folder(state, conv, FOLDER_N1); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N2); - CU_ASSERT_PTR_NULL_FATAL(folder); - CU_ASSERT_EQUAL(conv->dirty, 1); - - /* folder goes away properly when saved & re-loaded */ - r = conversation_save(state, C_CID, conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(conv->dirty, 0); - conversation_free(conv); - conv = NULL; - - conv = NULL; - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NOT_NULL(conv); - - CU_ASSERT_EQUAL(conv->num_records, 12); - CU_ASSERT_EQUAL(conv->exists, 11); - CU_ASSERT_EQUAL(conv->unseen, 5); - CU_ASSERT_EQUAL(conv->counts0, 2); - CU_ASSERT_EQUAL(conv->counts1, 7); - CU_ASSERT_EQUAL(conv->modseq, 56); - CU_ASSERT_EQUAL(num_folders(conv), 2); /* FOLDER1 gone now */ - folder = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NOT_NULL(folder); - CU_ASSERT_EQUAL(folder->num_records, 2); - CU_ASSERT_EQUAL(folder->exists, 1); - CU_ASSERT_EQUAL(folder->modseq, 7); - folder = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NOT_NULL(folder); - CU_ASSERT_EQUAL(folder->num_records, 10); - CU_ASSERT_EQUAL(folder->exists, 10); - CU_ASSERT_EQUAL(folder->modseq, 55); - folder = conversation_find_folder(state, conv, FOLDER_N1); - CU_ASSERT_PTR_NULL_FATAL(folder); - folder = conversation_find_folder(state, conv, FOLDER_N2); - CU_ASSERT_PTR_NULL_FATAL(folder); - CU_ASSERT_EQUAL(conv->dirty, 0); - - conversation_free(conv); - conv = NULL; - - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); - - free(counts); -} - -static void test_folder_ordering(void) -{ - int r; - struct conversations_state *state = NULL; - static const char FOLDER1 = "foobar.com!user.smurf"; - static const char FOLDER2 = "foobar.com!user.smurf.foo bar"; - static const char FOLDER3 = "foobar.com!user.smurf.quux.foonly"; - static const conversation_id_t C_CID = 0x10abcdef23456789ULL; - conversation_t *conv; - conv_folder_t *folder1; - conv_folder_t *folder2; - conv_folder_t *folder3; - int *counts = 0; - - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* Database is empty, so get should succeed and report no results */ - conv = NULL; - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NULL(conv); - - /* update should succeed */ - conv = conversation_new(state); - CU_ASSERT_PTR_NOT_NULL(conv); - CU_ASSERT_EQUAL(conv->dirty, 1); - - /* set up the folder names in order - we are going to discard - * this conversation, but the folder_number call will persist */ - conversation_update(state, conv, FOLDER1, 0, 0, 0, 0, 0); - conversation_update(state, conv, FOLDER2, 0, 0, 0, 0, 0); - conversation_update(state, conv, FOLDER3, 0, 0, 0, 0, 0); - - /* discard and recreate */ - conversation_free(conv); - conv = conversation_new(state); - - conversation_update(state, conv, FOLDER1, /*num_records*/1, - /*exists*/1, /*unseen*/0, counts, - /*modseq*/1); - - /* add folders out of order */ - conversation_update(state, conv, FOLDER3,/*num_records*/10, - /*exists*/10, /*unseen*/0, counts, - /*modseq*/55); - - /* save and reload here just to be sure */ - r = conversation_save(state, C_CID, conv); - CU_ASSERT_EQUAL(r, 0); - conversation_free(conv); - conv = NULL; - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NOT_NULL(conv); - - conversation_update(state, conv, FOLDER2,/*num_records*/2, - /*exists*/1, /*unseen*/0, counts, - /*modseq*/7); - - CU_ASSERT_EQUAL(conv->dirty, 1); - - /* check that they've been created in the same order */ - folder1 = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder1); - folder2 = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder2); - folder3 = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder3); - - /* in the right order! */ - CU_ASSERT_PTR_EQUAL(conv->folders, folder1); - CU_ASSERT_PTR_EQUAL(folder1->next, folder2); - CU_ASSERT_PTR_EQUAL(folder2->next, folder3); - CU_ASSERT_PTR_NULL(folder3->next); - - r = conversation_save(state, C_CID, conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(conv->dirty, 0); - conversation_free(conv); - conv = NULL; - - /* get should now succeed and report the value we gave it */ - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_EQUAL(conv->dirty, 0); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NOT_NULL(conv); - - /* check that they've been re-loaded in the same order */ - folder1 = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder1); - folder2 = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder2); - folder3 = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder3); - - /* in the right order! */ - CU_ASSERT_PTR_EQUAL(conv->folders, folder1); - CU_ASSERT_PTR_EQUAL(folder1->next, folder2); - CU_ASSERT_PTR_EQUAL(folder2->next, folder3); - CU_ASSERT_PTR_NULL(folder3->next); - - conversation_free(conv); - conv = NULL; - - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* open the db again */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* get should still succeed and report all values we gave it */ - conv = NULL; - r = conversation_load(state, C_CID, &conv); - - /* check that they are still in the same order */ - folder1 = conversation_find_folder(state, conv, FOLDER1); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder1); - folder2 = conversation_find_folder(state, conv, FOLDER2); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder2); - folder3 = conversation_find_folder(state, conv, FOLDER3); - CU_ASSERT_PTR_NOT_NULL_FATAL(folder3); - - /* in the right order! */ - CU_ASSERT_PTR_EQUAL(conv->folders, folder1); - CU_ASSERT_PTR_EQUAL(folder1->next, folder2); - CU_ASSERT_PTR_EQUAL(folder2->next, folder3); - CU_ASSERT_PTR_NULL(folder3->next); - - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); - - conversation_free(conv); - conv = NULL; - - free(counts); -} - -static void test_senders(void) -{ - int r; - struct conversations_state *state = NULL; - static const char FOLDER = "foobar.com!user.smurf"; - static const char NAME1 = "Smurf 1"; - static const char MAILBOX1 = "smurf"; - static const char DOMAIN1 = "foobar.com"; - static const char NAME2 = "Smurf 2"; - static const char MAILBOX2 = "smurf2"; - static const char DOMAIN2 = "foobar.com"; - static const char NAME3 = "Aardvark"; - static const char MAILBOX3 = "aardvark"; - static const char DOMAIN3 = "aalphabetsoup.com"; - static const conversation_id_t C_CID = 0x10abcdef23456789ULL; - conversation_t *conv; - conv_sender_t *sender1; - conv_sender_t *sender2; - conv_sender_t *sender3; - int *counts = 0; - - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* Database is empty, so get should succeed and report no results */ - conv = NULL; - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NULL(conv); - - /* update should succeed */ - conv = conversation_new(state); - CU_ASSERT_PTR_NOT_NULL(conv); - CU_ASSERT_EQUAL(conv->dirty, 1); - - conversation_add_sender(conv, NAME1, NULL, MAILBOX1, DOMAIN1); - conversation_add_sender(conv, NAME2, NULL, MAILBOX2, DOMAIN2); - conversation_add_sender(conv, NAME3, NULL, MAILBOX3, DOMAIN3); - - conversation_update(state, conv, FOLDER, /*num_records*/1, - /*exists*/1, /*unseen*/0, counts, - /*modseq*/1); - - /* there's no function for getting sender data, oh well */ - sender1 = conv->senders; - CU_ASSERT_PTR_NOT_NULL(sender1); - sender2 = sender1->next; - CU_ASSERT_PTR_NOT_NULL(sender2); - sender3 = sender2->next; - CU_ASSERT_PTR_NOT_NULL(sender3); - CU_ASSERT_PTR_NULL(sender3->next); - - /* check ordering */ - CU_ASSERT_STRING_EQUAL(sender1->name, NAME3); - CU_ASSERT_PTR_NULL(sender1->route); - CU_ASSERT_STRING_EQUAL(sender1->mailbox, MAILBOX3); - CU_ASSERT_STRING_EQUAL(sender1->domain, DOMAIN3); - - CU_ASSERT_STRING_EQUAL(sender2->name, NAME1); - CU_ASSERT_PTR_NULL(sender2->route); - CU_ASSERT_STRING_EQUAL(sender2->mailbox, MAILBOX1); - CU_ASSERT_STRING_EQUAL(sender2->domain, DOMAIN1); - - CU_ASSERT_STRING_EQUAL(sender3->name, NAME2); - CU_ASSERT_PTR_NULL(sender3->route); - CU_ASSERT_STRING_EQUAL(sender3->mailbox, MAILBOX2); - CU_ASSERT_STRING_EQUAL(sender3->domain, DOMAIN2); - - /* save and reload here just to be sure */ - r = conversation_save(state, C_CID, conv); - CU_ASSERT_EQUAL(r, 0); - conversation_free(conv); - conv = NULL; - r = conversation_load(state, C_CID, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NOT_NULL(conv); - - /* there's no function for getting sender data, oh well */ - sender1 = conv->senders; - CU_ASSERT_PTR_NOT_NULL(sender1); - sender2 = sender1->next; - CU_ASSERT_PTR_NOT_NULL(sender2); - sender3 = sender2->next; - CU_ASSERT_PTR_NOT_NULL(sender3); - CU_ASSERT_PTR_NULL(sender3->next); - - /* check ordering */ - CU_ASSERT_STRING_EQUAL(sender1->name, NAME3); - CU_ASSERT_PTR_NULL(sender1->route); - CU_ASSERT_STRING_EQUAL(sender1->mailbox, MAILBOX3); - CU_ASSERT_STRING_EQUAL(sender1->domain, DOMAIN3); - - CU_ASSERT_STRING_EQUAL(sender2->name, NAME1); - CU_ASSERT_PTR_NULL(sender2->route); - CU_ASSERT_STRING_EQUAL(sender2->mailbox, MAILBOX1); - CU_ASSERT_STRING_EQUAL(sender2->domain, DOMAIN1); - - CU_ASSERT_STRING_EQUAL(sender3->name, NAME2); - CU_ASSERT_PTR_NULL(sender3->route); - CU_ASSERT_STRING_EQUAL(sender3->mailbox, MAILBOX2); - CU_ASSERT_STRING_EQUAL(sender3->domain, DOMAIN2); - - conversation_free(conv); - conv = NULL; - - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* open the db again */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - /* get should still succeed and report all values we gave it */ - conv = NULL; - r = conversation_load(state, C_CID, &conv); - - /* there's no function for getting sender data, oh well */ - sender1 = conv->senders; - CU_ASSERT_PTR_NOT_NULL(sender1); - sender2 = sender1->next; - CU_ASSERT_PTR_NOT_NULL(sender2); - sender3 = sender2->next; - CU_ASSERT_PTR_NOT_NULL(sender3); - CU_ASSERT_PTR_NULL(sender3->next); - - /* check ordering */ - CU_ASSERT_STRING_EQUAL(sender1->name, NAME3); - CU_ASSERT_PTR_NULL(sender1->route); - CU_ASSERT_STRING_EQUAL(sender1->mailbox, MAILBOX3); - CU_ASSERT_STRING_EQUAL(sender1->domain, DOMAIN3); - - CU_ASSERT_STRING_EQUAL(sender2->name, NAME1); - CU_ASSERT_PTR_NULL(sender2->route); - CU_ASSERT_STRING_EQUAL(sender2->mailbox, MAILBOX1); - CU_ASSERT_STRING_EQUAL(sender2->domain, DOMAIN1); - - CU_ASSERT_STRING_EQUAL(sender3->name, NAME2); - CU_ASSERT_PTR_NULL(sender3->route); - CU_ASSERT_STRING_EQUAL(sender3->mailbox, MAILBOX2); - CU_ASSERT_STRING_EQUAL(sender3->domain, DOMAIN2); - - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); - - free(counts); -} - -static void gen_msgid_cid(int i, char *msgid, int msgidlen, - conversation_id_t *cidp) -{ - static const char * const domains = { - "fastmail.fm", - "example.com", - "gmail.com", - "yahoo.com", - "hotmail.com" - }; - snprintf(msgid, msgidlen, "<%04d.1298269537@%s>", - i, domainsi % VECTOR_SIZE(domains)); - - *cidp = 0xfeeddeadbeef0000ULL | (unsigned int)i; -} - -static void gen_cid_folder(int i, conversation_id_t *cidp, - strarray_t *mboxnames) -{ - int n; - int j; - static const char * const folders = { - "user.foo.INBOX", - "user.foo.Manilla", - "user.foo.VanillaGorilla", - "user.foo.SarsparillaGorilla" - }; - - *cidp = 0xfeeddeadbeef0000ULL | (unsigned int)i; - - strarray_truncate(mboxnames, 0); - n = 1 + (17 - i) % (VECTOR_SIZE(folders)-1); - CU_ASSERT(n > 0); - for (j = 0 ; j < n ; j++) - strarray_append(mboxnames, - folders(j + i/2) % VECTOR_SIZE(folders)); -} - -static void test_dump(void) -{ - int r; - struct conversations_state *state = NULL; - int fd; - FILE *fp; - char filename64; - char msgid40; - strarray_t mboxnames = STRARRAY_INITIALIZER; - conversation_id_t cid, cid2; - conversation_t *conv; - conv_folder_t *folder; - int i; - int j; -#define N_MSGID_TO_CID 500 -#define N_CID_TO_FOLDER 333 - struct stat sb; - - strcpy(filename, "/tmp/cyrus-conv.datXXXXXX"); - fd = mkstemp(filename); - CU_ASSERT_FATAL(fd >= 0); - fp = fdopen(fd, "r+"); - CU_ASSERT_PTR_NOT_NULL_FATAL(fp); - - memset(&state, 0, sizeof(state)); - - /* generate some data in the database */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - for (i = 0 ; i < N_MSGID_TO_CID ; i++) { - gen_msgid_cid(i, msgid, sizeof(msgid), &cid); - r = conversations_set_msgid(state, msgid, cid); - CU_ASSERT_EQUAL(r, 0); - } - for (i = 0 ; i < N_CID_TO_FOLDER ; i++) { - gen_cid_folder(i, &cid, &mboxnames); - conv = conversation_new(state); - CU_ASSERT_PTR_NOT_NULL(conv); - for (j = 0 ; j < mboxnames.count ; j++) { - conversation_update(state, conv, mboxnames.dataj, - /*num_records*/1, - /*exists*/1, /*unseen*/0, NULL, - /*modseq*/100); - } - r = conversation_save(state, cid, conv); - CU_ASSERT_EQUAL(r, 0); - conversation_free(conv); - conv = NULL; - } - - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* open and dump the database */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - conversations_dump(state, fp); - - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); - - /* do some basic checks on the output file */ - fflush(fp); - - r = fstat(fd, &sb); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT(sb.st_size > 20*N_MSGID_TO_CID + 20*N_CID_TO_FOLDER); - - r = (int)fseek(fp, 0L, SEEK_SET); - CU_ASSERT_EQUAL(r, 0); - - /* open and truncate the database */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - r = conversations_truncate(state); - CU_ASSERT_EQUAL(r, 0); - - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* check we can no longer find any of the data */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - for (i = 0 ; i < N_MSGID_TO_CID ; i++) { - gen_msgid_cid(i, msgid, sizeof(msgid), &cid); - r = conversations_get_msgid(state, msgid, &cid2); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid2, NULLCONVERSATION); - } - for (i = 0 ; i < N_CID_TO_FOLDER ; i++) { - gen_cid_folder(i, &cid, &mboxnames); - conv = NULL; - r = conversation_load(state, cid, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NULL(conv); - } - - /* now undump */ - r = conversations_undump(state, fp); - CU_ASSERT_EQUAL(r, 0); - - r = conversations_commit(&state); - CU_ASSERT_EQUAL(r, 0); - - /* finally check that we got all the data back */ - r = conversations_open_path(DBNAME, &state); - CU_ASSERT_EQUAL(r, 0); - - for (i = 0 ; i < N_MSGID_TO_CID ; i++) { - gen_msgid_cid(i, msgid, sizeof(msgid), &cid); - r = conversations_get_msgid(state, msgid, &cid2); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_EQUAL(cid, cid2); - } - for (i = 0 ; i < N_CID_TO_FOLDER ; i++) { - gen_cid_folder(i, &cid, &mboxnames); - conv = NULL; - r = conversation_load(state, cid, &conv); - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(conv); - CU_ASSERT_EQUAL(conv->modseq, 100); - CU_ASSERT_EQUAL(mboxnames.count, num_folders(conv)); - for (j = 0 ; j < mboxnames.count ; j++) { - folder = conversation_find_folder(state, conv, mboxnames.dataj); - CU_ASSERT_PTR_NOT_NULL(folder); - CU_ASSERT_EQUAL(folder->modseq, 100); - } - conversation_free(conv); - conv = NULL; - } - - r = conversations_abort(&state); - CU_ASSERT_EQUAL(r, 0); - - fclose(fp); - unlink(filename); - strarray_fini(&mboxnames); -#undef N_MSGID_TO_CID -#undef N_CID_TO_FOLDER -} - - -#define TESTCASE(in, exp) \ - { \ - struct buf b = BUF_INITIALIZER; \ - static const char _in = in; \ - static const char _exp = exp; \ - \ - buf_appendcstr(&b, _in); \ - conversation_normalise_subject(&b); \ - CU_ASSERT_EQUAL(b.len, sizeof(_exp)-1); \ - CU_ASSERT_STRING_EQUAL(b.s, _exp); \ - \ - buf_free(&b); \ - } - - -static void test_subject_normalise(void) -{ - TESTCASE("understanding merge history", - "understandingmergehistory"); - TESTCASE("Re: Alias of constant passed to sub", - "Aliasofconstantpassedtosub"); - TESTCASE("Re: RE: Re: Perl_peep recursion exceeds", - "Perl_peeprecursionexceeds"); - TESTCASE("Fwd: Re: Sv: Re: SV: vms rename Unix mode fixes", - "vmsrenameUnixmodefixes"); - TESTCASE("PATCH merging make_ext and make_ext_cross", - "mergingmake_extandmake_ext_cross"); - TESTCASE("Re: PATCH Parallel testing conflict", - "Paralleltestingconflict"); - TESTCASE("Re: PATCH Fwd: deprecate UNIVERSAL->import", - "deprecateUNIVERSAL->import"); -} - -#undef TESTCASE - - -static int set_up(void) -{ - int r; - - r = system("rm -rf " DBDIR); - if (r) - return r; - - r = mkdir(DBDIR, 0777); - if (r < 0) { - int e = errno; - perror(DBDIR); - return e; - } - - r = mkdir(DBDIR "/db", 0777); - if (r < 0) { - int e = errno; - perror(DBDIR "/db"); - return e; - } - - libcyrus_config_setstring(CYRUSOPT_CONFIG_DIR, DBDIR); - cyrusdb_init(); - config_conversations_db = "berkeley"; - - return 0; -} - -static int tear_down(void) -{ - int r; - - cyrusdb_done(); - config_conversations_db = NULL; - - r = system("rm -rf " DBDIR); - /* I'm ignoring you */ - - return 0; -}
View file
cyrus-imapd-2.5.tar.gz/cunit/dlist.testc
Changed
@@ -380,31 +380,4 @@ buf_free(&b2); } -static void test_deepstructure(void) -{ - struct dlist *dl = NULL; - struct dlist *di = NULL; - struct buf b = BUF_INITIALIZER; - struct buf b2 = BUF_INITIALIZER; - int r; - - buf_setcstr(&b, "%(toplevel %(sub thing) ANOTHER (value is %(list with interesting things) (in it)) ExTrA ExTrA READ \"all about it\" INCLUDING 123456)"); - - r = dlist_parsemap(&dl, 0, b.s, b.len); - - CU_ASSERT_EQUAL(r, 0); - CU_ASSERT_PTR_NOT_NULL(dl); - - di = dlist_getchild(dl, "INCLUDING"); - CU_ASSERT_EQUAL(dlist_num(di), 123456); - - dlist_printbuf(dl, 0, &b2); - - CU_ASSERT_STRING_EQUAL(buf_cstring(&b2), buf_cstring(&b)); - - dlist_free(&dl); - buf_free(&b); - buf_free(&b2); -} - /* vim: set ft=c: */
View file
cyrus-imapd-2.5.tar.gz/cunit/mboxname.testc
Changed
@@ -1,7 +1,6 @@ #if HAVE_CONFIG_H #include <config.h> #endif -#include <assert.h> #include "cunit/cunit.h" #include "libconfig.h" #include "imap/mboxname.h" @@ -351,45 +350,10 @@ toexternal_helper("shared.quux", "Chaired.shared.quux"); } -static void test_nextmodseq(void) -{ - static const char FREDNAME = "bloggs.com!user.fred"; - struct mboxname_parts parts; - char *fname; - - imapoptsIMAPOPT_CONVERSATIONS.val.b = 1; - - /* ensure there is no file */ - mboxname_to_parts(FREDNAME, &parts); - fname = mboxname_conf_getpath(&parts, "modseq"); - unlink(fname); - free(fname); - mboxname_free_parts(&parts); - - /* initial value should be 1 without file */ - CU_ASSERT_EQUAL(mboxname_nextmodseq(FREDNAME, 0), 1); - /* next value should always increment */ - CU_ASSERT_EQUAL(mboxname_nextmodseq(FREDNAME, 0), 2); - /* higher value should force a jump */ - CU_ASSERT_EQUAL(mboxname_nextmodseq(FREDNAME, 100), 101); - /* lower value should not decrease */ - CU_ASSERT_EQUAL(mboxname_nextmodseq(FREDNAME, 5), 102); -} - static enum enum_value old_config_virtdomains; -static union config_value old_config_unixhierarchysep; -static union config_value old_config_altnamespace; -static union config_value old_config_userprefix; -static union config_value old_config_sharedprefix; -static union config_value old_config_conversations; -static const char *old_config_defdomain; -static char *old_config_dir; static int set_up(void) { - char cwdPATH_MAX; - char *s; - /* * TODO: this is pretty hacky. There should be some * cleaner way of pushing aside the config for a moment @@ -398,43 +362,12 @@ */ old_config_virtdomains = config_virtdomains; config_virtdomains = IMAP_ENUM_VIRTDOMAINS_ON; - - old_config_dir = (char *)config_dir; - s = getcwd(cwd, sizeof(cwd)); - assert(s); - config_dir = strconcat(cwd, "/conf.d", (char *)NULL); - - old_config_defdomain = config_defdomain; - - old_config_unixhierarchysep = imapoptsIMAPOPT_UNIXHIERARCHYSEP.val; - old_config_altnamespace = imapoptsIMAPOPT_ALTNAMESPACE.val; - old_config_userprefix = imapoptsIMAPOPT_USERPREFIX.val; - old_config_sharedprefix = imapoptsIMAPOPT_SHAREDPREFIX.val; - old_config_conversations = imapoptsIMAPOPT_CONVERSATIONS.val; - return 0; } static int tear_down(void) { - char *cmd; - int r; - - cmd = strconcat("rm -rf \"", config_dir, "\"", (char *)NULL); - r = system(cmd); - assert(!r); - free(cmd); - free((char *)config_dir); - config_dir = old_config_dir; - config_virtdomains = old_config_virtdomains; - config_defdomain = old_config_defdomain; - imapoptsIMAPOPT_UNIXHIERARCHYSEP.val = old_config_unixhierarchysep; - imapoptsIMAPOPT_ALTNAMESPACE.val = old_config_altnamespace; - imapoptsIMAPOPT_USERPREFIX.val = old_config_userprefix; - imapoptsIMAPOPT_SHAREDPREFIX.val = old_config_sharedprefix; - imapoptsIMAPOPT_CONVERSATIONS.val = old_config_conversations; - return 0; }
View file
cyrus-imapd-2.5.tar.gz/cunit/message.testc
Changed
@@ -3,7 +3,6 @@ #endif #include "cunit/cunit.h" #include "parseaddr.h" -#include "util.h" #include "imap/message.h" static void test_parse_trivial(void) @@ -647,271 +646,6 @@ message_free_body(&body); } -static void test_x_me_message_id(void) -{ - static const char msg = -"From: Fred Bloggs <fbloggs@fastmail.fm>\r\n" -"To: Sarah Jane Smith <sjsmith@gmail.com>\r\n" -"Date: Wed, 27 Oct 2010 18:37:26 +1100\r\n" -"Subject: Trivial testing email\r\n" -"X-ME-Message-ID: <fake1700@fastmail.fm>\r\n" -"\r\n" -"Hello, World\n"; - int r; - struct body body; - - memset(&body, 0x45, sizeof(body)); - r = message_parse_mapped(msg, sizeof(msg)-1, &body); - - CU_ASSERT_EQUAL(r, 0); - - /* Message-ID: <fake1700@fastmail.fm> */ - CU_ASSERT_PTR_NULL(body.message_id); - CU_ASSERT_STRING_EQUAL(body.x_me_message_id, "<fake1700@fastmail.fm>"); - CU_ASSERT_PTR_NULL(body.in_reply_to); - CU_ASSERT_PTR_NULL(body.references); - - message_free_body(&body); -} - -static void test_zero_length_fieldnames(void) -{ -#define TEXT_PART \ - "Hello, World" -#define HTML_PART \ - "<html><head><title>Hello, World</title></head>\r\n" \ - "<body>\r\n" \ - "<p>Hello, World</p>\r\n" \ - "<body></html>" - - static const char msg = -"From: Fred Bloggs <fbloggs@fastmail.fm>\r\n" -"Reply-To: <bounce.me.harder@fastmail.fm>\r\n" -"To: Sarah Jane Smith <sjsmith@gmail.com>\r\n" -"Date: Thu, 28 Oct 2010 18:37:26 +1100\r\n" -"Subject: MIME testing email\r\n" -"X-Mailer: Norman\r\n" -"MIME-Version: 1.0\r\n" -"Content-Type: multipart/mixed; boundary=\"7225e50d962de81173be22223f706458743c3a9a\"\r\n" -"Content-Language: en\r\n" -"Message-ID: <fake1003@fastmail.fm>\r\n" -"\r\n" -"--7225e50d962de81173be22223f706458743c3a9a\r\n" -": \r\n" -"Content-Type: text/plain; charset=\"us-ascii\"\r\n" -": \r\n" -"\r\n" -TEXT_PART "\r\n" -"--7225e50d962de81173be22223f706458743c3a9a\r\n" -": \r\n" -"Content-Type: text/html; charset=\"us-ascii\"\r\n" -": \r\n" -"\r\n" -HTML_PART "\r\n" -"--7225e50d962de81173be22223f706458743c3a9a\r\n" -": \r\n" -"Content-Type: image/png\r\n" -"Content-Disposition: attachment; filename=cyrus-favicon.png\r\n" -"Content-Transfer-Encoding: base64\r\n" -": \r\n" -"\r\n" -"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\r\n" -"/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sBEQEMHNieJnIAAAIsSURBVDjL\r\n" -"tZNPSNNhGMc/r+xSUY4hbHO1LYyRSXRw4iaSRVFCiNBf8BA/mwWVB1sQFGgdgqjfph6i0EEUXaxJ\r\n" -"3tTmkA6zQxieVlR0GP0xy7n9Ftipng6/NZMM6dBzenmf5/083+d53gf+l42Njkm1zychTZPZ2Vn5\r\n" -"p8dRPSIuu0NcdodsdDgl4K+TqVRqRYj6dZhKpaQv2kuhYJBOp0GKXgGlzLBAMAjAdV2nakuVWka6\r\n" -"3N0tyYkJWc2G43GJ6vpyNbGBQXHZHZJMJFYFJBMJqfb55MHQkABYQpomV3p6QKmSVIC+SATD+AqA\r\n" -"x7OJ9o6OYtGKglEg3HWOkKYJle7N0hgIynA8LtlsdlUF2WxWkomENAYbxGV3iGXR6SHl38ePWwN4\r\n" -"vV5sNhsA/dEo+byBUgq3e0lBPpej55rOU38L5d/HwVrfJDx8JegjUrHdL726/tfsd2Ix8TTsFvQR\r\n" -"If5arPW7pAwUKAWebcyfv0144hnHDh0hl8uV+rGwsMDJ9hOcvT9C5tQN8NaAmIMoE5HizBWsK4e2\r\n" -"Czx+/4U9O5uYTCZ5Pj1N64EWRief8O1o2IwpNlNEsJS+kwhkXlAe78eo2sHM1lraOrsAmOu4CpmX\r\n" -"VAxeZL75ONTtL6kzAQKM34UPbzHOREpZ5i7dM8FrN4C3hvmmw/DoJryZgdbTvwEyaajdC82aWYoU\r\n" -"qWvWL//xAhzshMUCfH5neqp9PvlodS4tR7G2P5ZmhfvK/Cd+At3OXF7AwYF1AAAAAElFTkSuQmCC\r\n" -"\r\n--7225e50d962de81173be22223f706458743c3a9a--\r\n"; - int r; - struct body body; - struct body *part; - struct message_content mcontent; - const char *types2 = { NULL, NULL }; - struct bodypart **parts = NULL; - - memset(&body, 0x45, sizeof(body)); - r = message_parse_mapped(msg, sizeof(msg)-1, &body); - - CU_ASSERT_EQUAL(r, 0); - - /* Content-Type: */ - CU_ASSERT_STRING_EQUAL(body.type, "MULTIPART"); - CU_ASSERT_STRING_EQUAL(body.subtype, "MIXED"); - CU_ASSERT_PTR_NOT_NULL(body.params); - CU_ASSERT_STRING_EQUAL(body.params->attribute, "BOUNDARY"); - CU_ASSERT_STRING_EQUAL(body.params->value, "7225e50d962de81173be22223f706458743c3a9a"); - CU_ASSERT_PTR_NULL(body.params->next); - - CU_ASSERT_EQUAL(body.numparts, 3); - CU_ASSERT_PTR_NOT_NULL(body.subpart); - - part = &body.subpart0; - CU_ASSERT_PTR_NOT_NULL(part); - CU_ASSERT_STRING_EQUAL(part->type, "TEXT"); - CU_ASSERT_STRING_EQUAL(part->subtype, "PLAIN"); - CU_ASSERT_PTR_NOT_NULL(part->params); - CU_ASSERT_STRING_EQUAL(part->params->attribute, "CHARSET"); - CU_ASSERT_STRING_EQUAL(part->params->value, "us-ascii"); - CU_ASSERT_PTR_NULL(part->params->next); - CU_ASSERT_PTR_NULL(part->disposition); - CU_ASSERT_PTR_NULL(part->encoding); - - part = &body.subpart1; - CU_ASSERT_PTR_NOT_NULL(part); - CU_ASSERT_STRING_EQUAL(part->type, "TEXT"); - CU_ASSERT_STRING_EQUAL(part->subtype, "HTML"); - CU_ASSERT_PTR_NOT_NULL(part->params); - CU_ASSERT_STRING_EQUAL(part->params->attribute, "CHARSET"); - CU_ASSERT_STRING_EQUAL(part->params->value, "us-ascii"); - CU_ASSERT_PTR_NULL(part->params->next); - CU_ASSERT_PTR_NULL(part->disposition); - CU_ASSERT_PTR_NULL(part->encoding); - - part = &body.subpart2; - CU_ASSERT_PTR_NOT_NULL(part); - CU_ASSERT_STRING_EQUAL(part->type, "IMAGE"); - CU_ASSERT_STRING_EQUAL(part->subtype, "PNG"); - CU_ASSERT_PTR_NULL(part->params); - CU_ASSERT_STRING_EQUAL(part->disposition, "ATTACHMENT"); - CU_ASSERT_STRING_EQUAL(part->encoding, "BASE64"); - - mcontent.base = msg; - mcontent.len = sizeof(msg)-1; - mcontent.body = &body; - - types0 = "TEXT/PLAIN"; - parts = NULL; - message_fetch_part(&mcontent, types, &parts); - CU_ASSERT_PTR_NOT_NULL(parts); - if (parts) { - CU_ASSERT_PTR_NOT_NULL(parts0); - CU_ASSERT_PTR_NULL(parts1); - CU_ASSERT_STRING_EQUAL(parts0->decoded_body, TEXT_PART); - free(parts0); - free(parts); - parts = NULL; - } - - types0 = "TEXT/HTML"; - parts = NULL; - message_fetch_part(&mcontent, types, &parts); - CU_ASSERT_PTR_NOT_NULL(parts); - if (parts) { - CU_ASSERT_PTR_NOT_NULL(parts0); - CU_ASSERT_PTR_NULL(parts1); - CU_ASSERT_STRING_EQUAL(parts0->decoded_body, HTML_PART); - free(parts0); - free(parts); - parts = NULL; - } - - /* check cacheheaders */ - CU_ASSERT_PTR_NOT_NULL_FATAL(body.cacheheaders.s); - CU_ASSERT(strstr(body.cacheheaders.s, "Norman") != NULL); - CU_ASSERT_PTR_NULL(body.subpart0.cacheheaders.s); - CU_ASSERT_PTR_NULL(body.subpart1.cacheheaders.s); - CU_ASSERT_PTR_NULL(body.subpart2.cacheheaders.s); - - message_free_body(&body); -#undef TEXT_PART -#undef HTML_PART -} - -static void test_parameter_recovery(void) -{ -#define HTML_PART \ - "<html><head><title>Hello, World</title></head>\r\n" \ - "<body>\r\n" \ - "<p>Hello, World</p>\r\n" \ - "<body></html>" - - static const char msg = -"From: Fred Bloggs <fbloggs@fastmail.fm>\r\n" -"Reply-To: <bounce.me.harder@fastmail.fm>\r\n" -"To: Sarah Jane Smith <sjsmith@gmail.com>\r\n" -"Date: Thu, 28 Oct 2010 18:37:26 +1100\r\n" -"Subject: MIME testing email\r\n" -"X-Mailer: Norman\r\n" -"MIME-Version: 1.0\r\n" -/* The value of the "type" parameter contains an / but is - * not quoted, which is illegal under RFC2045 rules. We should - * be either accepting it, or ignoring it, but in either case - * the "boundary" that follows it should be accepted and used - * to pick apart the MIME structure. This actually happened - * in the wild. */ -"Content-Type: multipart/related; type=text/html; boundary=_related\r\n" -"Content-Language: en\r\n" -"Message-ID: <fake1003@fastmail.fm>\r\n" -"\r\n" -"--_related\r\n" -"Content-Type: text/html; charset=\"us-ascii\"\r\n" -"\r\n" -HTML_PART "\r\n" -"--_related\r\n" -"Content-Type: image/png\r\n" -"Content-Transfer-Encoding: base64\r\n" -"\r\n" -"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\r\n" -"/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sBEQEMHNieJnIAAAIsSURBVDjL\r\n" -"tZNPSNNhGMc/r+xSUY4hbHO1LYyRSXRw4iaSRVFCiNBf8BA/mwWVB1sQFGgdgqjfph6i0EEUXaxJ\r\n" -"3tTmkA6zQxieVlR0GP0xy7n9Ftipng6/NZMM6dBzenmf5/083+d53gf+l42Njkm1zychTZPZ2Vn5\r\n" -"p8dRPSIuu0NcdodsdDgl4K+TqVRqRYj6dZhKpaQv2kuhYJBOp0GKXgGlzLBAMAjAdV2nakuVWka6\r\n" -"3N0tyYkJWc2G43GJ6vpyNbGBQXHZHZJMJFYFJBMJqfb55MHQkABYQpomV3p6QKmSVIC+SATD+AqA\r\n" -"x7OJ9o6OYtGKglEg3HWOkKYJle7N0hgIynA8LtlsdlUF2WxWkomENAYbxGV3iGXR6SHl38ePWwN4\r\n" -"vV5sNhsA/dEo+byBUgq3e0lBPpej55rOU38L5d/HwVrfJDx8JegjUrHdL726/tfsd2Ix8TTsFvQR\r\n" -"If5arPW7pAwUKAWebcyfv0144hnHDh0hl8uV+rGwsMDJ9hOcvT9C5tQN8NaAmIMoE5HizBWsK4e2\r\n" -"Czx+/4U9O5uYTCZ5Pj1N64EWRief8O1o2IwpNlNEsJS+kwhkXlAe78eo2sHM1lraOrsAmOu4CpmX\r\n" -"VAxeZL75ONTtL6kzAQKM34UPbzHOREpZ5i7dM8FrN4C3hvmmw/DoJryZgdbTvwEyaajdC82aWYoU\r\n" -"qWvWL//xAhzshMUCfH5neqp9PvlodS4tR7G2P5ZmhfvK/Cd+At3OXF7AwYF1AAAAAElFTkSuQmCC\r\n" -"\r\n--_related--\r\n"; - int r; - struct body body; - struct body *part; - - memset(&body, 0x45, sizeof(body)); - r = message_parse_mapped(msg, sizeof(msg)-1, &body); - - CU_ASSERT_EQUAL(r, 0); - - /* Content-Type: */ - CU_ASSERT_STRING_EQUAL(body.type, "MULTIPART"); - CU_ASSERT_STRING_EQUAL(body.subtype, "RELATED"); - CU_ASSERT_PTR_NOT_NULL_FATAL(body.params); - CU_ASSERT_STRING_EQUAL(body.params->attribute, "BOUNDARY"); - CU_ASSERT_STRING_EQUAL(body.params->value, "_related"); - CU_ASSERT_PTR_NULL(body.params->next); - - CU_ASSERT_EQUAL(body.numparts, 2); - CU_ASSERT_PTR_NOT_NULL(body.subpart); - - part = &body.subpart0; - CU_ASSERT_PTR_NOT_NULL(part); - CU_ASSERT_STRING_EQUAL(part->type, "TEXT"); - CU_ASSERT_STRING_EQUAL(part->subtype, "HTML"); - CU_ASSERT_PTR_NOT_NULL(part->params); - CU_ASSERT_STRING_EQUAL(part->params->attribute, "CHARSET"); - CU_ASSERT_STRING_EQUAL(part->params->value, "us-ascii"); - CU_ASSERT_PTR_NULL(part->params->next); - CU_ASSERT_PTR_NULL(part->disposition); - CU_ASSERT_PTR_NULL(part->encoding); - - part = &body.subpart1; - CU_ASSERT_PTR_NOT_NULL(part); - CU_ASSERT_STRING_EQUAL(part->type, "IMAGE"); - CU_ASSERT_STRING_EQUAL(part->subtype, "PNG"); - CU_ASSERT_PTR_NULL(part->params); - CU_ASSERT_STRING_EQUAL(part->encoding, "BASE64"); - -#undef HTML_PART -} - - #undef TESTCASE #define TESTCASE(input, explen, expout) \ buf_reset(&b); \
View file
cyrus-imapd-2.5.tar.gz/cunit/search_expr.testc
Deleted
@@ -1,1348 +0,0 @@ -#include "config.h" -#include "cunit/cunit.h" -#include "prot.h" -#include "xmalloc.h" -#include "imap/imapd.h" -#include "imap/index.h" -#include "imap/imapparse.h" -#include "util.h" -#include "cunit/timezones.h" -#include "cunit/timeofday.h" - -static struct namespace ns; -static const char *userid = "fred"; -static struct auth_state *authstate = NULL; -static int isadmin = 0; - -#define DATE1_RFC3501 "8-Dec-2012" -#define DATE1_BEGIN "1354885200" -#define DATE1_MID "1354928400" -#define DATE1_END "1354971600" -#define DATE1_MID_TIME 1354928400 - -static void test_get_search_program(void) -{ -/*TODO: test initial state */ -/*TODO: test charset */ -#define TESTCASE(in, exp_out) \ - { \ - static const char _in = in; \ - static const char expected_out = exp_out; \ - static const char expected_errs = ""; \ - struct searchargs *searchargs; \ - int c; \ - char *actual_out = NULL; \ - struct protstream *pin; \ - struct protstream *pout; \ - struct buf actual_errs = BUF_INITIALIZER; \ - \ - searchargs = new_searchargs(".", 0, &ns, userid, authstate, isadmin); \ - pin = prot_readmap(_in, sizeof(_in)-1); \ - pout = prot_writebuf(&actual_errs); \ - c = get_search_program(pin, pout, searchargs); \ - CU_ASSERT_EQUAL(c, '\r'); \ - buf_cstring(&actual_errs); \ - CU_ASSERT_STRING_EQUAL(actual_errs.s, expected_errs); \ - if (c != EOF) { \ - CU_ASSERT_EQUAL(searchargs->charset, 0); \ - CU_ASSERT_PTR_NOT_NULL(searchargs->root); \ - actual_out = search_expr_serialise(searchargs->root); \ - CU_ASSERT_STRING_EQUAL(actual_out, expected_out); \ - } \ - \ - free(actual_out); \ - buf_free(&actual_errs); \ - prot_free(pin); \ - prot_free(pout); \ - freesearchargs(searchargs); \ - } - - /* - * Search criteria from RFC 3501 - */ - - /* <sequence set> */ - TESTCASE("1\r\n", - "(and (match msgno 1))"); - TESTCASE("1:4\r\n", - "(and (match msgno 1:4))"); - TESTCASE("1:*\r\n", - "(and (match msgno 1:*))"); - TESTCASE("1,3,5\r\n", - "(and (match msgno 1,3,5))"); - TESTCASE("1,3,5:7\r\n", - "(and (match msgno 1,3,5:7))"); - - /* ALL */ - TESTCASE("all\r\n", - "(and (true))"); - - /* ANSWERED */ - TESTCASE("answered\r\n", - "(and (match systemflags \\Answered))"); - - /* BCC <string> */ - TESTCASE("bcc \"shoreditch\"\r\n", - "(and (match bcc \"SHOREDITCH\"))"); - - /* BEFORE <date> */ - TESTCASE("before "DATE1_RFC3501"\r\n", - "(and (lt internaldate "DATE1_BEGIN"))"); - - /* BODY <string> */ - TESTCASE("body \"williamsburg\"\r\n", - "(and (match body \"WILLIAMSBURG\"))"); - - /* CC <string> */ - TESTCASE("cc \"brooklyn\"\r\n", - "(and (match cc \"BROOKLYN\"))"); - - /* DELETED */ - TESTCASE("deleted\r\n", - "(and (match systemflags \\Deleted))"); - - /* DRAFT */ - TESTCASE("draft\r\n", - "(and (match systemflags \\Draft))"); - - /* FLAGGED */ - TESTCASE("flagged\r\n", - "(and (match systemflags \\Flagged))"); - - /* FROM <string> */ - TESTCASE("from \"dreamcatcher\"\r\n", - "(and (match from \"DREAMCATCHER\"))"); - - /* HEADER <field-name> <string> */ - TESTCASE("header \"Cosby\" \"Sweater\"\r\n", - "(and (match header:cosby \"SWEATER\"))"); - - /* KEYWORD <flag> */ - TESTCASE("keyword $Mustache\r\n", - "(and (match keyword \"$Mustache\"))"); - - /* LARGER <n> */ - TESTCASE("larger 123\r\n", - "(and (gt size 123))"); - - /* NEW */ - TESTCASE("new\r\n", - "(and " - "(and " - "(not " - "(match indexflags \\Seen)" - ")" - " " - "(match indexflags \\Recent)" - ")" - ")"); - - /* NOT <search-key> */ - TESTCASE("not subject \"tumblr\"\r\n", - "(and (not (match subject \"TUMBLR\")))"); - - /* OLD */ - /* inexplicably, the RFC specifies OLD != NOT NEW */ - TESTCASE("old\r\n", - "(and (not (match indexflags \\Recent)))"); - - /* ON <date> */ - TESTCASE("on "DATE1_RFC3501"\r\n", - "(and " - "(and " - "(lt internaldate "DATE1_END")" - " " - "(ge internaldate "DATE1_BEGIN")" - ")" - ")"); - - /* OR <search-key> <search-key> */ - TESTCASE("or subject \"black\" subject \"white\"\r\n", - "(and " - "(or " - "(match subject \"BLACK\")" - " " - "(match subject \"WHITE\")" - ")" - ")"); - - /* RECENT */ - TESTCASE("recent\r\n", - "(and (match indexflags \\Recent))"); - - /* SEEN */ - TESTCASE("seen\r\n", - "(and (match indexflags \\Seen))"); - - /* SENTBEFORE <date> */ - TESTCASE("sentbefore "DATE1_RFC3501"\r\n", - "(and (lt sentdate "DATE1_BEGIN"))"); - - /* SENTON <date> */ - TESTCASE("senton "DATE1_RFC3501"\r\n", - "(and " - "(and " - "(lt sentdate "DATE1_END")" - " " - "(ge sentdate "DATE1_BEGIN")" - ")" - ")"); - - /* SENTSINCE <date> */ - /* SENTSINCE, like SINCE, includes the given date */ - TESTCASE("sentsince "DATE1_RFC3501"\r\n", - "(and (ge sentdate "DATE1_BEGIN"))"); - - /* SINCE <date> */ - /* Note: BEFORE excludes the date, SINCE includes it */ - TESTCASE("since "DATE1_RFC3501"\r\n", - "(and (ge internaldate "DATE1_BEGIN"))"); - - /* SMALLER <n> */ - /* Note: neither LARGER nor SMALLER includes the exact size */ - TESTCASE("smaller 456\r\n", - "(and (lt size 456))"); - - /* SUBJECT <string> */ - TESTCASE("subject \"selvage\"\r\n", - "(and (match subject \"SELVAGE\"))"); - - /* TEXT <string> */ - TESTCASE("text \"letterpress\"\r\n", - "(and (match text \"LETTERPRESS\"))"); - - /* TO <string> */ - TESTCASE("to \"readymade\"\r\n", - "(and (match to \"READYMADE\"))"); - - /* UID <sequence set> */ - TESTCASE("uid 1\r\n", - "(and (match uid 1))"); - TESTCASE("uid 1:4\r\n", - "(and (match uid 1:4))"); - TESTCASE("uid 1:*\r\n", - "(and (match uid 1:*))"); - TESTCASE("uid 1,3,5\r\n", - "(and (match uid 1,3,5))"); - TESTCASE("uid 1,3,5:7\r\n", - "(and (match uid 1,3,5:7))"); - - /* UNANSWERED */ - TESTCASE("unanswered\r\n", - "(and (not (match systemflags \\Answered)))"); - - /* UNDELETED */ - TESTCASE("undeleted\r\n", - "(and (not (match systemflags \\Deleted)))"); - - /* UNDRAFT */ - TESTCASE("undraft\r\n", - "(and (not (match systemflags \\Draft)))"); - - /* UNFLAGGED */ - TESTCASE("unflagged\r\n", - "(and (not (match systemflags \\Flagged)))"); - - /* UNKEYWORD <flag> */ - TESTCASE("unkeyword $Viral\r\n", - "(and (not (match keyword \"$Viral\")))"); - - /* UNSEEN */ - TESTCASE("unseen\r\n", - "(and (not (match indexflags \\Seen)))"); - - /* ( <search-key>* ) */ - TESTCASE("(subject \"bicycle\" to \"rights\")\r\n", - "(and " - "(and " - "(match subject \"BICYCLE\")" - " " - "(match to \"RIGHTS\")" - ")" - ")"); - TESTCASE("not (subject \"bicycle\" to \"rights\")\r\n", - "(and " - "(not " - "(and " - "(match subject \"BICYCLE\")" - " " - "(match to \"RIGHTS\")" - ")" - ")" - ")"); - TESTCASE("or (subject \"bicycle\" to \"rights\") from \"quinoa\"\r\n", - "(and " - "(or " - "(and " - "(match subject \"BICYCLE\")" - " " - "(match to \"RIGHTS\")" - ")" - " " - "(match from \"QUINOA\")" - ")" - ")"); - - /* - * Search criteria from RFC 4551 - */ - - /* MODSEQ <modseq> - * Note, the full RFC syntax is - * MODSEQ <entry-name> <entry-type-req> <mod-sequence-valzer> - * but we don't support the optional but in the middle */ -/*TODO: negative should fail*/ - TESTCASE("modseq 0\r\n", /* not a valid modseq but we can search on it */ - "(and (ge modseq 0))"); - TESTCASE("modseq 1\r\n", - "(and (ge modseq 1))"); - TESTCASE("modseq 12345\r\n", - "(and (ge modseq 12345))"); - TESTCASE("modseq 12345678\r\n", - "(and (ge modseq 12345678))"); - /* MODSEQ is a 64b value */ - TESTCASE("modseq 12345678901\r\n", - "(and (ge modseq 12345678901))"); - TESTCASE("modseq 2147483647\r\n", /* 0x7fff_ffff */ - "(and (ge modseq 2147483647))"); - TESTCASE("modseq 2147483648\r\n", /* 0x8000_0000 */ - "(and (ge modseq 2147483648))"); - TESTCASE("modseq 4294967295\r\n", /* 0xffff_ffff */ - "(and (ge modseq 4294967295))"); - TESTCASE("modseq 4294967296\r\n", /* 0x1_0000_0000 */ - "(and (ge modseq 4294967296))"); - TESTCASE("modseq 12345678901234\r\n", - "(and (ge modseq 12345678901234))"); - TESTCASE("modseq 18446744073709551615\r\n", /* 0xffff_ffff_ffff_ffff */ - "(and (ge modseq 18446744073709551615))"); - - /* - * Search criteria from RFC 5032 - */ - - /* OLDER <nz-number> in seconds ago */ - /* YOUNGER <nz-number> in seconds ago */ - /* Inexplicably, the RFC defines that *both* YOUNGER and OLDER - * include the specified second */ - time_push_fixed(DATE1_MID_TIME); - TESTCASE("older 0\r\n", - "(and (le internaldate "DATE1_MID"))"); - TESTCASE("older 43200\r\n", - "(and (le internaldate "DATE1_BEGIN"))"); - TESTCASE("younger 0\r\n", - "(and (ge internaldate "DATE1_MID"))"); - TESTCASE("younger 43200\r\n", - "(and (ge internaldate "DATE1_BEGIN"))"); - time_pop(); - - /* - * Search criteria from RFC 5257 - */ - /* ANNOTATION <entry> <attrib> </value> */ - TESTCASE("annotation /comment value \"stumptown\"\r\n", - "(and (match annotation (entry \"/comment\" attrib \"value\" value \"stumptown\")))"); - TESTCASE("annotation /comment value.priv \"stumptown\"\r\n", - "(and (match annotation (entry \"/comment\" attrib \"value.priv\" value \"stumptown\")))"); - TESTCASE("annotation /comment value.shared \"stumptown\"\r\n", - "(and (match annotation (entry \"/comment\" attrib \"value.shared\" value \"stumptown\")))"); - - /* - * Cyrus vendor specific search criteria - */ - /* FOLDER <mboxname> */ - TESTCASE("folder INBOX\r\n", - "(and (match folder \"user.fred\"))"); - TESTCASE("folder inbox\r\n", - "(and (match folder \"user.fred\"))"); - TESTCASE("folder inbox.GastroPub\r\n", - "(and (match folder \"user.fred.GastroPub\"))"); - - /* XLISTID <string> */ - TESTCASE("xlistid \"semiotics\"\r\n", - "(and (match listid \"SEMIOTICS\"))"); - - /* XCONTENTTYPE <string> */ - TESTCASE("xcontenttype text\r\n", - "(and (match contenttype \"TEXT\"))"); - TESTCASE("xcontenttype \"text\"\r\n", - "(and (match contenttype \"TEXT\"))"); - TESTCASE("xcontenttype \"plain\"\r\n", - "(and (match contenttype \"PLAIN\"))"); - TESTCASE("xcontenttype \"text_plain\"\r\n", - "(and (match contenttype \"TEXT_PLAIN\"))"); - -/* TODO: test that conv* are rejected if conversations:off */ - /* CONVFLAG <flag> */ - TESTCASE("convflag $Forage\r\n", - "(and (match convflags \"$forage\"))"); - - /* CONVREAD */ - TESTCASE("convread\r\n", - "(and (match convflags \"\\Seen\"))"); - - /* CONVUNREAD */ - TESTCASE("convunread\r\n", - "(and (not (match convflags \"\\Seen\")))"); - - /* CONVSEEN */ - TESTCASE("convseen\r\n", - "(and (match convflags \"\\Seen\"))"); - - /* CONVUNSEEN */ - TESTCASE("convunseen\r\n", - "(and (not (match convflags \"\\Seen\")))"); - - /* CONVMODSEQ <modseq> */ - TESTCASE("convmodseq 0\r\n", /* not a valid modseq but we can search on it */ - "(and (ge convmodseq 0))"); - TESTCASE("convmodseq 1\r\n", - "(and (ge convmodseq 1))"); - TESTCASE("convmodseq 12345\r\n", - "(and (ge convmodseq 12345))"); - TESTCASE("convmodseq 12345678\r\n", - "(and (ge convmodseq 12345678))"); - /* MODSEQ is a 64b value */ - TESTCASE("convmodseq 12345678901\r\n", - "(and (ge convmodseq 12345678901))"); - TESTCASE("convmodseq 2147483647\r\n", /* 0x7fff_ffff */ - "(and (ge convmodseq 2147483647))"); - TESTCASE("convmodseq 2147483648\r\n", /* 0x8000_0000 */ - "(and (ge convmodseq 2147483648))"); - TESTCASE("convmodseq 4294967295\r\n", /* 0xffff_ffff */ - "(and (ge convmodseq 4294967295))"); - TESTCASE("convmodseq 4294967296\r\n", /* 0x1_0000_0000 */ - "(and (ge convmodseq 4294967296))"); - TESTCASE("convmodseq 12345678901234\r\n", - "(and (ge convmodseq 12345678901234))"); - TESTCASE("convmodseq 18446744073709551615\r\n", /* 0xffff_ffff_ffff_ffff */ - "(and (ge convmodseq 18446744073709551615))"); - - /* CID <cid> */ - TESTCASE("cid NIL\r\n", - "(and (match cid NIL))"); - TESTCASE("cid 0000000000000000\r\n", - "(and (match cid NIL))"); - TESTCASE("cid 0123456789abcdef\r\n", - "(and (match cid 0123456789abcdef))"); - - /* TODO: charset */ - /* TODO: return */ - /* TODO: return */ - -#undef TESTCASE -} - -static void test_unserialise(void) -{ - search_expr_t *e; - - e = search_expr_unserialise("(true)"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e); - CU_ASSERT_EQUAL(e->op, SEOP_TRUE); - CU_ASSERT_PTR_NULL(e->attr); - CU_ASSERT_PTR_NULL(e->next); - CU_ASSERT_PTR_NULL(e->children); - CU_ASSERT_PTR_NULL(e->parent); - search_expr_free(e); - - e = search_expr_unserialise("(false)"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e); - CU_ASSERT_EQUAL(e->op, SEOP_FALSE); - CU_ASSERT_PTR_NULL(e->attr); - CU_ASSERT_PTR_NULL(e->next); - CU_ASSERT_PTR_NULL(e->children); - CU_ASSERT_PTR_NULL(e->parent); - search_expr_free(e); - - e = search_expr_unserialise("(not (false))"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e); - CU_ASSERT_EQUAL(e->op, SEOP_NOT); - CU_ASSERT_PTR_NULL(e->attr); - CU_ASSERT_PTR_NULL(e->next); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->children); - CU_ASSERT_PTR_NULL(e->parent); - CU_ASSERT_EQUAL(e->children->op, SEOP_FALSE); - CU_ASSERT_PTR_NULL(e->children->attr); - CU_ASSERT_PTR_NULL(e->children->next); - CU_ASSERT_PTR_NULL(e->children->children); - CU_ASSERT_PTR_EQUAL(e->children->parent, e); - search_expr_free(e); - - e = search_expr_unserialise("(and (false) (true))"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e); - CU_ASSERT_EQUAL(e->op, SEOP_AND); - CU_ASSERT_PTR_NULL(e->attr); - CU_ASSERT_PTR_NULL(e->next); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->children); - CU_ASSERT_PTR_NULL(e->parent); - CU_ASSERT_EQUAL(e->children->op, SEOP_FALSE); - CU_ASSERT_PTR_NULL(e->children->attr); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->children->next); - CU_ASSERT_PTR_NULL(e->children->children); - CU_ASSERT_PTR_EQUAL(e->children->parent, e); - CU_ASSERT_EQUAL(e->children->next->op, SEOP_TRUE); - CU_ASSERT_PTR_NULL(e->children->next->attr); - CU_ASSERT_PTR_NULL(e->children->next->next); - CU_ASSERT_PTR_NULL(e->children->next->children); - CU_ASSERT_PTR_EQUAL(e->children->next->parent, e); - search_expr_free(e); - - e = search_expr_unserialise("(or (false) (true))"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e); - CU_ASSERT_EQUAL(e->op, SEOP_OR); - CU_ASSERT_PTR_NULL(e->attr); - CU_ASSERT_PTR_NULL(e->next); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->children); - CU_ASSERT_PTR_NULL(e->parent); - CU_ASSERT_EQUAL(e->children->op, SEOP_FALSE); - CU_ASSERT_PTR_NULL(e->children->attr); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->children->next); - CU_ASSERT_PTR_NULL(e->children->children); - CU_ASSERT_PTR_EQUAL(e->children->parent, e); - CU_ASSERT_EQUAL(e->children->next->op, SEOP_TRUE); - CU_ASSERT_PTR_NULL(e->children->next->attr); - CU_ASSERT_PTR_NULL(e->children->next->next); - CU_ASSERT_PTR_NULL(e->children->next->children); - CU_ASSERT_PTR_EQUAL(e->children->next->parent, e); - search_expr_free(e); - - e = search_expr_unserialise("(match subject ETSY)"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e); - CU_ASSERT_EQUAL(e->op, SEOP_MATCH); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->attr); - CU_ASSERT_STRING_EQUAL(e->attr->name, "subject"); - CU_ASSERT_STRING_EQUAL(e->value.s, "ETSY"); - CU_ASSERT_PTR_NULL(e->next); - CU_ASSERT_PTR_NULL(e->children); - CU_ASSERT_PTR_NULL(e->parent); - search_expr_free(e); - - e = search_expr_unserialise("(fuzzymatch subject ETSY)"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e); - CU_ASSERT_EQUAL(e->op, SEOP_FUZZYMATCH); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->attr); - CU_ASSERT_STRING_EQUAL(e->attr->name, "subject"); - CU_ASSERT_STRING_EQUAL(e->value.s, "ETSY"); - CU_ASSERT_PTR_NULL(e->next); - CU_ASSERT_PTR_NULL(e->children); - CU_ASSERT_PTR_NULL(e->parent); - search_expr_free(e); - - e = search_expr_unserialise("(ge size 123)"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e); - CU_ASSERT_EQUAL(e->op, SEOP_GE); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->attr); - CU_ASSERT_STRING_EQUAL(e->attr->name, "size"); - CU_ASSERT_EQUAL(e->value.u, 123); - CU_ASSERT_PTR_NULL(e->next); - CU_ASSERT_PTR_NULL(e->children); - CU_ASSERT_PTR_NULL(e->parent); - search_expr_free(e); - - e = search_expr_unserialise("(match uid 1:4,7:*)"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e); - CU_ASSERT_EQUAL(e->op, SEOP_MATCH); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->attr); - CU_ASSERT_STRING_EQUAL(e->attr->name, "uid"); - CU_ASSERT_STRING_EQUAL(e->value.s, "1:4,7:*"); - CU_ASSERT_PTR_NULL(e->next); - CU_ASSERT_PTR_NULL(e->children); - CU_ASSERT_PTR_NULL(e->parent); - search_expr_free(e); - - e = search_expr_unserialise("(match annotation (entry \"/comment\" attrib \"value\" value \"Brooklyn\"))"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e); - CU_ASSERT_EQUAL(e->op, SEOP_MATCH); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->attr); - CU_ASSERT_STRING_EQUAL(e->attr->name, "annotation"); - CU_ASSERT_PTR_NOT_NULL_FATAL(e->value.annot); - CU_ASSERT_STRING_EQUAL(e->value.annot->entry, "/comment"); - CU_ASSERT_STRING_EQUAL(e->value.annot->attrib, "value"); - CU_ASSERT_STRING_EQUAL(e->value.annot->value.s, "Brooklyn"); - CU_ASSERT_PTR_NULL(e->next); - CU_ASSERT_PTR_NULL(e->children); - CU_ASSERT_PTR_NULL(e->parent); - search_expr_free(e); -} - -static void test_duplicate(void) -{ -#define TESTCASE(in) \ - { \ - static const char _in = (in); \ - search_expr_t *e; \ - search_expr_t *edup; \ - char *s; \ - char *sdup; \ - \ - e = search_expr_unserialise(_in); \ - CU_ASSERT_PTR_NOT_NULL_FATAL(e); \ - edup = search_expr_duplicate(e); \ - CU_ASSERT_PTR_NOT_NULL_FATAL(edup); \ - CU_ASSERT_PTR_NOT_EQUAL(e, edup); \ - s = search_expr_serialise(e); \ - CU_ASSERT_STRING_EQUAL(s, _in); \ - free(s); \ - sdup = search_expr_serialise(edup); \ - CU_ASSERT_STRING_EQUAL(sdup, _in); \ - free(sdup); \ - search_expr_free(e); \ - sdup = search_expr_serialise(edup); \ - CU_ASSERT_STRING_EQUAL(sdup, _in); \ - free(sdup); \ - search_expr_free(edup); \ - } - - TESTCASE("(false)"); - TESTCASE("(true)"); - TESTCASE("(not (false))"); - TESTCASE("(and (false) (true))"); - TESTCASE("(or (false) (true))"); - TESTCASE("(match subject \"ETSY\")"); - TESTCASE("(ge size 123)"); - TESTCASE("(gt size 456)"); - TESTCASE("(le size 789)"); - TESTCASE("(lt size 120)"); - TESTCASE("(match uid 1:4,7:*)"); - TESTCASE("(match annotation (entry \"/comment\" attrib \"value\" value \"Brooklyn\"))"); - -#undef TESTCASE -} - - -static void test_normalise(void) -{ -#define TESTCASE(in, exp_out) \ - { \ - static const char _in = (in); \ - static const char expected_out = (exp_out); \ - search_expr_t *e; \ - char *s; \ - \ - e = search_expr_unserialise(_in); \ - CU_ASSERT_PTR_NOT_NULL_FATAL(e); \ - search_expr_normalise(&e); \ - CU_ASSERT_PTR_NOT_NULL_FATAL(e); \ - s = search_expr_serialise(e); \ - CU_ASSERT_STRING_EQUAL(s, expected_out); \ - search_expr_free(e); \ - } - - /* - * A tree comprising a single comparison node - * is already in DNF. - */ - TESTCASE("(false)", - "(false)"); - TESTCASE("(true)", - "(true)"); - TESTCASE("(match subject \"ETSY\")", - "(match subject \"ETSY\")"); - TESTCASE("(le size 123)", - "(le size 123)"); - TESTCASE("(lt size 123)", - "(lt size 123)"); - TESTCASE("(ge size 123)", - "(ge size 123)"); - TESTCASE("(gt size 123)", - "(gt size 123)"); - - /* - * A tree comprising a single comparison node - * under a NOT node is already in DNF. - */ - TESTCASE("(not (false))", - "(not (false))"); - TESTCASE("(not (true))", - "(not (true))"); - TESTCASE("(not (match subject \"ETSY\"))", - "(not (match subject \"ETSY\"))"); - TESTCASE("(not (le size 123))", - "(not (le size 123))"); - TESTCASE("(not (lt size 123))", - "(not (lt size 123))"); - TESTCASE("(not (ge size 123))", - "(not (ge size 123))"); - TESTCASE("(not (gt size 123))", - "(not (gt size 123))"); - - /* - * Ordering of nodes. - */ - TESTCASE("(and " - "(match subject \"ALPHA\")" - " " - "(match subject \"BETA\")" - " " - "(match subject \"GAMMA\")" - ")", - "(and " - "(match subject \"ALPHA\")" - " " - "(match subject \"BETA\")" - " " - "(match subject \"GAMMA\")" - ")"); - TESTCASE("(and " - "(match subject \"GAMMA\")" - " " - "(match subject \"BETA\")" - " " - "(match subject \"ALPHA\")" - ")", - "(and " - "(match subject \"ALPHA\")" - " " - "(match subject \"BETA\")" - " " - "(match subject \"GAMMA\")" - ")"); - TESTCASE("(and " - "(match subject \"GAMMA\")" - " " - "(match subject \"ALPHA\")" - " " - "(match subject \"BETA\")" - ")", - "(and " - "(match subject \"ALPHA\")" - " " - "(match subject \"BETA\")" - " " - "(match subject \"GAMMA\")" - ")"); - - /* - * A tree comprising a single conjunctive node - * whose children are all comparison nodes or - * negations of comparison nodes, is already in DNF. - */ - TESTCASE("(and (le size 123) (match subject \"ETSY\"))", - "(and (le size 123) (match subject \"ETSY\"))"); - TESTCASE("(and (not (le size 123)) (match subject \"ETSY\"))", - "(and (match subject \"ETSY\") (not (le size 123)))"); - TESTCASE("(and (le size 123) (not (match subject \"ETSY\")))", - "(and (le size 123) (not (match subject \"ETSY\")))"); - - /* - * A tree comprising a single disjunctive node - * whose children are all comparison nodes or - * negations of comparison nodes, is already in DNF. - */ - TESTCASE("(or (le size 123) (match subject \"ETSY\"))", - "(or (le size 123) (match subject \"ETSY\"))"); - TESTCASE("(or (not (le size 123)) (match subject \"ETSY\"))", - "(or (match subject \"ETSY\") (not (le size 123)))"); - TESTCASE("(or (le size 123) (not (match subject \"ETSY\")))", - "(or (le size 123) (not (match subject \"ETSY\")))"); - - /* - * A tree already in fully populated DNF: one disjunctive - * node, then multiple conjunctive nodes, then negations, - * then comparisons. - */ - TESTCASE("(or " - "(and " - "(le size 123)" - " " - "(not " - "(match subject \"ETSY\")" - ")" - ")" - " " - "(and " - "(not " - "(le size 456)" - ")" - " " - "(match subject \"TUMBLR\")" - ")" - ")", - "(or " - "(and " - "(le size 123)" - " " - "(not " - "(match subject \"ETSY\")" - ")" - ")" - " " - "(and " - "(match subject \"TUMBLR\")" - " " - "(not " - "(le size 456)" - ")" - ")" - ")"); - - /* - * Elimination of trivial nodes - */ - TESTCASE("(and " - "(match subject \"COSBY\")" - ")", - "(match subject \"COSBY\")"); - TESTCASE("(or " - "(match subject \"COSBY\")" - ")", - "(match subject \"COSBY\")"); - TESTCASE("(and (not) (match subject \"COSBY\"))", - "(match subject \"COSBY\")"); - TESTCASE("(or (not) (match subject \"COSBY\"))", - "(match subject \"COSBY\")"); - - /* - * Combining similar parent & child nodes - */ - TESTCASE("(and " - "(match subject \"COSBY\")" - " " - "(and " - "(le size 123)" - " " - "(match subject \"TUMBLR\")" - ")" - " " - "(match subject \"SWEATER\")" - ")", - "(and " - "(le size 123)" - " " - "(match subject \"COSBY\")" - " " - "(match subject \"SWEATER\")" - " " - "(match subject \"TUMBLR\")" - ")"); - TESTCASE("(or " - "(match subject \"COSBY\")" - " " - "(or " - "(le size 123)" - " " - "(match subject \"TUMBLR\")" - ")" - " " - "(match subject \"SWEATER\")" - ")", - "(or " - "(le size 123)" - " " - "(match subject \"COSBY\")" - " " - "(match subject \"SWEATER\")" - " " - "(match subject \"TUMBLR\")" - ")"); - TESTCASE("(not " - "(not " - "(match subject \"SELVAGE\")" - ")" - ")", - "(match subject \"SELVAGE\")"); - TESTCASE("(not " - "(not " - "(not " - "(match subject \"SELVAGE\")" - ")" - ")" - ")", - "(not " - "(match subject \"SELVAGE\")" - ")"); - TESTCASE("(not " - "(not " - "(not " - "(not " - "(match subject \"SELVAGE\")" - ")" - ")" - ")" - ")", - "(match subject \"SELVAGE\")"); - - /* - * Applying DeMorgan's laws to invert an AND or OR - * which is a child or a NOT. - */ - TESTCASE("(not " - "(and " - "(le size 123)" - " " - "(match subject \"ETSY\")" - ")" - ")", - "(or " - "(not " - "(le size 123)" - ")" - " " - "(not " - "(match subject \"ETSY\")" - ")" - ")"); - TESTCASE("(not " - "(or " - "(le size 123)" - " " - "(match subject \"ETSY\")" - ")" - ")", - "(and " - "(not " - "(le size 123)" - ")" - " " - "(not " - "(match subject \"ETSY\")" - ")" - ")"); - - /* - * DeMorgan's laws plus combination of similar nodes - */ - TESTCASE("(not " - "(and " - "(not " - "(le size 123)" - ")" - " " - "(match subject \"ETSY\")" - ")" - ")", - "(or " - "(le size 123)" - " " - "(not " - "(match subject \"ETSY\")" - ")" - ")"); - TESTCASE("(not " - "(or " - "(not " - "(le size 123)" - ")" - " " - "(match subject \"ETSY\")" - ")" - ")", - "(and " - "(le size 123)" - " " - "(not " - "(match subject \"ETSY\")" - ")" - ")"); - - /* - * Distribution. - */ - TESTCASE("(and " - "(match subject \"TUMBLR\")" - " " - "(or " - "(le size 123)" - " " - "(match subject \"ETSY\")" - ")" - ")", - "(or " - "(and " - "(le size 123)" - " " - "(match subject \"TUMBLR\")" - ")" - " " - "(and " - "(match subject \"ETSY\")" - " " - "(match subject \"TUMBLR\")" - ")" - ")"); - TESTCASE("(and " - "(or " - "(le size 123)" - " " - "(match subject \"ETSY\")" - ")" - " " - "(match subject \"TUMBLR\")" - ")", - "(or " - "(and " - "(le size 123)" - " " - "(match subject \"TUMBLR\")" - ")" - " " - "(and " - "(match subject \"ETSY\")" - " " - "(match subject \"TUMBLR\")" - ")" - ")"); - TESTCASE("(and " - "(match subject \"COSBY\")" - " " - "(or " - "(le size 123)" - " " - "(match subject \"ETSY\")" - ")" - " " - "(match subject \"SWEATER\")" - ")", - "(or " - "(and " - "(le size 123)" - " " - "(match subject \"COSBY\")" - " " - "(match subject \"SWEATER\")" - ")" - " " - "(and " - "(match subject \"COSBY\")" - " " - "(match subject \"ETSY\")" - " " - "(match subject \"SWEATER\")" - ")" - ")"); - - /* - * Distribution plus elimination of trivial nodes - */ - TESTCASE("(and " - "(or " - "(le size 123)" - " " - "(match subject \"ETSY\")" - ")" - ")", - "(or " - "(le size 123)" - " " - "(match subject \"ETSY\")" - ")"); - -#undef TESTCASE -} - -static void test_countability(void) -{ -#define TESTCASE(in, exp) \ - { \ - static const char _in = (in); \ - unsigned int expected = (exp); \ - search_expr_t *e; \ - unsigned int actual; \ - \ - e = search_expr_unserialise(_in); \ - CU_ASSERT_PTR_NOT_NULL_FATAL(e); \ - actual = search_expr_get_countability(e); \ - CU_ASSERT_EQUAL(actual, expected); \ - search_expr_free(e); \ - } - - TESTCASE("(true)", SEC_EXISTS); - TESTCASE("(not (true))", SEC_EXISTS|SEC_NOT); - // The case (not (false)) doesn't work properly, but this - // doesn't matter because it will not occur in trees - // generated from IMAP syntax. - TESTCASE("(match indexflags \\Recent)", SEC_RECENT); - TESTCASE("(not (match indexflags \\Recent))", SEC_RECENT|SEC_NOT); - TESTCASE("(match indexflags \\Seen)", SEC_SEEN); - TESTCASE("(not (match indexflags \\Seen))", SEC_SEEN|SEC_NOT); - TESTCASE("(match convflags \\Seen)", SEC_CONVSEEN); - TESTCASE("(not (match convflags \\Seen))", SEC_CONVSEEN|SEC_NOT); - TESTCASE("(match subject \"ETSY\")", SEC_UNCOUNTED); - TESTCASE("(not (match subject \"ETSY\"))", SEC_UNCOUNTED|SEC_NOT); - TESTCASE("(and (match indexflags \\Seen) (match subject \"ETSY\"))", - SEC_UNCOUNTED|SEC_SEEN); - TESTCASE("(not (and (match indexflags \\Seen) (match subject \"ETSY\")))", - SEC_UNCOUNTED|SEC_SEEN|SEC_NOT); - TESTCASE("(and (not (match indexflags \\Seen)) (match subject \"ETSY\"))", - SEC_UNCOUNTED|SEC_SEEN|SEC_NOT); - -#undef TESTCASE -} - -static void add_subquery(const char *mboxname, search_expr_t *indexed, search_expr_t *e, void *rock) -{ - struct buf *buf = rock; - char *s; - - if (mboxname) - buf_printf(buf, "mboxname \"%s\" ", mboxname); - - if (indexed) { - s = search_expr_serialise(indexed); - buf_printf(buf, "indexed %s ", s); - free(s); - } - - s = search_expr_serialise(e); - buf_appendcstr(buf, s); - free(s); - - buf_putc(buf, '\n'); - buf_cstring(buf); - - search_expr_free(e); - search_expr_free(indexed); -} - -static void test_split_by_folder_and_index(void) -{ -#define TESTCASE(in, exp) \ - { \ - static const char _in = (in); \ - static const char expected = (exp); \ - search_expr_t *e; \ - struct buf actual = BUF_INITIALIZER; \ - \ - e = search_expr_unserialise(_in); \ - CU_ASSERT_PTR_NOT_NULL_FATAL(e); \ - search_expr_split_by_folder_and_index(e, add_subquery, &actual); \ - CU_ASSERT_STRING_EQUAL(actual.s, expected); \ - buf_free(&actual); \ - } - - /* Note, we expect that a (match message-id) node is NOT indexable - * whereas a (fuzzymatch subject) node IS indexable */ - - /* comparison nodes and negations of comparison nodes, which don't - * match any folders, get a NULL mboxname */ - TESTCASE("(false)", - "(false)\n"); - TESTCASE("(true)", - "(true)\n"); - TESTCASE("(match message-id \"ETSY\")", - "(match message-id \"ETSY\")\n"); - TESTCASE("(not (match message-id \"ETSY\"))", - "(not (match message-id \"ETSY\"))\n"); - - /* a folder match node a (true) node and that mboxname */ - TESTCASE("(match folder \"inbox.Cosby.Sweater\")", - "mboxname \"inbox.Cosby.Sweater\" (true)\n"); - - /* negation of a folder match node gets a NULL mboxname - it's - * supposed to match every folder *EXCEPT* the named one */ - TESTCASE("(not (match folder \"inbox.Cosby.Sweater\"))", - "(not (match folder \"inbox.Cosby.Sweater\"))\n"); - TESTCASE("(and (match message-id \"ETSY\") (not (match folder \"inbox.Cosby.Sweater\")))", - "(and (match message-id \"ETSY\") (not (match folder \"inbox.Cosby.Sweater\")))\n"); - - /* a conjuctive node which doesn't match folders - * gets a NULL mboxname */ - TESTCASE("(and (match message-id \"ETSY\") (le size 123))", - "(and (le size 123) (match message-id \"ETSY\"))\n"); - TESTCASE("(and (match message-id \"ETSY\") (not (le size 123)))", - "(and (match message-id \"ETSY\") (not (le size 123)))\n"); - TESTCASE("(and (le size 123) (not (match message-id \"ETSY\")))", - "(and (le size 123) (not (match message-id \"ETSY\")))\n"); - - /* a conjuctive node which does match a folder gets the remainder - * of the expression and that named mboxname */ - TESTCASE("(and (match folder \"inbox.Cosby.Sweater\") (match message-id \"ETSY\"))", - "mboxname \"inbox.Cosby.Sweater\" (match message-id \"ETSY\")\n"); - TESTCASE("(and (match folder \"inbox.Cosby.Sweater\") (not (match message-id \"ETSY\")))", - "mboxname \"inbox.Cosby.Sweater\" (not (match message-id \"ETSY\"))\n"); - TESTCASE("(and (match message-id \"ETSY\") (match folder \"inbox.Cosby.Sweater\"))", - "mboxname \"inbox.Cosby.Sweater\" (match message-id \"ETSY\")\n"); - TESTCASE("(and (not (match message-id \"ETSY\")) (match folder \"inbox.Cosby.Sweater\"))", - "mboxname \"inbox.Cosby.Sweater\" (not (match message-id \"ETSY\"))\n"); - - /* a disjunctive node which doesn't match folders - * or indexable attrs is untouched */ - TESTCASE("(or (match message-id \"ETSY\") (le size 123))", - "(or (le size 123) (match message-id \"ETSY\"))\n"); - TESTCASE("(or (match message-id \"ETSY\") (not (le size 123)))", - "(or (match message-id \"ETSY\") (not (le size 123)))\n"); - TESTCASE("(or (le size 123) (not (match message-id \"ETSY\")))", - "(or (le size 123) (not (match message-id \"ETSY\")))\n"); - - /* a disjuctive node which does match folders gets - * split into two expressions each with the given mboxname*/ - TESTCASE("(or (match folder \"inbox.Cosby.Sweater\") (le size 123))", - "mboxname \"inbox.Cosby.Sweater\" (true)\n" - "(le size 123)\n"); - TESTCASE("(or (match folder \"inbox.Mlkshk\") (match folder \"inbox.Tumblr\"))", - "mboxname \"inbox.Mlkshk\" (true)\n" - "mboxname \"inbox.Tumblr\" (true)\n"); - TESTCASE("(or (and (ge size 456) (match folder \"inbox.Mlkshk\")) (and (le size 123) (match folder \"inbox.Tumblr\")))", - "mboxname \"inbox.Mlkshk\" (ge size 456)\n" - "mboxname \"inbox.Tumblr\" (le size 123)\n"); - - /* indexed comparison nodes and their negations, which don't - * match any folders, get a NULL mboxname and a trivial scan. */ - TESTCASE("(fuzzymatch subject \"ETSY\")", - "indexed (fuzzymatch subject \"ETSY\") (true)\n"); - TESTCASE("(not (fuzzymatch subject \"ETSY\"))", - "indexed (not (fuzzymatch subject \"ETSY\")) (true)\n"); - - /* conjunctions of indexed comparison nodes and their negations, which - * don't match any folders, get a NULL mboxname and a trivial scan. */ - TESTCASE("(and (fuzzymatch subject \"ETSY\") (fuzzymatch subject \"TUMBLR\"))", - "indexed (and (fuzzymatch subject \"ETSY\") (fuzzymatch subject \"TUMBLR\")) (true)\n"); - - /* conjunctions of indexed comparison nodes and their negations, with - * non-indexed nodes, which don't match any folders, get a NULL mboxname - * two trees with the indexed nodes separated out */ - TESTCASE("(and (fuzzymatch subject \"ETSY\") (match message-id \"TUMBLR\"))", - "indexed (fuzzymatch subject \"ETSY\") (match message-id \"TUMBLR\")\n"); - TESTCASE("(and (match message-id \"TUMBLR\") (fuzzymatch subject \"ETSY\"))", - "indexed (fuzzymatch subject \"ETSY\") (match message-id \"TUMBLR\")\n"); - - /* conjunctions of indexed comparison nodes and their negations, which - * do match folders either positively or negatively, get a NULL mboxname - * because we favour the index */ - TESTCASE("(and (fuzzymatch subject \"ETSY\") (match folder \"inbox.Tumblr\"))", - "indexed (fuzzymatch subject \"ETSY\") (match folder \"inbox.Tumblr\")\n"); - TESTCASE("(and (match folder \"inbox.Tumblr\") (fuzzymatch subject \"ETSY\"))", - "indexed (fuzzymatch subject \"ETSY\") (match folder \"inbox.Tumblr\")\n"); - TESTCASE("(and (fuzzymatch subject \"ETSY\") (not (match folder \"inbox.Tumblr\")))", - "indexed (fuzzymatch subject \"ETSY\") (not (match folder \"inbox.Tumblr\"))\n"); - TESTCASE("(and (not (match folder \"inbox.Tumblr\")) (fuzzymatch subject \"ETSY\"))", - "indexed (fuzzymatch subject \"ETSY\") (not (match folder \"inbox.Tumblr\"))\n"); - - /* a disjuctive node where one side contains indexed matches, - * is split into multiple callbacks, some indexed, some not */ - TESTCASE("(or (match folder \"inbox.Cosby.Sweater\") (fuzzymatch subject \"ETSY\"))", - "mboxname \"inbox.Cosby.Sweater\" (true)\n" - "indexed (fuzzymatch subject \"ETSY\") (true)\n"); - TESTCASE("(or (match folder \"inbox.Cosby.Sweater\") (fuzzymatch subject \"ETSY\"))", - "mboxname \"inbox.Cosby.Sweater\" (true)\n" - "indexed (fuzzymatch subject \"ETSY\") (true)\n"); - TESTCASE("(or (fuzzymatch subject \"MLKSHK\") (fuzzymatch subject \"TUMBLR\"))", - "indexed (fuzzymatch subject \"MLKSHK\") (true)\n" - "indexed (fuzzymatch subject \"TUMBLR\") (true)\n"); - TESTCASE("(or (and (le size 123) (fuzzymatch subject \"TUMBLR\")) (and (ge size 456) (fuzzymatch subject \"MLKSHK\")))", - "indexed (fuzzymatch subject \"TUMBLR\") (le size 123)\n" - "indexed (fuzzymatch subject \"MLKSHK\") (ge size 456)\n"); -} - -/* a large un-normalised expression containing lots of ORs but no - * folder or indexable matches is split without being normalised, - * avoiding combinatorial explosion. */ -static void test_bigun(void) -{ -#define BIGUN \ -"(and " \ - "(or " \ - "(or " \ - "(or " \ - "(or " \ - "(match to \"Lorem\") " \ - "(match cc \"Lorem\")) " \ - "(match bcc \"Lorem\")) " \ - "(match from \"Lorem\")) " \ - "(match subject \"Lorem\")) " \ - "(or " \ - "(or " \ - "(or " \ - "(or " \ - "(match to \"ipsum\") " \ - "(match cc \"ipsum\")) " \ - "(match bcc \"ipsum\")) " \ - "(match from \"ipsum\")) " \ - "(match subject \"ipsum\")) " \ - "(or " \ - "(or " \ - "(or " \ - "(or " \ - "(match to \"dolor\") " \ - "(match cc \"dolor\")) " \ - "(match bcc \"dolor\")) " \ - "(match from \"dolor\")) " \ - "(match subject \"dolor\")) " \ - "(or " \ - "(or " \ - "(or " \ - "(or " \ - "(match to \"sit\") " \ - "(match cc \"sit\")) " \ - "(match bcc \"sit\")) " \ - "(match from \"sit\")) " \ - "(match subject \"sit\")))" - - TESTCASE(BIGUN, BIGUN "\n"); -#undef BIGUN -} - -/* a large un-normalised expression containing lots of ORs and a - * FOLDER match, is too complex to normalise without exceeding a - * resource limit, and ends up un-normalised. */ -static void test_bigun2(void) -{ -#define BIGUN \ -"(and " \ - "(or " \ - "(or " \ - "(or " \ - "(or " \ - "(match folder \"INBOX.Lorem\") " \ - "(match cc \"Lorem\")) " \ - "(match bcc \"Lorem\")) " \ - "(match from \"Lorem\")) " \ - "(match subject \"Lorem\")) " \ - "(or " \ - "(or " \ - "(or " \ - "(or " \ - "(match to \"ipsum\") " \ - "(match cc \"ipsum\")) " \ - "(match bcc \"ipsum\")) " \ - "(match from \"ipsum\")) " \ - "(match subject \"ipsum\")) " \ - "(or " \ - "(or " \ - "(or " \ - "(or " \ - "(match to \"dolor\") " \ - "(match cc \"dolor\")) " \ - "(match bcc \"dolor\")) " \ - "(match from \"dolor\")) " \ - "(match subject \"dolor\")) " \ - "(or " \ - "(or " \ - "(or " \ - "(or " \ - "(match to \"sit\") " \ - "(match folder \"INBOX.sit\")) " \ - "(match bcc \"sit\")) " \ - "(match from \"sit\")) " \ - "(match subject \"sit\")))" - - TESTCASE(BIGUN, BIGUN "\n"); -#undef BIGUN -} -#undef TESTCASE - -static int set_up(void) -{ - int r; - - search_attr_init(); - - r = mboxname_init_namespace(&ns, isadmin); - if (r) return r; - - push_tz(TZ_MELBOURNE); - - imapoptsIMAPOPT_CONVERSATIONS.val.b = 1; - - return 0; -} - -static int tear_down(void) -{ - imapoptsIMAPOPT_CONVERSATIONS.val.b = 0; - restore_tz(); - time_restore(); - return 0; -} - -/* vim: set ft=c: */
View file
cyrus-imapd-2.5.tar.gz/cunit/seqset.testc
Deleted
@@ -1,505 +0,0 @@ -#include "config.h" -#include "cunit/cunit.h" -#include "xmalloc.h" -#include "util.h" -#include "imap/sequence.h" - -static void test_empty(void) -{ - struct seqset *seq; - char *s; - - seq = seqset_init(/*maxval*/0, SEQ_SPARSE); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - CU_ASSERT_EQUAL(seq->len, 0); - CU_ASSERT_EQUAL(seqset_first(seq), 0); - CU_ASSERT_EQUAL(seqset_last(seq), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, ""); - free(s); - - seqset_free(seq); -} - -static void test_add_contiguous(void) -{ - struct seqset *seq; - char *s; - - seq = seqset_init(/*maxval*/0, SEQ_SPARSE); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - CU_ASSERT_EQUAL(seq->len, 0); - CU_ASSERT_EQUAL(seqset_first(seq), 0); - CU_ASSERT_EQUAL(seqset_last(seq), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, ""); - free(s); - - seqset_add(seq, 1, /*ismember*/1); - - CU_ASSERT_EQUAL(seq->len, 1); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1"); - free(s); - - seqset_add(seq, 2, /*ismember*/1); - - CU_ASSERT_EQUAL(seq->len, 1); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 2); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1:2"); - free(s); - - seqset_add(seq, 3, /*ismember*/1); - - CU_ASSERT_EQUAL(seq->len, 1); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 3); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1:3"); - free(s); - - seqset_add(seq, 4, /*ismember*/1); - - CU_ASSERT_EQUAL(seq->len, 1); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 4); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1:4"); - free(s); - - seqset_free(seq); -} - -static void test_add_noncontiguous(void) -{ - struct seqset *seq; - char *s; - - seq = seqset_init(/*maxval*/0, SEQ_SPARSE); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - CU_ASSERT_EQUAL(seq->len, 0); - CU_ASSERT_EQUAL(seqset_first(seq), 0); - CU_ASSERT_EQUAL(seqset_last(seq), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, ""); - free(s); - - seqset_add(seq, 1, /*ismember*/1); - - CU_ASSERT_EQUAL(seq->len, 1); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1"); - free(s); - - /* The seqset API is fundamentally broken in that we need - * to call seqset_add() with ismember=0 instead of just - * ignoring the non-member number and calling seqset_add() - * for the next member number. Duh. */ - seqset_add(seq, 2, /*ismember*/0); - - CU_ASSERT_EQUAL(seq->len, 1); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1"); - free(s); - - seqset_add(seq, 3, /*ismember*/1); - - CU_ASSERT_EQUAL(seq->len, 2); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 3); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1,3"); - free(s); - - seqset_add(seq, 4, /*ismember*/1); - - CU_ASSERT_EQUAL(seq->len, 2); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 4); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1,3:4"); - free(s); - - seqset_add(seq, 5, /*ismember*/1); - - CU_ASSERT_EQUAL(seq->len, 2); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 5); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 6), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1,3:5"); - free(s); - - seqset_free(seq); -} - -static void test_dup(void) -{ - struct seqset *seq; - struct seqset *seq2; - char *s; - - seq = seqset_init(/*maxval*/0, SEQ_SPARSE); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - seqset_add(seq, 1, /*ismember*/1); - seqset_add(seq, 2, /*ismember*/0); - seqset_add(seq, 3, /*ismember*/1); - seqset_add(seq, 4, /*ismember*/1); - seqset_add(seq, 5, /*ismember*/1); - - CU_ASSERT_EQUAL(seq->len, 2); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 5); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 6), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1,3:5"); - free(s); - - seq2 = seqset_dup(seq); - CU_ASSERT_PTR_NOT_NULL(seq2); - CU_ASSERT_PTR_NOT_EQUAL(seq, seq2); - - CU_ASSERT_EQUAL(seq->len, 2); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 5); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 6), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1,3:5"); - free(s); - - CU_ASSERT_EQUAL(seq2->len, 2); - CU_ASSERT_EQUAL(seqset_first(seq2), 1); - CU_ASSERT_EQUAL(seqset_last(seq2), 5); - CU_ASSERT_EQUAL(seqset_ismember(seq2, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq2, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq2, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq2, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq2, 4), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq2, 5), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq2, 6), 0); - s = seqset_cstring(seq2); - CU_ASSERT_STRING_EQUAL(s, "1,3:5"); - free(s); - - seqset_free(seq); - seqset_free(seq2); -} - -static void test_iteration(void) -{ - struct seqset *seq; - unsigned i; - char *s; - - seq = seqset_init(/*maxval*/0, SEQ_SPARSE); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - seqset_add(seq, 1, /*ismember*/1); - seqset_add(seq, 2, /*ismember*/0); - seqset_add(seq, 3, /*ismember*/1); - seqset_add(seq, 4, /*ismember*/1); - seqset_add(seq, 5, /*ismember*/1); - - CU_ASSERT_EQUAL(seq->len, 2); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 5); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 6), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1,3:5"); - free(s); - - /* HACK */ - seq->prev = 0; - seq->current = 0; - - i = seqset_getnext(NULL); - CU_ASSERT_EQUAL(i, 0); - - i = seqset_getnext(seq); - CU_ASSERT_EQUAL(i, 1); - i = seqset_getnext(seq); - CU_ASSERT_EQUAL(i, 3); - i = seqset_getnext(seq); - CU_ASSERT_EQUAL(i, 4); - i = seqset_getnext(seq); - CU_ASSERT_EQUAL(i, 5); - i = seqset_getnext(seq); - CU_ASSERT_EQUAL(i, 0); - i = seqset_getnext(seq); - CU_ASSERT_EQUAL(i, 0); - - seqset_free(seq); -} - -static void test_parse(void) -{ - struct seqset *seq; - char *s; - - seq = seqset_parse("1", NULL, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - CU_ASSERT_EQUAL(seq->len, 1); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1"); - free(s); - - seqset_free(seq); - - /* ----- */ - - seq = seqset_parse("1:3", NULL, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - CU_ASSERT_EQUAL(seq->len, 1); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 3); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1:3"); - free(s); - - seqset_free(seq); - - /* ----- */ - - seq = seqset_parse("1:3,5", NULL, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - CU_ASSERT_EQUAL(seq->len, 2); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 5); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 6), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1:3,5"); - free(s); - - seqset_free(seq); - - /* ----- */ - - seq = seqset_parse("1:3,5,8:11", NULL, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - CU_ASSERT_EQUAL(seq->len, 3); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), 11); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 6), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 7), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 8), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 9), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 10), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 11), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 12), 0); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1:3,5,8:11"); - free(s); - - seqset_free(seq); - - /* ----- */ - - seq = seqset_parse("1:*", NULL, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - CU_ASSERT_EQUAL(seq->len, 1); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), UINT_MAX); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 1); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1:*"); - free(s); - - seqset_free(seq); - - /* ----- */ - - seq = seqset_parse("1:3,5:*", NULL, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - CU_ASSERT_EQUAL(seq->len, 2); - CU_ASSERT_EQUAL(seqset_first(seq), 1); - CU_ASSERT_EQUAL(seqset_last(seq), UINT_MAX); - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 6), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 7), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 8), 1); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "1:3,5:*"); - free(s); - - seqset_free(seq); -} - -#if 0 -// XXX - this is test is correct AFAICS -// but it is currently failing, presumably due to some -// subtly buggy corner case in Cyrus. So just ignore. -static void XXX_test_star(void) -{ - struct seqset *seq; - char *s; - - /* The "*" character is specified in RFC 3501 with what might be - * considered odd semantics. It doesn't mean "infinity", it means - * a specific finite number which is the highest matchable number. - * This only matters in a few interesting corner cases. */ - - seq = seqset_parse("*", NULL, 0); - CU_ASSERT_PTR_NOT_NULL_FATAL(seq); - - CU_ASSERT_EQUAL(seq->len, 1); - CU_ASSERT_EQUAL(seqset_first(seq), UINT_MAX); - CU_ASSERT_EQUAL(seqset_last(seq), UINT_MAX); - s = seqset_cstring(seq); - CU_ASSERT_STRING_EQUAL(s, "*"); - free(s); - - /* maxval not specified - will not match anything yet */ - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 6), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 7), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 8), 0); - - /* maxval specified - will match maxval only */ - seq->maxval = 1; - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 6), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 7), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 8), 0); - - /* maxval specified - will match maxval only */ - seq->maxval = 3; - CU_ASSERT_EQUAL(seqset_ismember(seq, 0), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 1), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 2), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 3), 1); - CU_ASSERT_EQUAL(seqset_ismember(seq, 4), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 5), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 6), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 7), 0); - CU_ASSERT_EQUAL(seqset_ismember(seq, 8), 0); - - seqset_free(seq); -} -#endif - -/* vim: set ft=c: */
View file
cyrus-imapd-2.5.tar.gz/cunit/sieve.testc
Changed
@@ -16,7 +16,6 @@ #include "retry.h" #include "imap/spool.h" #include "map.h" -#include "util.h" #include "cyrusdb.h" #include "libcyr_cfg.h" #include "libconfig.h" @@ -655,7 +654,7 @@ } -static sieve_test_message_t *sieve_test_message_new(const char *text, int len) +static sieve_test_message_t *message_new(const char *text, int len) { sieve_test_message_t *msg; struct protstream *pin; @@ -711,7 +710,7 @@ sieve_test_message_t *msg; int r; - msg = sieve_test_message_new(text, strlen(text)); + msg = message_new(text, strlen(text)); CU_ASSERT_PTR_NOT_NULL(msg); r = sieve_execute_bytecode(ctx->exe, ctx->interp, ctx, msg);
View file
cyrus-imapd-2.5.tar.gz/cunit/squat.testc
Changed
@@ -55,27 +55,27 @@ TESTCASE(0x100); TESTCASE(0x10000); TESTCASE(0x1000000); - TESTCASE(0x100000000ULL); - TESTCASE(0x10000000000ULL); - TESTCASE(0x1000000000000ULL); - TESTCASE(0x100000000000000ULL); + TESTCASE(0x100000000); + TESTCASE(0x10000000000); + TESTCASE(0x1000000000000); + TESTCASE(0x100000000000000); TESTCASE(0x80); TESTCASE(0x8000); TESTCASE(0x800000); TESTCASE(0x80000000); - TESTCASE(0x8000000000ULL); - TESTCASE(0x800000000000ULL); - TESTCASE(0x80000000000000ULL); - TESTCASE(0x8000000000000000ULL); + TESTCASE(0x8000000000); + TESTCASE(0x800000000000); + TESTCASE(0x80000000000000); + TESTCASE(0x8000000000000000); TESTCASE(0xff); TESTCASE(0xffff); TESTCASE(0xffffff); TESTCASE(0xffffffff); - TESTCASE(0xffffffffffULL); - TESTCASE(0xffffffffffffULL); - TESTCASE(0xffffffffffffffULL); - TESTCASE(0xffffffffffffffffULL); - TESTCASE(0xcafebabebdefacedULL); + TESTCASE(0xffffffffff); + TESTCASE(0xffffffffffff); + TESTCASE(0xffffffffffffff); + TESTCASE(0xffffffffffffffff); + TESTCASE(0xcafebabebdefaced); #undef TESTCASE } @@ -107,25 +107,25 @@ TESTCASE(0x100); TESTCASE(0x10000); TESTCASE(0x1000000); - TESTCASE(0x100000000ULL); - TESTCASE(0x10000000000ULL); - TESTCASE(0x1000000000000ULL); - TESTCASE(0x100000000000000ULL); + TESTCASE(0x100000000); + TESTCASE(0x10000000000); + TESTCASE(0x1000000000000); + TESTCASE(0x100000000000000); TESTCASE(0x80); TESTCASE(0x8000); TESTCASE(0x800000); TESTCASE(0x80000000); - TESTCASE(0x8000000000ULL); - TESTCASE(0x800000000000ULL); - TESTCASE(0x80000000000000ULL); + TESTCASE(0x8000000000); + TESTCASE(0x800000000000); + TESTCASE(0x80000000000000); TESTCASE(0xff); TESTCASE(0xffff); TESTCASE(0xffffff); TESTCASE(0xffffffff); - TESTCASE(0xffffffffffULL); - TESTCASE(0xffffffffffffULL); - TESTCASE(0xffffffffffffffULL); - TESTCASE(0x4afebabebdefacedULL); + TESTCASE(0xffffffffff); + TESTCASE(0xffffffffffff); + TESTCASE(0xffffffffffffff); + TESTCASE(0x4afebabebdefaced); #undef TESTCASE } /* vim: set ft=c: */
View file
cyrus-imapd-2.5.tar.gz/cunit/timeofday.c
Deleted
@@ -1,318 +0,0 @@ -/* - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> -#include <assert.h> -#include <time.h> -#include "timeofday.h" - -extern int verbose; - -#define MICROSEC_PER_SEC (1000000) - -struct trans -{ - /* - * We transform time using the formula - * - * reported_time = (actual_time - start) * factor + epoch; - * - * where 'factor' is actually fractional. Yes, I realise that - * 'start' is mathematically redundant but it makes the code - * marginally easier to write and read. - */ - int64_t start; - int64_t epoch; - long factor_num; - long factor_den; -}; - -static int real_gettimeofday(struct timeval *, ...); - -#define MAX_TRANS_STACK 5 -static int n_trans_stack = 0; -static struct trans trans_stackMAX_TRANS_STACK; -static const struct trans identity = { 0, 0, 1, 1 }; - -/* - * Basic time manipulation. - * - * Internal time format is microseconds since the Unix epoch - * in a signed 64b integer which is convenient to use and - * allows some headroom for scaling. - */ - -static int64_t from_timeval(const struct timeval *tv) -{ - return (int64_t)tv->tv_usec + (int64_t)tv->tv_sec * MICROSEC_PER_SEC; -} - -static void to_timeval(int64_t t, struct timeval *tv) -{ - tv->tv_sec = t / MICROSEC_PER_SEC; - tv->tv_usec = t % MICROSEC_PER_SEC; -} - -static int64_t from_time_t(time_t tt) -{ - return (int64_t)tt * MICROSEC_PER_SEC; -} - -static time_t to_time_t(int64_t t) -{ - return t / MICROSEC_PER_SEC; -} - -static int64_t now(void) -{ - struct timeval tv = { 0xffffffff, 0xffffffff }; - int r = real_gettimeofday(&tv, NULL); - assert(r == 0); - assert(tv.tv_sec != 0xffffffff); - assert(tv.tv_usec != 0xffffffff); - return from_timeval(&tv); -} - -/* - * Time transform stack handling. - */ - -static const struct trans *trans_top(void) -{ - return (n_trans_stack ? &trans_stackn_trans_stack-1 : &identity); -} - -static int64_t transform(int64_t t) -{ - const struct trans *tr = trans_top(); - int64_t tt = ((t - tr->start) * tr->factor_num) / tr->factor_den + tr->epoch; - return tt; -} - -static void trans_push(const struct trans *tr) -{ - assert(n_trans_stack < MAX_TRANS_STACK); - trans_stackn_trans_stack++ = *tr; -} - -/* - * Make the reported time go faster or slower from now on. - * Reported times are continuous across this function. - */ -void time_push_rate(long n, long d) -{ - struct trans tr = *trans_top(); - tr.start = now(); - tr.epoch = transform(tr.start); - tr.factor_num *= n; - tr.factor_den *= d; - trans_push(&tr); -} - -/* - * Stop the flow of reported time - */ -void time_push_stop(void) -{ - time_push_rate(0, 1); -} - -/* - * Report a given fixed time - */ -void time_push_fixed(time_t fixed) -{ - struct trans tr = *trans_top(); - tr.start = 0; - tr.epoch = from_time_t(fixed); - tr.factor_num = 0; - tr.factor_den = 1; - trans_push(&tr); -} - -void time_pop(void) -{ - assert(n_trans_stack > 0); - --n_trans_stack; -} - -void time_restore(void) -{ - n_trans_stack = 0; -} - -/* - * Platform-specific libc interception code - */ - -#if defined(__GLIBC__) - -/* call the real libc function */ -static int real_gettimeofday(struct timeval *tv, ...) -{ - extern int __gettimeofday(struct timeval *, ...); - return __gettimeofday(tv, NULL); -} - -/* provide a function to hide the libc weak alias */ -int gettimeofday(struct timeval *tv, ...) -{ - to_timeval(transform(now()), tv); - return 0; -} - -time_t time(time_t *tp) -{ - time_t tt = to_time_t(transform(now())); - if (tp) *tp = tt; - return tt; -} - -#else -#error "Don't know how to intercept gettimeofday for this libc" -#endif - - -/* - * Tests - not usefully runnable, sorry. - */ -#if 0 -static void test_time_speedup(void) -{ - time_t clock; - - time(&clock); fputs(ctime(&clock), stderr); - - fputs("time_push_rate(5, 1)\n", stderr); - time_push_rate(10, 1); - - time(&clock); fputs(ctime(&clock), stderr); - sleep(1); - time(&clock); fputs(ctime(&clock), stderr); - sleep(1); - time(&clock); fputs(ctime(&clock), stderr); - sleep(1); - time(&clock); fputs(ctime(&clock), stderr); - sleep(1); - time(&clock); fputs(ctime(&clock), stderr); - - fputs("time_pop()\n", stderr); - time_pop(); - - time(&clock); fputs(ctime(&clock), stderr); -} - -static void test_time_slowdown(void) -{ - time_t clock; - int i; - - time(&clock); fputs(ctime(&clock), stderr); - - fputs("time_push_rate(1, 10)\n", stderr); - time_push_rate(1, 5); - - for (i = 0 ; i < 20 ; i++) { - time(&clock); fputs(ctime(&clock), stderr); - sleep(1); - } - time(&clock); fputs(ctime(&clock), stderr); - - fputs("time_pop()\n", stderr); - time_pop(); - - time(&clock); fputs(ctime(&clock), stderr); -} - -static void test_time_fixed(void) -{ - time_t clock; - - time(&clock); fputs(ctime(&clock), stderr); - - fputs("time_push_fixed(1354928400)\n", stderr); - time_push_fixed(1354928400); - - time(&clock); fputs(ctime(&clock), stderr); - sleep(1); - time(&clock); fputs(ctime(&clock), stderr); - sleep(1); - time(&clock); fputs(ctime(&clock), stderr); - sleep(1); - time(&clock); fputs(ctime(&clock), stderr); - sleep(1); - time(&clock); fputs(ctime(&clock), stderr); - - fputs("time_pop()\n", stderr); - time_pop(); - - time(&clock); fputs(ctime(&clock), stderr); -} - -static void test_time_fixed2(void) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); fputs(ctime(&tv.tv_sec), stderr); - - fputs("time_push_fixed(1354928400)\n", stderr); - time_push_fixed(1354928400); - - gettimeofday(&tv, NULL); fputs(ctime(&tv.tv_sec), stderr); - sleep(1); - gettimeofday(&tv, NULL); fputs(ctime(&tv.tv_sec), stderr); - sleep(1); - gettimeofday(&tv, NULL); fputs(ctime(&tv.tv_sec), stderr); - sleep(1); - gettimeofday(&tv, NULL); fputs(ctime(&tv.tv_sec), stderr); - sleep(1); - gettimeofday(&tv, NULL); fputs(ctime(&tv.tv_sec), stderr); - - fputs("time_pop()\n", stderr); - time_pop(); - - gettimeofday(&tv, NULL); fputs(ctime(&tv.tv_sec), stderr); -} -#endif -
View file
cyrus-imapd-2.5.tar.gz/cunit/timeofday.h
Deleted
@@ -1,54 +0,0 @@ -/* timeofday.c - time of day warping utilities for unit tests - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __CYRUS_CUNIT_TIMEOFDAY_H__ -#define __CYRUS_CUNIT_TIMEOFDAY_H__ - -#include <sys/types.h> - -extern void time_push_rate(long n, long d); -extern void time_push_stop(void); -extern void time_push_fixed(time_t fixed); -extern void time_pop(void); -extern void time_restore(void); - -#endif /* __CYRUS_CUNIT_TIMEOFDAY_H__ */
View file
cyrus-imapd-2.5.tar.gz/cunit/times.testc
Changed
@@ -51,10 +51,52 @@ #include <assert.h> #include "cunit/cunit.h" #include "times.h" -#include "cunit/timezones.h" + +#define TZ_UTC "UTC+00" +#define TZ_NEWYORK "EST+05" +#define TZ_MELBOURNE "AEST-11" #define UNINIT_TIMET ((time_t)0xdeadbeef) +#define MAX_TZ_STACK 5 +static int n_tz_stack = 0; +static char *tz_stackMAX_TZ_STACK; + +static inline void xxputenv(char *s, const char *f) +{ + if (verbose > 1) + fprintf(stderr, "\n%s:putenv(\"%s\")\n", f, s); + putenv(s); +} +#define putenv(s) xxputenv((s), __FUNCTION__) + +static char *stash_tz(const char *tz) +{ + char *s = malloc(4+(tz == NULL ? 0 : strlen(tz))); + assert(s); + sprintf(s, "TZ=%s", (tz == NULL ? "" : tz)); + assert(n_tz_stack < MAX_TZ_STACK-1); + return tz_stackn_tz_stack++ = s; +} + +static void push_tz(const char *tz) +{ + if (n_tz_stack == 0) + stash_tz(getenv("TZ")); + putenv(stash_tz(tz)); + tzset(); +} + +static void pop_tz(void) +{ + char *old; + assert(n_tz_stack > 1); + old = tz_stack--n_tz_stack; + putenv(tz_stackn_tz_stack-1); + tzset(); + free(old); +} + static int set_up(void) { /* @@ -69,7 +111,9 @@ static int tear_down(void) { - restore_tz(); + pop_tz(); + if (n_tz_stack != 1) + return -1; return 0; }
View file
cyrus-imapd-2.5.tar.gz/cunit/timezones.c
Deleted
@@ -1,95 +0,0 @@ -/* - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> -#include <assert.h> -#include <time.h> -#include "timezones.h" - -extern int verbose; - -#define MAX_TZ_STACK 5 -static int n_tz_stack = 0; -static char *tz_stackMAX_TZ_STACK; - -static inline void xxputenv(char *s, const char *f) -{ - if (verbose > 1) - fprintf(stderr, "\n%s:putenv(\"%s\")\n", f, s); - putenv(s); -} -#define putenv(s) xxputenv((s), __FUNCTION__) - -static char *stash_tz(const char *tz) -{ - char *s = malloc(4+(tz == NULL ? 0 : strlen(tz))); - assert(s); - sprintf(s, "TZ=%s", (tz == NULL ? "" : tz)); - assert(n_tz_stack < MAX_TZ_STACK-1); - return tz_stackn_tz_stack++ = s; -} - -void push_tz(const char *tz) -{ - if (n_tz_stack == 0) - stash_tz(getenv("TZ")); - putenv(stash_tz(tz)); - tzset(); -} - -void pop_tz(void) -{ - char *old; - assert(n_tz_stack > 1); - old = tz_stack--n_tz_stack; - putenv(tz_stackn_tz_stack-1); - tzset(); - free(old); -} - -void restore_tz(void) -{ - while (n_tz_stack > 1) - pop_tz(); -}
View file
cyrus-imapd-2.5.tar.gz/cunit/timezones.h
Deleted
@@ -1,57 +0,0 @@ -/* timezones.h - timezone utilities for unit tests - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __CYRUS_CUNIT_TIMEZONES_H__ -#define __CYRUS_CUNIT_TIMEZONES_H__ - -#include <stdio.h> -#include <stdarg.h> - -#define TZ_UTC "UTC+00" -#define TZ_NEWYORK "EST+05" -#define TZ_MELBOURNE "AEST-11" - -extern void push_tz(const char *tz); -extern void pop_tz(void); -extern void restore_tz(void); - -#endif /* __CYRUS_CUNIT_TIMEZONES_H__ */
View file
cyrus-imapd-2.5.tar.gz/doc/changes.html
Changed
@@ -8,124 +8,6 @@ </head> <body> -<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta9</h1> -<ul> -<li>Split merging of scheduling object resources from - sched_deliver_local() into deliver_merge_request() and - deliver_merge_reply().</li> -</ul> - -<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta8</h1> -<ul> -<li>Fixed bug in parsing of Accept header (now accepts <tt>*/*</tt> - and <tt>type/*</tt>).</li> -<li>Fixed telemetry logging bug (old garbage appearing in log).</li> -<li>Added a workaround for the DELETE bug in MacOS X 10.9.0 Calendar - client.</li> -</ul> - -<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta7</h1> -<ul> -<li>Added Timzone Service module along with associated admin tools - (ctl_zoneinfo, vzic).</li> -<li>Added support for accepting/returning jCal (requires Jansson) and - xCal data wherever iCalendar data is allowed.</li> -<li>Proxied responses (including chunked) are now piped to client - rather than being buffered and forwarded.</li> -<li>Better handling of COPY/MOVE between backends (including - LOCKs).</li> -<li>Fixed "annotation truncation" bug where the largest allowed - annotation value was slightly less than 4k (solves problem reading - CalDavZAP settings).</li> -<li>GET on ./well-known now returns a list of /.well-known/ URLs on - the server.</li> -<li>Added support for X-HTTP-Method-Override header in POST requests.</li> -<li>Added replacement functions for those not present in libxml2 < - v2.8.0</li> -<li>Plugged a few more memory leaks found by Valgrind.</li> -</ul> - -<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta6</h1> -<ul> -<li>Plugged several memory leaks found by Valgrind</li> -<li>Less verbose reconnect communication between frontend and - backend</li> -<li>GET on calendar-home-set now returns a list of subscribe-able - calendars</li> -<li>Auto-provisioning of calendars/addressbooks now works via a - frontend proxy</li> -<li>Fixed several conformance bugs detected by CalDAVTester</li> -<li>Added support for optionally adding Content-MD5 header to - responses (see <tt>httpcontentmd5</tt> option)</li> -<li>Fixed time-based queries for components other than VEVENT</li> -</ul> - -<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta5</h1> -<ul> -<li>RSS module now produces Atom 1.0 output rather than RSS 2.0 (we - prefer IETF standards)</li> -<li>PROPFIND/REPORT allprop/propname requests are now supported</li> -<li><tt>unixhierarchysep</tt> is now supported by all HTTP - modules</li> -<li>Completely rewrote write_body() - Range requests are now supported - on non-chunked Content-Encoded data</li> -<li>Added cache control response headers where appropriate to make - Redbot happy</li> -<li>Fixed handling of telemetry log file descriptors and - truncation</li> -<li>Allow GET on calendar collections for "exporting" entire - calendar</li> -<li>Fixed POST on calendar collection (add-member)</li> -<li>Fixed parsing of calendar-query filter</li> -<li>Added several CalDAV/iCalendar validation checks based on - CalDAVTester results</li> -</ul> - -<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta4</h1> -<ul> -<li>Always verify authorization credentials supplied by the client (a - proxy may be reusing an existing connection as a different user)</li> -<li>Don't bother supporting Digest qop=auth-int since no clients seem - to use it</li> -<li>Don't show addressbook mailboxes in IMAP LIST output</li> -<li>Plugged leaked memory found by Valgrind</li> -<li>Better handling of request/response bodies</li> -<li>Added <tt>httpprettytelemetry</tt> option</li> -<li>Added <tt>httpallowcors</tt> option (Cross-Origin Resource Sharing)</li> -</ul> - -<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta3</h1> -<ul> -<li>Plugged leaked memory found by Valgrind</li> -<li>Rewrote list_feeds() to not use memmem()</li> -<li>OPTIONS method can be used without authentication</li> -<li>Better handling of Connection:keep-alive</li> -<li>Added <tt>httpallowedurls</tt> option</li> -</ul> - -<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta2</h1> -<ul> -<li>Fixed security bug where a user-agent could access files - outside of <tt>httpdocroot</tt>. -<li>Changed annotation entry name scheme to be - "/vendor/cmu/cyrus-httpd/<" ns-href ">" prop</li> -</ul> - -<h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta1</h1> -<ul> -<li>Fixed Bug #3792: Build failures on Solaris 10</li> -<li>Path to user's DAV DB will be created, if necessary</li> -<li>Fixed bug where calendars and addressbooks were using the same - mailbox type flag</li> -<li>Fixed unsigned int / size_t mismatches in formatted output</li> -</ul> - -<h1>Changes to the Cyrus IMAP Server since 2.4.17</h1> -<ul> -<li>Added httpd service with CalDAV, CardDAV, RSS, and iSchedule - modules</li> -</ul> - <h1>Changes to the Cyrus IMAP Server since 2.4.16</h1> <ul> <li>A bunch of cleanups and fixes to compiling</li>
View file
cyrus-imapd-2.5.tar.gz/doc/install-http.html
Deleted
@@ -1,1152 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml"> -<head> -<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> -<title>Cyrus HTTP</title> -</head> -<body> - -<h1>Cyrus HTTP (RSS, CalDAV, CardDAV, iSchedule, Timezone Service)</h1> - -<b><i>Note that the HTTP service and associated modules in Cyrus are - still under development. This release should be considered beta - quality.</i></b> - -<h2>Introduction</h2> - -<p>Cyrus <tt>http</tt> service has the ability to:</p> - -<ul> -<li>Serve IMAP mailboxes as RSS feeds.</li> -<li>Act as a calendar and scheduling (CalDAV) server by using IMAP - mailboxes as calendar collections and RFC 5322 messages to store - iCalendar data.</li> -<li>Act as a contacts (CardDAV) server by using IMAP mailboxes as - addressbook collections and RFC 5322 messages to store vCard - data.</li> -<li>Allow scheduling transactions between separate calendaring and - scheduling systems via the iSchedule protocol <i>(currently only used - within a Cyrus Murder)</i>.</li> -<li>Act as a Timezone Service by serving iCalendar (VTIMEZONE) - data to client systems.</li> -<li>Serve static content (such as the RSS feed list template and/or - the CalDAV/CardDAV JavaScript clients mentioned below).</li> -</ul> - -<i>Unlike the <a href="http://httpd.apache.org/">Apache HTTP - Server</a>, Cyrus HTTP is NOT a general purpose HTTP server. Its - feature set is limited to what is required to support the - facilities listed above.</i> - -<p>This document assumes that you are familiar with building and - configuring a Cyrus server. If you have not already done so, please - read and understand the rest of the <a href="install.html">installation</a> - documentation before continuing. Note: The - "<a href="#install">Installation</a>" section below augments the - "<a href="install-compile.html">Compiling the IMAP Server</a>" - document. The remaining sections assume that your Cyrus server has - already been - successfully <a href="install-configure.html">configured</a>.</p> - -<p>This document also assumes that you are familiar with RSS, WebDAV, - calendaring, and contacts.</p> - - -<h2 id="install">Installation</h2> - -<p>You will need to build Cyrus with - the <tt>--enable-http</tt> configure option. This builds httpd - and the associated modules and utilities based on the availability - of the prerequisites listed below.</p> - -<h3>General Requirements</h3> - -<ul> -<li>Must have <a href="http://xmlsoft.org/">libxml2</a> - installed.</li> -<li>Must have a recent <a href="http://www.cyrusimap.org/">SASL</a> - release (v2.1.26 or later) in order to support HTTP Digest, - Negotiate, and NTLM authentication. Otherwise, only HTTP Basic - authentication will be available.</li> -</ul> - -<h3>Additional CalDAV / CardDAV Requirements</h3> -<ul> -<li>Must - have <a href="http://freeassociation.sourceforge.net/">libical</a> - installed.</li> -<li>Must have <a href="http://www.sqlite.org/">SQLite</a> v3.x (or - later) installed.</li> -<li>Optionally - install <a href="http://www.digip.org/jansson/">Jansson</a> for - jCal/jCard support.</li> -</ul> - -<!-- -<h3>Additional iSchedule Requirements</h3> -<ul> -<li>Must meet CalDAV requirements above.</li> -<li>Must - have <a href="http://www.opendkim.org/">OpenDKIM v2.x.x (or higher)</a> - installed.</li> -</ul> ---> - -<h3>Additional Timezone Service Requirements</h3> -<ul> -<li>Must - have <a href="http://freeassociation.sourceforge.net/">libical</a> - installed.</li> -<li>Must have <a href="http://www.digip.org/jansson/">Jansson</a> - installed.</li> -</ul> - -<h2 id="config">General Configuration</h2> - -<p>The Cyrus <tt>httpd</tt> service is configurable via several - options in <tt>imapd.conf</tt>. Several of those options are - discussed in the sections below. Admins should consult - the <tt>imapd.conf(5)</tt> manpage for the full list of options used - by the <tt>httpd</tt> service and its various modules.<p> - -<p>The support for RSS, CalDAV, and CardDAV is divided into separate - modules which run as part of the Cyrus <tt>httpd</tt> - service. Selection of which module(s) are enabled is - done by setting the <tt>httpmodules</tt> option accordingly. By - default, no modules are enabled.</p> - -<p>Cyrus <tt>httpd</tt> also can serve <i>static</i> content, the - location of which is set by the <tt>httpdocroot</tt> option. Any - content contained in the specified directory (including - sub-directories) will be served as static content only. - Cyrus <tt>httpd</tt> does NOT have the ability to execute any - server-side scripts.</p> - -<h3>HTTP Authentication</h3> - -<p>As with other Cyrus services, the Cyrus <tt>httpd</tt> service uses - Cyrus SASL to perform its authentication. Cyrus supports the - following HTTP authentication schemes: Basic, Digest, Negotiate - (Kerberos only), and NTLM. While Basic is available in all versions - of SASL, the remaining schemes are only available in Cyrus SASL - 2.1.16 (and higher).</p> - -<p>Similar to plaintext login commands supported by the other Cyrus - services (IMAP LOGIN, POP3 USER/PASS), the Cyrus <tt>httpd</tt> - service determines whether to advertise the HTTP Basic - authentication scheme based on the <tt>allowplaintext</tt> option - and whether the client has connected over a TLS protected connection - (HTTPS).</p> - -<p>The availability of the other HTTP authentication schemes is - controlled by the <tt>sasl_mech_list</tt> option. For - Cyrus <tt>httpd</tt> the <tt>DIGEST-MD5</tt>, <tt>GSS-SPNEGO</tt>, - and <tt>NTLM</tt> SASL plugins support the Digest, Negotiate, - and NTLM authentication schemes respectively, provided that these - plugins are installed on the server.</p> - -<h2>RSS Module</h2> - -<h3>Configuration</h3> - -<p>When enabled, the RSS module will default to serving ALL mailboxes - to which the authenticated user has access as RSS feeds. - The <tt>rss_feeds</tt> option can be used to limit the set of - mailboxes that can be served as RSS feeds. For example, - setting <tt>rss_feeds</tt> to <tt>*,!user</tt> will serve all shared - mailboxes, but no personal mailboxes.</p> - -<p>The list of available RSS feeds can be obtained by clients by - accessing the <tt>/rss/</tt> URL on the Cyrus server. By default, - the server will present the list as a simple unordered list in an - HTML document. To customize the look and feel of the feed list, - the <tt>rss_feedlist_template</tt> option can be used to point to a - HTML template file. This file can utilize Cascading Style Sheets, - JavaScript, etc. Any and all content that the template file - references MUST reside under the <tt>httpdocroot</tt> as set above. - Consult the <tt>imapd.conf(5)</tt> manpage for specifics on the - required contents of this custom file. Note that for sites - running Cyrus Murder, <tt>rss_feedlist_template</tt> only needs to - be set on frontend servers, since only those servers have the - complete mailbox list.</p> - -<h2>CalDAV Module</h2> - -<h3>Configuration</h3> - -<p>When enabled, the CalDAV module allows Cyrus to function as a - calendar and scheduling server. This module uses a subset of the - mailbox hierarchy as calendar collections, the toplevel of which is - specified by the <tt>calendarprefix</tt> option. The public - calendar hierarchy will reside at the toplevel of the shared mailbox - namespace. A user's personal calendar hierarchy will be a child of - their Inbox. For example, using the default value - for <tt>calendarprefix</tt>, a calendar named <tt>Default</tt> for - user <tt>murch</tt> would reside in the mailbox - named <tt>user.murch.#calendars.Default</tt>.<p> - -<p><i>Note that mailboxes in the calendar hierarchies (those - under <tt>calendarprefix</tt>) <b>SHOULD NOT</b> be accessed with an IMAP - client as doing so will leave a mailbox in a state unsuitable - for CalDAV. To this end, calendar mailboxes will not returned by - Cyrus <tt>imapd</tt> in response to an IMAP client's request for the - available mailbox list, but Cyrus <tt>imapd</tt> will not otherwise - prevent an IMAP client from accessing them.</i></p> - -<p>By default, the CalDAV module will automatically perform scheduling - operations when a scheduling object (invite or reply) is stored - on or deleted from the server. Support for the calendar-auto-schedule - feature can be disabled with the <tt>caldav_allowscheduling</tt> - option.</p> - - -<h3>Administration</h3> - -<h4>Calendar provisioning</h4> - -<p>The CalDAV module will automatically create the required calendars - for a user the first time that the user authenticates to the CalDAV - server. Note that the user MUST have an - existing <a href="install-admin-mb.html">IMAP Inbox</a> in order for - the calendars to be created.</p> - -<h4 id="ACLs">Calendar access controls</h4> - -<p>The CalDAV module uses the same access controls as the other Cyrus - services. The <tt>cyradm(1)</tt> tool can be used to adjust ACLs on - calendars as needed. The tables below show how the access controls - are used by the CalDAV module.</p> - -<br> -<table border> - <caption>Mapping of IMAP Rights to WebDAV Privileges & HTTP Methods</caption> - <tr> - <th>IMAP right</th> - <th>WebDAV privilege</th> - <th>HTTP methods</th> - </tr> - <tr> - <td>l - lookup</td> - <td rowspan=2>DAV:read - <br><i>(aggregates DAV:read-current-user-privilege-set, - <br>CALDAV:read-free-busy)</i></td> - <td rowspan=2>GET/HEAD, PROPFIND, REPORT, - <br>COPY/MOVE <i>(on target)</i></td> - </tr> - <tr> - <td>r - read</td> - </tr> - <tr> - <td>s - seen</td> - <td colspan=2/> - </tr> - <tr> - <td>w - write</td> - <td>DAV:write-properties</i></td> - <td>PROPPATCH, COPY/MOVE <i>(on target)</i></td> - </tr> - <tr> - <td>i - insert</td> - <td>DAV:write-content</td> - <td>PUT, LOCK, COPY/MOVE <i>(on target)</i></td> - </tr> - <tr> - <td>p - post</td> - <td>CYRUS:add-resource <i>(aggregated under DAV:bind)</i></td> - <td>POST</td> - </tr> - <tr> - <td>k - create mailbox</td> - <td>CYRUS:make-collection <i>(aggregated under DAV:bind)</i></td> - <td>MKCOL, MKCALENDAR</td> - </tr> - <tr> - <td>x - delete mailbox</td> - <td>CYRUS:remove-collection <i>(aggregated under DAV:unbind)</i></td> - <td>DELETE</td> - </tr> - <tr> - <td>t - delete message</td> - <td>CYRUS:remove-resource <i>(aggregated under DAV:unbind)</i></td> - <td>DELETE, MOVE <i>(on source)</i></td> - </tr> - <tr> - <td>e - expunge</td> - <td colspan=2/> - </tr> - <tr> - <td>a - admin</td> - <td>CYRUS:admin - <br><i>(aggregates DAV:read-acl, DAV:write-acl, DAV:unlock)</i></td> - <td>ACL, UNLOCK, PROPFIND <i>(DAV:acl only)</i></td> - </tr> - <tr> - <td rowspan=3>9 - free/busy</td> - <td>CALDAV:read-free-busy <i>(regular calendar collection only)</i></td> - <td>REPORT <i>(CALDAV:free-busy-query only)</i><td/> - </tr> - <tr> - <td>CALDAV:schedule-query-freebusy <i>(Scheduling Inbox only)</i></td> - <td rowspan=6/> - </tr> - <tr> - <td>CALDAV:schedule-send-freebusy <i>(Scheduling Outbox only)</i></td> - <tr> - <td rowspan=2>8 - invite</td> - <td>CALDAV:schedule-deliver-invite <i>(Scheduling Inbox only)</i></td> - </tr> - <tr> - <td>CALDAV:schedule-send-invite <i>(Scheduling Outbox only)</i></td> - </tr> - <tr> - <td rowspan=2>7 - reply</td> - <td>CALDAV:schedule-deliver-reply <i>(Scheduling Inbox only)</i></td> - </tr> - <tr> - <td>CALDAV:schedule-send-reply <i>(Scheduling Outbox only)</i></td> - </tr> -</table> -<br> - -<br> -<table border> - <caption>Default WebDAV Privileges by Collection</caption> - <tr> - <th>Collection</th> - <th>ACL</th> - </tr> - <tr> - <td>Regular Calendar</td> - <td>owner - DAV:all + CALDAV:read-free-busy (lrwipkxta9) - <br>anyone - CALDAV:read-free-busy (9)</td> - </tr> - <tr> - <td>Scheduling Inbox</td> - <td>owner - DAV:all + CALDAV:schedule-deliver (lrwipkxta789) - <br>anyone - CALDAV:schedule-deliver (789)</td> - </tr> - <tr> - <td>Scheduling Outbox</td> - <td>owner - DAV:all + CALDAV:schedule-send (lrwipkxta789)</td> - </tr> -</table> -<br> - - -<h3>Client Setup</h3> - -<h4>Mozilla Lightning</h4> - -<p>For each calendar that you would like to add to this client, - perform the following steps: - -<ol> -<li>Select the "File -> New -> Calendar..." menu option.</li> -<li>Select the "On the Network" option and click Continue.</li> -<li>Select "CalDAV" as the Format.</li> -<li>Enter a URL of the following form as - the Location: <tt>https://<servername>/dav/calendars/user/<userid>/<calendar>/</tt></li> -</ol> -</p> -<p>Cyrus will auto-provision a calendar with name "Default" which can - be used in the URL above.</p> - -<h4>Apple iCal</h4> - -<p>This client will autodetect all available calendars on a server. - To add a Cyrus server to this client, perform the following steps: - -<ol> -<li>Select the "Calendar -> Preferences" menu option.</li> -<li>Select the "Accounts" tab.</li> -<li>Click the "+" button.</li> -<li>Select "CalDAV" as the Account Type. -<li>Fill in User Name, Password, and Server Address accordingly.</li> -<li>Click Create.</li> -</ol> -</p> - -<h4>Apple iOS Calendar</h4> - -<p>This client will autodetect all available calendars on a server. - To add a Cyrus server to this client, perform the following steps: - -<ol> -<li>Run the "Settings" app.</li> -<li>Select the "Mail, Contacts, Calendars" menu.</li> -<li>Select the "Add Account..." menu.</li> -<li>Select the "Other" menu.</li> -<li>Select the "Add CalDAV Account" menu.</li> -<li>Fill in Server, User Name, Password, and Description accordingly.</li> -<li>Click Next.</li> -</ol> -</p> - -<h4>Evolution</h4> - -<p>This client will autodetect all available calendars on a server. - For each calendar that you would like to add to this client, - perform the following steps: - -<ol> -<li>Select the "New -> Calendar" menu option.</li> -<li>Select "CalDAV" as the Type.</li> -<li>Fill in Server and User accordingly.</li> -<li>Click "Find Calendars".</li> -<li>Select the desired calendar from the list.</li> -<li>Click "Apply".</li> -<li>Click "OK".</li> -</ol> -</p> - -<h4><a href="http://www.acal.me">aCal</a></h4> - -<p>This client will autodetect all available calendars on a server. - To add a Cyrus server to this client, perform the following steps: - -<ol> -<li>Press the Andoid "Menu" button.</li> -<li>Select "Settings".</li> -<li>Select "Servers".</li> -<li>Select "Add Server".</li> -<li>Select "Manual Configuration".</li> -<li>Fill in Username, Password, and User URL (servername) accordingly.</li> -<li>Press "Apply".</li> -</ol> -</p> - -<h4><a href="http://www.inf-it.com/open-source/clients/caldavzap/"> - CalDavZAP</a></h4> - -<p>This client will autodetect all available calendars on a server. - To configure this client for a Cyrus server, edit <tt>config.js</tt> as - follows: - -<ol> -<li>Set the <tt>href</tt> value in - the <tt>globalNetworkCheckSettings</tt> array to a URL of the following - form: <tt>https://<servername>/dav/principals/user/</tt> - <br>Note that the trailing "/" is REQUIRED.</li> -<li>Set the <tt>globalSettingsType</tt> option - to <tt>calendar-home-set</tt></li> -<li>Set any other options as desired - (e.g. <tt>globalDatepickerFirstDayOfWeek</tt>, <tt>globalTimeZone</tt>).</li> -</ol> -</p> - - -<h2>CardDAV Module</h2> - -<h3>Configuration</h3> - -<p>When enabled, the CardDAV module allows Cyrus to function as a - contacts server. This module uses a subset of the - mailbox hierarchy as addressbook collections, the toplevel of which is - specified by the <tt>addressbookprefix</tt> option. The public - addressbook hierarchy will reside at the toplevel of the shared mailbox - namespace. A user's personal addressbook hierarchy will be a child of - their Inbox. For example, using the default value - for <tt>addressbookprefix</tt>, an addressbook named <tt>Default</tt> for - user <tt>murch</tt> would reside in the mailbox - named <tt>user.murch.#addressbooks.Default</tt>.<p> - -<p><i>Note that mailboxes in the addressbook hierarchies (those - under <tt>addressbookprefix</tt>) <b>SHOULD NOT</b> be accessed with an IMAP - client as doing so will leave a mailbox in a state unsuitable - for CardDAV. To this end, addressbook mailboxes will not returned by - Cyrus <tt>imapd</tt> in response to an IMAP client's request for the - available mailbox list, but Cyrus <tt>imapd</tt> will not otherwise - prevent an IMAP client from accessing them.</i></p> - -<h3>Administration</h3> - -<h4>Addressbook provisioning</h4> - -<p>The CardDAV module will automatically create a default addressbook - for a user the first time that the user authenticates to the CardDAV - server. Note that the user MUST have an - existing <a href="install-admin-mb.html">IMAP Inbox</a> in order for - the addressbook to be created.</p> - -<h4>Addressbook access controls</h4> - -<p>Cyrus uses the same access controls for addressbooks as it does for - <a href="#ACLs">calendars</a>, except that the scheduling rights (7, - 8, 9) have no use with addressbooks and are ignored.</p> - -<h3>Client Setup</h3> - -<h4>Apple Contacts</h4> - -<p>This client will autodetect all available addressbooks on a server. - To add a Cyrus server to this client, perform the following steps: - -<ol> -<li>Select the "Contacts -> Preferences" menu option.</li> -<li>Select the "Accounts" tab.</li> -<li>Click the "+" button.</li> -<li>Select "CardDAV" as the Account Type. -<li>Fill in User Name, Password, and Server Address accordingly.</li> -<li>Click Create.</li> -</ol> -</p> - -<h4>Apple iOS Contacts</h4> - -<p>This client will autodetect all available addressbooks on a server. - To add a Cyrus server to this client, perform the following steps: - -<ol> -<li>Run the "Settings" app.</li> -<li>Select the "Mail, Contacts, Calendars" menu.</li> -<li>Select the "Add Account..." menu.</li> -<li>Select the "Other" menu.</li> -<li>Select the "Add CardDAV Account" menu.</li> -<li>Fill in Server, User Name, Password, and Description accordingly.</li> -<li>Click Next.</li> -</ol> -</p> - -<h4><a href="http://www.inf-it.com/open-source/clients/carddavmate/"> - CardDavMATE</a></h4> - -<p>This client will autodetect all available addressbooks on a server. - To configure this client for a Cyrus server, edit <tt>config.js</tt> as - follows: - -<ol> -<li>Set the <tt>href</tt> value in - the <tt>globalNetworkCheckSettings</tt> array to a URL of the following - form: <tt>https://<servername>/dav/principals/user/</tt> - <br>Note that the trailing "/" is REQUIRED.</li> -<li>Set the <tt>globalSettingsType</tt> option - to <tt>addressbook-home-set</tt></li> -<li>Set any other options as desired.</li> -</ol> -</p> - - -<h2>Timezone Service Module</h2> - -<h3>Configuration</h3> - -<p>When enabled, the Timezone module allows Cyrus to function as a - timezone service, providing timezone data to client systems. This - module stores timezone data in the <tt>zoneinfo/</tt> subdirectory of - the Cyrus configuration directory (as specified by - the <tt>configdir</tt> option). The data is indexed by a database - whose location is specified by the <tt>zoneinfo_db_path</tt> option, - using the format specified by the <tt>zoneinfo_db</tt> option.</p> - -<h3>Administration</h3> - -<p>This module is designed to use the <i>Time Zone Database</i> data - (a.k.a. <i>Olson Database</i>) converted to the iCalendar format. The - steps to populate the Cyrus <tt>zoneinfo/</tt> directory are as follows: - -<ol start=0> -<li>Build the <tt>vzic</tt> utility located in - the <tt>tools/vzic/</tt> subdirectory of the Cyrus source code. - Simply running <tt>make</tt> in the <tt>tools/vzic/</tt> - subdirectory should suffice.</li> -<li>Download the latest version of the Time Zone Database data - from <a href="http://www.iana.org/time-zones">IANA</a>. <i>Only the - data is required, NOT the code</i>.</li> -<li>Expand the downloaded timezone data into the temporary directory - of your choice.</li> -<li>Populate <tt>configdir/zoneinfo/</tt> with iCalendar data: - <p><i>Initial Install Only</i></p> - <ol type=a> - <li>Convert the raw data into iCalendar format by - running <tt>vzic</tt> as follows: - <p><tt>vzic --pure --olson-dir <location-of-raw-data> - --output-dir <configdir>/zoneinfo</tt></p> - <p>This will create and install iCalendar data directly into - the <tt>configdir/zoneinfo/</tt> directory.</p> - </li> - </ol> - <p><i>Updating Data Only</i></p> - <ol type=a> - <li>Convert the raw data into iCalendar format by - running <tt>vzic</tt> as follows: - <p><tt>vzic --pure --olson-dir <location-of-raw-data></tt></p> - <p>This will create a <tt>zoneinfo/</tt> subdirectory in your - current location (preferably <tt>tools/vzic/</tt>).</p> - </li> - <li>Merge new/updated iCalendar data into - the <tt>configdir/zoneinfo/</tt> directory by - running <tt>vzic-merge.pl</tt> in your current location: - <p><tt>vzic-merge.pl</tt></p> - </li> - </ol> -</li> -<li>Rebuild the Cyrus zoneinfo index by - running <tt>ctl_zoneinfo</tt> as follows: - <p><tt>ctl_zoneinfo -r <version-string></tt></p> - <p>where <tt><version-string></tt> describes the recently - downloaded timezone data (e.g. "Time Zone Database - v.2013h").</p></li> -<li>Verify that the zoneinfo index database and all iCalendar data - files/links are readable by the <tt>cyrus</tt> user.</li> -</ol> -</p> - - -<h2>iSchedule Module</h2> - -<p>This module will be automatically enabled if and only if both the - CalDAV module and the <tt>caldav_allowscheduling</tt> options are - enabled in a Cyrus Murder.</p> - -<p><i>Support for scheduling with external servers is currently under - development and will require a future release of OpenDKIM.</i></p> - -<!-- -<h3>Configuration</h3> - -<h3>Administration</h3> ---> - - -<h2>DomainKey Module</h2> - -<p><i>Currently unavailable. Will be available once iSchedule support to - external servers is available.</i></p> - -</body></html> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml"> -<head> -<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> -<title>Cyrus HTTP</title> -</head> -<body> - -<h1>Cyrus HTTP (RSS, CalDAV, CardDAV, iSchedule, DomainKey)</h1> - -<b><i>Note that the HTTP service and associated modules in Cyrus are - still under development. This release should be considered beta - quality.</i></b> - -<h2>Introduction</h2> - -<p>Cyrus <tt>http</tt> service has the ability to:</p> - -<ul> -<li>Serve IMAP mailboxes as RSS feeds.</li> -<li>Act as a calendar and scheduling (CalDAV) server by using IMAP - mailboxes as calendar collections and RFC 5322 messages to store - iCalendar data.</li> -<li>Act as a contacts (CardDAV) server by using IMAP mailboxes as - addressbook collections and RFC 5322 messages to store vCard - data.</li> -<li>Allow scheduling transactions between separate calendaring and - scheduling systems via the iSchedule protocol <i>(currently only used - within a Cyrus Murder)</i>.</li> -</ul> - -<i>Unlike the <a href="http://httpd.apache.org/">Apache HTTP - Server</a>, Cyrus HTTP is NOT a general purpose HTTP server. Its - feature set is limited to what is required to support the - facilities listed above.</i> - -<p>This document assumes that you are familiar with building and - configuring a Cyrus server. If you have not already done so, please - read and understand the rest of the <a href="install.html">installation</a> - documentation before continuing. Note: The - "<a href="#install">Installation</a>" section below augments the - "<a href="install-compile.html">Compiling the IMAP Server</a>" - document. The remaining sections assume that your Cyrus server has - already been - successfully <a href="install-configure.html">configured</a>.</p> - -<p>This document also assumes that you are familiar with RSS, WebDAV, - calendaring, and contacts.</p> - - -<h2 id="install">Installation</h2> - -<p>You will need to build Cyrus with - the <tt>--enable-http</tt> configure option. This builds httpd - and the associated modules and utilities based on the availability - of the prerequisites listed below.</p> - -<h3>General Requirements</h3> - -<ul> -<li>Must have <a href="http://xmlsoft.org/">libxml2</a> - installed.</li> -<li>Must have a recent SASL build (v2.1.26 or later) in order to - support HTTP Digest, Negotiate, and NTLM authentication. - Otherwise, only HTTP Basic authentication will be available</li> -</ul> - -<h3>CalDAV / CardDAV Requirements</h3> -<ul> -<li>Must - have <a href="http://freeassociation.sourceforge.net/">libical</a> - installed.</li> -<li>Must have <a href="http://www.sqlite.org/">SQLite</a> v3.x (or - later) installed.</li> -<li>Optionally - install <a href="https://github.com/jehiah/json-c">json-c</a> for - jCal/jCard support.</li> -</ul> - -<!-- -<h3>iSchedule Requirements</h3> -<ul> -<li>Must - have <a href="http://www.opendkim.org/">OpenDKIM v2.9.x (or higher)</a> - installed.</li> -</ul> ---> - -<h2 id="config">General Configuration</h2> - -<p>The Cyrus <tt>httpd</tt> service is configurable via several - options in <tt>imapd.conf</tt>. Several of those options are - discussed in the sections below. Admins should consult - the <tt>imapd.conf(5)</tt> manpage for the full list of options used - by the <tt>httpd</tt> service and its various modules.<p> - -<p>The support for RSS, CalDAV, and CardDAV is divided into separate - modules which run as part of the Cyrus <tt>httpd</tt> - service. Selection of which module(s) are enabled is - done by setting the <tt>httpmodules</tt> option accordingly. By - default, no modules are enabled.</p> - -<p>Cyrus <tt>httpd</tt> also can serve <i>static</i> content, the - location of which is set by the <tt>httpdocroot</tt> option. Any - content contained in the specified directory (including - sub-directories) will be served as static content only. - Cyrus <tt>httpd</tt> does NOT have the ability to execute any - server-side scripts.</p> - -<h3>HTTP Authentication</h3> - -<p>As with other Cyrus services, the Cyrus <tt>httpd</tt> service uses - Cyrus SASL to perform its authentication. Cyrus supports the - following HTTP authentication schemes: Basic, Digest, Negotiate - (Kerberos only), and NTLM. While Basic is available in all versions - of SASL, the remaining schemes are only available in Cyrus SASL - 2.1.16 (and higher).</p> - -<p>Similar to plaintext login commands supported by the other Cyrus - services (IMAP LOGIN, POP3 USER/PASS), the Cyrus <tt>httpd</tt> - service determines whether to advertise the HTTP Basic - authentication scheme based on the <tt>allowplaintext</tt> option - and whether the client has connected over a TLS protected connection - (HTTPS).</p> - -<p>The availability of the other HTTP authentication schemes is - controlled by the <tt>sasl_mech_list</tt> option. For - Cyrus <tt>httpd</tt> the <tt>DIGEST-MD5</tt>, <tt>GSS-SPNEGO</tt>, - and <tt>NTLM</tt> SASL plugins support the Digest, Negotiate, - and NTLM authentication schemes respectively, provided that these - plugins are installed on the server.</p> - -<h2>RSS Module</h2> - -<h3>Configuration</h3> - -<p>When enabled, the RSS module will default to serving ALL mailboxes - to which the authenticated user has access as RSS feeds. - The <tt>rss_feeds</tt> option can be used to limit the set of - mailboxes that can be served as RSS feeds. For example, - setting <tt>rss_feeds</tt> to <tt>*,!user</tt> will serve all shared - mailboxes, but no personal mailboxes.</p> - -<p>The list of available RSS feeds can be obtained by clients by - accessing the <tt>/rss/</tt> URL on the Cyrus server. By default, - the server will present the list as a simple unordered list in an - HTML document. To customize the look and feel of the feed list, - the <tt>rss_feedlist_template</tt> option can be used to point to a - HTML template file. This file can utilize Cascading Style Sheets, - JavaScript, etc. Any and all content that the template file - references MUST reside under the <tt>httpdocroot</tt> as set above. - Consult the <tt>imapd.conf(5)</tt> manpage for specifics on the - required contents of this custom file. Note that for sites - running Cyrus Murder, <tt>rss_feedlist_template</tt> only needs to - be set on frontend servers, since only those servers have the - complete mailbox list.</p> - -<h2>CalDAV Module</h2> - -<h3>Configuration</h3> - -<p>When enabled, the CalDAV module allows Cyrus to function as a - calendar and scheduling server. This module uses a subset of the - mailbox hierarchy as calendar collections, the toplevel of which is - specified by the <tt>calendarprefix</tt> option. The public - calendar hierarchy will reside at the toplevel of the shared mailbox - namespace. A user's personal calendar hierarchy will be a child of - their Inbox. For example, using the default value - for <tt>calendarprefix</tt>, a calendar named <tt>Default</tt> for - user <tt>murch</tt> would reside in the mailbox - named <tt>user.murch.#calendars.Default</tt>.<p> - -<p><i>Note that mailboxes in the calendar hierarchies (those - under <tt>calendarprefix</tt>) <b>SHOULD NOT</b> be accessed with an IMAP - client as doing so will leave a mailbox in a state unsuitable - for CalDAV. To this end, calendar mailboxes will not returned by - Cyrus <tt>imapd</tt> in response to an IMAP client's request for the - available mailbox list, but Cyrus <tt>imapd</tt> will not otherwise - prevent an IMAP client from accessing them.</i></p> - -<p>By default, the CalDAV module will automatically perform scheduling - operations when a scheduling object (invite or reply) is stored - on or deleted from the server. Support for the calendar-auto-schedule - feature can be disabled with the <tt>caldav_allowscheduling</tt> - option.</p> - - -<h3>Administration</h3> - -<h4>Calendar provisioning</h4> - -<p>The CalDAV module will automatically create the required calendars - for a user the first time that the user authenticates to the CalDAV - server. Note that the user MUST have an - existing <a href="install-admin-mb.html">IMAP Inbox</a> in order for - the calendars to be created.</p> - -<h4 id="ACLs">Calendar access controls</h4> - -<p>The CalDAV module uses the same access controls as the other Cyrus - services. The <tt>cyradm(1)</tt> tool can be used to adjust ACLs on - calendars as needed. The tables below show how the access controls - are used by the CalDAV module.</p> - -<br> -<table border> - <caption>Mapping of IMAP Rights to WebDAV Privileges & HTTP Methods</caption> - <tr> - <th>IMAP right</th> - <th>WebDAV privilege</th> - <th>HTTP methods</th> - </tr> - <tr> - <td>l - lookup</td> - <td rowspan=2>DAV:read - <br><i>(aggregates DAV:read-current-user-privilege-set, - <br>CALDAV:read-free-busy)</i></td> - <td rowspan=2>GET/HEAD, PROPFIND, REPORT, - <br>COPY/MOVE <i>(on target)</i></td> - </tr> - <tr> - <td>r - read</td> - </tr> - <tr> - <td>s - seen</td> - <td colspan=2/> - </tr> - <tr> - <td>w - write</td> - <td>DAV:write-properties</i></td> - <td>PROPPATCH, COPY/MOVE <i>(on target)</i></td> - </tr> - <tr> - <td>i - insert</td> - <td>DAV:write-content</td> - <td>PUT, LOCK, COPY/MOVE <i>(on target)</i></td> - </tr> - <tr> - <td>p - post</td> - <td>CYRUS:add-resource <i>(aggregated under DAV:bind)</i></td> - <td>POST</td> - </tr> - <tr> - <td>k - create mailbox</td> - <td>CYRUS:make-collection <i>(aggregated under DAV:bind)</i></td> - <td>MKCOL, MKCALENDAR</td> - </tr> - <tr> - <td>x - delete mailbox</td> - <td>CYRUS:remove-collection <i>(aggregated under DAV:unbind)</i></td> - <td>DELETE</td> - </tr> - <tr> - <td>t - delete message</td> - <td>CYRUS:remove-resource <i>(aggregated under DAV:unbind)</i></td> - <td>DELETE, MOVE <i>(on source)</i></td> - </tr> - <tr> - <td>e - expunge</td> - <td colspan=2/> - </tr> - <tr> - <td>a - admin</td> - <td>CYRUS:admin - <br><i>(aggregates DAV:read-acl, DAV:write-acl, DAV:unlock)</i></td> - <td>ACL, UNLOCK, PROPFIND <i>(DAV:acl only)</i></td> - </tr> - <tr> - <td rowspan=3>9 - free/busy</td> - <td>CALDAV:read-free-busy <i>(regular calendar collection only)</i></td> - <td>REPORT <i>(CALDAV:free-busy-query only)</i><td/> - </tr> - <tr> - <td>CALDAV:schedule-query-freebusy <i>(Scheduling Inbox only)</i></td> - <td rowspan=6/> - </tr> - <tr> - <td>CALDAV:schedule-send-freebusy <i>(Scheduling Outbox only)</i></td> - <tr> - <td rowspan=2>8 - invite</td> - <td>CALDAV:schedule-deliver-invite <i>(Scheduling Inbox only)</i></td> - </tr> - <tr> - <td>CALDAV:schedule-send-invite <i>(Scheduling Outbox only)</i></td> - </tr> - <tr> - <td rowspan=2>7 - reply</td> - <td>CALDAV:schedule-deliver-reply <i>(Scheduling Inbox only)</i></td> - </tr> - <tr> - <td>CALDAV:schedule-send-reply <i>(Scheduling Outbox only)</i></td> - </tr> -</table> -<br> - -<br> -<table border> - <caption>Default WebDAV Privileges by Collection</caption> - <tr> - <th>Collection</th> - <th>ACL</th> - </tr> - <tr> - <td>Regular Calendar</td> - <td>owner - DAV:all + CALDAV:read-free-busy (lrwipkxta9) - <br>anyone - CALDAV:read-free-busy (9)</td> - </tr> - <tr> - <td>Scheduling Inbox</td> - <td>owner - DAV:all + CALDAV:schedule-deliver (lrwipkxta789) - <br>anyone - CALDAV:schedule-deliver (789)</td> - </tr> - <tr> - <td>Scheduling Outbox</td> - <td>owner - DAV:all + CALDAV:schedule-send (lrwipkxta789)</td> - </tr> -</table> -<br> - - -<h3>Client Setup</h3> - -<h4>Mozilla Lightning</h4> - -<p>For each calendar that you would like to add to this client, - perform the following steps: - -<ol> -<li>Select the "File -> New -> Calendar..." menu option.</li> -<li>Select the "On the Network" option and click Continue.</li> -<li>Select "CalDAV" as the Format.</li> -<li>Enter a URL of the following form as - the Location: <tt>https://<servername>/dav/calendars/user/<userid>/<calendar>/</tt></li> -</ol> -</p> -<p>Cyrus will auto-provision a calendar with name "Default" which can - be used in the URL above.</p> - -<h4>Apple iCal</h4> - -<p>This client will autodetect all available calendars on a server. - To add a Cyrus server to this client, perform the following steps: - -<ol> -<li>Select the "Calendar -> Preferences" menu option.</li> -<li>Select the "Accounts" tab.</li> -<li>Click the "+" button.</li> -<li>Select "CalDAV" as the Account Type. -<li>Fill in User Name, Password, and Server Address accordingly.</li> -<li>Click Create.</li> -</ol> -</p> - -<h4>Apple iOS Calendar</h4> - -<p>This client will autodetect all available calendars on a server. - To add a Cyrus server to this client, perform the following steps: - -<ol> -<li>Run the "Settings" app.</li> -<li>Select the "Mail, Contacts, Calendars" menu.</li> -<li>Select the "Add Account..." menu.</li> -<li>Select the "Other" menu.</li> -<li>Select the "Add CalDAV Account" menu.</li> -<li>Fill in Server, User Name, Password, and Description accordingly.</li> -<li>Click Next.</li> -</ol> -</p> - -<h4>Evolution</h4> - -<p>This client will autodetect all available calendars on a server. - For each calendar that you would like to add to this client, - perform the following steps: - -<ol> -<li>Select the "New -> Calendar" menu option.</li> -<li>Select "CalDAV" as the Type.</li> -<li>Fill in Server and User accordingly.</li> -<li>Click "Find Calendars".</li> -<li>Select the desired calendar from the list.</li> -<li>Click "Apply".</li> -<li>Click "OK".</li> -</ol> -</p> - -<h4><a href="http://www.acal.me">aCal</a></h4> - -<p>This client will autodetect all available calendars on a server. - To add a Cyrus server to this client, perform the following steps: - -<ol> -<li>Press the Andoid "Menu" button.</li> -<li>Select "Settings".</li> -<li>Select "Servers".</li> -<li>Select "Add Server".</li> -<li>Select "Manual Configuration".</li> -<li>Fill in Username, Password, and User URL (servername) accordingly.</li> -<li>Press "Apply".</li> -</ol> -</p> - -<h4><a href="http://www.inf-it.com/open-source/clients/caldavzap/"> - CalDavZAP</a></h4> - -<p>This client will autodetect all available calendars on a server. - To configure this client for a Cyrus server, edit <tt>config.js</tt> as - follows: - -<ol> -<li>Set the <tt>href</tt> value in - the <tt>globalNetworkCheckSettings</tt> array to a URL of the following - form: <tt>https://<servername>/dav/principals/user/</tt> - <br>Note that the trailing "/" is REQUIRED.</li> -<li>Set the <tt>globalSettingsType</tt> option - to <tt>calendar-home-set</tt></li> -<li>Set any other options as desired - (e.g. <tt>globalDatepickerFirstDayOfWeek</tt>, <tt>globalTimeZone</tt>).</li> -</ol> -</p> - - -<h2>CardDAV Module</h2> - -<h3>Configuration</h3> - -<p>When enabled, the CardDAV module allows Cyrus to function as a - contacts server. This module uses a subset of the - mailbox hierarchy as addressbook collections, the toplevel of which is - specified by the <tt>addressbookprefix</tt> option. The public - addressbook hierarchy will reside at the toplevel of the shared mailbox - namespace. A user's personal addressbook hierarchy will be a child of - their Inbox. For example, using the default value - for <tt>addressbookprefix</tt>, an addressbook named <tt>Default</tt> for - user <tt>murch</tt> would reside in the mailbox - named <tt>user.murch.#addressbooks.Default</tt>.<p> - -<p><i>Note that mailboxes in the addressbook hierarchies (those - under <tt>addressbookprefix</tt>) <b>SHOULD NOT</b> be accessed with an IMAP - client as doing so will leave a mailbox in a state unsuitable - for CardDAV. To this end, addressbook mailboxes will not returned by - Cyrus <tt>imapd</tt> in response to an IMAP client's request for the - available mailbox list, but Cyrus <tt>imapd</tt> will not otherwise - prevent an IMAP client from accessing them.</i></p> - -<h3>Administration</h3> - -<h4>Addressbook provisioning</h4> - -<p>The CardDAV module will automatically create a default addressbook - for a user the first time that the user authenticates to the CardDAV - server. Note that the user MUST have an - existing <a href="install-admin-mb.html">IMAP Inbox</a> in order for - the addressbook to be created.</p> - -<h4>Addressbook access controls</h4> - -<p>Cyrus uses the same access controls for addressbooks as it does for - <a href="#ACLs">calendars</a>, except that the scheduling rights (7, - 8, 9) have no use with addressbooks and are ignored.</p> - -<h3>Client Setup</h3> - -<h4>Apple Contacts</h4> - -<p>This client will autodetect all available addressbooks on a server. - To add a Cyrus server to this client, perform the following steps: - -<ol> -<li>Select the "Contacts -> Preferences" menu option.</li> -<li>Select the "Accounts" tab.</li> -<li>Click the "+" button.</li> -<li>Select "CardDAV" as the Account Type. -<li>Fill in User Name, Password, and Server Address accordingly.</li> -<li>Click Create.</li> -</ol> -</p> - -<h4>Apple iOS Contacts</h4> - -<p>This client will autodetect all available addressbooks on a server. - To add a Cyrus server to this client, perform the following steps: - -<ol> -<li>Run the "Settings" app.</li> -<li>Select the "Mail, Contacts, Calendars" menu.</li> -<li>Select the "Add Account..." menu.</li> -<li>Select the "Other" menu.</li> -<li>Select the "Add CardDAV Account" menu.</li> -<li>Fill in Server, User Name, Password, and Description accordingly.</li> -<li>Click Next.</li> -</ol> -</p> - -<h4><a href="http://www.inf-it.com/open-source/clients/carddavmate/"> - CardDavMATE</a></h4> - -<p>This client will autodetect all available addressbooks on a server. - To configure this client for a Cyrus server, edit <tt>config.js</tt> as - follows: - -<ol> -<li>Set the <tt>href</tt> value in - the <tt>globalNetworkCheckSettings</tt> array to a URL of the following - form: <tt>https://<servername>/dav/principals/user/</tt> - <br>Note that the trailing "/" is REQUIRED.</li> -<li>Set the <tt>globalSettingsType</tt> option - to <tt>addressbook-home-set</tt></li> -<li>Set any other options as desired.</li> -</ol> -</p> - - -<h2>iSchedule Module</h2> - -<p>This module will be automatically enabled if and only if both the - CalDAV module and the <tt>caldav_allowscheduling</tt> options are - enabled in a Cyrus Murder.</p> - -<p><i>Support for scheduling with external servers is currently under - development and will require a future release of OpenDKIM.</i></p> - -<!-- -<h3>Configuration</h3> - -<h3>Administration</h3> ---> - -<h2>DomainKey Module</h2> - -<p><i>Currently unavailable. Will be available once iSchedule support to - external servers is available.</i></p> - -</body></html>
View file
cyrus-imapd-2.5.tar.gz/doc/install-upgrade.html
Changed
@@ -10,12 +10,9 @@ <h1>Upgrading From Previous Versions</h1> -<h2>Upgrading from 2.4.17-caldav-beta6 or earlier</h2> +<h2>Upgrading from 2.4 to 2.5</h2> <ul> -<li>The time span calculations for various VCALENDAR components, - especially VTODO (tasks), have been fixed. We recommend ALL sites - run the <tt>dav_reconstruct</tt> utility for each of their CalDAV - users.</li> +<li>The default port for Sieve was changed from 2000 to 4190. </ul> <h2>Upgrading from 2.4.12</h2>
View file
cyrus-imapd-2.5.tar.gz/doc/install.html
Changed
@@ -37,8 +37,6 @@ <li><a href="install-sieve.html">Installing SIEVE</a> <li><a href="install-snmpmon.html">SNMP Monitoring</a> <li><a href="install-netnews.html">Cyrus and Netnews</a> -<li><a href="install-http.html">Cyrus HTTP (RSS, CalDAV, CardDAV, - Timezone Service)</a> <li><a href="install-virtdomains.html">Virtual Domains</a> <li><a href="install-replication.html">Cyrus Replication</a> <li><a href="install-murder.html">Cyrus Murder: The IMAP @@ -62,5 +60,7 @@ </ul> <P><HR> +last modified: $Date: 2006/11/30 17:11:16 $ +<br> <A HREF="index.html">Return</A> to the Cyrus IMAP Server Home Page </BODY></HTML>
View file
cyrus-imapd-2.5.tar.gz/doc/internal/caldav_scheduling_flowchart.html
Deleted
@@ -1,337 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" - "http://www.w3.org/TR/html4/strict.dtd"> -<html> -<head> - <title>Cyrus CalDAV Scheduling Flowchart</title> - <meta http-equiv="content-type" content="text/html; charset=UTF-8"> -</head> -<body> - <h1>Cyrus CalDAV Scheduling Flowchart</h1> - - <h3 id="caldav_put">caldav_put() - create/modify via HTTP PUT on a - resource or POST (add-member) on a calendar</h3> - <ol> - <li>Check if the new resource is a scheduling resource (contains - ORGANIZER property). If not, skip to step 4.</li> - <li>Check for (and load) any existing resource.</li> - <li>Check if the authenticated user matches ORGANIZER. If yes, - goto <a href="#sched_request">sched_request()</a>, otherwise - goto <a href="#sched_reply">sched_reply()</a>.</li> - <li>Store the new/modified resource.</li> - </ol> - - <h3 id="caldav_delete_sched">caldav_delete_sched() - remove via HTTP - DELETE on a resource</h3> - <ol> - <li>Check if the existing resource is a scheduling resource (has - Schedule-Tag). If not, we are done.</li> - <li>Load the existing resource.</li> - <li>Check if the authenticated user matches ORGANIZER. If yes, - goto <a href="#sched_request">sched_request()</a>, otherwise - goto <a href="#sched_reply">sched_reply()</a>.</li> - </ol> - - <h3 id="caldav_post">caldav_post() - busytime query via HTTP POST on - Scheduling Outbox</h3> - <ol> - <li>Check the ACL on the owner's Scheduling Outbox. If the - authenticated user doesn't have the DACL_SCHEDFB right, fail.</li> - <li><a href="#sched_busytime_query">sched_busytime_query()</a>.</li> - </ol> - - <hr> - - <h3 id="sched_request">sched_request() - perform an organizer - request / attendee status update</h3> - <ol> - <li>Check the ACL on the owner's Scheduling Outbox. If the - authenticated user doesn't have the DACL_INVITE right, fail.</li> - <li>If the request includes a resource, then set METHOD:REQUEST, - otherwise set METHOD:CANCEL.</li> - <li>Create an iTIP message template, copying over any - CALSCALE property and VTIMEZONE components.</li> - <li>If not an attendee status update and the existing resource is a - scheduling resource: - Foreach component in the existing resource, add it and - its SEQUENCE to our hash table keyed by RECURRENCE-ID (for - comparison against new/modified resource).</li> - <li>Create a hash table of attendees. This will hold - attendee-specific iTIP messages.</li> - <li>Foreach component in the new/modified resource:</li> - <ol type=a> - <li>Lookup (by RECURRENCE-ID) and remove the component from the - hash table of existing components.</li> - <li>If the component exists compare all of DTSTART, DTEND, - DURATION, RRULE, RDATE, EXDATE to those of the new - component.</li> - <li>If the component is new or changed, - then <a href="#process_attendees">process_attendees()</a>.</li> - </ol> - <li>Foreach remaining component in the hash table of existing - components do <a href="#sched_cancel">sched_cancel()</a>.</li> - <li>Foreach iTIP message in our hash table of - ATTENDEES, <a href="#sched_deliver">sched_deliver()</a> the iTIP - message.</li> - <li>Foreach component in the new/modified resource update the - SCHEDULE-STATUS of each ATTENDEE.</li> - </ol> - - <h3 id="process_attendees">process_attendees() - create a suitable - iTIP request message for each attendee</h3> - <ol> - <li>Foreach ATTENDEE in the component, remove the SCHEDULE-STATUS - parameter, and set PROPSTAT=NEEDS-ACTION if required.</li> - <li>Make a copy of the component and - <a href="#clean_component">clean_component()</a>.</li> - <li>Foreach ATTENDEE in the cleaned component:</li> - <ol type=a> - <li>Check the CalDAV Scheduling parameters. If SCHEDULE-AGENT - != SERVER, skip to the next attendee.</li> - <li>Lookup attendee in our hash table.</li> - <li>If it doesn't exist, create a clone of our iTIP template and - insert it into our hash table of attendees.</li> - <li>Add the component to the attendee's iTIP message.</li> - <li>Add the component “number” to our mask of new components - appearing in the attendee's iTIP message.</li> - </ol> - <li>If the component is not the "master", foreach attendee do - <a href="#sched_exclude">sched_exclude()</a>.</li> - </ol> - - <h3 id="sched_exclude">sched_exclude() - exclude an attendee from a - recurrence instance</h3> - <ol> - <li>If the component did not appear in the attendee's iTIP - message, add an EXDATE property (based on the RECURRENCE-ID of - the component) to the master component of the attendee's iTIP - message.</li> - </ol> - - <h3 id="sched_cancel">sched_cancel() - cancel an organizer event/task</h3> - <ol> - <li>Set STATUS:CANCELLED on the component.</li> - <li><a href="#process_attendees">process_attendees()</a>.</li> - </ol> - - <hr> - - <h3 id="sched_reply">sched_reply() - perform an attendee reply</h3> - <ol> - <li>Check the CalDAV Scheduling parameters on ORGANIZER. If - SCHEDULE-AGENT != SERVER, we are done.</li> - <li>Check the ACL on the owner's Scheduling Outbox. If the - authenticated user doesn't have the DACL_REPLY right, fail.</li> - <li>Create a new iTIP (METHOD:REPLY) message, copying over any - CALSCALE property and VTIMEZONE components.</li> - <li>Foreach component in the existing resource:</li> - <ol type=a> - <li><a href="#trim_attendees">trim_attendees()</a>.</li> - <li>Add the trimmed component and the attendee's PARTSTAT to our - hash table keyed by RECURRENCE-ID (for comparison against - new/modified resource).</li> - </ol> - <li>Foreach component in the new/modified resource:</li> - <ol type=a> - <li><a href="#trim_attendees">trim_attendees()</a>.</li> - <li>Lookup (by RECURRENCE-ID) and remove the component from the - hash table of existing components.</li> - <li>If the component exists, compare the PARTSTAT of the - ATTENDEE to that of the new component.</li> - <li>If the component is new or the PARTSTAT has changed:</li> - <ol type=i> - <li><a href="#clean_component">clean_component()</a>.</li> - <li>Add the component to our iTIP message.</li> - <li>Add the component “number” to our mask of new components - appearing in our iTIP message.</li> - </ol> - </ol> - <li>Foreach remaining component in the hash table of existing - components do <a href="#sched_decline">sched_decline()</a>.</li> - <li><a href="#sched_deliver">sched_deliver()</a> our iTIP - message.</li> - <li>Foreach component in the new/modified resource that appeared - in our iTIP message, update the SCHEDULE-STATUS of the ORGANIZER.</li> - </ol> - - <h3 id="trim_attendees">trim_attendees() - remove all attendees - other than the one replying</h3> - <ol> - <li>Clone the component and remove all ATTENDEE properties other - than the one corresponding to the owner of the calendar.</li> - <li>Return the ATTENDEE property of owner, his/her PARTSTAT - parameter, and the RECURRENCE-ID of the component.</li> - </ol> - - <h3 id="sched_decline">sched_decline() - decline a recurrence - instance for an attendee</h3> - <ol> - <li>Set PARTSTAT of ATTENDEE to DECLINED.</li> - <li><a href="#clean_component">clean_component()</a>.</li> - <li>Add the component to our iTIP message.</li> - </ol> - - <hr> - - <h3 id="clean_component">clean_component() - sanitize a component - for use in an iTIP message</h3> - <ol> - <li>Update DTSTAMP.</li> - <li>Remove any VALARM components.</li> - <li>For a reply/decline only, remove scheduling parameters from - ORGANIZER.</li> - </ol> - - <h3 id="sched_deliver">sched_deliver() - deliver an iTIP message to - a recipient</h3> - <ol> - <li>Lookup the recipient.</li> - <li>If local to our server goto - <a href="#sched_deliver_local">sched_deliver_local()</a>, - otherwise goto - <a href="#sched_deliver_remote">sched_deliver_remote()</a>.</li> - </ol> - - <hr> - - <h3 id="sched_deliver_local">sched_deliver_local() - deliver an - iTIP message to a local user</h3> - <ol> - <li>Check the ACL on the owner's Scheduling Inbox. If the - sender doesn't have the proper right (DACL_INVITE for - request/cancel, DACL_REPLY for reply), fail.</li> - <li>Search the recipient's calendars for a resource having the - specified UID.</li> - <li>If the resource doesn't exist:</li> - <ol type=a> - <li>If the iTIP method is REPLY, fail (we are done).</li> - <li>If the iTIP method is CANCEL, ignore it (we are done).</li> - <li>Otherwise, create a new (empty) attendee object and target - the recipient's Default calendar.</li> - </ol> - <li>Otherwise, load the existing resource.</li> - <li>Update the new/existing resource:</li> - <ol type=a> - <li>If the iTIP method is CANCEL, set STATUS:CANCELLED on all - existing components.</li> - <li>If the iTIP method is REPLY, do - <a href="#deliver_merge_reply">deliver_merge_reply()</a>.</li> - <li>If the iTIP method is REQUEST, do - <a href="#deliver_merge_request">deliver_merge_request()</a>.</li> - </ol> - <li>Store the new/updated resource in the recipient's target - calendar.</li> - <li>Record the delivery status (SCHEDULE-STATUS).</li> - <li>If the iTIP message is something other than just a PARTSTAT - update from an attendee, store the iTIP message as a new - resource in the recipient's Inbox.</li> - <li>If the iTIP method is REPLY, send an update other attendees - via <a href="#sched_request">sched_request()</a>.</li> - </ol> - - <h3 id="deliver_merge_reply">deliver_merge_reply() - update - an organizer resource with an attendee reply</h3> - <ol> - <li>Foreach component in the existing resource, add it to our - hash table keyed by RECURRENCE-ID (for comparison against - iTIP message).</li> - <li>Foreach component in the iTIP message:</li> - <ol type=a> - <li>Lookup (by RECURRENCE-ID) the component from the - hash table of existing components.</li> - <li>If the component doesn't exist (new recurrence overridden by - ATTENDEE) create a new recurring component:</li> - <ol type=i> - <li>Clone the existing master component.</li> - <li>Remove the RRULE property.</li> - <li>Add the RECURRENCE-ID from the iTIP message.</li> - <li>Replace the DTSTART, DTEND, SEQUENCE properties with those - from the iTIP message.</li> - <li>Add the new component to our existing resource.</li> - </ol> - <li>Get the sending ATTENDEE from the iTIP message.</li> - <li>Find the matching ATTENDEE in the existing component.</li> - <li>If not found (ATTENDEE added themselves to this recurrence), - add new ATTENDEE to the component.</li> - <li>Set the ATTENDEE PARTSTAT, RSVP, and SCHEDULE-STATUS - parameters in the existing component.</li> - </ol> - <li>Return the sending ATTENDEE.</li> - </ol> - - <h3 id="deliver_merge_request">deliver_merge_request() - - create/update an attendee resource with an organizer request</h3> - <ol> - <li>Foreach VTIMEZONE component in the existing resource, add it - to our hash table keyed by TZID (for comparison against iTIP - message).</li> - <li>Foreach VTIMEZONE component in the iTIP message:</li> - <ol type=a> - <li>Lookup (by TZID) the VTIMEZONE component from the hash table of - existing components.</li> - <li>If the component exists, remove it from the existing - object.</li> - <li>Add the VTIMEZONE from the iTIP message to our existing - object.</li> - </ol> - <li>Foreach component in the existing resource, add it to our - hash table keyed by RECURRENCE-ID (for comparison against - iTIP message).</li> - <li>Foreach component in the iTIP message:</li> - <ol type=a> - <li>Clone a new component from the iTIP component.</li> - <li>Lookup (by RECURRENCE-ID) the component from the - hash table of existing components.</li> - <li>If the component exists:</li> - <ol type=i> - <li>Compare the SEQUENCE of the new component to the existing - component to see if it has changed.</li> - <li>Copy any COMPLETED, PERCENT-COMPLETE, or TRANSP properties - from the existing component to the new component.</li> - <li>Copy any ORGANIZER SCHEDULE-STATUS parameter - from the existing component to the new component.</li> - <li>Remove the existing component from the existing object.</li> - </ol> - <li>Add the new component to the existing object.</li> - </ol> - </ol> - - <hr> - - <h3 id="sched_deliver_remote">sched_deliver_remote() - deliver an - iTIP message to a remote user</h3> - <ol> - <li>If the recipient is local to our Murder, goto - <a href="#isched_send">isched_send()</a>, otherwise - goto <a href="#imip_send">imip_send()</a>.</li> - <li>Retrieve status of iTIP message delivery.</li> - </ol> - - <h3 id="isched_send">isched_send() - deliver an iTIP message to a - remote user via iSchedule (HTTP)</h3> - <ol> - </ol> - - <h3 id="imip_send">imip_send() - deliver an iTIP message to a - remote user via iMIP (SMTP)</h3> - <ol> - </ol> - - <hr> - - <h3 id="sched_busytime_query">sched_busytime_query() - perform a - busytime query</h3> - <ol> - </ol> - - <h3 id="busytime_query_local">busytime_query_local() - perform a - busytime query on a local user</h3> - <ol> - </ol> - - <h3 id="busytime_query_remote">busytime_query_remote() - perform a - busytime query on a remote user</h3> - <ol> - </ol> - -</body> -</html>
View file
cyrus-imapd-2.5.tar.gz/doc/internal/database-formats.html
Changed
@@ -158,21 +158,6 @@ Data: <Version>TAB<Deny List (comma-separated wildmat patterns)>TAB<Deny Message> </pre> -<h2>Timezone Info (zoneinfo.db)</h2> - -<p>This database is used for the timezone service and contains records - relating to timezones and their aliases. The database is indexed by - timezone ID and each data record contains the database version - number, a record type, a timestamp, and an optional list of strings - (either aliases for a timezone or the reference timezone for an - alias). The format of each record is as follows:</p> - -<pre> -Key: <TZID> - -Data: <Version>SP<Record Type>SP<Timestamp>SP<Data Strings (TAB-separated)> -</pre> - <h2>Seen State (<userid>.seen)</h2> <p>This database is a per-user database and maintains the list of @@ -191,7 +176,7 @@ <h2>Subscriptions (<userid>.sub)</h2> -<p>This database is per-user and contains the list of +<p>This database is a per-user database and contains the list of mailboxes to which the user has subscribed. The database is indexed by mailbox name and each data record contains no data. The format of each record is follows:</p> @@ -205,7 +190,7 @@ <h2>Mailbox Keys (<userid>.mboxkey)</h2> -<p>This database is per-user and contains the list of +<p>This database is a per-user database and contains the list of mailbox access keys which are used for generating URLAUTH-authorized URLs. The database is indexed by mailbox name and each data record contains the database version number and the associated access key. @@ -217,76 +202,5 @@ Data: <Version (2 bytes)><Access Key (multi-byte)> </pre> - -<h2>DAV Index (<userid>.dav)</h2> - -<p>This SQLite database is per-user and primarily maintains a - mapping from DAV resource names (URLs) to the corresponding Cyrus - mailboxes and IMAP message UIDs. The database is designed to have - one table per resource type (iCalendar, vCard, etc) with each table - containing metadata specific to that resource type.</p> - -<h3>CalDAV</h3> - -<p>The format of the iCalendar table used by CalDAV is as follows:</p> - -<pre> -CREATE TABLE ical_objs ( - rowid INTEGER PRIMARY KEY, - creationdate INTEGER, - mailbox TEXT NOT NULL, - resource TEXT NOT NULL, - imap_uid INTEGER, - lock_token TEXT, - lock_owner TEXT, - lock_ownerid TEXT, - lock_expire INTEGER, - comp_type INTEGER, - ical_uid TEXT, - organizer TEXT, - dtstart TEXT, - dtend TEXT, - recurring INTEGER, - transp INTEGER, - sched_tag TEXT, - UNIQUE( mailbox, resource ) -); -</pre> - -<p>Because CalDAV Scheduling requires the server to locate a resource - by iCalendar UID regardless of which calendar collection (mailbox) - it resides in, the iCalendar table has an additional index as follows:</p> - -<pre> -CREATE INDEX idx_ical_uid ON ical_objs ( ical_uid ); -</pre> - -<h3>CardDAV</h3> - -<p>The format of the vCard table used by CardDAV is as follows (work - in progress):</p> - -<pre> -CREATE TABLE vcard_objs ( - rowid INTEGER PRIMARY KEY, - creationdate INTEGER, - mailbox TEXT NOT NULL, - resource TEXT NOT NULL, - imap_uid INTEGER, - lock_token TEXT, - lock_owner TEXT, - lock_ownerid TEXT, - lock_expire INTEGER, - version INTEGER, - vcard_uid TEXT, - kind INTEGER, - fullname TEXT, - name TEXT, - nickname TEXT, - email TEXT, - UNIQUE( mailbox, resource ) -); -</pre> - </body> </html>
View file
cyrus-imapd-2.5.tar.gz/doc/man.html
Changed
@@ -47,7 +47,6 @@ <LI><A HREF="man/deliver.8.html"><TT>deliver</TT>(8)</A> <LI><A HREF="man/fetchnews.8.html"><TT>fetchnews</TT>(8)</A> <LI><A HREF="man/fud.8.html"><TT>fud</TT>(8)</A> -<LI><A HREF="man/httpd.8.html"><TT>httpd</TT>(8)</A> <LI><A HREF="man/idled.8.html"><TT>idled</TT>(8)</A> <LI><A HREF="man/imapd.8.html"><TT>imapd</TT>(8)</A> <LI><A HREF="man/ipurge.8.html"><TT>ipurge</TT>(8)</A>
View file
cyrus-imapd-2.5.tar.gz/doc/specs.html
Changed
@@ -211,67 +211,7 @@ draft-ietf-sieve-managesieve</A></TD> <TD>A Protocol for Remotely Managing Sieve Scripts</TD></TR> -<TR><TD COLSPAN=2><br><h2>HTTP</h2></TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc2616">RFC 2616</A></TD> -<TD>Hypertext Transfer Protocol -- HTTP/1.1 -<BR><I>being revised by</I> -<A HREF="http://tools.ietf.org/id/draft-ietf-httpbis"> -draft-ietf-httpbis-*</A></TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc2617">RFC 2617</A></TD> -<TD>HTTP Authentication: Basic and Digest Access Authentication</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc4559">RFC 4559</A></TD> -<TD>SPNEGO-based Kerberos and NTLM HTTP Authentication in Microsoft Windows</TD></TR> -<TR><TD><A HREF="http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/MS-NTHT.pdf">MS-NTHT</A></TD> -<TD>NTLM Over HTTP Protocol Specification</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc2817">RFC 2817</A></TD> -<TD>HTTP Upgrading to TLS Within HTTP/1.1</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc2818">RFC 2818</A></TD> -<TD>HTTP Over TLS</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc6797">RFC 6797</A></TD> -<TD>HTTP Strict Transport Security (HSTS)</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc4918">RFC 4918</A></TD> -<TD>HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc3253">RFC 3253</A></TD> -<TD>Versioning Extensions to WebDAV (Web Distributed Authoring and Versioning)</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc3744">RFC 3744</A></TD> -<TD>Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc4331">RFC 4331</A></TD> -<TD>Quota and Size Properties for Distributed Authoring and Versioning (DAV) Collections</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc5397">RFC 5397</A></TD> -<TD>WebDAV Current Principal Extension</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc5689">RFC 5689</A></TD> -<TD>Extended MKCOL for Web Distributed Authoring and Versioning (WebDAV)</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc5995">RFC 5995</A></TD> -<TD>Using POST to Add Members to Web Distributed Authoring and Versioning (WebDAV) Collections</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc6578">RFC 6578</A></TD> -<TD>Collection Synchronization for Web Distributed Authoring and Versioning (WebDAV)</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc4791">RFC 4791</A></TD> -<TD>Calendaring Extensions to WebDAV (CalDAV)</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc6638">RFC 6638</a></TD> -<TD>Scheduling Extensions to CalDAV</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc6352">RFC 6352</a></TD> -<TD>CardDAV: vCard Extensions to Web Distributed Authoring and Versioning (WebDAV)</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc6764">RFC 6764</A></TD> -<TD>Locating Services for Calendaring Extensions to WebDAV (CalDAV) and vCard Extensions to WebDAV (CardDAV)</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/draft-desruisseaux-ischedule">draft-desruisseaux-ischedule</A></TD> -<TD>Internet Calendar Scheduling Protocol (iSchedule)</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/draft-douglass-timezone-service">draft-douglass-timezone-service</A></TD> -<TD>Timezone Service Protocol</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/draft-thomson-hybi-http-timeout">draft-thomson-hybi-http-timeout</A></TD> -<TD>Hypertext Transfer Protocol (HTTP) Keep-Alive Header</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded">draft-ietf-appsawg-http-forwarded</A></TD> -<TD>Forwarded HTTP Extension</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/draft-snell-http-prefer">draft-snell-http-prefer</A></TD> -<TD>Prefer Header for HTTP</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/draft-murchison-webdav-prefer">draft-murchison-webdav-prefer</A></TD> -<TD>Use of the Prefer Header Field in Web Distributed Authoring and Versioning (WebDAV)</TD></TR> -<TR><TD><A HREF="http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-ctag.txt">caldav-ctag</A></TD> -<TD>Calendar Collection Entity Tag (CTag) in CalDAV</TD></TR> -<TR><TD><A HREF="http://msdn.microsoft.com/en-us/library/aa142743%28v=exchg.65%29.aspx">Brief Header</A></TD><TD>Microsoft 'Brief' header extension</TD></TR> - <TR><TD COLSPAN=2><br><h2>Other</h2></TD></TR> -<TR><TD><A HREF="http://www.ietf.org/rfc/rfc3656.txt">RFC 3656</a></TD> -<TD>MUPDATE Protocol (For Cyrus Murder)</TD></TR> <TR><TD><A HREF="http://www.ietf.org/rfc/rfc5322.txt">RFC 5322</A></TD> <TD>Internet Message Format</TD></TR> <TR><TD><A HREF="http://www.ietf.org/rfc/rfc5536.txt">RFC 5536</A></TD> @@ -282,18 +222,4 @@ <TD>MUPDATE Protocol (For Cyrus Murder)</TD></TR> <TR><TD><A HREF="http://www.ietf.org/rfc/rfc5423.txt">RFC 5423</A></TD> <TD>Internet Message Store Events</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc4287">RFC 4287</a></TD> -<TD>The Atom Syndication Format</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc5545">RFC 5545</a></TD> -<TD>Internet Calendaring and Scheduling Core Object Specification (iCalendar) -</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc5546">RFC 5546</a></TD> -<TD>iCalendar Transport-Independent Interoperability Protocol (iTIP) -</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc6047">RFC 6047</a></TD> -<TD>iCalendar Message-Based Interoperability Protocol (iMIP)</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc6350">RFC 6350</a></TD> -<TD>vCard Format Specification</TD></TR> -<TR><TD><A HREF="http://tools.ietf.org/html/rfc6376">RFC 6376</a></TD> -<TD>DomainKeys Identified Mail (DKIM) Signatures</TD></TR> </TABLE>
View file
cyrus-imapd-2.5.tar.gz/imap/Makefile.in
Deleted
@@ -1,407 +0,0 @@ -# Makefile for cyrus imap server and associated programs -# -# Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# -# 3. The name "Carnegie Mellon University" must not be used to -# endorse or promote products derived from this software without -# prior written permission. For permission or any legal -# details, please contact -# Carnegie Mellon University -# Center for Technology Transfer and Enterprise Creation -# 4615 Forbes Avenue -# Suite 302 -# Pittsburgh, PA 15213 -# (412) 268-7393, fax: (412) 268-7395 -# innovation@andrew.cmu.edu -# -# 4. Redistributions of any form whatsoever must retain the following -# acknowledgment: -# "This product includes software developed by Computing Services -# at Carnegie Mellon University (http://www.cmu.edu/computing/)." -# -# CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO -# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE -# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN -# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING -# OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -DEFINES=-DSETPROCTITLE - -# \Seen state database. Either 'db' (berkeley db) or -# 'local' (legacy flat file). -SEEN=seen_db.o - -srcdir = @srcdir@ -top_srcdir = @top_srcdir@ -VPATH = @srcdir@ - -CC = @CC@ -INSTALL = @INSTALL@ -RANLIB = @RANLIB@ -AWK = @AWK@ - -CYRUS_USER=@cyrus_user@ -CYRUS_GROUP=@cyrus_group@ - -DEFS = @DEFS@ @LOCALDEFS@ -CPPFLAGS = -I.. -I$(srcdir)/../lib @COM_ERR_CPPFLAGS@ @SIEVE_CPPFLAGS@ @CPPFLAGS@ @SASLFLAGS@ @XML2_CFLAGS@ @ICAL_CFLAGS@ @SQLITE3_CFLAGS@ @DKIM_CFLAGS@ -IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@ - -RSS_OBJS = http_rss.o -DAV_OBJS = http_dav.o dav_db.o -CALDAV_OBJS = http_caldav.o caldav_db.o smtpclient.o xcal.o jcal.o -CARDDAV_OBJS = http_carddav.o carddav_db.o -ISCHED_OBJS = http_ischedule.o -TIMEZONE_OBJS = http_timezone.o zoneinfo_db.o -HTTP_OBJS = httpd.o http_err.o http_proxy.o @HTTP_OBJS@ -HTTP_LIBS = @XML2_LIBS@ @ICAL_LIBS@ @SQLITE3_LIBS@ @DKIM_LIBS@ @JSON_LIBS@ - -SIEVE_OBJS = @SIEVE_OBJS@ -SIEVE_LIBS = @SIEVE_LIBS@ -IMAP_COM_ERR_LIBS = @IMAP_COM_ERR_LIBS@ -LIB_WRAP = @LIB_WRAP@ -LIBS = $(IMAP_LIBS) $(IMAP_COM_ERR_LIBS) -DEPLIBS = ../lib/libcyrus.a ../lib/libcyrus_min.a @DEPLIBS@ - -CFLAGS = @CFLAGS@ -LDFLAGS = @LDFLAGS@ @COM_ERR_LDFLAGS@ - -SHELL = /bin/sh -MAKEDEPEND = @MAKEDEPEND@ - -COMPILE_ET=@COMPILE_ET@ - -# -# Some notes on purify -- -# you probably want to run the make as the cyrus user as -# purify sets the cache directory based on the user. So, -# if you don't, purify can't find the instrumented libraries -# and so you don't get any useful information. -# It may also help to run purify by hand to instrument any of -# the dynamic libraries that may crop up during run time. -# -PURIFY=/usr/local/bin/purify -PUREOPT= -best-effort -logfile=/tmp/pure/%v.%p.log -always_use_cache_dir -cachedir=/usr/tmp/$(USER) -QUANTIFY=/usr/local/bin/quantify -QUANTOPT=-windows=no -filename-prefix=/tmp/quant/%v.%p -write-summary-file= -logfile=/tmp/quant/%v.%p.log - -prefix = @prefix@ -exec_prefix = @exec_prefix@ -cyrus_prefix = @cyrus_prefix@ -service_path = @service_path@ - -LOBJS= append.o mailbox.o mboxlist.o mupdate-client.o mboxname.o message.o \ - global.o imap_err.o mupdate_err.o proc.o setproctitle.o \ - convert_code.o duplicate.o saslclient.o saslserver.o ../lib/signals.o \ - annotate.o search_engines.o squat.o squat_internal.o mbdump.o \ - imapparse.o telemetry.o user.o notify.o idle.o quota_db.o \ - sync_log.o $(SEEN) mboxkey.o backend.o tls.o message_guid.o \ - statuscache_db.o userdeny_db.o sequence.o upgrade_index.o \ - dlist.o version.o dav_util.o - -IMAPDOBJS=pushstats.o imapd.o proxy.o imap_proxy.o index.o - -LMTPOBJS=lmtpstats.o lmtpengine.o spool.o - -# Your typical objects for the command line utilities -CLIOBJS=cli_fatal.o mutex_fake.o - -SERVICE=../master/service.o -SERVICETHREAD=../master/service-thread.o - -PROGS = imapd lmtpd pop3d \ - fud smmapd reconstruct quota mbpath ipurge cyr_dbtool cyr_synclog \ - cyrdump chk_cyrus cvt_cyrusdb deliver ctl_mboxlist \ - ctl_deliver ctl_cyrusdb squatter mbexamine cyr_expire arbitron \ - unexpunge cyr_df cyr_sequence cyr_userseen \ - @IMAP_PROGS@ - -BUILTSOURCES = imap_err.c imap_err.h pushstats.c pushstats.h \ - lmtpstats.c lmtpstats.h mupdate_err.c mupdate_err.h \ - nntp_err.c nntp_err.h http_err.c http_err.h - -all: $(BUILTSOURCES) $(PROGS) $(SUIDPROGS) - -pure: imapd.pure lmtpd.pure mupdate.pure - - -install: - $(srcdir)/../install-sh -d ${DESTDIR}$(service_path) - for file in $(PROGS); \ - do \ - $(INSTALL) -m 755 $$file $(DESTDIR)$(service_path) || exit 1; \ - done - ln -f $(DESTDIR)$(service_path)/pop3d $(DESTDIR)$(service_path)/pop3proxyd - ln -f $(DESTDIR)$(service_path)/imapd $(DESTDIR)$(service_path)/proxyd - ln -f $(DESTDIR)$(service_path)/lmtpd $(DESTDIR)$(service_path)/lmtpproxyd - -.c.o: - $(CC) -c $(CPPFLAGS) $(DEFS) $(CFLAGS) \ - $< - -### libimap - -libimap.a: $(LOBJS) - rm -f libimap.a - ar cr libimap.a $(LOBJS) - $(RANLIB) libimap.a - -### Built Source Files - -version.o: ../xversion.h - -pushstats.c: pushstats.snmp $(srcdir)/../snmp/snmpgen - $(srcdir)/../snmp/snmpgen $(srcdir)/pushstats.snmp - -pushstats.h: pushstats.c - -lmtpstats.c: lmtpstats.snmp $(srcdir)/../snmp/snmpgen - $(srcdir)/../snmp/snmpgen $(srcdir)/lmtpstats.snmp - -lmtpstats.h: lmtpstats.c - -imap_err.c: imap_err.et - $(COMPILE_ET) $(srcdir)/imap_err.et - -imap_err.h: imap_err.c - -nntp_err.c: nntp_err.et - $(COMPILE_ET) $(srcdir)/nntp_err.et - -nntp_err.h: nntp_err.c - -mupdate_err.c: mupdate_err.et - $(COMPILE_ET) $(srcdir)/mupdate_err.et - -mupdate_err.h: mupdate_err.c - -http_err.c: http_err.et - $(COMPILE_ET) $(srcdir)/http_err.et - -http_err.h: http_err.c - -### Services -idled: idled.o mutex_fake.o libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o idled \ - idled.o mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) - -lmtpd: lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) mutex_fake.o \ - libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(SERVICE) - $(CC) $(LDFLAGS) -o lmtpd \ - $(SERVICE) lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) \ - mutex_fake.o libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) - -lmtpd.pure: lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) \ - mutex_fake.o libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(SERVICE) - $(PURIFY) $(PUREOPT) $(CC) $(LDFLAGS) -o lmtpd.pure \ - $(SERVICE) lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) \ - mutex_fake.o libimap.a $(SIEVE_LIBS) $(DEPLIBS) $(LIBS) $(LIB_WRAP) - -imapd: $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE) - $(CC) $(LDFLAGS) -o imapd \ - $(SERVICE) $(IMAPDOBJS) mutex_fake.o \ - libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) - -imapd.pure: $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE) - $(PURIFY) $(PUREOPT) $(CC) $(LDFLAGS) -o imapd.pure \ - $(SERVICE) $(IMAPDOBJS) mutex_fake.o libimap.a \ - $(DEPLIBS) $(LIBS) $(LIB_WRAP) - -imapd.quant: $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE) - $(QUANTIFY) $(QUANTOPT) $(CC) $(LDFLAGS) -o imapd.quant \ - $(SERVICE) $(IMAPDOBJS) mutex_fake.o libimap.a \ - $(DEPLIBS) $(LIBS) $(LIB_WRAP) - -mupdate: mupdate.o mupdate-slave.o mupdate-client.o mutex_pthread.o tls.o \ - libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o mupdate \ - $(SERVICETHREAD) mupdate.o mupdate-slave.o mupdate-client.o \ - mutex_pthread.o tls.o libimap.a \ - $(DEPLIBS) $(LIBS) $(LIB_WRAP) -lpthread - -mupdate.pure: mupdate.o mupdate-slave.o mupdate-client.o mutex_pthread.o \ - libimap.a $(DEPLIBS) - $(PURIFY) $(PUREOPT) $(CC) $(LDFLAGS) -o mupdate.pure \ - $(SERVICETHREAD) mupdate.o mupdate-slave.o mupdate-client.o \ - mutex_pthread.o libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) -lpthread - -pop3d: pop3d.o proxy.o backend.o tls.o mutex_fake.o libimap.a \ - $(DEPLIBS) $(SERVICE) - $(CC) $(LDFLAGS) -o pop3d pop3d.o proxy.o backend.o tls.o $(SERVICE) \ - mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) - -nntpd: nntpd.o proxy.o backend.o index.o smtpclient.o spool.o tls.o \ - mutex_fake.o nntp_err.o libimap.a $(DEPLIBS) $(SERVICE) - $(CC) $(LDFLAGS) -o nntpd nntpd.o proxy.o backend.o index.o spool.o \ - smtpclient.o tls.o $(SERVICE) mutex_fake.o nntp_err.o \ - libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) - -fud: fud.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) - $(CC) $(LDFLAGS) -o fud $(SERVICE) fud.o mutex_fake.o libimap.a \ - $(DEPLIBS) $(LIBS) $(LIB_WRAP) - -smmapd: smmapd.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) - $(CC) $(LDFLAGS) -o smmapd $(SERVICE) smmapd.o mutex_fake.o libimap.a \ - $(DEPLIBS) $(LIBS) $(LIB_WRAP) - -sync_server: sync_server.o sync_support.o \ - imapparse.o tls.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE) - $(CC) $(LDFLAGS) -o \ - sync_server sync_server.o sync_support.o \ - imapparse.o tls.o $(SERVICE) libimap.a mutex_fake.o \ - $(DEPLIBS) $(LIBS) $(LIB_WRAP) - -httpd: $(HTTP_OBJS) proxy.o backend.o index.o spool.o tls.o mutex_fake.o \ - libimap.a $(DEPLIBS) $(SERVICE) - $(CC) $(LDFLAGS) -o httpd $(HTTP_OBJS) \ - proxy.o backend.o index.o spool.o tls.o mutex_fake.o libimap.a \ - $(SERVICE) $(DEPLIBS) $(LIBS) $(LIB_WRAP) $(HTTP_LIBS) - -### Command Line Utilities -arbitron: arbitron.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o arbitron arbitron.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -cyr_userseen: cyr_userseen.o mutex_fake.o libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o cyr_userseen cyr_userseen.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -cyr_sequence: cyr_sequence.o mutex_fake.o libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o cyr_sequence cyr_sequence.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -cyr_dbtool: cyr_dbtool.o mutex_fake.o libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o cyr_dbtool cyr_dbtool.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -cyr_synclog: cyr_synclog.o mutex_fake.o libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o cyr_synclog cyr_synclog.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -cvt_cyrusdb: cvt_cyrusdb.o mutex_fake.o libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o cvt_cyrusdb cvt_cyrusdb.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -chk_cyrus: chk_cyrus.o mutex_fake.o libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o chk_cyrus chk_cyrus.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -deliver: deliver.o $(LMTPOBJS) proxy.o mutex_fake.o libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o deliver deliver.o $(LMTPOBJS) proxy.o \ - mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) - -ctl_deliver: ctl_deliver.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - $@ ctl_deliver.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) - -ctl_mboxlist: ctl_mboxlist.o mupdate-client.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o $@ ctl_mboxlist.o mupdate-client.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -ctl_cyrusdb: ctl_cyrusdb.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - $@ ctl_cyrusdb.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) - -cyr_expire: cyr_expire.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o $@ cyr_expire.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -fetchnews: fetchnews.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - $@ fetchnews.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) - -squatter: squatter.o index.o squat_build.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o squatter squatter.o index.o squat_build.o \ - $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) - -mbpath: mbpath.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o mbpath mbpath.o $(CLIOBJS) libimap.a \ - $(DEPLIBS) $(LIBS) - -ipurge: ipurge.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o ipurge ipurge.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -cyr_virusscan: cyr_virusscan.o index.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o cyr_virusscan cyr_virusscan.o index.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) -lclamav - -cyrdump: cyrdump.o index.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o cyrdump cyrdump.o index.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -cyr_df: cyr_df.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - cyr_df cyr_df.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) - -mbexamine: mbexamine.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - mbexamine mbexamine.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) - -reconstruct: reconstruct.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - reconstruct reconstruct.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) - -dav_reconstruct: dav_reconstruct.o caldav_db.o dav_db.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - $@ dav_reconstruct.o caldav_db.o dav_db.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) $(HTTP_LIBS) - -ctl_zoneinfo: ctl_zoneinfo.o zoneinfo_db.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - $@ ctl_zoneinfo.o zoneinfo_db.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) $(HTTP_LIBS) - -quota: quota.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o quota quota.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -tls_prune: tls_prune.o tls.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - $@ tls_prune.o tls.o $(CLIOBJS) libimap.a $(DEPLIBS) $(LIBS) - -unexpunge: unexpunge.o $(CLIOBJS) libimap.a $(DEPLIBS) - $(CC) $(LDFLAGS) -o $@ unexpunge.o $(CLIOBJS) \ - libimap.a $(DEPLIBS) $(LIBS) - -sync_client: sync_client.o sync_support.o \ - backend.o tls.o imapparse.o libimap.a mutex_fake.o $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - sync_client sync_client.o sync_support.o \ - backend.o tls.o imapparse.o libimap.a mutex_fake.o $(DEPLIBS) $(LIBS) - -sync_reset: sync_reset.o sync_support.o \ - libimap.a mutex_fake.o $(DEPLIBS) - $(CC) $(LDFLAGS) -o \ - sync_reset sync_reset.o sync_support.o \ - libimap.a mutex_fake.o $(DEPLIBS) $(LIBS) - -### Other Misc Targets - -clean: - rm -f *.o *.a Makefile.bak makedepend.log \ - $(BUILTSOURCES) $(PROGS) $(SUIDPROGS) cyr_virusscan - -distclean: clean - rm -f Makefile - -depend: imap_err.h - ${MAKEDEPEND} $(CPPFLAGS) $(DEFS) $(CFLAGS) *.c $(srcdir)/*.c 1>makedepend.log 2>&1 - -# DO NOT DELETE THIS LINE -- make depend depends on it.
View file
cyrus-imapd-2.5.tar.gz/imap/annotate.c
Changed
@@ -811,45 +811,56 @@ const char **entryp, const char **useridp) { - static struct buf keybuf; +#define NFIELDS 3 + const char *fieldsNFIELDS; + int nfields = 0; const char *p; - const char *end; - - buf_setmap(&keybuf, key, keysize); - buf_putc(&keybuf, '\0'); /* safety tricks due to broken FM code */ - p = buf_cstring(&keybuf); - end = p + keysize; - + unsigned int uid = 0; + const char *mboxname = ""; + + /* paranoia: ensure the last character in the key is + * a NUL, which it should be because of the way we + * always build keys */ + if (keykeysize-1) + return IMAP_ANNOTATION_BADENTRY; + keysize--; /* * paranoia: split the key into fields on NUL characters. * We would use strarray_nsplit() for this, except that * by design that function cannot split on NULs and does * not handle embedded NULs. */ + fieldsnfields++ = key; + for (p = key ; (p-key) < keysize ; p++) { + if (!*p) { + if (nfields == NFIELDS) + return IMAP_ANNOTATION_BADENTRY; + fieldsnfields++ = p+1; + } + } + if (nfields != NFIELDS) + return IMAP_ANNOTATION_BADENTRY; if (d->mboxname) { - *mboxnamep = d->mboxname; - *uidp = 0; - while (*p && p < end) *uidp = (10 * (*uidp)) + (*p++ - '0'); - if (p < end) p++; - else return IMAP_ANNOTATION_BADENTRY; + /* per-folder db for message scope annotations */ + char *end = NULL; + uid = strtoul(fields0, &end, 10); + if (uid == 0 || end == NULL || *end) + return IMAP_ANNOTATION_BADENTRY; + mboxname = d->mboxname; } else { /* global db for mailnbox & server scope annotations */ - *uidp = 0; - *mboxnamep = p; - while (*p && p < end) p++; - if (p < end) p++; - else return IMAP_ANNOTATION_BADENTRY; + uid = 0; + mboxname = fields0; } - *entryp = p; /* XXX: trailing NULLs on non-userid keys? Bogus just at FM */ - while (*p && p < end) p++; - if (p < end && !*p) - *useridp = p+1; - else - *useridp = NULL; + if (mboxnamep) *mboxnamep = mboxname; + if (uidp) *uidp = uid; + if (entryp) *entryp = fields1; + if (useridp) *useridp = fields2; return 0; +#undef NFIELDS } #if DEBUG @@ -938,28 +949,26 @@ struct find_rock *frock = (struct find_rock *) rock; const char *mboxname, *entry, *userid; unsigned int uid; - char newkeyMAX_MAILBOX_NAME+1; - size_t newkeylen; struct buf value = BUF_INITIALIZER; + char keycopyMAX_MAILBOX_PATH+1; int r; assert(keylen < MAX_MAILBOX_PATH); + /* take a copy, we may be deleting this record, so the key + * pointer will no longer be valid */ + memcpy(keycopy, key, keylen); + #if DEBUG syslog(LOG_ERR, "find_cb: found key %s in %s", - key_as_string(frock->d, key, keylen), frock->d->filename); + key_as_string(frock->d, keycopy, keylen), frock->d->filename); #endif - r = split_key(frock->d, key, keylen, &mboxname, + r = split_key(frock->d, keycopy, keylen, &mboxname, &uid, &entry, &userid); if (r) return r; - newkeylen = make_key(mboxname, uid, entry, userid, newkey, sizeof(newkey)); - if (keylen != newkeylen || strncmp(newkey, key, keylen)) { - syslog(LOG_ERR, "find_cb: bogus key %s %d %s %s (%d %d)", mboxname, uid, entry, userid, (int)keylen, (int)newkeylen); - } - r = split_attribs(data, datalen, &value); if (!r) r = frock->proc(mboxname, uid, entry, userid, &value, frock->rock); @@ -1564,26 +1573,6 @@ buf_free(&value); } -static void annotation_get_usermodseq(annotate_state_t *state, - struct annotate_entry_list *entry) -{ - struct buf value = BUF_INITIALIZER; - modseq_t modseq; - char *mboxname = NULL; - - assert(state); - assert(state->userid); - - mboxname = mboxname_user_mbox(state->userid, NULL); - modseq = mboxname_readmodseq(mboxname); - - buf_printf(&value, "%llu", modseq); - - output_entryatt(state, entry->name, state->userid, &value); - free(mboxname); - buf_free(&value); -} - static void annotation_get_uniqueid(annotate_state_t *state, struct annotate_entry_list *entry) { @@ -1943,17 +1932,6 @@ annotation_set_tofile, (void *)"motd" },{ - /* The "usemodseq" was added with conversations support, to allow - * a single value to show any changes to anything about a user */ - "/vendor/cmu/cyrus-imapd/usermodseq", - ATTRIB_TYPE_UINT, - BACKEND_ONLY, - ATTRIB_VALUE_PRIV, - 0, - annotation_get_usermodseq, - /*set*/NULL, - NULL - },{ "/vendor/cmu/cyrus-imapd/expire", ATTRIB_TYPE_UINT, PROXY_AND_BACKEND, @@ -2225,19 +2203,13 @@ /************************** Annotation Storing *****************************/ EXPORTED int annotatemore_lookup(const char *mboxname, const char *entry, - const char *userid, struct buf *value) + const char *userid, struct buf *value) { return annotatemore_msg_lookup(mboxname, /*uid*/0, entry, userid, value); } -EXPORTED int annotatemore_lookupmask(const char *mboxname, const char *entry, - const char *userid, struct buf *value) -{ - return annotatemore_msg_lookupmask(mboxname, /*uid*/0, entry, userid, value); -} - EXPORTED int annotatemore_msg_lookup(const char *mboxname, uint32_t uid, const char *entry, - const char *userid, struct buf *value) + const char *userid, struct buf *value) { char keyMAX_MAILBOX_PATH+1; size_t keylen, datalen; @@ -2269,20 +2241,6 @@ return r; } -EXPORTED int annotatemore_msg_lookupmask(const char *mboxname, uint32_t uid, const char *entry, - const char *userid, struct buf *value) -{ - int r; - value->len = 0; /* just in case! */ - /* only if the user isn't the owner, we look for a masking value */ - if (!mboxname_userownsmailbox(userid, mboxname)) - r = annotatemore_msg_lookup(mboxname, uid, entry, userid, value); - /* and if there isn't one, we fall through to the shared value */ - if (value->len == 0) - r = annotatemore_msg_lookup(mboxname, uid, entry, NULL, value); - return r; -} - static int read_old_value(annotate_db_t *d, const char *key, int keylen, struct buf *valp) @@ -2404,66 +2362,6 @@ return r; } -EXPORTED int annotatemore_rawwrite(const char *mboxname, const char *entry, - const char *userid, const struct buf *value) -{ - char keyMAX_MAILBOX_PATH+1; - int keylen, r; - annotate_db_t *d = NULL; - uint32_t uid = 0; - - r = _annotate_getdb(mboxname, uid, CYRUSDB_CREATE, &d); - if (r) goto done; - - /* must be in a transaction to modify the db */ - annotate_begin(d); - - keylen = make_key(mboxname, uid, entry, userid, key, sizeof(key)); - - if (value->s == NULL) { - do { - r = cyrusdb_delete(d->db, key, keylen, tid(d), /*force*/1); - } while (r == CYRUSDB_AGAIN); - } - else { - struct buf data = BUF_INITIALIZER; - unsigned long l; - static const char contenttype = "text/plain"; /* fake */ - - l = htonl(value->len); - buf_appendmap(&data, (const char *)&l, sizeof(l)); - - buf_appendmap(&data, value->s, value->len); - buf_putc(&data, '\0'); - - /* - * Older versions of Cyrus expected content-type and - * modifiedsince fields after the value. We don't support those - * but we write out default values just in case the database - * needs to be read by older versions of Cyrus - */ - buf_appendcstr(&data, contenttype); - buf_putc(&data, '\0'); - - l = 0; /* fake modifiedsince */ - buf_appendmap(&data, (const char *)&l, sizeof(l)); - - do { - r = cyrusdb_store(d->db, key, keylen, data.s, data.len, tid(d)); - } while (r == CYRUSDB_AGAIN); - - buf_free(&data); - } - - if (r) goto done; - r = annotate_commit(d); - -done: - annotate_putdb(&d); - - return r; -} - EXPORTED int annotatemore_write(const char *mboxname, const char *entry, const char *userid, const struct buf *value) { @@ -2475,24 +2373,15 @@ { struct mailbox *mailbox = NULL; int r = 0; - annotate_db_t *d = NULL; - r = _annotate_getdb(mboxname, uid, CYRUSDB_CREATE, &d); - if (r) goto done; - - if (mboxname) { + if (mboxname) r = mailbox_open_iwl(mboxname, &mailbox); - if (r) goto done; - } - r = write_entry(mailbox, uid, entry, userid, value, /*ignorequota*/1); - if (r) goto done; + if (!r) + r = write_entry(mailbox, uid, entry, userid, value, /*ignorequota*/1); - r = annotate_commit(d); - -done: - annotate_putdb(&d); - mailbox_close(&mailbox); + if (mailbox) + mailbox_close(&mailbox); return r; } @@ -2506,23 +2395,11 @@ entry, userid, value, /*ignorequota*/1); } -EXPORTED int annotate_state_writemask(annotate_state_t *state, - const char *entry, - const char *userid, - const struct buf *value) -{ - /* if the user is the owner, then write to the shared namespace */ - if (mboxname_userownsmailbox(userid, state->mailbox->name)) - return annotate_state_write(state, entry, NULL, value); - else - return annotate_state_write(state, entry, userid, value); -} - static int annotate_canon_value(struct buf *value, int type) { char *p = NULL; - unsigned long uwhatever = 0; - long whatever = 0; + unsigned long uwhatever; + long whatever; /* check for NIL */ if (value->s == NULL) @@ -3058,7 +2935,7 @@ const char *newuserid = userid; if (rrock->olduserid && rrock->newuserid && - !strcmpsafe(rrock->olduserid, userid)) { + !strcmp(rrock->olduserid, userid)) { /* renaming a user, so change the userid for priv annots */ newuserid = rrock->newuserid; }
View file
cyrus-imapd-2.5.tar.gz/imap/annotate.h
Changed
@@ -157,23 +157,14 @@ const char *userid, const struct buf *value); int annotatemore_msg_write(const char *mboxname, uint32_t uid, const char *entry, const char *userid, const struct buf *value); -/* flat out ignore modseq and quota and everything */ -int annotatemore_rawwrite(const char *mboxname, const char *entry, - const char *userid, const struct buf *value); /* lookup a single annotation and return result */ int annotatemore_lookup(const char *mboxname, const char *entry, const char *userid, struct buf *value); -/* same but check shared if per-user doesn't exist */ -int annotatemore_lookupmask(const char *mboxname, const char *entry, - const char *userid, struct buf *value); /* lookup a single per-message annotation and return result */ int annotatemore_msg_lookup(const char *mboxname, uint32_t uid, const char *entry, const char *userid, struct buf *value); -/* same but check shared if per-user doesn't exist */ -int annotatemore_msg_lookupmask(const char *mboxname, uint32_t uid, const char *entry, - const char *userid, struct buf *value); /* store annotations. Requires an open transaction */ int annotate_state_store(annotate_state_t *state, struct entryattlist *l); @@ -182,9 +173,6 @@ * Requires an open transaction. */ int annotate_state_write(annotate_state_t *, const char *entry, const char *userid, const struct buf *value); -/* same but write to shared if the user owns the mailbox */ -int annotate_state_writemask(annotate_state_t *, const char *entry, - const char *userid, const struct buf *value); /* rename the annotations for 'oldmboxname' to 'newmboxname' * if 'olduserid' is non-NULL then the private annotations
View file
cyrus-imapd-2.5.tar.gz/imap/append.c
Changed
@@ -62,8 +62,6 @@ #include "message.h" #include "append.h" #include "global.h" -#include "prot.h" -#include "sync_log.h" #include "xmalloc.h" #include "xstrlcpy.h" #include "xstrlcat.h" @@ -76,7 +74,6 @@ #include "annotate.h" #include "message_guid.h" #include "strarray.h" -#include "conversations.h" struct stagemsg { char fname1024; @@ -205,6 +202,11 @@ as->auth_state = auth_state; as->isadmin = isadmin; + /* make sure we can open the cache file, so we + * abort early otherwise */ + r = mailbox_ensure_cache(mailbox, 0); + if (r) return r; + /* initialize seen list creator */ as->internalseen = mailbox_internal_seen(mailbox, as->userid); as->seen_seq = seqset_init(0, SEQ_SPARSE); @@ -249,15 +251,15 @@ EXPORTED int append_commit(struct appendstate *as) { int r = 0; - + if (as->s == APPEND_DONE) return 0; if (as->nummsg) { /* Calculate new index header information */ as->mailbox->i.last_appenddate = time(0); - /* log the append so rolling squatter can index this mailbox */ - sync_log_append(as->mailbox->name); + /* the cache will be dirty even if we hand added the records */ + as->mailbox->cache_dirty = 1; /* set seen state */ if (as->userid0) @@ -859,7 +861,7 @@ { struct mailbox *mailbox = as->mailbox; struct index_record record; - const char *fname; + char *fname; FILE *destfile; int i, r; strarray_t *newflags = NULL; @@ -931,7 +933,7 @@ /* Create message file */ as->nummsg++; - fname = mailbox_record_fname(mailbox, &record); + fname = mailbox_message_fname(mailbox, record.uid); r = mailbox_copyfile(stagefile, fname, nolink); destfile = fopen(fname, "r"); @@ -951,7 +953,6 @@ fsync(fileno(destfile)); fclose(destfile); } - if (!r && config_getstring(IMAPOPT_ANNOTATION_CALLOUT)) { if (flags) newflags = strarray_dup(flags); @@ -986,15 +987,16 @@ } } } + if (r) + goto out; /* Handle flags the user wants to set in the message */ - if (!r && flags) { + if (flags) { r = append_apply_flags(as, mboxevent, &record, flags); if (r) goto out; } - /* Write out index file entry */ - if (!r) r = mailbox_append_index_record(mailbox, &record); + r = mailbox_append_index_record(mailbox, &record); out: if (newflags) @@ -1056,7 +1058,7 @@ { struct mailbox *mailbox = as->mailbox; struct index_record record; - const char *fname; + char *fname; FILE *destfile; int r; struct mboxevent *mboxevent = NULL; @@ -1069,7 +1071,7 @@ record.internaldate = internaldate; /* Create message file */ - fname = mailbox_record_fname(mailbox, &record); + fname = mailbox_message_fname(mailbox, record.uid); as->nummsg++; unlink(fname); @@ -1158,7 +1160,7 @@ load_annot_cb, &user_annots); if (r) goto out; - fname = mailbox_record_fname(as->mailbox, record); + fname = mailbox_message_fname(as->mailbox, record->uid); if (!fname) goto out; f = fopen(fname, "r"); @@ -1176,7 +1178,7 @@ r = callout_run(fname, body, &user_annots, &system_annots, &flags); if (r) goto out; - record->system_flags &= (FLAG_SEEN | FLAGS_INTERNAL); + record->system_flags &= FLAG_SEEN; memset(&record->user_flags, 0, sizeof(record->user_flags)); r = append_apply_flags(as, NULL, record, &flags); if (r) goto out; @@ -1229,13 +1231,12 @@ { int msg; struct index_record record; + char *srcfname, *destfname = NULL; int r = 0; - int userflag; - int i; - char *srcfname, *destfname; + int flag, userflag; annotate_state_t *astate = NULL; struct mboxevent *mboxevent = NULL; - + if (!nummsg) { append_abort(as); return 0; @@ -1248,29 +1249,30 @@ /* Copy/link all files and cache info */ for (msg = 0; msg < nummsg; msg++) { - record = copymsgmsg.record; - - /* renumber the message into the new mailbox */ + zero_index(record); record.uid = as->mailbox->i.last_uid + 1; as->nummsg++; - /* user flags are special - different numbers, so look them up */ + /* copy the parts that are the same regardless */ + record.internaldate = copymsgmsg.internaldate; + message_guid_copy(&record.guid, ©msgmsg.guid); + + /* Handle any flags that need to be copied */ if (as->myrights & ACL_WRITE) { - for (i = 0; i < strarray_size(©msgmsg.flags); i++) { - const char *flag = strarray_nth(©msgmsg.flags, i); - r = mailbox_user_flag(as->mailbox, flag, &userflag, 1); + /* deleted is special, different ACL */ + record.system_flags = + copymsgmsg.system_flags & ~FLAG_DELETED; + + for (flag = 0; copymsgmsg.flagflag; flag++) { + r = mailbox_user_flag(as->mailbox, + copymsgmsg.flagflag, &userflag, 1); if (r) goto out; record.user_flagsuserflag/32 |= 1<<(userflag&31); } } - else { - /* only flag allow to be kept without ACL_WRITE is DELETED */ - record.system_flags &= FLAG_DELETED; - } - - /* deleted flag has its own ACL */ - if (!(as->myrights & ACL_DELETEMSG)) { - record.system_flags &= ~FLAG_DELETED; + /* deleted flag copy as well */ + if (as->myrights & ACL_DELETEMSG) { + record.system_flags |= copymsgmsg.system_flags & FLAG_DELETED; } /* should this message be marked \Seen? */ @@ -1279,15 +1281,22 @@ } /* Link/copy message file */ - srcfname = xstrdup(mailbox_record_fname(mailbox, ©msgmsg.record)); - destfname = xstrdup(mailbox_record_fname(as->mailbox, &record)); - r = mailbox_copyfile(srcfname, destfname, nolink); free(destfname); + srcfname = xstrdup(mailbox_message_fname(mailbox, copymsgmsg.uid)); + destfname = xstrdup(mailbox_message_fname(as->mailbox, record.uid)); + r = mailbox_copyfile(srcfname, destfname, nolink); free(srcfname); if (r) goto out; - /* wipe out the cache offset so it rewrites */ - record.cache_offset = 0; + /* Write out cache info, copy other info */ + record.sentdate = copymsgmsg.sentdate; + record.size = copymsgmsg.size; + record.header_size = copymsgmsg.header_size; + record.gmtime = copymsgmsg.gmtime; + record.content_lines = copymsgmsg.content_lines; + record.cache_version = copymsgmsg.cache_version; + record.cache_crc = copymsgmsg.cache_crc; + record.crec = copymsgmsg.crec; /* Write out index file entry */ r = mailbox_append_index_record(as->mailbox, &record); @@ -1299,16 +1308,17 @@ r = mailbox_get_annotate_state(as->mailbox, record.uid, &astate); if (r) goto out; - r = annotate_msg_copy(mailbox, copymsgmsg.olduid, + r = annotate_msg_copy(mailbox, copymsgmsg.uid, as->mailbox, record.uid, as->userid); if (r) goto out; mboxevent_extract_record(mboxevent, as->mailbox, &record); - mboxevent_extract_copied_record(mboxevent, mailbox, copymsgmsg.olduid); + mboxevent_extract_copied_record(mboxevent, mailbox, copymsgmsg.uid); } out: + free(destfname); if (r) { append_abort(as); return r; @@ -1369,8 +1379,3 @@ seen_close(&seendb); return r; } - -EXPORTED const char *append_stagefname(struct stagemsg *stage) -{ - return strarray_nth(&stage->parts, 0); -}
View file
cyrus-imapd-2.5.tar.gz/imap/append.h
Changed
@@ -50,13 +50,22 @@ #include "sequence.h" #include "strarray.h" #include "annotate.h" -#include "conversations.h" struct copymsg { - uint32_t olduid; - int seen:1; - struct index_record record; - strarray_t flags; + unsigned long uid; + time_t internaldate; + time_t sentdate; + time_t gmtime; + unsigned long size; + unsigned long header_size; + unsigned long content_lines; + unsigned long cache_version; + unsigned long cache_crc; + struct cacherecord crec; + int seen; + struct message_guid guid; + bit32 system_flags; + char *flagMAX_USER_FLAGS+1; }; /* it's ridiculous i have to expose this structure if i want to allow @@ -149,6 +158,4 @@ extern int append_run_annotator(struct appendstate *as, struct index_record *record); -extern const char *append_stagefname(struct stagemsg *stage); - #endif /* INCLUDED_APPEND_H */
View file
cyrus-imapd-2.5.tar.gz/imap/arbitron.c
Changed
@@ -135,7 +135,7 @@ break; case 'D': { - unsigned month = 0, day = 0, year = 0; + unsigned month, day, year; struct tm date; if (strlen(optarg) < 8 ||
View file
cyrus-imapd-2.5.tar.gz/imap/autocreate.c
Changed
@@ -699,7 +699,7 @@ } if (autocreatequota >= 0 || autocreatequotamessage >= 0) { - quota_t newquotasQUOTA_NUMRESOURCES; + int newquotasQUOTA_NUMRESOURCES; int res; for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++)
View file
cyrus-imapd-2.5.tar.gz/imap/calalarmd.c
Deleted
@@ -1,171 +0,0 @@ -/* calalarmd.c - daemon for sending calendar alarms - * - * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <sys/types.h> -#include <syslog.h> -#include <sys/stat.h> -#include <stdlib.h> -#include <errno.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <signal.h> -#include <fcntl.h> -#include <math.h> - -#include "global.h" -#include "xmalloc.h" -#include "exitcodes.h" -#include "caldav_db.h" -#include "caldav_alarm.h" - -extern int optind; -extern char *optarg; - -static int debugmode = 0; - -EXPORTED void fatal(const char *msg, int err) -{ - if (debugmode) fprintf(stderr, "dying with %s %d\n",msg,err); - syslog(LOG_CRIT, "%s", msg); - syslog(LOG_NOTICE, "exiting"); - - cyrus_done(); - - exit(err); -} - -static void shut_down(int ec) __attribute__((noreturn)); -static void shut_down(int ec) -{ - caldav_done(); - dav_done(); - annotatemore_close(); - mboxlist_close(); - mboxlist_done(); - cyrus_done(); - exit(ec); -} - -int main(int argc, char **argv) -{ - int opt; - pid_t pid; - char *alt_config = NULL; - - if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { - fatal("must run as the Cyrus user", EC_USAGE); - } - - while ((opt = getopt(argc, argv, "C:di:")) != EOF) { - switch (opt) { - case 'C': /* alt config file */ - alt_config = optarg; - break; - case 'd': /* don't fork. debugging mode */ - debugmode = 1; - break; - default: - fprintf(stderr, "invalid argument\n"); - exit(EC_USAGE); - break; - } - } - - cyrus_init(alt_config, "calalarmd", 0, 0); - - mboxlist_init(0); - mboxlist_open(NULL); - - annotatemore_open(); - - dav_init(); - caldav_init(); - - mboxevent_init(); - - signals_set_shutdown(shut_down); - signals_add_handlers(0); - - /* fork unless we were given the -d option or we're running as a daemon */ - if (debugmode == 0 && !getenv("CYRUS_ISDAEMON")) { - - pid = fork(); - - if (pid == -1) { - perror("fork"); - exit(1); - } - - if (pid != 0) { /* parent */ - exit(0); - } - } - /* child */ - - for (;;) { - struct timeval start, end; - double totaltime; - int tosleep; - - signals_poll(); - - gettimeofday(&start, 0); - caldav_alarm_process(); - gettimeofday(&end, 0); - - signals_poll(); - - totaltime = timesub(&start, &end); - tosleep = 10 - round(totaltime); - if (tosleep > 0) - sleep(tosleep); - } - - /* NOTREACHED */ - shut_down(1); -} -
View file
cyrus-imapd-2.5.tar.gz/imap/caldav_alarm.c
Deleted
@@ -1,854 +0,0 @@ -/* caldav_alarm.c -- implementation of global CalDAV alarm database - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#include <syslog.h> -#include <string.h> - -#include <libical/ical.h> - -#include "caldav_alarm.h" -#include "cyrusdb.h" -#include "exitcodes.h" -#include "httpd.h" -#include "http_dav.h" -#include "imap_err.h" -#include "http_err.h" -#include "libconfig.h" -#include "mboxname.h" -#include "util.h" -#include "xstrlcat.h" -#include "xmalloc.h" - -enum { - STMT_BEGIN, - STMT_COMMIT, - STMT_ROLLBACK, - STMT_INSERT_ALARM, - STMT_INSERT_RECIPIENT, - STMT_DELETE, - STMT_DELETEALL, - STMT_DELETEMAILBOX, - STMT_DELETEUSER, - STMT_SELECT_ALARM, - STMT_SELECT_RECIPIENT -}; - -#define NUM_STMT 11 - -struct caldav_alarm_db { - sqlite3 *db; - int refcount; - sqlite3_stmt *stmtNUM_STMT; - int in_transaction; -}; - -static struct namespace caldav_alarm_namespace; - -EXPORTED int caldav_alarm_init(void) -{ - int r; - - /* Set namespace -- force standard (internal) */ - if ((r = mboxname_init_namespace(&caldav_alarm_namespace, 1))) { - syslog(LOG_ERR, "%s", error_message(r)); - fatal(error_message(r), EC_CONFIG); - } - - /* XXX initializes sqlite, but we're not really supposed to know that */ - return dav_init(); -} - - -EXPORTED int caldav_alarm_done(void) -{ - /* XXX shuts down sqlite, but we're not really supposed to know that */ - return dav_done(); -} - - -#define CMD_DROP "DROP TABLE IF EXISTS alarms;" - -#define CMD_CREATE \ - "CREATE TABLE IF NOT EXISTS alarms (" \ - " rowid INTEGER PRIMARY KEY AUTOINCREMENT," \ - " mailbox TEXT NOT NULL," \ - " resource TEXT NOT NULL," \ - " action INTEGER NOT NULL," \ - " nextalarm TEXT NOT NULL," \ - " tzid TEXT NOT NULL," \ - " start TEXT NOT NULL," \ - " end TEXT NOT NULL" \ - ");" \ - "CREATE TABLE IF NOT EXISTS alarm_recipients (" \ - " rowid INTEGER PRIMARY KEY AUTOINCREMENT," \ - " alarmid INTEGER NOT NULL," \ - " email TEXT NOT NULL," \ - " FOREIGN KEY (alarmid) REFERENCES alarms (rowid) ON DELETE CASCADE" \ - ");" \ - "CREATE INDEX IF NOT EXISTS idx_alarm_id ON alarm_recipients ( alarmid );" - -static struct caldav_alarm_db *my_alarmdb; -static struct mboxlock *my_alarmdb_lock; - -/* get a database handle to the alarm db */ -EXPORTED struct caldav_alarm_db *caldav_alarm_open() -{ - if (my_alarmdb) { - my_alarmdb->refcount++; - return my_alarmdb; - } - - sqlite3 *db; - const char *cmds = CMD_CREATE; - - char *dbfilename = strconcat(config_dir, "/caldav_alarm.sqlite3", NULL); - - int r = mboxname_lock("$CALDAVALARMDB", &my_alarmdb_lock, LOCK_EXCLUSIVE); - if (r) - return NULL; - - int rc = sqlite3_open(dbfilename, &db); - if (rc != SQLITE_OK) { - syslog(LOG_ERR, "IOERROR: caldav_alarm_open (open): %s", db ? sqlite3_errmsg(db) : "failed"); - sqlite3_close(db); - free(dbfilename); - mboxname_release(&my_alarmdb_lock); - return NULL; - } - - free(dbfilename); - - rc = sqlite3_exec(db, "PRAGMA foreign_keys = ON;", NULL, NULL, NULL); - if (rc) goto fail; - - if (cmds) { - rc = sqlite3_exec(db, cmds, NULL, NULL, NULL); - if (rc) goto fail; - } - - my_alarmdb = xzmalloc(sizeof(struct caldav_alarm_db)); - my_alarmdb->db = db; - my_alarmdb->refcount = 1; - my_alarmdb->in_transaction = 0; - - return my_alarmdb; - -fail: - syslog(LOG_ERR, "IOERROR: caldav_alarm_open (exec): %s", sqlite3_errmsg(db)); - sqlite3_close(db); - mboxname_release(&my_alarmdb_lock); - return NULL; -} - -/* close this handle */ -EXPORTED int caldav_alarm_close(struct caldav_alarm_db *alarmdb) -{ - assert(my_alarmdb == alarmdb); - - if (--(my_alarmdb->refcount)) return 0; - - int i; - for (i = 0; i < NUM_STMT; i++) { - sqlite3_stmt *stmt = my_alarmdb->stmti; - if (stmt) sqlite3_finalize(stmt); - } - - sqlite3_close(my_alarmdb->db); - - mboxname_release(&my_alarmdb_lock); - - free(my_alarmdb); - my_alarmdb = NULL; - - return 0; -} - -#define CMD_BEGIN "BEGIN TRANSACTION;" - -EXPORTED int caldav_alarm_begin(struct caldav_alarm_db *alarmdb) -{ - int rc = dav_exec(alarmdb->db, CMD_BEGIN, NULL, NULL, NULL, - &alarmdb->stmtSTMT_BEGIN); - if (rc) return rc; - alarmdb->in_transaction = 1; - return 0; -} - - -#define CMD_COMMIT "COMMIT TRANSACTION;" - -EXPORTED int caldav_alarm_commit(struct caldav_alarm_db *alarmdb) -{ - int rc = dav_exec(alarmdb->db, CMD_COMMIT, NULL, NULL, NULL, - &alarmdb->stmtSTMT_COMMIT); - if (rc) return rc; - alarmdb->in_transaction = 0; - return 0; -} - - -#define CMD_ROLLBACK "ROLLBACK TRANSACTION;" - -EXPORTED int caldav_alarm_rollback(struct caldav_alarm_db *alarmdb) -{ - int rc = dav_exec(alarmdb->db, CMD_ROLLBACK, NULL, NULL, NULL, - &alarmdb->stmtSTMT_ROLLBACK); - if (rc) return rc; - alarmdb->in_transaction = 0; - return 0; -} - -#define CMD_INSERT_ALARM \ - "INSERT INTO alarms" \ - " ( mailbox, resource, action, nextalarm, tzid, start, end )" \ - " VALUES" \ - " ( :mailbox, :resource, :action, :nextalarm, :tzid, :start, :end )" \ - ";" - -#define CMD_INSERT_RECIPIENT \ - "INSERT INTO alarm_recipients" \ - " ( alarmid, email )" \ - " VALUES" \ - " ( :alarmid, :email )" \ - ";" - -/* add a calendar alarm */ -EXPORTED int caldav_alarm_add(struct caldav_alarm_db *alarmdb, struct caldav_alarm_data *alarmdata) -{ - assert(alarmdb); - assert(alarmdata); - - struct bind_val bval = { - { ":mailbox", SQLITE_TEXT, { .s = alarmdata->mailbox } }, - { ":resource", SQLITE_TEXT, { .s = alarmdata->resource } }, - { ":action", SQLITE_INTEGER, { .i = alarmdata->action } }, - { ":nextalarm", SQLITE_TEXT, { .s = icaltime_as_ical_string(alarmdata->nextalarm) } }, - { ":tzid", SQLITE_TEXT, { .s = alarmdata->tzid } }, - { ":start", SQLITE_TEXT, { .s = icaltime_as_ical_string(alarmdata->start) } }, - { ":end", SQLITE_TEXT, { .s = icaltime_as_ical_string(alarmdata->end) } }, - { NULL, SQLITE_NULL, { .s = NULL } } - }; - - int in_transaction = 0; - int rc = 0; - - if (!alarmdb->in_transaction) { - rc = caldav_alarm_begin(alarmdb); - if (rc) - return rc; - in_transaction = 1; - } - - /* XXX deal with SQLITE_FULL */ - rc = dav_exec(alarmdb->db, CMD_INSERT_ALARM, bval, NULL, NULL, &alarmdb->stmtSTMT_INSERT_ALARM); - if (rc) { - if (in_transaction) - caldav_alarm_rollback(alarmdb); - return rc; - } - - alarmdata->rowid = sqlite3_last_insert_rowid(alarmdb->db); - - int i; - for (i = 0; i < strarray_size(&alarmdata->recipients); i++) { - const char *email = strarray_nth(&alarmdata->recipients, i); - - struct bind_val rbval = { - { ":alarmid", SQLITE_INTEGER, { .i = alarmdata->rowid } }, - { ":email", SQLITE_TEXT, { .s = email } }, - { NULL, SQLITE_NULL, { .s = NULL } } - }; - - rc = dav_exec(alarmdb->db, CMD_INSERT_RECIPIENT, rbval, NULL, NULL, &alarmdb->stmtSTMT_INSERT_RECIPIENT); - if (rc) { - if (in_transaction) - caldav_alarm_rollback(alarmdb); - return rc; - } - } - - if (in_transaction) - return caldav_alarm_commit(alarmdb); - - return 0; -} - -#define CMD_DELETE \ - "DELETE FROM alarms WHERE" \ - " rowid = :rowid" \ - ";" - -/* delete a single alarm */ -static int caldav_alarm_delete_row(struct caldav_alarm_db *alarmdb, struct caldav_alarm_data *alarmdata) -{ - assert(alarmdb); - assert(alarmdata); - - struct bind_val bval = { - { ":rowid", SQLITE_INTEGER, { .i = alarmdata->rowid } }, - { NULL, SQLITE_NULL, { .s = NULL } } - }; - - return dav_exec(alarmdb->db, CMD_DELETE, bval, NULL, NULL, &alarmdb->stmtSTMT_DELETE); -} - -#define CMD_DELETEALL \ - "DELETE FROM alarms WHERE" \ - " mailbox = :mailbox AND" \ - " resource = :resource" \ - ";" - -/* delete all alarms matching the event */ -EXPORTED int caldav_alarm_delete_all(struct caldav_alarm_db *alarmdb, struct caldav_alarm_data *alarmdata) -{ - assert(alarmdb); - assert(alarmdata); - - struct bind_val bval = { - { ":mailbox", SQLITE_TEXT, { .s = alarmdata->mailbox } }, - { ":resource", SQLITE_TEXT, { .s = alarmdata->resource } }, - { NULL, SQLITE_NULL, { .s = NULL } } - }; - - return dav_exec(alarmdb->db, CMD_DELETEALL, bval, NULL, NULL, &alarmdb->stmtSTMT_DELETEALL); -} - -#define CMD_DELETEMAILBOX \ - "DELETE FROM alarms WHERE" \ - " mailbox = :mailbox" \ - ";" - -/* delete all alarms matching the event */ -EXPORTED int caldav_alarm_delmbox(struct caldav_alarm_db *alarmdb, const char *mboxname) -{ - assert(alarmdb); - - struct bind_val bval = { - { ":mailbox", SQLITE_TEXT, { .s = mboxname } }, - { NULL, SQLITE_NULL, { .s = NULL } } - }; - - return dav_exec(alarmdb->db, CMD_DELETEMAILBOX, bval, NULL, NULL, &alarmdb->stmtSTMT_DELETEMAILBOX); -} - -#define CMD_DELETEUSER \ - "DELETE FROM alarms WHERE" \ - " mailbox LIKE :prefix" \ - ";" - -/* delete all alarms matching the event */ -EXPORTED int caldav_alarm_delete_user(struct caldav_alarm_db *alarmdb, const char *userid) -{ - assert(alarmdb); - char mailboxnameMAX_MAILBOX_NAME; - struct mboxname_parts parts; - - mboxname_userid_to_parts(userid, &parts); - - mboxname_parts_to_internal(&parts, mailboxname); - size_t len = strlen(mailboxname); - if (len + 3 > MAX_MAILBOX_NAME) return IMAP_INTERNAL; - mailboxnamelen = '.'; - mailboxnamelen+1 = '%'; - mailboxnamelen+2 = '\0'; - - struct bind_val bval = { - { ":prefix", SQLITE_TEXT, { .s = mailboxname } }, - { NULL, SQLITE_NULL, { .s = NULL } } - }; - - return dav_exec(alarmdb->db, CMD_DELETEUSER, bval, NULL, NULL, &alarmdb->stmtSTMT_DELETEUSER); -} - -enum trigger_type { - TRIGGER_RELATIVE_START, - TRIGGER_RELATIVE_END, - TRIGGER_ABSOLUTE -}; -struct trigger_data { - icalcomponent *alarm; - struct icaltriggertype trigger; - enum trigger_type type; - enum caldav_alarm_action action; -}; - -struct recur_cb_data { - struct trigger_data *triggerdata; - int ntriggers; - struct icaltimetype now; - icalcomponent *nextalarm; - enum caldav_alarm_action nextaction; - struct icaltimetype nextalarmtime; - struct icaltimetype eventstart; - struct icaltimetype eventend; -}; - -/* icalcomponent_foreach_recurrence() callback to find closest future event */ -static void recur_cb(icalcomponent *comp, struct icaltime_span *span, void *rock) -{ - struct recur_cb_data *rdata = (struct recur_cb_data *) rock; - int is_date = icaltime_is_date(icalcomponent_get_dtstart(comp)); - icaltimezone *utc = icaltimezone_get_utc_timezone(); - - /* is_date true ensures that the time is set to 00:00:00, but - * we still want to use it as a full datetime later, so we - * clear the flag once we're done with it */ - struct icaltimetype start = - icaltime_from_timet_with_zone(span->start, is_date, utc); - struct icaltimetype end = - icaltime_from_timet_with_zone(span->end, is_date, utc); - start.is_date = end.is_date = 0; - - int i; - for (i = 0; i < rdata->ntriggers; i++) { - struct trigger_data *tdata = &(rdata->triggerdatai); - - struct icaltimetype alarmtime; - switch (tdata->type) { - case TRIGGER_RELATIVE_START: - alarmtime = icaltime_add(start, tdata->trigger.duration); - break; - case TRIGGER_RELATIVE_END: - alarmtime = icaltime_add(end, tdata->trigger.duration); - break; - case TRIGGER_ABSOLUTE: - alarmtime = tdata->trigger.time; - break; - default: - /* doesn't happen */ - continue; - } - - if (icaltime_compare(alarmtime, rdata->now) > 0 && - (!rdata->nextalarm || - icaltime_compare(alarmtime, rdata->nextalarmtime) < 0)) { - - rdata->nextalarm = tdata->alarm; - - rdata->nextaction = tdata->action; - - memcpy(&(rdata->nextalarmtime), &alarmtime, sizeof(struct icaltimetype)); - - memcpy(&(rdata->eventstart), &start, sizeof(struct icaltimetype)); - memcpy(&(rdata->eventend), &end, sizeof(struct icaltimetype)); - } - } -} - -/* fill alarmdata with data for next alarm for given entry */ -EXPORTED int caldav_alarm_prepare( - icalcomponent *ical, struct caldav_alarm_data *alarmdata, - enum caldav_alarm_action wantaction, icaltimetype after) -{ - assert(ical); - assert(alarmdata); - - icalcomponent *comp = icalcomponent_get_first_real_component(ical); - - /* if there's no VALARM on this item then we have nothing to do */ - int nalarms = icalcomponent_count_components(comp, ICAL_VALARM_COMPONENT); - if (nalarms == 0) - return 1; - - int ntriggers = 0; - struct trigger_data *triggerdata = - (struct trigger_data *) xmalloc(sizeof(struct trigger_data) * nalarms); - - icalcomponent *alarm; - for ( alarm = icalcomponent_get_first_component(comp, ICAL_VALARM_COMPONENT); - alarm; - alarm = icalcomponent_get_next_component(comp, ICAL_VALARM_COMPONENT)) { - - icalproperty *prop; - icalvalue *val; - - prop = icalcomponent_get_first_property(alarm, ICAL_ACTION_PROPERTY); - if (!prop) - /* no action, invalid alarm, skip */ - continue; - - val = icalproperty_get_value(prop); - enum icalproperty_action action = icalvalue_get_action(val); - if (!(action == ICAL_ACTION_DISPLAY || action == ICAL_ACTION_EMAIL)) - /* we only want DISPLAY and EMAIL, skip */ - continue; - - if ( - (wantaction == CALDAV_ALARM_ACTION_DISPLAY && action != ICAL_ACTION_DISPLAY) || - (wantaction == CALDAV_ALARM_ACTION_EMAIL && action != ICAL_ACTION_EMAIL) - ) - /* specific action was requested and this doesn't match, skip */ - continue; - - prop = icalcomponent_get_first_property(alarm, ICAL_TRIGGER_PROPERTY); - if (!prop) - /* no trigger, invalid alarm, skip */ - continue; - - val = icalproperty_get_value(prop); - - struct trigger_data *tdata = &(triggerdatantriggers); - - tdata->alarm = alarm; - tdata->action = - action == - ICAL_ACTION_DISPLAY ? CALDAV_ALARM_ACTION_DISPLAY : - ICAL_ACTION_EMAIL ? CALDAV_ALARM_ACTION_EMAIL : - CALDAV_ALARM_ACTION_NONE; - - struct icaltriggertype trigger = icalvalue_get_trigger(val); - /* XXX validate trigger */ - memcpy(&(tdata->trigger), &trigger, sizeof(struct icaltriggertype)); - - if (icalvalue_isa(val) == ICAL_DURATION_VALUE) { - icalparameter *param = icalproperty_get_first_parameter(prop, ICAL_RELATED_PARAMETER); - if (param && icalparameter_get_related(param) == ICAL_RELATED_END) - tdata->type = TRIGGER_RELATIVE_END; - else - tdata->type = TRIGGER_RELATIVE_START; - } - else - tdata->type = TRIGGER_ABSOLUTE; - - ntriggers++; - assert(ntriggers <= nalarms); - } - - icaltimezone *utc = icaltimezone_get_utc_timezone(); - - struct recur_cb_data rdata = { - .triggerdata = triggerdata, - .ntriggers = ntriggers, - .now = after, - .nextalarm = NULL, - .nextalarmtime = icaltime_null_time(), - .eventstart = icaltime_null_time(), - .eventend = icaltime_null_time() - }; - - /* See if its a recurring event */ - if (icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY) || - icalcomponent_get_first_property(comp, ICAL_RDATE_PROPERTY) || - icalcomponent_get_first_property(comp, ICAL_EXDATE_PROPERTY)) { - - icalcomponent_foreach_recurrence( - comp, - rdata.now, - icaltime_from_timet_with_zone(INT_MAX, 0, utc), - recur_cb, - &rdata); - - /* Handle overridden recurrences */ - icalcomponent *subcomp = comp; - while ((subcomp = icalcomponent_get_next_component(subcomp, ICAL_VEVENT_COMPONENT))) { - struct icalperiodtype period; - caldav_get_period(subcomp, ICAL_VEVENT_COMPONENT, &period); - icaltime_span span = icaltime_span_new(period.start, period.end, 0); - recur_cb(subcomp, &span, &rdata); - } - } - - else { - /* not recurring, use dtstart/dtend instead */ - struct icalperiodtype period; - caldav_get_period(comp, ICAL_VEVENT_COMPONENT, &period); - icaltime_span span = icaltime_span_new(period.start, period.end, 0); - recur_cb(comp, &span, &rdata); - } - - /* no next alarm, nothing more to do! */ - if (!rdata.nextalarm) - return 1; - - /* now fill out alarmdata with all the stuff from event/ocurrence/alarm */ - alarmdata->action = rdata.nextaction; - - alarmdata->nextalarm = rdata.nextalarmtime; - - /* dtstart timezone is good enough for alarm purposes */ - icalproperty *prop = NULL; - icalparameter *param = NULL; - const char *tzid = NULL; - - prop = icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY); - if (prop) param = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER); - if (param) tzid = icalparameter_get_tzid(param); - alarmdata->tzid = xstrdup(tzid ? tzid : "floating"); - - memcpy(&(alarmdata->start), &(rdata.eventstart), sizeof(struct icaltimetype)); - memcpy(&(alarmdata->end), &(rdata.eventend), sizeof(struct icaltimetype)); - - icalproperty *attendee = icalcomponent_get_first_property(rdata.nextalarm, ICAL_ATTENDEE_PROPERTY); - while (attendee) { - const char *email = icalproperty_get_value_as_string(attendee); - if (email) - strarray_append(&alarmdata->recipients, email); - attendee = icalcomponent_get_next_property(rdata.nextalarm, ICAL_ATTENDEE_PROPERTY); - } - - return 0; -} - -/* clean up alarmdata after prepare */ -void caldav_alarm_fini(struct caldav_alarm_data *alarmdata) -{ - free((void *) alarmdata->tzid); - alarmdata->tzid = NULL; - strarray_fini(&alarmdata->recipients); -} - -#define CMD_SELECT_ALARM \ - "SELECT rowid, mailbox, resource, action, nextalarm, tzid, start, end" \ - " FROM alarms WHERE" \ - " nextalarm <= :before" \ - ";" - -#define CMD_SELECT_RECIPIENT \ - "SELECT email" \ - " FROM alarm_recipients WHERE" \ - " alarmid = :alarmid" \ - ";" - -struct alarmdata_list { - struct alarmdata_list *next; - struct caldav_alarm_data data; - int do_delete; -}; - -static int alarm_read_cb(sqlite3_stmt *stmt, void *rock) -{ - struct alarmdata_list **list = (struct alarmdata_list **) rock; - - struct alarmdata_list *n = xzmalloc(sizeof(struct alarmdata_list)); - - n->data.rowid = sqlite3_column_int(stmt, 0); - n->data.mailbox = xstrdup((const char *) sqlite3_column_text(stmt, 1)); - n->data.resource = xstrdup((const char *) sqlite3_column_text(stmt, 2)); - n->data.action = sqlite3_column_int(stmt, 3); - n->data.nextalarm = icaltime_from_string((const char *) sqlite3_column_text(stmt, 4)); - n->data.tzid = xstrdup((const char *) sqlite3_column_text(stmt, 5)); - n->data.start = icaltime_from_string((const char *) sqlite3_column_text(stmt, 6)); - n->data.end = icaltime_from_string((const char *) sqlite3_column_text(stmt, 7)); - - n->do_delete = 1; // unless something goes wrong, we will delete this alarm - - n->next = *list; - *list = n; - - return 0; -} - -static int recipient_read_cb(sqlite3_stmt *stmt, void *rock) -{ - strarray_t *recipients = (strarray_t *) rock; - - strarray_append(recipients, (const char *) sqlite3_column_text(stmt, 0)); - - return 0; -} - -/* process alarms with triggers within before a given time */ -EXPORTED int caldav_alarm_process() -{ - syslog(LOG_DEBUG, "processing alarms"); - - // all alarms in the past and within the next 60 seconds - icaltimetype before = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone()); - icaltime_adjust(&before, 0, 0, 0, 60); - - struct bind_val bval = { - { ":before", SQLITE_TEXT, { .s = icaltime_as_ical_string(before) } }, - { NULL, SQLITE_NULL, { .s = NULL } } - }; - - struct alarmdata_list *list = NULL, *scan; - - struct caldav_alarm_db *alarmdb = caldav_alarm_open(); - if (!alarmdb) - return HTTP_SERVER_ERROR; - - int rc = dav_exec(alarmdb->db, CMD_SELECT_ALARM, bval, &alarm_read_cb, &list, - &alarmdb->stmtSTMT_SELECT_ALARM); - if (rc) - goto done; - - for (scan = list; scan; scan = scan->next) { - struct mailbox *mailbox = NULL; - char *userid = NULL; - struct caldav_db *caldavdb = NULL; - icalcomponent *ical = NULL; - struct buf msg_buf = BUF_INITIALIZER; - struct buf calname_buf = BUF_INITIALIZER; - static const char *displayname_annot = ANNOT_NS "<" XML_NS_DAV ">displayname"; - - syslog(LOG_DEBUG, - "processing alarm rowid %llu mailbox %s resource %s action %d nextalarm %s tzid %s start %s end %s", - scan->data.rowid, scan->data.mailbox, - scan->data.resource, scan->data.action, - icaltime_as_ical_string(scan->data.nextalarm), - scan->data.tzid, - icaltime_as_ical_string(scan->data.start), - icaltime_as_ical_string(scan->data.end) - ); - - rc = mailbox_open_irl(scan->data.mailbox, &mailbox); - if (rc == IMAP_MAILBOX_NONEXISTENT) - /* mailbox was deleted or something, nothing we can do */ - continue; - if (rc) { - /* transient open error, don't delete this alarm */ - scan->do_delete = 0; - continue; - } - - userid = xstrdup(mboxname_to_userid(mailbox->name)); - - caldavdb = caldav_open_mailbox(mailbox, 0); - if (!caldavdb) { - /* XXX mailbox exists but caldav structure doesn't? delete event? */ - scan->do_delete = 0; - goto done_item; - } - - struct caldav_data *cdata = NULL; - caldav_lookup_resource(caldavdb, mailbox->name, scan->data.resource, 0, &cdata); - if (!cdata || !cdata->ical_uid) - /* resource not found, nothing we can do */ - goto done_item; - - struct index_record record; - memset(&record, 0, sizeof(struct index_record)); - rc = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record, NULL); - if (rc) { - /* XXX no index record? item deleted or transient error? */ - scan->do_delete = 0; - goto done_item; - } - - rc = mailbox_map_record(mailbox, &record, &msg_buf); - if (rc) { - /* XXX no message? index is wrong? yikes */ - scan->do_delete = 0; - goto done_item; - } - - ical = icalparser_parse_string(buf_base(&msg_buf) + record.header_size); - buf_free(&msg_buf); - - rc = annotatemore_lookup(mailbox->name, displayname_annot, NULL, &calname_buf); - if (rc || !calname_buf.len) buf_setcstr(&calname_buf, strrchr(mailbox->name, '.') + 1); - buf_cstring(&calname_buf); - - mailbox_close(&mailbox); - mailbox = NULL; - - caldav_close(caldavdb); - caldavdb = NULL; - - if (!ical) - /* XXX log error */ - goto done_item; - - /* fill out recipients */ - struct bind_val rbval = { - { ":alarmid", SQLITE_INTEGER, { .i = scan->data.rowid } }, - { NULL, SQLITE_NULL, { .s = NULL } } - }; - - rc = dav_exec(alarmdb->db, CMD_SELECT_RECIPIENT, rbval, recipient_read_cb, - &scan->data.recipients, - &alarmdb->stmtSTMT_SELECT_RECIPIENT); - - struct mboxevent *event = mboxevent_new(EVENT_CALENDAR_ALARM); - mboxevent_extract_icalcomponent(event, ical, userid, buf_base(&calname_buf), - scan->data.action, scan->data.nextalarm, - scan->data.tzid, - scan->data.start, scan->data.end, - &scan->data.recipients); - mboxevent_notify(event); - mboxevent_free(&event); - - /* set up for next alarm */ - struct caldav_alarm_data alarmdata = { - .mailbox = scan->data.mailbox, - .resource = scan->data.resource, - }; - - if (!caldav_alarm_prepare(ical, &alarmdata, scan->data.action, before)) { - rc = caldav_alarm_add(alarmdb, &alarmdata); - caldav_alarm_fini(&alarmdata); - /* report error, but don't do anything */ - } - -done_item: - buf_free(&calname_buf); - buf_free(&msg_buf); - if (ical) icalcomponent_free(ical); - if (caldavdb) caldav_close(caldavdb); - if (userid) free(userid); - if (mailbox) mailbox_close(&mailbox); - } - - for (scan = list; scan; scan = scan->next) - if (scan->do_delete) - caldav_alarm_delete_row(alarmdb, &scan->data); - -done: - caldav_alarm_close(alarmdb); - - scan = list; - while (scan) { - struct alarmdata_list *next = scan->next; - - free((void*) scan->data.mailbox); - free((void*) scan->data.resource); - free((void*) scan->data.tzid); - free(scan); - - scan = next; - } - - return rc; -}
View file
cyrus-imapd-2.5.tar.gz/imap/caldav_alarm.h
Deleted
@@ -1,113 +0,0 @@ -/* caldav_alarm.h -- interface to global CalDAV alarm database - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef CALDAV_ALARM_DB_H -#define CALDAV_ALARM_DB_H - -#include <config.h> - -#include <sqlite3.h> -#include "strarray.h" - -struct caldav_alarm_db; - -enum caldav_alarm_action { - CALDAV_ALARM_ACTION_NONE = 0, - CALDAV_ALARM_ACTION_DISPLAY = 1, - CALDAV_ALARM_ACTION_EMAIL = 2, - - CALDAV_ALARM_ACTION_FIRST = CALDAV_ALARM_ACTION_DISPLAY, - CALDAV_ALARM_ACTION_LAST = CALDAV_ALARM_ACTION_EMAIL -}; - -struct caldav_alarm_data { - sqlite3_int64 rowid; - const char *mailbox; - const char *resource; - enum caldav_alarm_action action; - icaltimetype nextalarm; - const char *tzid; - icaltimetype start; - icaltimetype end; - strarray_t recipients; -}; - -/* prepare for caldav alarm operations in this process */ -int caldav_alarm_init(void); - -/* done with all caldav operations for this process */ -int caldav_alarm_done(void); - -/* get a database handle to the alarm db */ -struct caldav_alarm_db *caldav_alarm_open(void); - -/* close this handle */ -int caldav_alarm_close(struct caldav_alarm_db *alarmdb); - -/* transactions */ -int caldav_alarm_begin(struct caldav_alarm_db *alarmdb); -int caldav_alarm_commit(struct caldav_alarm_db *alarmdb); -int caldav_alarm_rollback(struct caldav_alarm_db *alarmdb); - -/* add a calendar alarm */ -int caldav_alarm_add(struct caldav_alarm_db *alarmdb, struct caldav_alarm_data *alarmdata); - -/* delete all alarms matching the event */ -int caldav_alarm_delete_all(struct caldav_alarm_db *alarmdb, struct caldav_alarm_data *alarmdata); - -/* delete all alarms for a user */ -int caldav_alarm_delete_user(struct caldav_alarm_db *alarmdb, const char *userid); - -/* fill alarmdata with data for next alarm of given type for given entry */ -int caldav_alarm_prepare(icalcomponent *ical, struct caldav_alarm_data *alarmdata, enum caldav_alarm_action action, icaltimetype after); - -/* delete entire mailbox's alarms */ -int caldav_alarm_delmbox(struct caldav_alarm_db *alarmdb, const char *mboxname); - -/* clean up alarmdata after prepare */ -void caldav_alarm_fini(struct caldav_alarm_data *alarmdata); - -/* distribute alarms with triggers in the next minute */ -int caldav_alarm_process(); - -#endif /* CALDAV_ALARM_H */
View file
cyrus-imapd-2.5.tar.gz/imap/caldav_db.c
Deleted
@@ -1,852 +0,0 @@ -/* caldav_db.c -- implementation of per-user CalDAV database - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#include <syslog.h> -#include <string.h> - -#include <libical/ical.h> - -#include "caldav_db.h" -#include "cyrusdb.h" -#include "exitcodes.h" -#include "httpd.h" -#include "http_dav.h" -#include "libconfig.h" -#include "mboxname.h" -#include "util.h" -#include "xstrlcat.h" -#include "xmalloc.h" - -struct icaltimetype world_start; -struct icaltimetype world_end; - -enum { - STMT_SELRSRC, - STMT_SELUID, - STMT_SELMBOX, - STMT_INSERT, - STMT_UPDATE, - STMT_DELETE, - STMT_DELMBOX, - STMT_BEGIN, - STMT_COMMIT, - STMT_ROLLBACK -}; - -#define NUM_STMT 10 - -struct caldav_db { - sqlite3 *db; /* DB handle */ - char sched_inboxMAX_MAILBOX_BUFFER;/* DB owner's scheduling Inbox */ - sqlite3_stmt *stmtNUM_STMT; /* prepared statements */ - struct buf mailbox; /* buffers for copies of column text */ - struct buf resource; - struct buf lock_token; - struct buf lock_owner; - struct buf lock_ownerid; - struct buf ical_uid; - struct buf organizer; - struct buf dtstart; - struct buf dtend; - struct buf sched_tag; -}; - - -static struct namespace caldav_namespace; - -EXPORTED int caldav_init(void) -{ - int r; - - /* Set namespace -- force standard (internal) */ - if ((r = mboxname_init_namespace(&caldav_namespace, 1))) { - syslog(LOG_ERR, "%s", error_message(r)); - fatal(error_message(r), EC_CONFIG); - } - - r = dav_init(); - caldav_alarm_init(); - - /* we're interested in a 200 year range for now */ - world_start = icaltime_from_string("1900-01-01T00:00:00Z"); - world_end = icaltime_from_string("2099-12-31T23:59:59Z"); - return r; -} - - -EXPORTED int caldav_done(void) -{ - caldav_alarm_done(); - return dav_done(); -} - - -#define CMD_DROP "DROP TABLE IF EXISTS ical_objs;" - -#define CMD_CREATE \ - "CREATE TABLE IF NOT EXISTS ical_objs (" \ - " rowid INTEGER PRIMARY KEY," \ - " creationdate INTEGER," \ - " mailbox TEXT NOT NULL," \ - " resource TEXT NOT NULL," \ - " imap_uid INTEGER," \ - " lock_token TEXT," \ - " lock_owner TEXT," \ - " lock_ownerid TEXT," \ - " lock_expire INTEGER," \ - " comp_type INTEGER," \ - " ical_uid TEXT," \ - " organizer TEXT," \ - " dtstart TEXT," \ - " dtend TEXT," \ - " recurring INTEGER," \ - " transp INTEGER," \ - " sched_tag TEXT," \ - " UNIQUE( mailbox, resource ) );" \ - "CREATE INDEX IF NOT EXISTS idx_ical_uid ON ical_objs ( ical_uid );" - -static struct caldav_db *caldav_open_fname(const char *fname, int flags) -{ - sqlite3 *db; - struct caldav_db *caldavdb = NULL; - const char *cmds = CMD_CREATE; - - if (flags & CALDAV_TRUNC) cmds = CMD_DROP CMD_CREATE; - - db = dav_open(fname, cmds); - - if (db) { - caldavdb = xzmalloc(sizeof(struct caldav_db)); - caldavdb->db = db; - } - - return caldavdb; -} - -EXPORTED struct caldav_db *caldav_open_userid(const char *userid, int flags) -{ - struct caldav_db *caldavdb = NULL; - struct buf fname = BUF_INITIALIZER; - - dav_getpath_byuserid(&fname, userid); - caldavdb = caldav_open_fname(buf_cstring(&fname), flags); - buf_free(&fname); - - /* Construct mbox name corresponding to userid's scheduling Inbox */ - strncpy(caldavdb->sched_inbox, caldav_mboxname(userid, SCHED_INBOX), sizeof(caldavdb->sched_inbox)); - - return caldavdb; -} - -/* Open DAV DB corresponding to userid */ -EXPORTED struct caldav_db *caldav_open_mailbox(struct mailbox *mailbox, int flags) -{ - struct caldav_db *caldavdb = NULL; - const char *userid = mboxname_to_userid(mailbox->name); - - if (userid) { - caldavdb = caldav_open_userid(userid, flags); - } - else { - struct buf fname = BUF_INITIALIZER; - dav_getpath(&fname, mailbox); - caldavdb = caldav_open_fname(buf_cstring(&fname), flags); - buf_free(&fname); - } - - return caldavdb; -} - - -/* Close DAV DB */ -EXPORTED int caldav_close(struct caldav_db *caldavdb) -{ - int i, r = 0; - - if (!caldavdb) return 0; - - buf_free(&caldavdb->mailbox); - buf_free(&caldavdb->resource); - buf_free(&caldavdb->lock_token); - buf_free(&caldavdb->lock_owner); - buf_free(&caldavdb->lock_ownerid); - buf_free(&caldavdb->ical_uid); - buf_free(&caldavdb->organizer); - buf_free(&caldavdb->dtstart); - buf_free(&caldavdb->dtend); - buf_free(&caldavdb->sched_tag); - - for (i = 0; i < NUM_STMT; i++) { - sqlite3_stmt *stmt = caldavdb->stmti; - if (stmt) sqlite3_finalize(stmt); - } - - r = dav_close(caldavdb->db); - - free(caldavdb); - - return r; -} - - -#define CMD_BEGIN "BEGIN TRANSACTION;" - -EXPORTED int caldav_begin(struct caldav_db *caldavdb) -{ - return dav_exec(caldavdb->db, CMD_BEGIN, NULL, NULL, NULL, - &caldavdb->stmtSTMT_BEGIN); -} - - -#define CMD_COMMIT "COMMIT TRANSACTION;" - -EXPORTED int caldav_commit(struct caldav_db *caldavdb) -{ - return dav_exec(caldavdb->db, CMD_COMMIT, NULL, NULL, NULL, - &caldavdb->stmtSTMT_COMMIT); -} - - -#define CMD_ROLLBACK "ROLLBACK TRANSACTION;" - -EXPORTED int caldav_abort(struct caldav_db *caldavdb) -{ - return dav_exec(caldavdb->db, CMD_ROLLBACK, NULL, NULL, NULL, - &caldavdb->stmtSTMT_ROLLBACK); -} - - -struct read_rock { - struct caldav_db *db; - struct caldav_data *cdata; - int (*cb)(void *rock, void *data); - void *rock; -}; - -static const char *column_text_to_buf(const char *text, struct buf *buf) -{ - if (text) { - buf_setcstr(buf, text); - text = buf_cstring(buf); - } - - return text; -} - -static int read_cb(sqlite3_stmt *stmt, void *rock) -{ - struct read_rock *rrock = (struct read_rock *) rock; - struct caldav_db *db = rrock->db; - struct caldav_data *cdata = rrock->cdata; - int r = 0; - - memset(cdata, 0, sizeof(struct caldav_data)); - - cdata->dav.rowid = sqlite3_column_int(stmt, 0); - cdata->dav.creationdate = sqlite3_column_int(stmt, 1); - cdata->dav.imap_uid = sqlite3_column_int(stmt, 4); - cdata->dav.lock_expire = sqlite3_column_int(stmt, 8); - cdata->comp_type = sqlite3_column_int(stmt, 9); - cdata->recurring = sqlite3_column_int(stmt, 14); - cdata->transp = sqlite3_column_int(stmt, 15); - - if (rrock->cb) { - /* We can use the column data directly for the callback */ - cdata->dav.mailbox = (const char *) sqlite3_column_text(stmt, 2); - cdata->dav.resource = (const char *) sqlite3_column_text(stmt, 3); - cdata->dav.lock_token = (const char *) sqlite3_column_text(stmt, 5); - cdata->dav.lock_owner = (const char *) sqlite3_column_text(stmt, 6); - cdata->dav.lock_ownerid = (const char *) sqlite3_column_text(stmt, 7); - cdata->ical_uid = (const char *) sqlite3_column_text(stmt, 10); - cdata->organizer = (const char *) sqlite3_column_text(stmt, 11); - cdata->dtstart = (const char *) sqlite3_column_text(stmt, 12); - cdata->dtend = (const char *) sqlite3_column_text(stmt, 13); - cdata->sched_tag = (const char *) sqlite3_column_text(stmt, 16); - r = rrock->cb(rrock->rock, cdata); - } - else { - /* For single row SELECTs like caldav_read(), - * we need to make a copy of the column data before - * it gets flushed by sqlite3_step() or sqlite3_reset() */ - cdata->dav.mailbox = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 2), - &db->mailbox); - cdata->dav.resource = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 3), - &db->resource); - cdata->dav.lock_token = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 5), - &db->lock_token); - cdata->dav.lock_owner = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 6), - &db->lock_owner); - cdata->dav.lock_ownerid = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 7), - &db->lock_ownerid); - cdata->ical_uid = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 10), - &db->ical_uid); - cdata->organizer = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 11), - &db->organizer); - cdata->dtstart = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 12), - &db->dtstart); - cdata->dtend = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 13), - &db->dtend); - cdata->sched_tag = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 16), - &db->sched_tag); - } - - return r; -} - - -#define CMD_SELRSRC \ - "SELECT rowid, creationdate, mailbox, resource, imap_uid," \ - " lock_token, lock_owner, lock_ownerid, lock_expire," \ - " comp_type, ical_uid, organizer, dtstart, dtend," \ - " recurring, transp, sched_tag" \ - " FROM ical_objs" \ - " WHERE ( mailbox = :mailbox AND resource = :resource );" - -EXPORTED int caldav_lookup_resource(struct caldav_db *caldavdb, - const char *mailbox, const char *resource, - int lock, struct caldav_data **result) -{ - struct bind_val bval = { - { ":mailbox", SQLITE_TEXT, { .s = mailbox } }, - { ":resource", SQLITE_TEXT, { .s = resource } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - static struct caldav_data cdata; - struct read_rock rrock = { caldavdb, &cdata, NULL, NULL }; - int r; - - *result = memset(&cdata, 0, sizeof(struct caldav_data)); - - if (lock) { - /* begin a transaction */ - r = dav_exec(caldavdb->db, CMD_BEGIN, NULL, NULL, NULL, - &caldavdb->stmtSTMT_BEGIN); - if (r) return r; - } - - r = dav_exec(caldavdb->db, CMD_SELRSRC, bval, &read_cb, &rrock, - &caldavdb->stmtSTMT_SELRSRC); - if (!r && !cdata.dav.rowid) r = CYRUSDB_NOTFOUND; - - return r; -} - - -#define CMD_SELUID \ - "SELECT rowid, creationdate, mailbox, resource, imap_uid," \ - " lock_token, lock_owner, lock_ownerid, lock_expire," \ - " comp_type, ical_uid, organizer, dtstart, dtend," \ - " recurring, transp, sched_tag" \ - " FROM ical_objs" \ - " WHERE ( ical_uid = :ical_uid AND mailbox != :inbox);" - -EXPORTED int caldav_lookup_uid(struct caldav_db *caldavdb, const char *ical_uid, - int lock, struct caldav_data **result) -{ - struct bind_val bval = { - { ":ical_uid", SQLITE_TEXT, { .s = ical_uid } }, - { ":inbox", SQLITE_TEXT, { .s = caldavdb->sched_inbox } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - static struct caldav_data cdata; - struct read_rock rrock = { caldavdb, &cdata, NULL, NULL }; - int r; - - *result = memset(&cdata, 0, sizeof(struct caldav_data)); - - if (lock) { - /* begin a transaction */ - r = dav_exec(caldavdb->db, CMD_BEGIN, NULL, NULL, NULL, - &caldavdb->stmtSTMT_BEGIN); - if (r) return r; - } - - r = dav_exec(caldavdb->db, CMD_SELUID, bval, &read_cb, &rrock, - &caldavdb->stmtSTMT_SELUID); - if (!r && !cdata.dav.rowid) r = CYRUSDB_NOTFOUND; - - return r; -} - - -#define CMD_SELMBOX \ - "SELECT rowid, creationdate, mailbox, resource, imap_uid," \ - " lock_token, lock_owner, lock_ownerid, lock_expire," \ - " comp_type, ical_uid, organizer, dtstart, dtend," \ - " recurring, transp, sched_tag" \ - " FROM ical_objs WHERE mailbox = :mailbox;" - -EXPORTED int caldav_foreach(struct caldav_db *caldavdb, const char *mailbox, - int (*cb)(void *rock, void *data), - void *rock) -{ - struct bind_val bval = { - { ":mailbox", SQLITE_TEXT, { .s = mailbox } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - struct caldav_data cdata; - struct read_rock rrock = { caldavdb, &cdata, cb, rock }; - - return dav_exec(caldavdb->db, CMD_SELMBOX, bval, &read_cb, &rrock, - &caldavdb->stmtSTMT_SELMBOX); -} - - -#define CMD_INSERT \ - "INSERT INTO ical_objs (" \ - " creationdate, mailbox, resource, imap_uid," \ - " lock_token, lock_owner, lock_ownerid, lock_expire," \ - " comp_type, ical_uid, organizer, dtstart, dtend," \ - " recurring, transp, sched_tag )" \ - " VALUES (" \ - " :creationdate, :mailbox, :resource, :imap_uid," \ - " :lock_token, :lock_owner, :lock_ownerid, :lock_expire," \ - " :comp_type, :ical_uid, :organizer, :dtstart, :dtend," \ - " :recurring, :transp, :sched_tag );" - -#define CMD_UPDATE \ - "UPDATE ical_objs SET" \ - " imap_uid = :imap_uid," \ - " lock_token = :lock_token," \ - " lock_owner = :lock_owner," \ - " lock_ownerid = :lock_ownerid," \ - " lock_expire = :lock_expire," \ - " comp_type = :comp_type," \ - " ical_uid = :ical_uid," \ - " organizer = :organizer," \ - " dtstart = :dtstart," \ - " dtend = :dtend," \ - " recurring = :recurring," \ - " transp = :transp," \ - " sched_tag = :sched_tag" \ - " WHERE rowid = :rowid;" - -EXPORTED int caldav_write(struct caldav_db *caldavdb, struct caldav_data *cdata, - int commit) -{ - struct bind_val bval = { - { ":imap_uid", SQLITE_INTEGER, { .i = cdata->dav.imap_uid } }, - { ":lock_token", SQLITE_TEXT, { .s = cdata->dav.lock_token } }, - { ":lock_owner", SQLITE_TEXT, { .s = cdata->dav.lock_owner } }, - { ":lock_ownerid", SQLITE_TEXT, { .s = cdata->dav.lock_ownerid } }, - { ":lock_expire", SQLITE_INTEGER, { .i = cdata->dav.lock_expire } }, - { ":comp_type", SQLITE_INTEGER, { .i = cdata->comp_type } }, - { ":ical_uid", SQLITE_TEXT, { .s = cdata->ical_uid } }, - { ":organizer", SQLITE_TEXT, { .s = cdata->organizer } }, - { ":dtstart", SQLITE_TEXT, { .s = cdata->dtstart } }, - { ":dtend", SQLITE_TEXT, { .s = cdata->dtend } }, - { ":recurring", SQLITE_INTEGER, { .i = cdata->recurring } }, - { ":transp", SQLITE_INTEGER, { .i = cdata->transp } }, - { ":sched_tag", SQLITE_TEXT, { .s = cdata->sched_tag } }, - { NULL, SQLITE_NULL, { .s = NULL } }, - { NULL, SQLITE_NULL, { .s = NULL } }, - { NULL, SQLITE_NULL, { .s = NULL } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - const char *cmd; - sqlite3_stmt **stmt; - int r; - - if (cdata->dav.rowid) { - cmd = CMD_UPDATE; - stmt = &caldavdb->stmtSTMT_UPDATE; - - bval13.name = ":rowid"; - bval13.type = SQLITE_INTEGER; - bval13.val.i = cdata->dav.rowid; - } - else { - cmd = CMD_INSERT; - stmt = &caldavdb->stmtSTMT_INSERT; - - bval13.name = ":creationdate"; - bval13.type = SQLITE_INTEGER; - bval13.val.i = cdata->dav.creationdate; - bval14.name = ":mailbox"; - bval14.type = SQLITE_TEXT; - bval14.val.s = cdata->dav.mailbox; - bval15.name = ":resource"; - bval15.type = SQLITE_TEXT; - bval15.val.s = cdata->dav.resource; - } - - r = dav_exec(caldavdb->db, cmd, bval, NULL, NULL, stmt); - - if (!r && commit) { - /* commit transaction */ - return dav_exec(caldavdb->db, CMD_COMMIT, NULL, NULL, NULL, - &caldavdb->stmtSTMT_COMMIT); - } - - return r; -} - - -#define CMD_DELETE "DELETE FROM ical_objs WHERE rowid = :rowid;" - -EXPORTED int caldav_delete(struct caldav_db *caldavdb, unsigned rowid, int commit) -{ - struct bind_val bval = { - { ":rowid", SQLITE_INTEGER, { .i = rowid } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - int r; - - r = dav_exec(caldavdb->db, CMD_DELETE, bval, NULL, NULL, - &caldavdb->stmtSTMT_DELETE); - - if (!r && commit) { - /* commit transaction */ - return dav_exec(caldavdb->db, CMD_COMMIT, NULL, NULL, NULL, - &caldavdb->stmtSTMT_COMMIT); - } - - return r; -} - - -#define CMD_DELMBOX "DELETE FROM ical_objs WHERE mailbox = :mailbox;" - -EXPORTED int caldav_delmbox(struct caldav_db *caldavdb, const char *mailbox, int commit) -{ - struct bind_val bval = { - { ":mailbox", SQLITE_TEXT, { .s = mailbox } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - int r; - - r = dav_exec(caldavdb->db, CMD_DELMBOX, bval, NULL, NULL, - &caldavdb->stmtSTMT_DELMBOX); - - if (!r && commit) { - /* commit transaction */ - return dav_exec(caldavdb->db, CMD_COMMIT, NULL, NULL, NULL, - &caldavdb->stmtSTMT_COMMIT); - } - - return r; -} - - -/* Get time period (start/end) of a component based in RFC 4791 Sec 9.9 */ -EXPORTED void caldav_get_period(icalcomponent *comp, icalcomponent_kind kind, - struct icalperiodtype *period) -{ - icaltimezone *utc = icaltimezone_get_utc_timezone(); - - period->start = - icaltime_convert_to_zone(icalcomponent_get_dtstart(comp), utc); - period->end = - icaltime_convert_to_zone(icalcomponent_get_dtend(comp), utc); - - switch (kind) { - case ICAL_VEVENT_COMPONENT: - if (icaltime_is_null_time(period->end)) { - /* No DTEND or DURATION */ - memcpy(&period->end, &period->start, - sizeof(struct icaltimetype)); - - if (icaltime_is_date(period->start)) { - /* DTSTART is not DATE-TIME */ - struct icaldurationtype dur; - - dur = icaldurationtype_from_int(60*60*24 - 1); /* P1D */ - icaltime_add(period->end, dur); - } - } - break; - - case ICAL_VTODO_COMPONENT: { - struct icaltimetype due = icalcomponent_get_due(comp); - - if (!icaltime_is_null_time(period->start)) { - /* Has DTSTART */ - if (icaltime_is_null_time(period->end)) { - /* No DURATION */ - memcpy(&period->end, &period->start, - sizeof(struct icaltimetype)); - - if (!icaltime_is_null_time(due)) { - /* Has DUE */ - if (icaltime_compare(due, period->start) < 0) - memcpy(&period->start, &due, sizeof(struct icaltimetype)); - if (icaltime_compare(due, period->end) > 0) - memcpy(&period->end, &due, sizeof(struct icaltimetype)); - } - } - } - else { - icalproperty *prop; - - /* No DTSTART */ - if (!icaltime_is_null_time(due)) { - /* Has DUE */ - memcpy(&period->start, &due, sizeof(struct icaltimetype)); - memcpy(&period->end, &due, sizeof(struct icaltimetype)); - } - else if ((prop = - icalcomponent_get_first_property(comp, - ICAL_COMPLETED_PROPERTY))) { - /* Has COMPLETED */ - period->start = - icaltime_convert_to_zone(icalproperty_get_completed(prop), - utc); - memcpy(&period->end, &period->start, sizeof(struct icaltimetype)); - - if ((prop = - icalcomponent_get_first_property(comp, - ICAL_CREATED_PROPERTY))) { - /* Has CREATED */ - struct icaltimetype created = - icaltime_convert_to_zone(icalproperty_get_created(prop), - utc); - if (icaltime_compare(created, period->start) < 0) - memcpy(&period->start, &created, sizeof(struct icaltimetype)); - if (icaltime_compare(created, period->end) > 0) - memcpy(&period->end, &created, sizeof(struct icaltimetype)); - } - } - else if ((prop = - icalcomponent_get_first_property(comp, - ICAL_CREATED_PROPERTY))) { - /* Has CREATED */ - period->start = - icaltime_convert_to_zone(icalproperty_get_created(prop), - utc); - memcpy(&period->end, &period->start, sizeof(struct icaltimetype)); - } - else { - /* Always */ - period->start = world_start; - period->end = world_end; - } - } - break; - } - - case ICAL_VJOURNAL_COMPONENT: - if (!icaltime_is_null_time(period->start)) { - /* Has DTSTART */ - memcpy(&period->end, &period->start, - sizeof(struct icaltimetype)); - - if (icaltime_is_date(period->start)) { - /* DTSTART is not DATE-TIME */ - struct icaldurationtype dur; - - dur = icaldurationtype_from_int(60*60*24 - 1); /* P1D */ - icaltime_add(period->end, dur); - } - } - else { - /* Never */ - period->start = world_end; - period->end = world_start; - } - break; - - case ICAL_VFREEBUSY_COMPONENT: - if (icaltime_is_null_time(period->start) || - icaltime_is_null_time(period->end)) { - /* No DTSTART or DTEND */ - icalproperty *fb = - icalcomponent_get_first_property(comp, - ICAL_FREEBUSY_PROPERTY); - - - if (fb) { - /* Has FREEBUSY */ - /* XXX Convert FB period into our period */ - } - else { - /* Never */ - period->start = world_end; - period->end = world_start; - } - } - break; - - default: - break; - } -} - - -/* icalcomponent_foreach_recurrence() callback to find earliest/latest time */ -static void recur_cb(icalcomponent *comp, struct icaltime_span *span, - void *rock) -{ - struct icalperiodtype *period = (struct icalperiodtype *) rock; - int is_date = icaltime_is_date(icalcomponent_get_dtstart(comp)); - icaltimezone *utc = icaltimezone_get_utc_timezone(); - struct icaltimetype start = - icaltime_from_timet_with_zone(span->start, is_date, utc); - struct icaltimetype end = - icaltime_from_timet_with_zone(span->end, is_date, utc); - - if (icaltime_compare(start, period->start) < 0) - memcpy(&period->start, &start, sizeof(struct icaltimetype)); - - if (icaltime_compare(end, period->end) > 0) - memcpy(&period->end, &end, sizeof(struct icaltimetype)); -} - - -EXPORTED void caldav_make_entry(icalcomponent *ical, struct caldav_data *cdata) -{ - icalcomponent *comp = icalcomponent_get_first_real_component(ical); - icalcomponent_kind kind; - icalproperty *prop; - unsigned mykind = 0, recurring = 0, transp = 0; - struct icalperiodtype span; - - /* Get iCalendar UID */ - cdata->ical_uid = icalcomponent_get_uid(comp); - - /* Get component type */ - kind = icalcomponent_isa(comp); - switch (kind) { - case ICAL_VEVENT_COMPONENT: mykind = CAL_COMP_VEVENT; break; - case ICAL_VTODO_COMPONENT: mykind = CAL_COMP_VTODO; break; - case ICAL_VJOURNAL_COMPONENT: mykind = CAL_COMP_VJOURNAL; break; - case ICAL_VFREEBUSY_COMPONENT: mykind = CAL_COMP_VFREEBUSY; break; - case ICAL_VAVAILABILITY_COMPONENT: mykind = CAL_COMP_VAVAILABILITY; break; - default: break; - } - cdata->comp_type = mykind; - - /* Get organizer */ - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - if (prop) cdata->organizer = icalproperty_get_organizer(prop)+7; - else cdata->organizer = NULL; - - /* Get transparency */ - prop = icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY); - if (prop) { - icalvalue *transp_val = icalproperty_get_value(prop); - - switch (icalvalue_get_transp(transp_val)) { - case ICAL_TRANSP_TRANSPARENT: - case ICAL_TRANSP_TRANSPARENTNOCONFLICT: - transp = 1; - break; - - default: - transp = 0; - break; - } - } - cdata->transp = transp; - - /* Get base dtstart and dtend */ - caldav_get_period(comp, kind, &span); - - /* See if its a recurring event */ - if (icalcomponent_get_first_property(comp,ICAL_RRULE_PROPERTY) || - icalcomponent_get_first_property(comp,ICAL_RDATE_PROPERTY) || - icalcomponent_get_first_property(comp,ICAL_EXDATE_PROPERTY)) { - /* fetch the RRULE to check again later */ - icalproperty *rrule_prop = icalcomponent_get_first_property(comp,ICAL_RRULE_PROPERTY); - - /* Recurring - find widest time range that includes events */ - recurring = 1; - - icalcomponent_foreach_recurrence( - comp, - world_start, - world_end, - recur_cb, - &span); - - /* Handle overridden recurrences */ - while ((comp = icalcomponent_get_next_component(ical, kind))) { - struct icalperiodtype period; - - caldav_get_period(comp, kind, &period); - - if (icaltime_compare(period.start, span.start) < 0) - memcpy(&span.start, &period.start, sizeof(struct icaltimetype)); - - if (icaltime_compare(period.end, span.end) > 0) - memcpy(&span.end, &period.end, sizeof(struct icaltimetype)); - } - - if (rrule_prop) { - struct icalrecurrencetype rrule = icalproperty_get_rrule(rrule_prop); - /* infinte rule? Ignore all times */ - if (icaltime_is_null_time(rrule.until) && !rrule.count) - memcpy(&span.end, &world_end, sizeof(struct icaltimetype)); - } - } - - cdata->dtstart = icaltime_as_ical_string(span.start); - cdata->dtend = icaltime_as_ical_string(span.end); - cdata->recurring = recurring; -} - - -EXPORTED const char *caldav_mboxname(const char *userid, const char *name) -{ - struct buf boxbuf = BUF_INITIALIZER; - const char *res = NULL; - - buf_setcstr(&boxbuf, config_getstring(IMAPOPT_CALENDARPREFIX)); - - if (name) { - size_t len = strcspn(name, "/"); - buf_putc(&boxbuf, '.'); - buf_appendmap(&boxbuf, name, len); - } - - res = mboxname_user_mbox(userid, buf_cstring(&boxbuf)); - - buf_free(&boxbuf); - - return res; -}
View file
cyrus-imapd-2.5.tar.gz/imap/caldav_db.h
Deleted
@@ -1,151 +0,0 @@ -/* caldav_db.h -- abstract interface for per-user CalDAV database - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef CALDAV_DB_H -#define CALDAV_DB_H - -#include <config.h> - -#include <libical/ical.h> - -#include "dav_db.h" - -#ifndef HAVE_VAVAILABILITY -/* Allow us to compile without #ifdef HAVE_VAVAILABILITY everywhere */ -#define ICAL_VAVAILABILITY_COMPONENT ICAL_XLICINVALID_COMPONENT -#endif - -/* Bitmask of calendar components */ -enum { - /* "Real" components - MUST remain in this order (values used in DAV DB) */ - CAL_COMP_VEVENT = (1<<0), - CAL_COMP_VTODO = (1<<1), - CAL_COMP_VJOURNAL = (1<<2), - CAL_COMP_VFREEBUSY = (1<<3), - CAL_COMP_VAVAILABILITY = (1<<4), - /* Append additional "real" components here */ - - /* Other components - values don't matter - prepend here */ - CAL_COMP_VALARM = (1<<13), - CAL_COMP_VTIMEZONE = (1<<14), - CAL_COMP_VCALENDAR = (1<<15) -}; - -struct caldav_db; - -struct icaltimetype world_start; -struct icaltimetype world_end; - - -#define CALDAV_CREATE 0x01 -#define CALDAV_TRUNC 0x02 - -struct caldav_data { - struct dav_data dav; /* MUST be first so we can typecast */ - unsigned comp_type; - const char *ical_uid; - const char *organizer; - const char *dtstart; - const char *dtend; - unsigned recurring; - unsigned transp; - const char *sched_tag; -}; - -/* prepare for caldav operations in this process */ -int caldav_init(void); - -/* done with all caldav operations for this process */ -int caldav_done(void); - -/* get a database handle corresponding to mailbox */ -struct caldav_db *caldav_open_mailbox(struct mailbox *mailbox, int flags); -struct caldav_db *caldav_open_userid(const char *userid, int flags); - -/* close this handle */ -int caldav_close(struct caldav_db *caldavdb); - -/* lookup an entry from 'caldavdb' by resource - (optionally inside a transaction for updates) */ -int caldav_lookup_resource(struct caldav_db *caldavdb, - const char *mailbox, const char *resource, - int lock, struct caldav_data **result); - -/* lookup an entry from 'caldavdb' by iCal UID - (optionally inside a transaction for updates) */ -int caldav_lookup_uid(struct caldav_db *caldavdb, const char *ical_uid, - int lock, struct caldav_data **result); - -/* process each entry for 'mailbox' in 'caldavdb' with cb() */ -int caldav_foreach(struct caldav_db *caldavdb, const char *mailbox, - int (*cb)(void *rock, void *data), - void *rock); - -/* write an entry to 'caldavdb' */ -int caldav_write(struct caldav_db *caldavdb, struct caldav_data *cdata, - int commit); - -/* delete an entry from 'caldavdb' */ -int caldav_delete(struct caldav_db *caldavdb, unsigned rowid, int commit); - -/* delete all entries for 'mailbox' from 'caldavdb' */ -int caldav_delmbox(struct caldav_db *caldavdb, const char *mailbox, int commit); - -/* begin transaction */ -int caldav_begin(struct caldav_db *caldavdb); - -/* commit transaction */ -int caldav_commit(struct caldav_db *caldavdb); - -/* abort transaction */ -int caldav_abort(struct caldav_db *caldavdb); - -/* create caldav_data from icalcomponent */ -void caldav_make_entry(icalcomponent *ical, struct caldav_data *cdata); - -const char *caldav_mboxname(const char *userid, const char *name); - -/* Get time period (start/end) of a component based in RFC 4791 Sec 9.9 */ -void caldav_get_period(icalcomponent *comp, icalcomponent_kind kind, struct icalperiodtype *period); - -#endif /* CALDAV_DB_H */
View file
cyrus-imapd-2.5.tar.gz/imap/carddav_db.c
Deleted
@@ -1,633 +0,0 @@ -/* carddav_db.c -- implementation of per-user CardDAV database - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#include <syslog.h> -#include <string.h> - -#include "carddav_db.h" -#include "cyrusdb.h" -#include "httpd.h" -#include "http_dav.h" -#include "libconfig.h" -#include "util.h" -#include "xstrlcat.h" -#include "xmalloc.h" - - -enum { - STMT_SELRSRC, - STMT_SELUID, - STMT_SELMBOX, - STMT_INSERT, - STMT_DELETE, - STMT_DELMBOX, - STMT_BEGIN, - STMT_COMMIT, - STMT_ROLLBACK, - STMT_INSERT_EMAIL, - STMT_INSERT_GROUP, - STMT_GETEMAIL, - STMT_GETGROUP, - NUM_STMT -}; - -#define NUM_BUFS 10 - -struct carddav_db { - sqlite3 *db; /* DB handle */ - sqlite3_stmt *stmtNUM_STMT; /* prepared statements */ - struct buf bufsNUM_BUFS; /* buffers for copies of column text */ -}; - - -EXPORTED int carddav_init(void) -{ - return dav_init(); -} - - -EXPORTED int carddav_done(void) -{ - return dav_done(); -} - - -#define CMD_DROP_OBJ "DROP TABLE IF EXISTS vcard_objs;" - -#define CMD_CREATE_OBJ \ - "CREATE TABLE IF NOT EXISTS vcard_objs (" \ - " rowid INTEGER PRIMARY KEY," \ - " creationdate INTEGER," \ - " mailbox TEXT NOT NULL," \ - " resource TEXT NOT NULL," \ - " imap_uid INTEGER," \ - " lock_token TEXT," \ - " lock_owner TEXT," \ - " lock_ownerid TEXT," \ - " lock_expire INTEGER," \ - " version INTEGER," \ - " vcard_uid TEXT," \ - " kind INTEGER," \ - " fullname TEXT," \ - " name TEXT," \ - " nickname TEXT," \ - " UNIQUE( mailbox, resource ) );" \ - "CREATE INDEX IF NOT EXISTS idx_vcard_fn ON vcard_objs ( fullname );" \ - "CREATE INDEX IF NOT EXISTS idx_vcard_uid ON vcard_objs ( vcard_uid );" - -#define CMD_DROP_EM "DROP TABLE IF EXISTS vcard_emails;" - -#define CMD_CREATE_EM \ - "CREATE TABLE IF NOT EXISTS vcard_emails (" \ - " rowid INTEGER PRIMARY KEY," \ - " objid INTEGER," \ - " pos INTEGER NOT NULL," /* for sorting */ \ - " email TEXT NOT NULL," \ - " FOREIGN KEY (objid) REFERENCES vcard_objs (rowid) ON DELETE CASCADE );" \ - "CREATE INDEX IF NOT EXISTS idx_vcard_email ON vcard_emails ( email );" - -#define CMD_DROP_GR "DROP TABLE IF EXISTS vcard_groups;" - -#define CMD_CREATE_GR \ - "CREATE TABLE IF NOT EXISTS vcard_groups (" \ - " rowid INTEGER PRIMARY KEY," \ - " objid INTEGER," \ - " pos INTEGER NOT NULL," /* for sorting */ \ - " member_uid TEXT NOT NULL," \ - " FOREIGN KEY (objid) REFERENCES vcard_objs (rowid) ON DELETE CASCADE );" - -#define CMD_DROP CMD_DROP_OBJ CMD_DROP_EM CMD_DROP_GR -#define CMD_CREATE CMD_CREATE_OBJ CMD_CREATE_EM CMD_CREATE_GR - -/* Open DAV DB corresponding to mailbox */ -static struct carddav_db *carddav_open_fname(const char *fname, int flags) -{ - sqlite3 *db; - struct carddav_db *carddavdb = NULL; - const char *cmds = CMD_CREATE; - - if (flags & CARDDAV_TRUNC) cmds = CMD_DROP CMD_CREATE; - - db = dav_open(fname, cmds); - - if (db) { - carddavdb = xzmalloc(sizeof(struct carddav_db)); - carddavdb->db = db; - } - - return carddavdb; -} - -EXPORTED struct carddav_db *carddav_open_userid(const char *userid, int flags) -{ - struct buf fname = BUF_INITIALIZER; - struct carddav_db *carddavdb = NULL; - - dav_getpath_byuserid(&fname, userid); - carddavdb = carddav_open_fname(buf_cstring(&fname), flags); - buf_free(&fname); - - return carddavdb; -} - -EXPORTED struct carddav_db *carddav_open_mailbox(struct mailbox *mailbox, int flags) -{ - struct buf fname = BUF_INITIALIZER; - struct carddav_db *carddavdb = NULL; - - dav_getpath(&fname, mailbox); - carddavdb = carddav_open_fname(buf_cstring(&fname), flags); - buf_free(&fname); - - return carddavdb; -} - - -/* Close DAV DB */ -EXPORTED int carddav_close(struct carddav_db *carddavdb) -{ - int i, r = 0; - - if (!carddavdb) return 0; - - for (i = 0; i < NUM_BUFS; i++) { - buf_free(&carddavdb->bufsi); - } - - for (i = 0; i < NUM_STMT; i++) { - sqlite3_stmt *stmt = carddavdb->stmti; - if (stmt) sqlite3_finalize(stmt); - } - - r = dav_close(carddavdb->db); - - free(carddavdb); - - return r; -} - - -#define CMD_BEGIN "BEGIN TRANSACTION;" - -EXPORTED int carddav_begin(struct carddav_db *carddavdb) -{ - return dav_exec(carddavdb->db, CMD_BEGIN, NULL, NULL, NULL, - &carddavdb->stmtSTMT_BEGIN); -} - - -#define CMD_COMMIT "COMMIT TRANSACTION;" - -EXPORTED int carddav_commit(struct carddav_db *carddavdb) -{ - return dav_exec(carddavdb->db, CMD_COMMIT, NULL, NULL, NULL, - &carddavdb->stmtSTMT_COMMIT); -} - - -#define CMD_ROLLBACK "ROLLBACK TRANSACTION;" - -EXPORTED int carddav_abort(struct carddav_db *carddavdb) -{ - return dav_exec(carddavdb->db, CMD_ROLLBACK, NULL, NULL, NULL, - &carddavdb->stmtSTMT_ROLLBACK); -} - - -struct read_rock { - struct carddav_db *db; - struct carddav_data *cdata; - int (*cb)(void *rock, void *data); - void *rock; -}; - -static const char *column_text_to_buf(const char *text, struct buf *buf) -{ - if (text) { - buf_setcstr(buf, text); - text = buf_cstring(buf); - } - - return text; -} - -static int read_cb(sqlite3_stmt *stmt, void *rock) -{ - struct read_rock *rrock = (struct read_rock *) rock; - struct carddav_db *db = rrock->db; - struct carddav_data *cdata = rrock->cdata; - int r = 0; - - memset(cdata, 0, sizeof(struct carddav_data)); - - cdata->dav.rowid = sqlite3_column_int(stmt, 0); - cdata->dav.creationdate = sqlite3_column_int(stmt, 1); - cdata->dav.imap_uid = sqlite3_column_int(stmt, 4); - cdata->dav.lock_expire = sqlite3_column_int(stmt, 8); - cdata->version = sqlite3_column_int(stmt, 9); - cdata->kind = sqlite3_column_int(stmt, 11); - - if (rrock->cb) { - /* We can use the column data directly for the callback */ - cdata->dav.mailbox = (const char *) sqlite3_column_text(stmt, 2); - cdata->dav.resource = (const char *) sqlite3_column_text(stmt, 3); - cdata->dav.lock_token = (const char *) sqlite3_column_text(stmt, 5); - cdata->dav.lock_owner = (const char *) sqlite3_column_text(stmt, 6); - cdata->dav.lock_ownerid = (const char *) sqlite3_column_text(stmt, 7); - cdata->vcard_uid = (const char *) sqlite3_column_text(stmt, 10); - cdata->fullname = (const char *) sqlite3_column_text(stmt, 12); - cdata->name = (const char *) sqlite3_column_text(stmt, 13); - cdata->nickname = (const char *) sqlite3_column_text(stmt, 14); - r = rrock->cb(rrock->rock, cdata); - } - else { - /* For single row SELECTs like carddav_read(), - * we need to make a copy of the column data before - * it gets flushed by sqlite3_step() or sqlite3_reset() */ - cdata->dav.mailbox = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 2), - &db->bufs0); - cdata->dav.resource = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 3), - &db->bufs1); - cdata->dav.lock_token = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 5), - &db->bufs2); - cdata->dav.lock_owner = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 6), - &db->bufs3); - cdata->dav.lock_ownerid = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 7), - &db->bufs4); - cdata->vcard_uid = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 10), - &db->bufs5); - cdata->fullname = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 12), - &db->bufs6); - cdata->name = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 13), - &db->bufs7); - cdata->nickname = - column_text_to_buf((const char *) sqlite3_column_text(stmt, 14), - &db->bufs8); - } - - return r; -} - - -#define CMD_SELRSRC \ - "SELECT rowid, creationdate, mailbox, resource, imap_uid," \ - " lock_token, lock_owner, lock_ownerid, lock_expire," \ - " version, vcard_uid, kind, fullname, name, nickname" \ - " FROM vcard_objs" \ - " WHERE ( mailbox = :mailbox AND resource = :resource );" - -EXPORTED int carddav_lookup_resource(struct carddav_db *carddavdb, - const char *mailbox, const char *resource, - int lock, struct carddav_data **result) -{ - struct bind_val bval = { - { ":mailbox", SQLITE_TEXT, { .s = mailbox } }, - { ":resource", SQLITE_TEXT, { .s = resource } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - static struct carddav_data cdata; - struct read_rock rrock = { carddavdb, &cdata, NULL, NULL }; - int r; - - *result = memset(&cdata, 0, sizeof(struct carddav_data)); - - if (lock) { - /* begin a transaction */ - r = dav_exec(carddavdb->db, CMD_BEGIN, NULL, NULL, NULL, - &carddavdb->stmtSTMT_BEGIN); - if (r) return r; - } - - r = dav_exec(carddavdb->db, CMD_SELRSRC, bval, &read_cb, &rrock, - &carddavdb->stmtSTMT_SELRSRC); - if (!r && !cdata.dav.rowid) r = CYRUSDB_NOTFOUND; - - return r; -} - - -#define CMD_SELUID \ - "SELECT rowid, creationdate, mailbox, resource, imap_uid," \ - " lock_token, lock_owner, lock_ownerid, lock_expire," \ - " version, vcard_uid, kind, fullname, name, nickname" \ - " FROM vcard_objs" \ - " WHERE ( vcard_uid = :vcard_uid );" - -EXPORTED int carddav_lookup_uid(struct carddav_db *carddavdb, const char *vcard_uid, - int lock, struct carddav_data **result) -{ - struct bind_val bval = { - { ":vcard_uid", SQLITE_TEXT, { .s = vcard_uid } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - static struct carddav_data cdata; - struct read_rock rrock = { carddavdb, &cdata, NULL, NULL }; - int r; - - *result = memset(&cdata, 0, sizeof(struct carddav_data)); - - if (lock) { - /* begin a transaction */ - r = dav_exec(carddavdb->db, CMD_BEGIN, NULL, NULL, NULL, - &carddavdb->stmtSTMT_BEGIN); - if (r) return r; - } - - r = dav_exec(carddavdb->db, CMD_SELUID, bval, &read_cb, &rrock, - &carddavdb->stmtSTMT_SELUID); - if (!r && !cdata.dav.rowid) r = CYRUSDB_NOTFOUND; - - return r; -} - - -#define CMD_SELMBOX \ - "SELECT rowid, creationdate, mailbox, resource, imap_uid," \ - " lock_token, lock_owner, lock_ownerid, lock_expire," \ - " version, vcard_uid, kind, fullname, name, nickname" \ - " FROM vcard_objs WHERE mailbox = :mailbox;" - -EXPORTED int carddav_foreach(struct carddav_db *carddavdb, const char *mailbox, - int (*cb)(void *rock, void *data), - void *rock) -{ - struct bind_val bval = { - { ":mailbox", SQLITE_TEXT, { .s = mailbox } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - struct carddav_data cdata; - struct read_rock rrock = { carddavdb, &cdata, cb, rock }; - - return dav_exec(carddavdb->db, CMD_SELMBOX, bval, &read_cb, &rrock, - &carddavdb->stmtSTMT_SELMBOX); -} - -#define CMD_GETEMAIL \ - "SELECT rowid" \ - " FROM vcard_emails" \ - " WHERE ( email = :email );" - -static int foundemail_cb(sqlite3_stmt *stmt, void *rock) -{ - int *foundp = (int *)rock; - if (sqlite3_column_int(stmt, 0)) - *foundp = 1; - return 0; -} - -EXPORTED int carddav_getemail(struct carddav_db *carddavdb, const char *email) -{ - struct bind_val bval = { - { ":email", SQLITE_TEXT, { .s = email } }, - { NULL, SQLITE_NULL, { .s = NULL } } - }; - int found = 0; - int r; - - r = dav_exec(carddavdb->db, CMD_GETEMAIL, bval, &foundemail_cb, &found, - &carddavdb->stmtSTMT_GETEMAIL); - if (r) { - /* XXX syslog */ - } - - return found; -} - -#define CMD_GETGROUP \ - "SELECT E.email FROM vcard_emails E" \ - " JOIN vcard_objs CO JOIN vcard_groups G JOIN vcard_objs GO" \ - " WHERE E.objid = CO.rowid AND CO.vcard_uid = G.member_uid AND G.objid = GO.rowid" \ - " AND E.pos = 0 AND GO.fullname = :group" - -static int foundgroup_cb(sqlite3_stmt *stmt, void *rock) -{ - strarray_t *array = (strarray_t *)rock; - strarray_add(array, (const char *)sqlite3_column_text(stmt, 0)); - return 0; -} - -EXPORTED strarray_t *carddav_getgroup(struct carddav_db *carddavdb, const char *group) -{ - struct bind_val bval = { - { ":group", SQLITE_TEXT, { .s = group } }, - { NULL, SQLITE_NULL, { .s = NULL } } - }; - strarray_t *found = strarray_new(); - int r; - - r = dav_exec(carddavdb->db, CMD_GETGROUP, bval, &foundgroup_cb, found, - &carddavdb->stmtSTMT_GETGROUP); - if (r) { - /* XXX syslog */ - } - - if (!strarray_size(found)) { - strarray_free(found); - return NULL; - } - - return found; -} - - -#define CMD_INSERT_EMAIL \ - "INSERT INTO vcard_emails ( objid, pos, email )" \ - " VALUES ( :objid, :pos, :email );" - -/* no commit */ -static int carddav_write_emails(struct carddav_db *carddavdb, struct carddav_data *cdata) -{ - struct bind_val bval = { - { ":objid", SQLITE_INTEGER, { .i = cdata->dav.rowid } }, - { ":pos", SQLITE_INTEGER, { .i = 0 } }, - { ":email", SQLITE_TEXT, { .s = NULL } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - int r; - int i; - - for (i = 0; i < strarray_size(&cdata->emails); i++) { - bval1.val.i = i; - bval2.val.s = strarray_nth(&cdata->emails, i); - r = dav_exec(carddavdb->db, CMD_INSERT_EMAIL, bval, NULL, NULL, - &carddavdb->stmtSTMT_INSERT_EMAIL); - if (r) return r; - } - - return 0; -} - -#define CMD_INSERT_GROUP \ - "INSERT INTO vcard_groups ( objid, pos, member_uid )" \ - " VALUES ( :objid, :pos, :member_uid );" - -/* no commit */ -static int carddav_write_groups(struct carddav_db *carddavdb, struct carddav_data *cdata) -{ - struct bind_val bval = { - { ":objid", SQLITE_INTEGER, { .i = cdata->dav.rowid } }, - { ":pos", SQLITE_INTEGER, { .i = 0 } }, - { ":member_uid", SQLITE_TEXT, { .s = NULL } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - int r; - int i; - - for (i = 0; i < strarray_size(&cdata->member_uids); i++) { - bval1.val.i = i; - bval2.val.s = strarray_nth(&cdata->member_uids, i); - r = dav_exec(carddavdb->db, CMD_INSERT_GROUP, bval, NULL, NULL, - &carddavdb->stmtSTMT_INSERT_GROUP); - if (r) return r; - } - - return 0; -} - -#define CMD_INSERT \ - "INSERT INTO vcard_objs (" \ - " creationdate, mailbox, resource, imap_uid," \ - " lock_token, lock_owner, lock_ownerid, lock_expire," \ - " version, vcard_uid, kind, fullname, name, nickname)" \ - " VALUES (" \ - " :creationdate, :mailbox, :resource, :imap_uid," \ - " :lock_token, :lock_owner, :lock_ownerid, :lock_expire," \ - " :version, :vcard_uid, :kind, :fullname, :name, :nickname );" - -EXPORTED int carddav_write(struct carddav_db *carddavdb, struct carddav_data *cdata, - int commit) -{ - struct bind_val bval = { - { ":creationdate", SQLITE_INTEGER, { .i = cdata->dav.creationdate } }, - { ":mailbox", SQLITE_TEXT, { .s = cdata->dav.mailbox } }, - { ":resource", SQLITE_TEXT, { .s = cdata->dav.resource } }, - { ":imap_uid", SQLITE_INTEGER, { .i = cdata->dav.imap_uid } }, - { ":lock_token", SQLITE_TEXT, { .s = cdata->dav.lock_token } }, - { ":lock_owner", SQLITE_TEXT, { .s = cdata->dav.lock_owner } }, - { ":lock_ownerid", SQLITE_TEXT, { .s = cdata->dav.lock_ownerid } }, - { ":lock_expire", SQLITE_INTEGER, { .i = cdata->dav.lock_expire } }, - { ":version", SQLITE_INTEGER, { .i = cdata->version } }, - { ":vcard_uid", SQLITE_TEXT, { .s = cdata->vcard_uid } }, - { ":kind", SQLITE_INTEGER, { .i = cdata->kind } }, - { ":fullname", SQLITE_TEXT, { .s = cdata->fullname } }, - { ":name", SQLITE_TEXT, { .s = cdata->name } }, - { ":nickname", SQLITE_TEXT, { .s = cdata->nickname } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - int r; - - if (cdata->dav.rowid) { - r = carddav_delete(carddavdb, cdata->dav.rowid, /*commit*/0); - if (r) return r; - } - - r = dav_exec(carddavdb->db, CMD_INSERT, bval, NULL, NULL, - &carddavdb->stmtSTMT_INSERT); - if (r) return r; - - cdata->dav.rowid = sqlite3_last_insert_rowid(carddavdb->db); - - r = carddav_write_emails(carddavdb, cdata); - if (r) return r; - - r = carddav_write_groups(carddavdb, cdata); - if (r) return r; - - /* commit transaction */ - if (commit) { - r = carddav_commit(carddavdb); - if (r) return r; - } - - return 0; -} - - -#define CMD_DELETE "DELETE FROM vcard_objs WHERE rowid = :rowid;" - -EXPORTED int carddav_delete(struct carddav_db *carddavdb, unsigned rowid, int commit) -{ - struct bind_val bval = { - { ":rowid", SQLITE_INTEGER, { .i = rowid } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - int r; - - r = dav_exec(carddavdb->db, CMD_DELETE, bval, NULL, NULL, - &carddavdb->stmtSTMT_DELETE); - if (r) return r; - - /* commit transaction */ - if (commit) { - r = carddav_commit(carddavdb); - if (r) return r; - } - - return 0; -} - - -#define CMD_DELMBOX "DELETE FROM vcard_objs WHERE mailbox = :mailbox;" - -EXPORTED int carddav_delmbox(struct carddav_db *carddavdb, const char *mailbox, int commit) -{ - struct bind_val bval = { - { ":mailbox", SQLITE_TEXT, { .s = mailbox } }, - { NULL, SQLITE_NULL, { .s = NULL } } }; - int r; - - r = dav_exec(carddavdb->db, CMD_DELMBOX, bval, NULL, NULL, - &carddavdb->stmtSTMT_DELMBOX); - if (r) return r; - - /* commit transaction */ - if (commit) { - r = carddav_commit(carddavdb); - if (r) return r; - } - - return 0; -}
View file
cyrus-imapd-2.5.tar.gz/imap/carddav_db.h
Deleted
@@ -1,121 +0,0 @@ -/* carddav_db.h -- abstract interface for per-user CardDAV database - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef CARDDAV_DB_H -#define CARDDAV_DB_H - -#include <config.h> - -#include "dav_db.h" -#include "strarray.h" - -struct carddav_db; - -#define CARDDAV_CREATE 0x01 -#define CARDDAV_TRUNC 0x02 - -struct carddav_data { - struct dav_data dav; /* MUST be first so we can typecast */ - unsigned version; - const char *vcard_uid; - unsigned kind; - const char *fullname; - const char *name; - const char *nickname; - strarray_t emails; - strarray_t member_uids; -}; - -/* prepare for carddav operations in this process */ -int carddav_init(void); - -/* done with all carddav operations for this process */ -int carddav_done(void); - -/* get a database handle corresponding to mailbox */ -struct carddav_db *carddav_open_mailbox(struct mailbox *mailbox, int flags); -struct carddav_db *carddav_open_userid(const char *userid, int flags); - -/* close this handle */ -int carddav_close(struct carddav_db *carddavdb); - -/* lookup an entry from 'carddavdb' by resource - (optionally inside a transaction for updates) */ -int carddav_lookup_resource(struct carddav_db *carddavdb, - const char *mailbox, const char *resource, - int lock, struct carddav_data **result); - -/* lookup an entry from 'carddavdb' by iCal UID - (optionally inside a transaction for updates) */ -int carddav_lookup_uid(struct carddav_db *carddavdb, const char *ical_uid, - int lock, struct carddav_data **result); - -/* check if an email address exists on any card */ -int carddav_getemail(struct carddav_db *carddavdb, const char *key); -strarray_t *carddav_getgroup(struct carddav_db *carddavdb, const char *key); - -/* process each entry for 'mailbox' in 'carddavdb' with cb() */ -int carddav_foreach(struct carddav_db *carddavdb, const char *mailbox, - int (*cb)(void *rock, void *data), - void *rock); - -/* write an entry to 'carddavdb' */ -int carddav_write(struct carddav_db *carddavdb, struct carddav_data *cdata, - int commit); - -/* delete an entry from 'carddavdb' */ -int carddav_delete(struct carddav_db *carddavdb, unsigned rowid, int commit); - -/* delete all entries for 'mailbox' from 'carddavdb' */ -int carddav_delmbox(struct carddav_db *carddavdb, const char *mailbox, int commit); - -/* begin transaction */ -int carddav_begin(struct carddav_db *carddavdb); - -/* commit transaction */ -int carddav_commit(struct carddav_db *carddavdb); - -/* abort transaction */ -int carddav_abort(struct carddav_db *carddavdb); - -#endif /* CARDDAV_DB_H */
View file
cyrus-imapd-2.5.tar.gz/imap/conversations.c
Deleted
@@ -1,1957 +0,0 @@ -/* conversations.c -- Routines for dealing with the conversation database - * - * Copyright (c) 1994-2010 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> - -#include <stdio.h> -#include <string.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <netinet/in.h> -#include <errno.h> -#include <ctype.h> -#include <stdlib.h> - -#include "acl.h" -#include "annotate.h" -#include "append.h" -#include "assert.h" -#include "charset.h" -#include "dlist.h" -#include "exitcodes.h" -#include "hash.h" -#include "imap_err.h" -#include "global.h" -#include "imapd.h" -#include "lsort.h" -#include "mailbox.h" -#include "map.h" -#include "mboxname.h" -#include "message.h" -#include "parseaddr.h" -#include "search_engines.h" -#include "seen.h" -#include "strhash.h" -#include "stristr.h" -#include "sync_log.h" -#include "syslog.h" -#include "util.h" -#include "xmalloc.h" -#include "xstrlcpy.h" -#include "xstrlcat.h" -#include "xstats.h" -#include "times.h" - -#include "conversations.h" - -#define CONVERSATION_ID_STRMAX (1+sizeof(conversation_id_t)*2) - -/* per user conversations db extension */ -#define FNAME_CONVERSATIONS_SUFFIX "conversations" -#define FNKEY "$FOLDER_NAMES" -#define CFKEY "$COUNTED_FLAGS" - -#define DB config_conversations_db - -#define CONVERSATIONS_VERSION 0 - -static char *convdir = NULL; -static char *suffix = NULL; - -static int check_msgid(const char *msgid, size_t len, size_t *lenp); -static int _conversations_parse(const char *data, size_t datalen, - arrayu64_t *cids, time_t *stampp); -static int _conversations_set_key(struct conversations_state *state, - const char *key, size_t keylen, - const arrayu64_t *cids, time_t stamp); - -EXPORTED void conversations_set_directory(const char *dir) -{ - free(convdir); - convdir = xstrdupnull(dir); -} - -EXPORTED void conversations_set_suffix(const char *suff) -{ - free(suffix); - suffix = xstrdupnull(suff); -} - -static char *conversations_path(struct mboxname_parts *parts) -{ - const char *suff = (suffix ? suffix : FNAME_CONVERSATIONS_SUFFIX); - /* only users have conversations. Later we may introduce the idea of - * "conversationroot" in the same way we have quotaroot, but for now - * it's hard-coded as the user */ - if (!parts->userid) - return NULL; - if (convdir) - return strconcat(convdir, "/", parts->userid, ".", suff, (char *)NULL); - return mboxname_conf_getpath(parts, suff); -} - -EXPORTED char *conversations_getuserpath(const char *username) -{ - struct mboxname_parts parts; - char *fname; - - if (mboxname_userid_to_parts(username, &parts)) - return NULL; - fname = conversations_path(&parts); - mboxname_free_parts(&parts); - - return fname; -} - -EXPORTED char *conversations_getmboxpath(const char *mboxname) -{ - struct mboxname_parts parts; - char *fname; - - if (mboxname_to_parts(mboxname, &parts)) - return NULL; - fname = conversations_path(&parts); - mboxname_free_parts(&parts); - - return fname; -} - -static int _init_counted(struct conversations_state *state, - const char *val, int vallen) -{ - int r; - - if (!vallen) { - val = config_getstring(IMAPOPT_CONVERSATIONS_COUNTED_FLAGS); - if (!val) val = ""; - vallen = strlen(val); - r = cyrusdb_store(state->db, CFKEY, strlen(CFKEY), - val, vallen, &state->txn); - if (r) { - syslog(LOG_ERR, "Failed to write counted_flags"); - return r; - } - } - - /* remove any existing value */ - if (state->counted_flags) { - strarray_free(state->counted_flags); - state->counted_flags = NULL; - } - - /* add the value only if there's some flags set */ - if (vallen) { - state->counted_flags = strarray_nsplit(val, vallen, " ", /*flags*/0); - } - - return 0; -} - -EXPORTED int conversations_open_path(const char *fname, struct conversations_state **statep) -{ - struct conversations_open *open = NULL; - const char *val = NULL; - size_t vallen = 0; - int r = 0; - - if (!fname) - return IMAP_MAILBOX_BADNAME; - - for (open = open_conversations; open; open = open->next) { - if (!strcmp(open->s.path, fname)) - return IMAP_CONVERSATIONS_ALREADY_OPEN; - } - - open = xzmalloc(sizeof(struct conversations_open)); - - r = cyrusdb_open(DB, fname, CYRUSDB_CREATE, &open->s.db); - if (r || open->s.db == NULL) { - free(open); - return IMAP_IOERROR; - } - - open->s.path = xstrdup(fname); - open->next = open_conversations; - open_conversations = open; - - /* ensure a write lock immediately, and also load the counted flags */ - cyrusdb_fetchlock(open->s.db, CFKEY, strlen(CFKEY), - &val, &vallen, &open->s.txn); - _init_counted(&open->s, val, vallen); - - /* we should just read the folder names up front too */ - open->s.folder_names = strarray_new(); - - /* if there's a value, parse as a dlist */ - if (!cyrusdb_fetch(open->s.db, FNKEY, strlen(FNKEY), - &val, &vallen, &open->s.txn)) { - struct dlist *dl = NULL; - struct dlist *dp; - dlist_parsemap(&dl, 0, val, vallen); - for (dp = dl->head; dp; dp = dp->next) { - strarray_append(open->s.folder_names, dlist_cstring(dp)); - } - dlist_free(&dl); - } - - /* create the status cache */ - construct_hash_table(&open->s.folderstatus, open->s.folder_names->count/4+4, 0); - - /* create the cid rename table */ - construct_hashu64_table(&open->s.cidrenames, 1023, 0); - - *statep = &open->s; - - return 0; -} - -EXPORTED int conversations_open_user(const char *username, struct conversations_state **statep) -{ - char *path = conversations_getuserpath(username); - int r; - if (!path) return IMAP_MAILBOX_BADNAME; - r = conversations_open_path(path, statep); - free(path); - return r; -} - -EXPORTED int conversations_open_mbox(const char *mboxname, struct conversations_state **statep) -{ - char *path = conversations_getmboxpath(mboxname); - int r; - if (!path) return IMAP_MAILBOX_BADNAME; - r = conversations_open_path(path, statep); - free(path); - return r; -} - -EXPORTED struct conversations_state *conversations_get_path(const char *fname) -{ - struct conversations_open *open = NULL; - - for (open = open_conversations; open; open = open->next) { - if (!strcmp(open->s.path, fname)) - return &open->s; - } - - return NULL; -} - -EXPORTED struct conversations_state *conversations_get_user(const char *username) -{ - struct conversations_state *state; - char *path = conversations_getuserpath(username); - if (!path) return NULL; - state = conversations_get_path(path); - free(path); - return state; -} - -EXPORTED struct conversations_state *conversations_get_mbox(const char *mboxname) -{ - struct conversations_state *state; - char *path = conversations_getmboxpath(mboxname); - if (!path) return NULL; - state = conversations_get_path(path); - free(path); - return state; -} - - -static void _conv_remove(struct conversations_state *state) -{ - struct conversations_open **prevp = &open_conversations; - struct conversations_open *cur; - - for (cur = open_conversations; cur; cur = cur->next) { - if (state == &cur->s) { - /* found it! */ - *prevp = cur->next; - free(cur->s.path); - if (cur->s.counted_flags) - strarray_free(cur->s.counted_flags); - if (cur->s.folder_names) - strarray_free(cur->s.folder_names); - free(cur); - return; - } - prevp = &cur->next; - } - - fatal("unknown conversation db closed", EC_SOFTWARE); -} - -static void conversations_abortcache(struct conversations_state *state) -{ - /* still gotta clean up */ - free_hash_table(&state->folderstatus, free); -} - -static void conversations_abortcidrenames(struct conversations_state *state) -{ - /* still gotta clean up */ - free_hashu64_table(&state->cidrenames, free); -} - -static void commitstatus_cb(const char *key, void *data, void *rock) -{ - conv_status_t *status = (conv_status_t *)data; - struct conversations_state *state = (struct conversations_state *)rock; - - conversation_storestatus(state, key, strlen(key), status); - /* just in case convdb has a higher modseq for any reason (aka deleted and - * recreated while a replica was still valid with the old user) */ - mboxname_setmodseq(key+1, status->modseq); - sync_log_mailbox(key+1); /* skip the leading F */ -} - -static void conversations_commitcache(struct conversations_state *state) -{ - hash_enumerate(&state->folderstatus, commitstatus_cb, state); - free_hash_table(&state->folderstatus, free); -} - -struct rename_rock { - struct conversations_state *state; - conversation_id_t from_cid; - conversation_id_t to_cid; - unsigned long entries_seen; - unsigned long entries_renamed; -}; - -static int do_one_rename(void *rock, - const char *key, size_t keylen, - const char *data, size_t datalen) -{ - struct rename_rock *rrock = (struct rename_rock *)rock; - arrayu64_t cids = ARRAYU64_INITIALIZER; - time_t stamp; - int r; - int removed; - - r = check_msgid(key, keylen, NULL); - if (r) return r; - - r = _conversations_parse(data, datalen, &cids, &stamp); - if (r) goto done; - - rrock->entries_seen++; - - removed = arrayu64_remove_all(&cids, rrock->from_cid); - if (!removed) goto done; - - arrayu64_add(&cids, rrock->to_cid); - - rrock->entries_renamed++; - - r = _conversations_set_key(rrock->state, key, keylen, - &cids, stamp); - -done: - arrayu64_fini(&cids); - return r; -} - -EXPORTED void conversations_rename_cidentry(struct conversations_state *state, - conversation_id_t from, - conversation_id_t to) -{ - struct rename_rock rrock; - - if (from == to) return; - - memset(&rrock, 0, sizeof(rrock)); - rrock.state = state; - rrock.from_cid = from; - rrock.to_cid = to; - - cyrusdb_foreach(state->db, "<", 1, NULL, do_one_rename, &rrock, &state->txn); - - syslog(LOG_NOTICE, "conversations_rename_cid: saw %lu entries, renamed %lu" - " from " CONV_FMT " to " CONV_FMT, - rrock.entries_seen, rrock.entries_renamed, - from, to); -} - -static void commitrename_cb(uint64_t fromval, void *data, void *rock) -{ - conversation_id_t to = *((conversation_id_t *)data); - conversation_id_t from = (conversation_id_t)fromval; - struct conversations_state *state = (struct conversations_state *)rock; - - conv_folder_t *folder = NULL; - conversation_t *conv = NULL; - int r = 0; - - while ((data = hashu64_lookup(to, &state->cidrenames))) { - /* got renamed again! */ - to = *((uint64_t *)data); - } - - /* Use the B record to find the mailboxes for a CID rename. - * The rename events will decrease the NUM_RECORDS count back - * to zero, and the record will delete itself! */ - r = conversation_load(state, from, &conv); - if (r) return; - if (!conv) return; - - for (folder = conv->folders ; folder ; folder = folder->next) { - const char *mboxname = strarray_nth(state->folder_names, folder->number); - struct mailbox *mailbox = NULL; - - r = mailbox_open_iwl(mboxname, &mailbox); - if (r) break; - - r = mailbox_cid_rename(mailbox, from, to); - mailbox_close(&mailbox); - - if (r) break; - } - - conversation_free(conv); - - /* XXX - COULD try to read the B key and confirm that it doesn't exist any more... */ -} - -static void conversations_commitcidrenames(struct conversations_state *state) -{ - hashu64_enumerate(&state->cidrenames, commitrename_cb, state); - free_hashu64_table(&state->cidrenames, free); -} - -EXPORTED int conversations_abort(struct conversations_state **statep) -{ - struct conversations_state *state = *statep; - - if (!state) return 0; - - *statep = NULL; - - /* clean up hashes */ - conversations_abortcidrenames(state); - conversations_abortcache(state); - - if (state->db) { - if (state->txn) - cyrusdb_abort(state->db, state->txn); - cyrusdb_close(state->db); - } - - _conv_remove(state); - - return 0; -} - -EXPORTED int conversations_commit(struct conversations_state **statep) -{ - struct conversations_state *state = *statep; - int r = 0; - - if (!state) return 0; - - *statep = NULL; - - /* clean up the renames first, it will update the cache */ - conversations_commitcidrenames(state); - /* cache second - also writes to to DB */ - conversations_commitcache(state); - - /* finally it's safe to commit the DB itself */ - if (state->db) { - if (state->txn) - r = cyrusdb_commit(state->db, state->txn); - cyrusdb_close(state->db); - } - - _conv_remove(state); - - return r; -} - -static int check_msgid(const char *msgid, size_t len, size_t *lenp) -{ - if (msgid == NULL) - return IMAP_INVALID_IDENTIFIER; - - if (!len) - len = strlen(msgid); - - if (msgid0 != '<' || msgidlen-1 != '>' || strchr(msgid, '@') == NULL) - return IMAP_INVALID_IDENTIFIER; - - if (lenp) - *lenp = len; - - return 0; -} - -static int _conversations_set_key(struct conversations_state *state, - const char *key, size_t keylen, - const arrayu64_t *cids, time_t stamp) -{ - int r; - struct buf buf; - int version = CONVERSATIONS_VERSION; - int i; - - /* XXX: should this be a delete operation? */ - assert(cids->count); - - buf_init(&buf); - - if (state->db == NULL) - return IMAP_IOERROR; - - buf_printf(&buf, "%d ", version); - for (i = 0; i < cids->count; i++) { - conversation_id_t cid = arrayu64_nth(cids, i); - if (i) buf_putc(&buf, ','); - buf_printf(&buf, CONV_FMT, cid); - } - buf_printf(&buf, " %lu", stamp); - - r = cyrusdb_store(state->db, - key, keylen, - buf.s, buf.len, - &state->txn); - - buf_free(&buf); - if (r) - return IMAP_IOERROR; - - return 0; -} - -static int _sanity_check_counts(conversation_t *conv) -{ - conv_folder_t *folder; - uint32_t num_records = 0; - uint32_t exists = 0; - - for (folder = conv->folders; folder; folder = folder->next) { - num_records += folder->num_records; - exists += folder->exists; - } - - if (num_records != conv->num_records) - return IMAP_INTERNAL; - - if (exists != conv->exists) - return IMAP_INTERNAL; - - return 0; -} - - -EXPORTED int conversations_add_msgid(struct conversations_state *state, - const char *msgid, - conversation_id_t cid) -{ - arrayu64_t cids = ARRAYU64_INITIALIZER; - size_t keylen; - int r = 0; - - r = check_msgid(msgid, 0, &keylen); - if (r) goto done; - - r = conversations_get_msgid(state, msgid, &cids); - - /* read failure will mean cids is empty, but we can still add this one */ - if (r || arrayu64_find(&cids, cid, 0) < 0) { - arrayu64_append(&cids, cid); - r = _conversations_set_key(state, msgid, keylen, &cids, time(NULL)); - } - -done: - arrayu64_fini(&cids); - return r; -} - -static int _conversations_parse(const char *data, size_t datalen, - arrayu64_t *cids, time_t *stampp) -{ - const char *rest; - size_t restlen; - int r; - conversation_id_t cid; - bit64 tval; - bit64 version; - - /* make sure we don't leak old data */ - arrayu64_truncate(cids, 0); - - r = parsenum(data, &rest, datalen, &version); - if (r) return IMAP_MAILBOX_BADFORMAT; - - if (rest0 != ' ') - return IMAP_MAILBOX_BADFORMAT; - rest++; /* skip space */ - restlen = datalen - (rest - data); - - if (version != CONVERSATIONS_VERSION) { - /* XXX - an error code for "incorrect version"? */ - return IMAP_MAILBOX_BADFORMAT; - } - - if (restlen < 17) - return IMAP_MAILBOX_BADFORMAT; - - while (1) { - r = parsehex(rest, &rest, 16, &cid); - if (r) return IMAP_MAILBOX_BADFORMAT; - arrayu64_append(cids, cid); - if (rest0 == ' ') break; - if (rest0 != ',') return IMAP_MAILBOX_BADFORMAT; - rest++; /* skip comma */ - } - - if (rest0 != ' ') - return IMAP_MAILBOX_BADFORMAT; - rest++; /* skip space */ - restlen = datalen - (rest - data); - - r = parsenum(rest, &rest, restlen, &tval); - if (r) return IMAP_MAILBOX_BADFORMAT; - - /* should have parsed to the end of the string */ - if (rest - data != (int)datalen) - return IMAP_MAILBOX_BADFORMAT; - - if (stampp) *stampp = tval; - - return 0; -} - -EXPORTED int conversations_get_msgid(struct conversations_state *state, - const char *msgid, - arrayu64_t *cids) -{ - size_t keylen; - size_t datalen = 0; - const char *data; - int r; - - r = check_msgid(msgid, 0, &keylen); - if (r) - return r; - - r = cyrusdb_fetch(state->db, - msgid, keylen, - &data, &datalen, - &state->txn); - - if (!r) r = _conversations_parse(data, datalen, cids, NULL); - - if (r) arrayu64_truncate(cids, 0); - - return 0; -} - -/* - * Normalise a subject string, to a form which can be used for deciding - * whether a message belongs in the same conversation as it's antecedent - * messages. What we're doing here is the same idea as the "base - * subject" algorithm described in RFC5256 but slightly adapted from - * experience. Differences are: - * - * - We eliminate all whitespace; RFC5256 normalises any sequence - * of whitespace characters to a single SP. We do this because - * we have observed combinations of buggy client software both - * add and remove whitespace around folding points. - * - * - Because we eliminate whitespace entirely, and whitespace helps - * delimit some of our other replacements, we do that whitespace - * step last instead of first. - * - * - We eliminate leading tokens like Re: and Fwd: using a simpler - * and more generic rule than RFC5256's; this rule catches a number - * of semantically identical prefixes in other human languages, but - * unfortunately also catches lots of other things. We think we can - * get away with this because the normalised subject is never directly - * seen by human eyes, so some information loss is acceptable as long - * as the subjects in different messages match correctly. - */ -EXPORTED void conversation_normalise_subject(struct buf *s) -{ - static int initialised_res = 0; - static regex_t whitespace_re; - static regex_t relike_token_re; - static regex_t blob_re; - int r; - - if (!initialised_res) { - r = regcomp(&whitespace_re, " \t\r\n+", REG_EXTENDED); - assert(r == 0); - r = regcomp(&relike_token_re, "^ \t*A-Za-z0-9+:", REG_EXTENDED); - assert(r == 0); - r = regcomp(&blob_re, "^ \t*\\^+\\", REG_EXTENDED); - assert(r == 0); - initialised_res = 1; - } - - /* step 1 is to decode any RFC2047 MIME encoding of the header - * field, but we assume that has already happened */ - - /* step 2 is to eliminate all "Re:"-like tokens and blobs - * at the start */ - while (buf_replace_one_re(s, &relike_token_re, NULL) || - buf_replace_one_re(s, &blob_re, NULL)) - ; - - /* step 3 is eliminating whitespace. */ - buf_replace_all_re(s, &whitespace_re, NULL); -} - -static int write_folders(struct conversations_state *state) -{ - struct dlist *dl = dlist_newlist(NULL, NULL); - struct buf buf = BUF_INITIALIZER; - int r; - int i; - - for (i = 0; i < state->folder_names->count; i++) { - const char *fname = strarray_nth(state->folder_names, i); - dlist_setatom(dl, NULL, fname); - } - - dlist_printbuf(dl, 0, &buf); - dlist_free(&dl); - - r = cyrusdb_store(state->db, FNKEY, strlen(FNKEY), - buf.s, buf.len, &state->txn); - - buf_free(&buf); - - return r; -} - -static int folder_number(struct conversations_state *state, - const char *name, - int create_flag) -{ - int pos = strarray_find(state->folder_names, name, 0); - int r; - - /* if we have to add it, then save the keys back */ - if (pos < 0 && create_flag) { - /* replace the first unused if there is one */ - pos = strarray_find(state->folder_names, "-", 0); - if (pos >= 0) - strarray_set(state->folder_names, pos, name); - /* otherwise append */ - else - pos = strarray_append(state->folder_names, name); - - /* store must succeed */ - r = write_folders(state); - if (r) abort(); - } - - return pos; -} - -static int folder_number_rename(struct conversations_state *state, - const char *from_name, - const char *to_name) -{ - int pos = strarray_find(state->folder_names, from_name, 0); - - if (pos < 0) return 0; /* nothing to do! */ - - /* replace the name - set to '-' if deleted */ - strarray_set(state->folder_names, pos, to_name ? to_name : "-"); - - return write_folders(state); -} - -EXPORTED int conversation_storestatus(struct conversations_state *state, - const char *key, size_t keylen, - const conv_status_t *status) -{ - struct dlist *dl = NULL; - struct buf buf = BUF_INITIALIZER; - int version = CONVERSATIONS_VERSION; - int r; - - dl = dlist_newlist(NULL, NULL); - dlist_setnum64(dl, "MODSEQ", status->modseq); - dlist_setnum32(dl, "EXISTS", status->exists); - dlist_setnum32(dl, "UNSEEN", status->unseen); - - buf_printf(&buf, "%d ", version); - dlist_printbuf(dl, 0, &buf); - dlist_free(&dl); - - r = cyrusdb_store(state->db, - key, keylen, - buf.s, buf.len, - &state->txn); - - buf_free(&buf); - - return r; -} - -EXPORTED int conversation_setstatus(struct conversations_state *state, - const char *mboxname, - const conv_status_t *status) -{ - char *key = strconcat("F", mboxname, (char *)NULL); - conv_status_t *cachestatus = NULL; - - cachestatus = hash_lookup(key, &state->folderstatus); - if (!cachestatus) { - cachestatus = xzmalloc(sizeof(conv_status_t)); - hash_insert(key, cachestatus, &state->folderstatus); - } - - /* either way it's in the hash, update the value */ - *cachestatus = *status; - - free(key); - - return 0; -} - -EXPORTED int conversation_store(struct conversations_state *state, - const char *key, int keylen, - conversation_t *conv) -{ - struct dlist *dl, *n, *nn; - struct buf buf = BUF_INITIALIZER; - const conv_folder_t *folder; - const conv_sender_t *sender; - int version = CONVERSATIONS_VERSION; - int i; - int r; - - dl = dlist_newlist(NULL, NULL); - dlist_setnum64(dl, "MODSEQ", conv->modseq); - dlist_setnum32(dl, "NUMRECORDS", conv->num_records); - dlist_setnum32(dl, "EXISTS", conv->exists); - dlist_setnum32(dl, "UNSEEN", conv->unseen); - n = dlist_newlist(dl, "COUNTS"); - if (state->counted_flags) { - for (i = 0; i < state->counted_flags->count; i++) { - const char *flag = strarray_nth(state->counted_flags, i); - dlist_setnum32(n, flag, conv->countsi); - } - } - - n = dlist_newlist(dl, "FOLDER"); - for (folder = conv->folders ; folder ; folder = folder->next) { - if (!folder->num_records) - continue; - nn = dlist_newlist(n, "FOLDER"); - dlist_setnum32(nn, "FOLDERNUM", folder->number); - dlist_setnum64(nn, "MODSEQ", folder->modseq); - dlist_setnum32(nn, "NUMRECORDS", folder->num_records); - dlist_setnum32(nn, "EXISTS", folder->exists); - dlist_setnum32(nn, "UNSEEN", folder->unseen); - } - - n = dlist_newlist(dl, "SENDER"); - i = 0; - for (sender = conv->senders ; sender ; sender = sender->next) { - if (!sender->exists) - continue; - /* don't ever store more than 100 senders */ - if (++i >= 100) break; - nn = dlist_newlist(n, "SENDER"); - /* envelope form */ - dlist_setatom(nn, "NAME", sender->name); - dlist_setatom(nn, "ROUTE", sender->route); - dlist_setatom(nn, "MAILBOX", sender->mailbox); - dlist_setatom(nn, "DOMAIN", sender->domain); - dlist_setnum32(nn, "LASTSEEN", sender->lastseen); - dlist_setnum32(nn, "EXISTS", sender->exists); - } - - dlist_setatom(dl, "SUBJECT", conv->subject); - - dlist_setnum32(dl, "SIZE", conv->size); - - buf_printf(&buf, "%d ", version); - dlist_printbuf(dl, 0, &buf); - dlist_free(&dl); - - if (_sanity_check_counts(conv)) { - syslog(LOG_ERR, "IOERROR: conversations_audit on store: %s %.*s %.*s", - state->path, keylen, key, (int)buf.len, buf.s); - } - - r = cyrusdb_store(state->db, key, keylen, buf.s, buf.len, &state->txn); - - buf_free(&buf); - - return r; -} - -static int _conversation_save(struct conversations_state *state, - const char *key, int keylen, - conversation_t *conv) -{ - const conv_folder_t *folder; - int r; - - /* see if any 'F' keys need to be changed */ - for (folder = conv->folders ; folder ; folder = folder->next) { - const char *mboxname = strarray_nth(state->folder_names, folder->number); - int exists_diff = 0; - int unseen_diff = 0; - conv_status_t status = CONV_STATUS_INIT; - - /* case: full removal of conversation - make sure to remove - * unseen as well */ - if (folder->exists) { - if (folder->prev_exists) { - /* both exist, just check for unseen changes */ - unseen_diff = !!conv->unseen - !!conv->prev_unseen; - } - else { - /* adding, check if it's unseen */ - exists_diff = 1; - if (conv->unseen) unseen_diff = 1; - } - } - else if (folder->prev_exists) { - /* removing, check if it WAS unseen */ - exists_diff = -1; - if (conv->prev_unseen) unseen_diff = -1; - } - else { - /* we don't care about unseen if the cid is not registered - * in this folder, and wasn't previously either */ - } - - /* XXX - it's super inefficient to be doing this for - * every cid in every folder in the transaction. Big - * wins available by caching these in memory and writing - * once at the end of the transaction */ - r = conversation_getstatus(state, mboxname, &status); - if (r) goto done; - if (exists_diff || unseen_diff || status.modseq < conv->modseq) { - if (status.modseq < conv->modseq) - status.modseq = conv->modseq; - status.exists += exists_diff; - status.unseen += unseen_diff; - r = conversation_setstatus(state, mboxname, &status); - if (r) goto done; - } - } - - if (conv->num_records) { - r = conversation_store(state, key, keylen, conv); - } - else { - /* last existing record removed - clean up the 'B' record */ - r = cyrusdb_delete(state->db, key, keylen, &state->txn, 1); - } - - -done: - if (!r) - conv->dirty = 0; - - return r; -} - -EXPORTED int conversation_save(struct conversations_state *state, - conversation_id_t cid, - conversation_t *conv) -{ - char bkeyCONVERSATION_ID_STRMAX+2; - - if (!conv) - return IMAP_INTERNAL; - if (!conv->dirty) - return 0; - - /* old pre-conversations message, nothing to do */ - if (!cid) - return 0; - xstats_inc(CONV_SAVE); - - snprintf(bkey, sizeof(bkey), "B" CONV_FMT, cid); - - return _conversation_save(state, bkey, strlen(bkey), conv); -} - -EXPORTED int conversation_parsestatus(const char *data, size_t datalen, - conv_status_t *status) -{ - bit64 version; - const char *rest; - size_t restlen; - struct dlist *dl = NULL; - struct dlist *n; - int r; - - status->modseq = 0; - status->exists = 0; - status->unseen = 0; - - r = parsenum(data, &rest, datalen, &version); - if (r) return IMAP_MAILBOX_BADFORMAT; - - if (rest0 != ' ') - return IMAP_MAILBOX_BADFORMAT; - rest++; /* skip space */ - restlen = datalen - (rest - data); - - if (version != CONVERSATIONS_VERSION) { - /* XXX - an error code for "incorrect version"? */ - return IMAP_MAILBOX_BADFORMAT; - } - - r = dlist_parsemap(&dl, 0, rest, restlen); - if (r) return r; - - n = dl->head; - if (n) { - status->modseq = dlist_num(n); - n = n->next; - } - if (n) { - status->exists = dlist_num(n); - n = n->next; - } - if (n) { - status->unseen = dlist_num(n); - } - - dlist_free(&dl); - return 0; -} - -EXPORTED int conversation_getstatus(struct conversations_state *state, - const char *mboxname, - conv_status_t *status) -{ - char *key = strconcat("F", mboxname, (char *)NULL); - const char *data; - size_t datalen; - int r = 0; - conv_status_t *cachestatus = NULL; - - cachestatus = hash_lookup(key, &state->folderstatus); - if (cachestatus) { - *status = *cachestatus; - goto done; - } - - if (!state->db) { - r = IMAP_IOERROR; - goto done; - } - - r = cyrusdb_fetch(state->db, - key, strlen(key), - &data, &datalen, - &state->txn); - - if (r == CYRUSDB_NOTFOUND) { - /* not existing is not an error */ - r = 0; - goto done; - } - if (r) goto done; - - r = conversation_parsestatus(data, datalen, status); - - done: - if (r) - syslog(LOG_ERR, "IOERROR: conversations invalid status %s", mboxname); - - free(key); - - return r; -} - -EXPORTED conv_folder_t *conversation_get_folder(conversation_t *conv, - int number, int create_flag) -{ - conv_folder_t *folder, **nextp = &conv->folders; - - if (number < 0) - return NULL; - - /* first check if it already exists */ - for (folder = conv->folders ; folder ; folder = folder->next) { - if (folder->number < number) - nextp = &folder->next; - else if (folder->number == number) - return folder; - else - break; - } - - if (!create_flag) - return NULL; - - /* not found, create a new one */ - folder = xzmalloc(sizeof(*folder)); - folder->number = number; - folder->next = *nextp; - *nextp = folder; - conv->dirty = 1; - - return folder; -} - -EXPORTED int conversation_parse(struct conversations_state *state, - const char *data, size_t datalen, - conversation_t **convp) -{ - const char *rest; - int i; - int restlen; - bit64 version; - struct dlist *dl = NULL; - struct dlist *n, *nn; - conversation_t *conv; - conv_folder_t *folder; - int r; - - *convp = NULL; - - r = parsenum(data, &rest, datalen, &version); - if (r) return IMAP_MAILBOX_BADFORMAT; - - if (rest0 != ' ') return IMAP_MAILBOX_BADFORMAT; - rest++; /* skip space */ - restlen = datalen - (rest - data); - - if (version != CONVERSATIONS_VERSION) return IMAP_MAILBOX_BADFORMAT; - - r = dlist_parsemap(&dl, 0, rest, restlen); - if (r) return r; - - conv = conversation_new(state); - - n = dlist_getchildn(dl, 0); - if (n) - conv->modseq = dlist_num(n); - n = dlist_getchildn(dl, 1); - if (n) - conv->num_records = dlist_num(n); - n = dlist_getchildn(dl, 2); - if (n) - conv->exists = dlist_num(n); - n = dlist_getchildn(dl, 3); - if (n) - conv->unseen = dlist_num(n); - n = dlist_getchildn(dl, 4); - if (state->counted_flags) { - nn = n ? n->head : NULL; - for (i = 0; i < state->counted_flags->count; i++) { - if (nn) { - conv->countsi = dlist_num(nn); - nn = nn->next; - } - else - conv->countsi = 0; - } - } - - n = dlist_getchildn(dl, 5); - for (n = (n ? n->head : NULL) ; n ; n = n->next) { - int number; - nn = dlist_getchildn(n, 0); - if (!nn) - continue; - number = dlist_num(nn); - folder = conversation_get_folder(conv, number, 1); - - nn = dlist_getchildn(n, 1); - if (nn) - folder->modseq = dlist_num(nn); - nn = dlist_getchildn(n, 2); - if (nn) - folder->num_records = dlist_num(nn); - nn = dlist_getchildn(n, 3); - if (nn) - folder->exists = dlist_num(nn); - nn = dlist_getchildn(n, 4); - if (nn) - folder->unseen = dlist_num(nn); - - folder->prev_exists = folder->exists; - } - - n = dlist_getchildn(dl, 6); - for (n = (n ? n->head : NULL) ; n ; n = n->next) { - struct dlist *nn2, *nn3, *nn4, *nn5, *nn6; - nn = dlist_getchildn(n, 0); - nn2 = dlist_getchildn(n, 1); - nn3 = dlist_getchildn(n, 2); - nn4 = dlist_getchildn(n, 3); - nn5 = dlist_getchildn(n, 4); - nn6 = dlist_getchildn(n, 5); - if (nn6) - conversation_update_sender(conv, nn->sval, nn2->sval, - nn3->sval, nn4->sval, - dlist_num(nn5), dlist_num(nn6)); - else if (nn4) /* XXX: remove when cleaned up - handle old-style too */ - conversation_update_sender(conv, nn->sval, nn2->sval, - nn3->sval, nn4->sval, - 0/*time_t*/, (1<<30)/*exists*/); - /* INSANE EXISTS NUMBER MEANS IT NEVER GETS CLEANED UP */ - } - - n = dlist_getchildn(dl, 7); - if (n) conv->subject = xstrdupnull(dlist_cstring(n)); - - n = dlist_getchildn(dl, 8); - if (n) conv->size = dlist_num(n); - - conv->prev_unseen = conv->unseen; - - dlist_free(&dl); - conv->dirty = 0; - *convp = conv; - return 0; -} - -EXPORTED int conversation_load(struct conversations_state *state, - conversation_id_t cid, - conversation_t **convp) -{ - const char *data; - size_t datalen; - char bkeyCONVERSATION_ID_STRMAX+2; - int r; - - snprintf(bkey, sizeof(bkey), "B" CONV_FMT, cid); - r = cyrusdb_fetch(state->db, - bkey, strlen(bkey), - &data, &datalen, - &state->txn); - - if (r == CYRUSDB_NOTFOUND) { - *convp = NULL; - return 0; - } else if (r != CYRUSDB_OK) { - return r; - } - xstats_inc(CONV_LOAD); - - r = conversation_parse(state, data, datalen, convp); - if (r) { - syslog(LOG_ERR, "IOERROR: conversations invalid conversation " - CONV_FMT, cid); - *convp = NULL; - } - - if (_sanity_check_counts(*convp)) { - syslog(LOG_ERR, "IOERROR: conversations_audit on load: %s %s %.*s", - state->path, bkey, (int)datalen, data); - } - - return 0; -} - -/* Parse just enough of the B record to retrieve the modseq. - * Fortunately the modseq is the first field after the record version - * number, given the way that _conversation_save() and dlist works. See - * _conversation_load() for the full shebang. */ -static int _conversation_load_modseq(const char *data, int datalen, - modseq_t *modseqp) -{ - const char *p = data; - const char *end = data + datalen; - bit64 version = ~0ULL; - int r; - - r = parsenum(p, &p, (end-p), &version); - if (r || version != CONVERSATIONS_VERSION) - return IMAP_MAILBOX_BADFORMAT; - - if ((end - p) < 4 || p0 != ' ' || p1 != '(') - return IMAP_MAILBOX_BADFORMAT; - p += 2; /* skip space and left parenthesis */ - - r = parsenum(p, &p, (end-p), modseqp); - if ((end - p) < 1 || *p != ' ') - return IMAP_MAILBOX_BADFORMAT; - - return 0; -} - -EXPORTED int conversation_get_modseq(struct conversations_state *state, - conversation_id_t cid, - modseq_t *modseqp) -{ - const char *data; - size_t datalen; - char bkeyCONVERSATION_ID_STRMAX+2; - int r; - - snprintf(bkey, sizeof(bkey), "B" CONV_FMT, cid); - r = cyrusdb_fetch(state->db, - bkey, strlen(bkey), - &data, &datalen, - &state->txn); - - if (r == CYRUSDB_NOTFOUND) { - *modseqp = 0; - return 0; - } else if (r != CYRUSDB_OK) { - return r; - } - xstats_inc(CONV_GET_MODSEQ); - - r = _conversation_load_modseq(data, datalen, modseqp); - if (r) { - syslog(LOG_ERR, "IOERROR: conversation_get_modseq: invalid conversation " - CONV_FMT, cid); - *modseqp = 0; - } - - return 0; -} - -EXPORTED conv_folder_t *conversation_find_folder(struct conversations_state *state, - conversation_t *conv, - const char *mboxname) -{ - int number = folder_number(state, mboxname, /*create*/0); - return conversation_get_folder(conv, number, /*create*/0); -} - -/* Compare a sender vs a new sender key (mailbox and domain). - * Returns 0 if identical, nonzero if different (sign indicates - * sort order, like strcmp()). - * - * This is not quite RFC compliant: we are comparing the - * localpart case insensitively even though the RFC says the - * interpretation is up to the domain itself. However this - * seems to yield better results. IRIS-1484 */ -static int sender_cmp(const conv_sender_t *sender, - const char *mailbox, - const char *domain) -{ - int d = strcasecmp(sender->domain, domain); - if (!d) - d = strcasecmp(sender->mailbox, mailbox); - return d; -} - -/* Choose a preferred mailbox. Returns <0 if @a is preferred, - * 0 if we don't care, and >0 if @b is preferred */ -static int sender_preferred_mailbox(const char *a, const char *b) -{ - /* choosing the lexically earlier string tends to keep - * capital letters, which is an arbitrary asthetic */ - return strcmpsafe(a, b); -} - -/* Choose a preferred domain. Returns <0 if @a is preferred, - * 0 if we don't care, and >0 if @b is preferred */ -static int sender_preferred_domain(const char *a, const char *b) -{ - /* choosing the lexically earlier string tends to keep - * capital letters, which is an arbitrary asthetic */ - return strcmpsafe(a, b); -} - -/* Choose a preferred route. Returns <0 if @a is preferred, - * 0 if we don't care, and >0 if @b is preferred */ -static int sender_preferred_route(const char *a, const char *b) -{ - /* choosing the lexically earlier string tends to keep - * capital letters, which is an arbitrary asthetic */ - return strcmpsafe(a, b); -} - -static int has_non_ascii(const char *s) -{ - for ( ; *s ; s++) { - if (*(unsigned char *)s > 0x7f) - return 1; - } - return 0; -} - -/* Choose a preferred name. Returns <0 if @a is preferred, - * 0 if we don't care, and >0 if @b is preferred */ -static int sender_preferred_name(const char *a, const char *b) -{ - int d; - char *sa = NULL; - char *sb = NULL; - - sa = charset_parse_mimeheader((a ? a : "")); - sb = charset_parse_mimeheader((b ? b : "")); - - /* A name with characters > 0x7f is preferred to a flat - * ascii one, on the assumption that this is more likely to - * contain an actual name rather than a romanisation. */ - d = has_non_ascii(sb) - has_non_ascii(sa); - - /* A longer name is preferred over a shorter. */ - if (!d) - d = strlen(sb) - strlen(sa); - - /* The lexically earlier name is preferred (earlier on the grounds - * that's more likely to start with a capital letter) */ - if (!d) - d = strcmp(sa, sb); - - if (!d) - d = strcmpsafe(a, b); - - free(sa); - free(sb); - return d; -} - -EXPORTED void conversation_update_sender(conversation_t *conv, - const char *name, - const char *route, - const char *mailbox, - const char *domain, - time_t lastseen, - int delta_exists) -{ - conv_sender_t *sender, *ptr, **nextp = &conv->senders; - - if (!mailbox || !domain) return; - - /* always re-stitch the found record, it's just simpler */ - for (sender = conv->senders; sender; sender = sender->next) { - if (!sender_cmp(sender, mailbox, domain)) - break; - nextp = &sender->next; - } - - if (sender) { - /* unstitch */ - *nextp = sender->next; - } - else { - /* we start with zero */ - sender = xzmalloc(sizeof(*sender)); - } - - /* counts first, may be just removing it */ - if (delta_exists <= 0 && (uint32_t)(- delta_exists) >= sender->exists) { - conv->dirty = 1; - free(sender->name); - free(sender->route); - free(sender->mailbox); - free(sender->domain); - free(sender); - return; - } - - /* otherwise update the counter */ - sender->exists += delta_exists; - - /* ensure the database is consistent regardless - * of message arrival order, update the record if the newly - * seen values are more preferred */ - if (!sender->name || sender_preferred_name(sender->name, name) > 0) { - free(sender->name); - sender->name = xstrdupnull(name); - } - - if (!sender->route || sender_preferred_route(sender->route, route) > 0) { - free(sender->route); - sender->route = xstrdupnull(route); - } - - if (!sender->mailbox || sender_preferred_mailbox(sender->mailbox, mailbox) > 0) { - free(sender->mailbox); - sender->mailbox = xstrdup(mailbox); - } - - if (!sender->domain || sender_preferred_domain(sender->domain, domain) > 0) { - free(sender->domain); - sender->domain = xstrdup(domain); - } - - /* last seen for display sorting */ - if (sender->lastseen < lastseen) { - sender->lastseen = lastseen; - } - - /* now re-stitch it into place */ - nextp = &conv->senders; - for (ptr = conv->senders; ptr; ptr = ptr->next) { - if (ptr->lastseen < sender->lastseen) - break; - if (sender->lastseen == ptr->lastseen && - sender_cmp(ptr, mailbox, domain) > 0) - break; - nextp = &ptr->next; - } - - sender->next = *nextp; - *nextp = sender; - - conv->dirty = 1; -} - -static void _apply_delta(uint32_t *valp, int delta) -{ - if (delta >= 0) { - *valp += delta; - } - else { - uint32_t decrease = -delta; - /* let us die where it broke */ - if (decrease <= *valp) - *valp -= decrease; - else - *valp = 0; - } -} - -EXPORTED void conversation_update(struct conversations_state *state, - conversation_t *conv, const char *mboxname, - int delta_num_records, - int delta_exists, int delta_unseen, - int delta_size, int *delta_counts, - modseq_t modseq) -{ - conv_folder_t *folder; - int number = folder_number(state, mboxname, /*create*/1); - int i; - - folder = conversation_get_folder(conv, number, /*create*/1); - - if (delta_num_records) { - _apply_delta(&conv->num_records, delta_num_records); - _apply_delta(&folder->num_records, delta_num_records); - conv->dirty = 1; - } - if (delta_exists) { - _apply_delta(&conv->exists, delta_exists); - _apply_delta(&folder->exists, delta_exists); - conv->dirty = 1; - } - if (delta_unseen) { - _apply_delta(&conv->unseen, delta_unseen); - _apply_delta(&folder->unseen, delta_unseen); - conv->dirty = 1; - } - if (delta_size) { - _apply_delta(&conv->size, delta_size); - conv->dirty = 1; - } - if (state->counted_flags) { - for (i = 0; i < state->counted_flags->count; i++) { - if (delta_countsi) { - _apply_delta(&conv->countsi, delta_countsi); - conv->dirty = 1; - } - } - } - if (modseq > conv->modseq) { - conv->modseq = modseq; - conv->dirty = 1; - } - if (modseq > folder->modseq) { - folder->modseq = modseq; - conv->dirty = 1; - } -} - -EXPORTED conversation_t *conversation_new(struct conversations_state *state) -{ - conversation_t *conv; - - conv = xzmalloc(sizeof(conversation_t)); - if (state->counted_flags) - conv->counts = xzmalloc(sizeof(uint32_t) * state->counted_flags->count); - conv->dirty = 1; - xstats_inc(CONV_NEW); - - return conv; -} - -EXPORTED void conversation_free(conversation_t *conv) -{ - conv_folder_t *folder; - conv_sender_t *sender; - - if (!conv) return; - - while ((folder = conv->folders)) { - conv->folders = folder->next; - free(folder); - } - - while ((sender = conv->senders)) { - conv->senders = sender->next; - free(sender->name); - free(sender->route); - free(sender->mailbox); - free(sender->domain); - free(sender); - } - - free(conv->subject); - free(conv->counts); - free(conv); -} - - -struct prune_rock { - struct conversations_state *state; - time_t thresh; - unsigned int nseen; - unsigned int ndeleted; -}; - -static int prunecb(void *rock, - const char *key, size_t keylen, - const char *data, size_t datalen) -{ - struct prune_rock *prock = (struct prune_rock *)rock; - arrayu64_t cids = ARRAYU64_INITIALIZER; - time_t stamp; - int r; - - prock->nseen++; - r = check_msgid(key, keylen, NULL); - if (r) goto done; - - r = _conversations_parse(data, datalen, &cids, &stamp); - if (r) goto done; - - /* keep records newer than the threshold */ - if (stamp >= prock->thresh) - goto done; - - prock->ndeleted++; - - r = cyrusdb_delete(prock->state->db, - key, keylen, - &prock->state->txn, - /*force*/1); - -done: - arrayu64_fini(&cids); - return r; -} - -EXPORTED int conversations_prune(struct conversations_state *state, - time_t thresh, unsigned int *nseenp, - unsigned int *ndeletedp) -{ - struct prune_rock rock = { state, thresh, 0, 0 }; - - cyrusdb_foreach(state->db, "<", 1, NULL, prunecb, &rock, &state->txn); - - if (nseenp) - *nseenp = rock.nseen; - if (ndeletedp) - *ndeletedp = rock.ndeleted; - - return 0; -} - -/* NOTE: this makes an "ATOM" return */ -EXPORTED const char *conversation_id_encode(conversation_id_t cid) -{ - static char text2*sizeof(cid)+1; - - if (cid != NULLCONVERSATION) { - snprintf(text, sizeof(text), CONV_FMT, cid); - } else { - strncpy(text, "NIL", sizeof(text)); - } - - return text; -} - -EXPORTED int conversation_id_decode(conversation_id_t *cid, const char *text) -{ - if (!strcmp(text, "NIL")) { - *cid = NULLCONVERSATION; - } else { - if (strlen(text) != 16) return 0; - *cid = strtoull(text, 0, 16); - } - return 1; -} - -EXPORTED void conversations_rename_cid(struct conversations_state *state, - conversation_id_t from_cid, - conversation_id_t to_cid) -{ - uint64_t *valptr; - - if (!from_cid) - return; - - if (from_cid == to_cid) - return; - - /* we never rename down! */ - assert(from_cid < to_cid); - - valptr = hashu64_lookup(from_cid, &state->cidrenames); - if (valptr) { - /* already there or better? */ - if (*valptr >= to_cid) - return; - free(valptr); - } - - valptr = xmalloc(sizeof(uint64_t)); - *valptr = to_cid; - - hashu64_insert(from_cid, valptr, &state->cidrenames); -} - -static int folder_key_rename(struct conversations_state *state, - const char *from_name, - const char *to_name) -{ - const char *val; - size_t vallen; - char *oldkey = strconcat("F", from_name, (void *)NULL); - int r = 0; - - r = cyrusdb_fetch(state->db, oldkey, strlen(oldkey), - &val, &vallen, &state->txn); - if (r) { - if (r == CYRUSDB_NOTFOUND) r = 0; /* nothing to delete */ - goto done; - } - - /* create before deleting so val is still valid */ - if (to_name) { - char *newkey = strconcat("F", to_name, (void *)NULL); - r = cyrusdb_store(state->db, newkey, strlen(newkey), - val, vallen, &state->txn); - free(newkey); - if (r) goto done; - } - - r = cyrusdb_delete(state->db, oldkey, strlen(oldkey), &state->txn, 1); - - done: - free(oldkey); - - return r; -} - -EXPORTED int conversations_rename_folder(struct conversations_state *state, - const char *from_name, - const char *to_name) -{ - int r; - - assert(from_name); - - r = folder_number_rename(state, from_name, to_name); - if (r) return r; - - r = folder_key_rename(state, from_name, to_name); - if (r) return r; - - if (to_name) { - syslog(LOG_NOTICE, "conversations_rename_folder: renamed %s to %s", - from_name, to_name); - } - else { - syslog(LOG_NOTICE, "conversations_rename_folder: deleted %s", - from_name); - } - - return 0; -} - - -static int zero_b_cb(void *rock, - const char *key, - size_t keylen, - const char *val, - size_t vallen) -{ - struct conversations_state *state = (struct conversations_state *)rock; - conversation_t *conv = NULL; - conv_folder_t *folder; - conv_sender_t *sender; - int r; - int i; - - r = conversation_parse(state, val, vallen, &conv); - if (r) { - r = cyrusdb_delete(state->db, key, keylen, &state->txn, /*force*/1); - return r; - } - - /* leave modseq untouched */ - conv->num_records = 0; - conv->exists = 0; - conv->unseen = 0; - - /* zero out all the counted counts */ - if (state->counted_flags) { - for (i = 0; i < state->counted_flags->count; i++) - conv->countsi = 0; - } - - for (folder = conv->folders; folder; folder = folder->next) { - /* keep the modseq */ - folder->num_records = 0; - folder->exists = 0; - folder->unseen = 0; - } - - /* just zero out senders */ - while ((sender = conv->senders)) { - conv->senders = sender->next; - free(sender->name); - free(sender->route); - free(sender->mailbox); - free(sender->domain); - free(sender); - } - - /* keep the subject of course */ - - conv->size = 0; - - r = conversation_store(state, key, keylen, conv); - conversation_free(conv); - - return r; -} - -static int zero_f_cb(void *rock, - const char *key, - size_t keylen, - const char *val, - size_t vallen) -{ - struct conversations_state *state = (struct conversations_state *)rock; - conv_status_t status; - int r; - - r = conversation_parsestatus(val, vallen, &status); - if (r) { - r = cyrusdb_delete(state->db, key, keylen, &state->txn, /*force*/1); - return r; - } - - /* leave modseq unchanged */ - status.exists = 0; - status.unseen = 0; - - return conversation_storestatus(state, key, keylen, &status); -} - -EXPORTED int conversations_zero_counts(struct conversations_state *state) -{ - int r = 0; - - /* wipe B counts */ - r = cyrusdb_foreach(state->db, "B", 1, NULL, zero_b_cb, - state, &state->txn); - if (r) return r; - - /* wipe F counts */ - r = cyrusdb_foreach(state->db, "F", 1, NULL, zero_f_cb, - state, &state->txn); - if (r) return r; - - /* re-init the counted flags */ - r = _init_counted(state, NULL, 0); - if (r) return r; - - return r; -} - -static int cleanup_b_cb(void *rock, - const char *key, - size_t keylen, - const char *val, - size_t vallen) -{ - struct conversations_state *state = (struct conversations_state *)rock; - conversation_t *conv = NULL; - int r; - - r = conversation_parse(state, val, vallen, &conv); - if (r) return r; - - /* should be gone, wipe it */ - if (!conv->num_records) - r = cyrusdb_delete(state->db, key, keylen, &state->txn, 1); - - conversation_free(conv); - - return r; -} - -EXPORTED int conversations_cleanup_zero(struct conversations_state *state) -{ - /* check B counts */ - return cyrusdb_foreach(state->db, "B", 1, NULL, cleanup_b_cb, - state, &state->txn); -} - -EXPORTED void conversations_dump(struct conversations_state *state, FILE *fp) -{ - cyrusdb_dumpfile(state->db, "", 0, fp, &state->txn); -} - -EXPORTED int conversations_truncate(struct conversations_state *state) -{ - return cyrusdb_truncate(state->db, &state->txn); -} - -EXPORTED int conversations_undump(struct conversations_state *state, FILE *fp) -{ - return cyrusdb_undumpfile(state->db, fp, &state->txn); -} - - - -#undef DB
View file
cyrus-imapd-2.5.tar.gz/imap/conversations.h
Deleted
@@ -1,242 +0,0 @@ -/* conversations.h -- Routines for dealing with the conversations database - * - * Copyright (c) 1994-2010 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __CYRUS_CONVERSATIONS_H_ -#define __CYRUS_CONVERSATIONS_H_ 1 - -#if HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdio.h> -#include <string.h> -#include "arrayu64.h" -#include "hash.h" -#include "hashu64.h" -#include "strarray.h" -#include "util.h" - -typedef bit64 conversation_id_t; -#define CONV_FMT "%016llx" -#define NULLCONVERSATION (0ULL) - -struct conversations_state { - struct db *db; - struct txn *txn; - strarray_t *counted_flags; - strarray_t *folder_names; - hash_table folderstatus; - struct hashu64_table cidrenames; - char *path; -}; - -struct conversations_open { - struct conversations_state s; - struct conversations_open *next; -}; - -struct conversations_open *open_conversations; - -typedef struct conversation conversation_t; -typedef struct conv_folder conv_folder_t; -typedef struct conv_sender conv_sender_t; -typedef struct conv_status conv_status_t; - -#define MAX_CONVERSATION_FLAGS 256 - -struct conv_folder { - conv_folder_t *next; - int number; - modseq_t modseq; - uint32_t num_records; - uint32_t exists; - uint32_t unseen; - uint32_t prev_exists; -}; - -struct conv_sender { - conv_sender_t *next; - char *name; - char *route; - char *mailbox; - char *domain; - time_t lastseen; - uint32_t exists; -}; - -struct conv_status { - modseq_t modseq; - uint32_t exists; - uint32_t unseen; -}; -#define CONV_STATUS_INIT {0, 0, 0} - -struct conversation { - modseq_t modseq; - uint32_t num_records; - uint32_t exists; - uint32_t unseen; - uint32_t prev_unseen; - uint32_t size; - uint32_t *counts; - conv_folder_t *folders; - conv_sender_t *senders; - char *subject; - int dirty; -}; - -/* Sets the suffix used for conversations db filenames. Only needed - * when doing special weird stuff like the conversations audit mode */ -extern void conversations_set_directory(const char *dir); -extern void conversations_set_suffix(const char *suff); -extern char *conversations_getmboxpath(const char *mboxname); -extern char *conversations_getuserpath(const char *username); - -extern int conversations_open_path(const char *path, - struct conversations_state **statep); -extern int conversations_open_user(const char *username, - struct conversations_state **statep); -extern int conversations_open_mbox(const char *mboxname, - struct conversations_state **statep); -extern struct conversations_state *conversations_get_path(const char *path); -extern struct conversations_state *conversations_get_user(const char *username); -extern struct conversations_state *conversations_get_mbox(const char *mboxname); - -/* either of these close */ -extern int conversations_abort(struct conversations_state **state); -extern int conversations_commit(struct conversations_state **state); - -/* functions for CONVDB_MSGID database only */ -extern int conversations_add_msgid(struct conversations_state *state, - const char *msgid, - conversation_id_t cid); -extern int conversations_get_msgid(struct conversations_state *state, - const char *msgid, - arrayu64_t *cids); -extern conv_folder_t *conversation_get_folder(conversation_t *conv, - int number, int create_flag); - -extern void conversation_normalise_subject(struct buf *); - -/* F record items */ -extern int conversation_getstatus(struct conversations_state *state, - const char *mboxname, - conv_status_t *status); -extern int conversation_setstatus(struct conversations_state *state, - const char *mboxname, - const conv_status_t *status); -extern int conversation_storestatus(struct conversations_state *state, - const char *key, size_t keylen, - const conv_status_t *status); -extern int conversation_parsestatus(const char *data, size_t datalen, - conv_status_t *status); - -/* B record items */ -extern int conversation_get_modseq(struct conversations_state *state, - conversation_id_t cid, - modseq_t *modseqp); -extern int conversation_save(struct conversations_state *state, - conversation_id_t cid, - conversation_t *conv); -extern int conversation_load(struct conversations_state *state, - conversation_id_t cid, - conversation_t **convp); -extern int conversation_parse(struct conversations_state *state, - const char *data, size_t datalen, - conversation_t **convp); -extern int conversation_store(struct conversations_state *state, - const char *key, int keylen, - conversation_t *conv); -/* Update the internal data about a conversation, enforcing - * consistency rules (e.g. the conversation's modseq is the - * maximum of all the per-folder modseqs). Sets conv->dirty - * if any data actually changed. */ -extern void conversation_update(struct conversations_state *state, - conversation_t *conv, - const char *mboxname, - int delta_num_records, - int delta_exists, - int delta_unseen, - int delta_size, - int *delta_counts, - modseq_t modseq); -extern conv_folder_t *conversation_find_folder(struct conversations_state *state, - conversation_t *, - const char *mboxname); -extern conversation_t *conversation_new(struct conversations_state *state); -extern void conversation_free(conversation_t *); - -extern void conversation_update_sender(conversation_t *conv, - const char *name, - const char *route, - const char *mailbox, - const char *domain, - time_t lastseen, - int delta_exists); - -extern int conversations_prune(struct conversations_state *state, - time_t thresh, unsigned int *, - unsigned int *); -extern void conversations_dump(struct conversations_state *, FILE *); -extern int conversations_undump(struct conversations_state *, FILE *); - -extern int conversations_truncate(struct conversations_state *); - -extern const char *conversation_id_encode(conversation_id_t cid); -extern int conversation_id_decode(conversation_id_t *cid, const char *text); - -extern void conversations_rename_cid(struct conversations_state *state, - conversation_id_t from_cid, - conversation_id_t to_cid); -extern void conversations_rename_cidentry(struct conversations_state *state, - conversation_id_t from, - conversation_id_t to); - - -extern int conversations_zero_counts(struct conversations_state *state); -extern int conversations_cleanup_zero(struct conversations_state *state); - -extern int conversations_rename_folder(struct conversations_state *state, - const char *from_name, - const char *to_name); - -#endif /* __CYRUS_CONVERSATIONS_H_ */
View file
cyrus-imapd-2.5.tar.gz/imap/ctl_conversationsdb.c
Deleted
@@ -1,1021 +0,0 @@ -/* - * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <stdlib.h> -#include <stdio.h> -#include <syslog.h> -#include <string.h> -#include <sys/stat.h> - -/* cyrus includes */ -#include "assert.h" -#include "bsearch.h" -#include "exitcodes.h" -#include "global.h" -#include "imap_err.h" -#include "index.h" -#include "conversations.h" -#include "mailbox.h" -#include "mboxlist.h" -#include "message.h" -#include "sync_log.h" -#include "sysexits.h" -#include "util.h" -#include "xmalloc.h" - -#if !HAVE___ATTRIBUTE__ -#define __attribute__(x) -#endif - -/* config.c stuff */ -const int config_need_data = CONFIG_NEED_PARTITION_DATA; -static struct namespace conv_namespace; - -enum { UNKNOWN, DUMP, UNDUMP, ZERO, BUILD, RECALC, AUDIT, CHECKFOLDERS }; - -int verbose = 0; - -char *prev_userid; -int mode = UNKNOWN; -static const char *audit_temp_directory; - -static int do_dump(const char *fname) -{ - struct conversations_state *state = NULL; - struct stat sb; - int r; - - /* What we really want here is read-only database access without - * the create-if-nonexistant semantics. However, the cyrusdb - * interface makes it difficult to do that properly. In the - * meantime, we can just check if the file exists here. */ - r = stat(fname, &sb); - if (r < 0) { - perror(fname); - return -1; - } - - r = conversations_open_path(fname, &state); - if (r) { - fprintf(stderr, "Failed to open conversations database %s: %s\n", - fname, error_message(r)); - return -1; - } - - conversations_dump(state, stdout); - - conversations_commit(&state); - return 0; -} - -static int do_undump(const char *fname) -{ - struct conversations_state *state; - int r; - - r = conversations_open_path(fname, &state); - if (r) { - fprintf(stderr, "Failed to open conversations database %s: %s\n", - fname, error_message(r)); - return -1; - } - - r = conversations_truncate(state); - if (r) { - fprintf(stderr, "Failed to truncate conversations database %s: %s\n", - fname, error_message(r)); - goto out; - } - - r = conversations_undump(state, stdin); - if (r) { - fprintf(stderr, "Failed to undump to conversations database %s: %s\n", - fname, error_message(r)); - goto out; - } - - r = conversations_commit(&state); - if (r) - fprintf(stderr, "Failed to commit conversations database %s: %s\n", - fname, error_message(r)); - -out: - conversations_abort(&state); - return r; -} - -static int zero_cid_cb(const char *mboxname, - int matchlen __attribute__((unused)), - int maycreate __attribute__((unused)), - void *rock __attribute__((unused))) -{ - struct mailbox *mailbox = NULL; - struct index_record record; - int r; - uint32_t recno; - - r = mailbox_open_iwl(mboxname, &mailbox); - if (r) return r; - - for (recno = 1; recno <= mailbox->i.num_records; recno++) { - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) goto done; - - /* already zero, fine */ - if (record.cid == NULLCONVERSATION) - continue; - - record.cid = NULLCONVERSATION; - r = mailbox_rewrite_index_record(mailbox, &record); - if (r) goto done; - } - - done: - mailbox_close(&mailbox); - return r; -} - -static int do_zero(const char *inboxname) -{ - char bufMAX_MAILBOX_NAME; - int r; - struct conversations_state *state = NULL; - - r = conversations_open_mbox(inboxname, &state); - - r = zero_cid_cb(inboxname, 0, 0, NULL); - if (r) return r; - - snprintf(buf, sizeof(buf), "%s.*", inboxname); - r = mboxlist_findall(NULL, buf, 1, NULL, - NULL, zero_cid_cb, NULL); - - conversations_commit(&state); - - return r; -} - -static int build_cid_cb(const char *mboxname, - int matchlen __attribute__((unused)), - int maycreate __attribute__((unused)), - void *rock __attribute__((unused))) -{ - struct mailbox *mailbox = NULL; - struct index_record record; - uint32_t recno; - int r; - struct conversations_state *cstate = conversations_get_mbox(mboxname); - - if (!cstate) return IMAP_CONVERSATIONS_NOT_OPEN; - - r = mailbox_open_iwl(mboxname, &mailbox); - if (r) return r; - - for (recno = 1; recno <= mailbox->i.num_records; recno++) { - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) goto done; - - /* already assigned, fine */ - if (record.cid != NULLCONVERSATION) - continue; - - /* no file, can't calculate */ - if (record.system_flags & FLAG_UNLINKED) - continue; - - r = mailbox_cacherecord(mailbox, &record); - if (r) goto done; - - r = message_update_conversations(cstate, &record, NULL); - if (r) goto done; - - r = mailbox_rewrite_index_record(mailbox, &record); - if (r) goto done; - } - - done: - mailbox_close(&mailbox); - return r; -} - -static int do_build(const char *inboxname) -{ - char bufMAX_MAILBOX_NAME; - int r; - struct conversations_state *state = NULL; - - r = conversations_open_mbox(inboxname, &state); - - r = build_cid_cb(inboxname, 0, 0, NULL); - if (r) return r; - - snprintf(buf, sizeof(buf), "%s.*", inboxname); - r = mboxlist_findall(NULL, buf, 1, NULL, - NULL, build_cid_cb, NULL); - - conversations_commit(&state); - return r; -} - -static int recalc_counts_cb(const char *mboxname, - int matchlen __attribute__((unused)), - int maycreate __attribute__((unused)), - void *rock __attribute__((unused))) -{ - struct mailbox *mailbox = NULL; - int r; - - r = mailbox_open_irl(mboxname, &mailbox); - if (r) return r; - - if (verbose) - printf("%s\n", mboxname); - - r = mailbox_add_conversations(mailbox); - - mailbox_close(&mailbox); - return r; -} - -static int audit_counts_cb(const char *mboxname, - int matchlen __attribute__((unused)), - int maycreate __attribute__((unused)), - void *rock __attribute__((unused))) -{ - struct mailbox *mailbox = NULL; - int r; - - r = mailbox_open_irl(mboxname, &mailbox); - if (r) return r; - - if (verbose) - printf("%s\n", mboxname); - - r = mailbox_add_conversations(mailbox); - - mailbox_close(&mailbox); - return r; -} - -static int do_recalc(const char *inboxname) -{ - char bufMAX_MAILBOX_NAME; - int r; - struct conversations_state *state = NULL; - - r = conversations_open_mbox(inboxname, &state); - if (r) return r; - - r = conversations_zero_counts(state); - if (r) goto err; - - r = recalc_counts_cb(inboxname, 0, 0, NULL); - if (r) goto err; - - snprintf(buf, sizeof(buf), "%s.*", inboxname); - r = mboxlist_findall(NULL, buf, 1, NULL, - NULL, recalc_counts_cb, NULL); - if (r) goto err; - - r = conversations_cleanup_zero(state); - if (r) goto err; - - conversations_commit(&state); - return 0; - -err: - conversations_abort(&state); - return r; -} - -struct cursor -{ - struct db *db; - struct txn **txnp; - const char *key; size_t keylen; - const char *data; size_t datalen; - int err; -}; - -static void cursor_init(struct cursor *c, - struct db *db, struct txn **txnp) -{ - memset(c, 0, sizeof(*c)); - c->db = db; - c->txnp = txnp; -} - -static int cursor_next(struct cursor *c) -{ - if (!c->err) - c->err = cyrusdb_fetchnext(c->db, - c->key, c->keylen, - &c->key, &c->keylen, - &c->data, &c->datalen, - c->txnp); - return c->err; -} - -static int blob_compare(const char *a, size_t alen, - const char *b, size_t blen) -{ - int d = memcmp(a, b, MIN(alen, blen)); - if (!d) - d = alen - blen; - return d; -} - -static int next_diffable_record(struct cursor *c) -{ - for (;;) - { - int r = cursor_next(c); - if (r) return r; - - /* skip < records, they won't be in the - * temp database and we don't care so much */ - if (c->key0 == '<') - continue; - - /* Subject, not re-calculated */ - if (c->key0 == 'S') - continue; - - return 0; - } -} - -static unsigned int diff_records(struct conversations_state *a, - struct conversations_state *b) -{ - unsigned int ndiffs = 0; - int ra, rb; - struct cursor ca, cb; - int keydelta; - int delta; - - cursor_init(&ca, a->db, &a->txn); - ra = cursor_next(&ca); - - cursor_init(&cb, b->db, &b->txn); - rb = cursor_next(&cb); - - while (!ra || !rb) { - keydelta = blob_compare(ca.key, ca.keylen, cb.key, cb.keylen); - if (rb || keydelta < 0) { - if (ra) break; - ndiffs++; - if (verbose) - printf("REALONLY: \"%.*s\" data \"%.*s\"\n", - (int)ca.keylen, ca.key, (int)ca.datalen, ca.data); - ra = next_diffable_record(&ca); - continue; - } - if (ra || keydelta > 0) { - if (rb) break; - ndiffs++; - if (verbose) - printf("TEMPONLY: \"%.*s\" data \"%.*s\"\n", - (int)cb.keylen, cb.key, (int)cb.datalen, cb.data); - rb = next_diffable_record(&cb); - continue; - } - - /* both exist an are the same key */ - delta = blob_compare(ca.data, ca.datalen, cb.data, cb.datalen); - if (delta) { - ndiffs++; - if (verbose) - printf("REAL: \"%.*s\" data \"%.*s\"\n" - "TEMP: \"%.*s\" data \"%.*s\"\n", - (int)ca.keylen, ca.key, (int)ca.datalen, ca.data, - (int)cb.keylen, cb.key, (int)cb.datalen, cb.data); - } - - ra = next_diffable_record(&ca); - rb = next_diffable_record(&cb); - } - - return ndiffs; -} - -static int fix_modseqs(struct conversations_state *a, - struct conversations_state *b) -{ - int ra, rb; - struct cursor ca, cb; - int keydelta; - int r; - - cursor_init(&ca, a->db, &a->txn); - ra = cursor_next(&ca); - - cursor_init(&cb, b->db, &b->txn); - rb = cursor_next(&cb); - - while (!ra || !rb) { - keydelta = blob_compare(ca.key, ca.keylen, cb.key, cb.keylen); - if (rb || keydelta < 0) { - if (ra) break; - if (ca.key0 == 'F') { - conv_status_t status = CONV_STATUS_INIT; - /* need to add record if it's zero */ - r = conversation_parsestatus(ca.data, ca.datalen, &status); - if (r) return r; - if (status.exists == 0) { - r = conversation_storestatus(b, ca.key, ca.keylen, &status); - if (r) { - fprintf(stderr, "Failed to store conversations " - "record \"%.*s\" to %s: %s, giving up\n", - (int)ca.keylen, ca.key, - b->path, error_message(r)); - return r; - } - } - /* otherwise it's a bug, so leave it in for reporting */ - } - ra = cursor_next(&ca); - continue; - } - if (ra || keydelta > 0) { - if (rb) break; - rb = cursor_next(&cb); - continue; - } - - /* folders? Just modseq check */ - if (ca.key0 == 'F') { - /* check if modseq is higher for real */ - conv_status_t statusa = CONV_STATUS_INIT; - conv_status_t statusb = CONV_STATUS_INIT; - /* need to add record if it's zero */ - r = conversation_parsestatus(ca.data, ca.datalen, &statusa); - if (r) { - fprintf(stderr, "Failed to parse conversations " - "record \"%.*s\" in %s: %s\n", - (int)ca.keylen, ca.key, - a->path, error_message(r)); - /* There's no need to report failure to the caller - the - * record diffing passing that occurs after this will - * also pick up the same problem */ - goto next; - } - r = conversation_parsestatus(cb.data, cb.datalen, &statusb); - if (r) { - fprintf(stderr, "Failed to parse conversations " - "record \"%.*s\" in %s: %s\n", - (int)cb.keylen, cb.key, - b->path, error_message(r)); - goto next; - } - if (statusa.modseq > statusb.modseq) { - statusb.modseq = statusa.modseq; - r = conversation_storestatus(b, cb.key, cb.keylen, &statusb); - if (r) { - fprintf(stderr, "Failed to store conversations " - "record \"%.*s\" to %s: %s, giving up\n", - (int)cb.keylen, cb.key, - b->path, error_message(r)); - /* If we cannot write to the temp DB, something is - * drastically wrong and we need to report a failure */ - return r; - } - } - } - if (ca.key0 == 'B') { - /* B keys - check all the modseqs, both top level and per folder */ - conversation_t *conva = NULL; - conversation_t *convb = NULL; - conv_folder_t *foldera; - conv_folder_t *folderb; - conv_sender_t *sendera; - - r = conversation_parse(a, ca.data, ca.datalen, &conva); - if (r) { - fprintf(stderr, "Failed to parse conversations " - "record \"%.*s\" in %s: %s\n", - (int)ca.keylen, ca.key, - a->path, error_message(r)); - goto next; - } - r = conversation_parse(b, cb.data, cb.datalen, &convb); - if (r) { - fprintf(stderr, "Failed to parse conversations " - "record \"%.*s\" in %s: %s\n", - (int)cb.keylen, cb.key, - b->path, error_message(r)); - conversation_free(conva); - goto next; - } - - /* because expunged messages could have had higher modseqs, - * we need to re-copy any higher modseqs in */ - if (conva->modseq > convb->modseq) - convb->modseq = conva->modseq; - - for (foldera = conva->folders; foldera; foldera = foldera->next) { - folderb = conversation_get_folder(convb, foldera->number, 1); - if (folderb->modseq < foldera->modseq) - folderb->modseq = foldera->modseq; - } - - /* senders are timestamped, and the timestamp might be for a - * deleted message! */ - for (sendera = conva->senders; sendera; sendera = sendera->next) { - /* always update! The delta logic will ensure we don't add - * the record if it's not already at least present in the - * other conversation */ - conversation_update_sender(convb, sendera->name, sendera->route, - sendera->mailbox, sendera->domain, - sendera->lastseen, /*delta_count*/0); - } - - /* be nice to know if this is needed, but at least twoskip - * will dedup for us */ - r = conversation_store(b, cb.key, cb.keylen, convb); - - /* free first before checking for errors */ - conversation_free(conva); - conversation_free(convb); - - if (r) { - fprintf(stderr, "Failed to store conversations " - "record \"%.*s\" to %s: %s, giving up\n", - (int)cb.keylen, cb.key, - b->path, error_message(r)); - return r; - } - } - -next: - ra = cursor_next(&ca); - rb = cursor_next(&cb); - } - - return 0; -} - -int do_checkfolders(const char *inboxname) -{ - int r; - struct conversations_state *state = NULL; - strarray_t *copy1, *copy2; - - /* open the DB */ - r = conversations_open_mbox(inboxname, &state); - if (r) { - fprintf(stderr, "Cannot open conversations db %s: %s\n", - inboxname, error_message(r)); - goto out; - } - - /* don't mess with the original */ - copy1 = strarray_dup(state->folder_names); - /* remove empty folders first, they will duplicate for sure */ - strarray_remove_all(copy1, "-"); - copy2 = strarray_dup(copy1); - strarray_sort(copy2, cmpstringp_raw); - strarray_uniq(copy2); - if (copy1->count != copy2->count) { - printf("DUPLICATE %s\n", inboxname); - } - else { - printf("OK %s\n", inboxname); - } - strarray_free(copy1); - strarray_free(copy2); - -out: - conversations_abort(&state); - return r; -} - -static int do_audit(const char *inboxname) -{ - char bufMAX_MAILBOX_NAME; - int r; - char temp_suffix64; - char *filename_temp = NULL; - char *filename_real = NULL; - struct conversations_state *state_temp = NULL; - struct conversations_state *state_real = NULL; - unsigned int ndiffs = 0; - - if (verbose) - printf("Inbox %s\n", inboxname); - - if (verbose) - printf("Pass 1: recalculate counts into temporary db\n"); - - /* Generate a unique suffix for the temp db */ - snprintf(temp_suffix, sizeof(temp_suffix), - "conversations.audit.%d", (int)getpid()); - - /* Get the filenames */ - filename_real = conversations_getmboxpath(inboxname); - conversations_set_suffix(temp_suffix); - conversations_set_directory(audit_temp_directory); - filename_temp = conversations_getmboxpath(inboxname); - conversations_set_suffix(NULL); - conversations_set_directory(NULL); - assert(strcmp(filename_temp, filename_real)); - - /* Initialise the temp copy of the database */ - unlink(filename_temp); - r = cyrusdb_copyfile(filename_real, filename_temp); - if (r) { - fprintf(stderr, "Cannot make temp copy of conversations db %s: %s\n", - filename_real, error_message(r)); - goto out; - } - - /* Begin recalculating in the temp db */ - r = conversations_open_path(filename_temp, &state_temp); - if (r) { - fprintf(stderr, "Cannot open conversations db %s: %s\n", - filename_temp, error_message(r)); - goto out; - } - - r = conversations_zero_counts(state_temp); - if (r) { - fprintf(stderr, "Failed to zero counts in %s: %s\n", - filename_temp, error_message(r)); - goto out; - } - - /* - * Set the conversations db suffix during the recalc pass, so that - * calls to conversations_open_mbox() from the mailbox code get - * redirected to the temporary db. - */ - conversations_set_suffix(temp_suffix); - conversations_set_directory(audit_temp_directory); - - r = audit_counts_cb(inboxname, 0, 0, NULL); - if (r) { - fprintf(stderr, "Failed to recalculate counts in %s: %s\n", - filename_temp, error_message(r)); - goto out; - } - - snprintf(buf, sizeof(buf), "%s.*", inboxname); - r = mboxlist_findall(NULL, buf, 1, NULL, - NULL, audit_counts_cb, NULL); - if (r) { - fprintf(stderr, "Failed to recalculate counts in %s: %s\n", - filename_temp, error_message(r)); - goto out; - } - - r = conversations_cleanup_zero(state_temp); - if (r) { - fprintf(stderr, "Failed to cleanup zero counts in %s: %s\n", - filename_temp, error_message(r)); - goto out; - } - - conversations_set_suffix(NULL); - conversations_set_directory(NULL); - - r = conversations_commit(&state_temp); - if (r) { - fprintf(stderr, "Cannot commit conversations db %s: %s\n", - filename_temp, error_message(r)); - goto out; - } - - if (verbose) - printf("Pass 2: find differences from recalculated to live dbs\n"); - - r = conversations_open_path(filename_temp, &state_temp); - if (r) { - fprintf(stderr, "Cannot open conversations db %s: %s\n", - filename_temp, error_message(r)); - goto out; - } - - r = conversations_open_path(filename_real, &state_real); - if (r) { - fprintf(stderr, "Cannot open conversations db %s: %s\n", - filename_real, error_message(r)); - goto out; - } - - r = fix_modseqs(state_real, state_temp); - if (r) { - /* Error reported in fix_modseqs() */ - goto out; - } - - ndiffs += diff_records(state_real, state_temp); - if (ndiffs) - printf("%s is BROKEN (%u differences)\n", inboxname, ndiffs); - else if (verbose) - printf("%s is OK\n", inboxname); - -out: - if (state_temp) - conversations_abort(&state_temp); - if (state_real) - conversations_abort(&state_real); - conversations_set_suffix(NULL); - conversations_set_directory(NULL); - if (filename_temp) - unlink(filename_temp); - free(filename_temp); - free(filename_real); - return r; -} - -static int usage(const char *name) - __attribute__((noreturn)); - -static int do_user(const char *userid) -{ - char *inboxname; - char *fname; - int r = 0; - - fname = conversations_getuserpath(userid); - if (fname == NULL) { - fprintf(stderr, "Unable to get conversations database " - "filename for userid \"%s\"\n", - userid); - return EC_USAGE; - } - - inboxname = mboxname_user_mbox(userid, NULL); - if (inboxname == NULL) { - free(fname); - fprintf(stderr, "Invalid userid %s", userid); - return EC_USAGE; - } - - switch (mode) - { - case DUMP: - if (do_dump(fname)) - r = EC_NOINPUT; - break; - - case UNDUMP: - if (do_undump(fname)) - r = EC_NOINPUT; - break; - - case ZERO: - if (do_zero(inboxname)) - r = EC_NOINPUT; - break; - - case BUILD: - if (do_build(inboxname)) - r = EC_NOINPUT; - break; - - case RECALC: - if (do_recalc(inboxname)) - r = EC_NOINPUT; - break; - - case AUDIT: - if (do_audit(inboxname)) - r = EC_NOINPUT; - break; - - case CHECKFOLDERS: - if (do_checkfolders(inboxname)) - r = EC_NOINPUT; - break; - - case UNKNOWN: - fatal("UNKNOWN MODE", EC_SOFTWARE); - } - - free(fname); - free(inboxname); - - return r; -} - -static int do_mailbox(char *name, - int namelen, - int maycreate __attribute__((unused)), - void *rock __attribute__((unused))) -{ - char *mboxname = xstrndup(name, namelen); - const char *userid = mboxname_to_userid(mboxname); - - if (mboxname_isdeletedmailbox(mboxname, NULL)) - goto done; - - if (userid && strcmp(userid, prev_userid)) { - printf("%s\n", userid); - do_user(userid); - free(prev_userid); - prev_userid = xstrdup(userid); - } - -done: - free(mboxname); - - return 0; -} - -int main(int argc, char **argv) -{ - int c; - const char *alt_config = NULL; - const char *userid = NULL; - int r = 0; - int recursive = 0; - - if ((geteuid()) == 0 && (become_cyrus(/*ismaster*/0) != 0)) { - fatal("must run as the Cyrus user", EC_USAGE); - } - - while ((c = getopt(argc, argv, "durzAbvRFC:T:")) != EOF) { - switch (c) { - case 'd': - if (mode != UNKNOWN) - usage(argv0); - mode = DUMP; - break; - - case 'r': - recursive = 1; - break; - - case 'u': - if (mode != UNKNOWN) - usage(argv0); - mode = UNDUMP; - break; - - case 'z': - if (mode != UNKNOWN) - usage(argv0); - mode = ZERO; - break; - - case 'b': - if (mode != UNKNOWN) - usage(argv0); - mode = BUILD; - break; - - case 'R': - if (mode != UNKNOWN) - usage(argv0); - mode = RECALC; - break; - - case 'A': - if (mode != UNKNOWN) - usage(argv0); - mode = AUDIT; - break; - - case 'F': - if (mode != UNKNOWN) - usage(argv0); - mode = CHECKFOLDERS; - break; - - case 'v': - verbose++; - break; - - case 'C': /* alt config file */ - alt_config = optarg; - break; - - case 'T': /* tmpfs directory for audit */ - audit_temp_directory = optarg; - break; - - default: - usage(argv0); - break; - } - } - - if (mode == UNKNOWN) - usage(argv0); - - if (optind == argc-1) - userid = argvoptind; - else if (recursive) - userid = ""; - else - usage(argv0); - - cyrus_init(alt_config, "ctl_conversationsdb", 0, 0); - - mboxlist_init(0); - mboxlist_open(NULL); - - sync_log_init(); - - if (recursive) { - char *buf = xmalloc(strlen(userid) + 2); - prev_userid = xstrdup(""); - strcpy(buf, userid); - strcat(buf, "*"); - - if ((r = mboxname_init_namespace(&conv_namespace, 1)) != 0) { - syslog(LOG_ERR, "%s", error_message(r)); - fatal(error_message(r), EC_CONFIG); - } - - (*conv_namespace.mboxlist_findall)(&conv_namespace, buf, 1, 0, 0, - do_mailbox, NULL); - - free(prev_userid); - free(buf); - } - else - do_user(userid); - - sync_log_done(); - - mboxlist_close(); - mboxlist_done(); - - cyrus_done(); - - return r; -} - -static int usage(const char *name) -{ - fprintf(stderr, "usage: %s options -u|-d|-z|-f -r username\n", name); - fprintf(stderr, "\n"); - fprintf(stderr, "options are:\n"); - fprintf(stderr, " -v be more verbose\n"); - fprintf(stderr, " -C altconfig use altconfig instead of imapd.conf\n"); - fprintf(stderr, " -u undump the conversations database from stdin\n"); - fprintf(stderr, " -d dump the conversations database to stdout\n"); - fprintf(stderr, " -z zero the conversations DB (make all NULLs)\n"); - fprintf(stderr, " -b build conversations entries for any NULL records\n"); - fprintf(stderr, " -R recalculate all counts\n"); - fprintf(stderr, " -A audit conversations DB counts\n"); - fprintf(stderr, " -F check folder names\n"); - fprintf(stderr, " -T dir store temporary data for audit in dir\n"); - fprintf(stderr, "\n"); - fprintf(stderr, " -r recursive mode: username is a prefix\n"); - - exit(EC_USAGE); -} - -void fatal(const char* s, int code) -{ - fprintf(stderr, "ctl_conversationsdb: %s\n", s); - cyrus_done(); - exit(code); -} -
View file
cyrus-imapd-2.5.tar.gz/imap/ctl_cyrusdb.c
Changed
@@ -134,7 +134,7 @@ if (userid) { struct buf buf = BUF_INITIALIZER; buf_setcstr(&buf, mbentry->legacy_specialuse); - annotatemore_rawwrite(name, "/specialuse", userid, &buf); + annotatemore_write(name, "/specialuse", userid, &buf); buf_free(&buf); } free(mbentry->legacy_specialuse); @@ -329,23 +329,27 @@ if (!rotated) { /* rotate the backup directories -- ONE time only */ - char *file; + char *tail; DIR *dirp; struct dirent *dirent; + tail = backup2 + strlen(backup2); + /* remove db.backup2 */ dirp = opendir(backup2); + strcat(tail++, "/"); if (dirp) { while ((dirent = readdir(dirp)) != NULL) { if (dirent->d_name0 == '.') continue; - file = strconcat(backup2, "/", dirent->d_name, (char *)NULL); - unlink(file); - free(file); + + strcpy(tail, dirent->d_name); + unlink(backup2); } closedir(dirp); } + tail-1 = '\0'; r2 = rmdir(backup2); /* move db.backup1 to db.backup2 */
View file
cyrus-imapd-2.5.tar.gz/imap/ctl_zoneinfo.c
Deleted
@@ -1,310 +0,0 @@ -/* ctl_zoneinfo.c -- Program to perform operations on zoneinfo db - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <dirent.h> -#include <sys/stat.h> -#include <sys/types.h> - -#include <libical/ical.h> - -#include "annotate.h" /* for strlist functionality */ -#include "global.h" -#include "hash.h" -#include "map.h" -#include "util.h" -#include "xmalloc.h" -#include "zoneinfo_db.h" - -extern int optind; -extern char *optarg; - -/* config.c stuff */ -const int config_need_data = 0; - -int verbose = 0; - -/* forward declarations */ -void usage(void); -void free_zoneinfo(void *data); -void store_zoneinfo(const char *tzid, void *data, void *rock); -void do_zonedir(const char *prefix, struct hash_table *tzentries, - struct zoneinfo *info); -void shut_down(int code); - - -int main(int argc, char **argv) -{ - int opt, r = 0; - char *alt_config = NULL, *version = NULL; - enum { REBUILD, NONE } op = NONE; - - if ((geteuid()) == 0 && (become_cyrus() != 0)) { - fatal("must run as the Cyrus user", EC_USAGE); - } - - while ((opt = getopt(argc, argv, "C:r:v")) != EOF) { - switch (opt) { - case 'C': /* alt config file */ - alt_config = optarg; - break; - - case 'r': - if (op == NONE) { - op = REBUILD; - version = optarg; - } - else usage(); - break; - - case 'v': - verbose = 1; - break; - - default: - usage(); - } - } - - cyrus_init(alt_config, "ctl_zoneinfo", 0); - - signals_set_shutdown(&shut_down); - signals_add_handlers(0); - - switch (op) { - case REBUILD: { - struct hash_table tzentries; - struct zoneinfo *zi; - struct txn *tid = NULL; - char prefix2048; - - construct_hash_table(&tzentries, 500, 1); - - /* Add INFO record (overall lastmod and TZ DB source version) */ - zi = xzmalloc(sizeof(struct zoneinfo)); - zi->type = ZI_INFO; - appendstrlist(&zi->data, version); - hash_insert(INFO_TZID, zi, &tzentries); - - snprintf(prefix, sizeof(prefix), "%s%s", config_dir, FNAME_ZONEINFODIR); - - do_zonedir(prefix, &tzentries, zi); - - zoneinfo_open(NULL); - - hash_enumerate(&tzentries, &store_zoneinfo, &tid); - - zoneinfo_close(tid); - - free_hash_table(&tzentries, &free_zoneinfo); - break; - } - - case NONE: - r = 2; - usage(); - break; - } - - cyrus_done(); - - return r; -} - - -void usage(void) -{ - fprintf(stderr, - "usage: zoneinfo_reconstruct -C <alt_config> -v" - " -r <version-string>\n"); - exit(EC_USAGE); -} - - -/* Add all ZONEs and LINKs in the given directory to the hash table */ -void do_zonedir(const char *dir, struct hash_table *tzentries, - struct zoneinfo *info) -{ - DIR *dirp; - struct dirent *dirent; - - signals_poll(); - - if (verbose) printf("Rebuilding %s\n", dir); - - dirp = opendir(dir); - if (!dirp) { - fprintf(stderr, "can't open zoneinfo directory %s\n", dir); - } - - while ((dirent = readdir(dirp))) { - char path2048, *tzid; - int plen; - struct stat sbuf; - struct zoneinfo *zi; - - if (*dirent->d_name == '.') continue; - - plen = snprintf(path, sizeof(path), "%s/%s", dir, dirent->d_name); - lstat(path, &sbuf); - - if (S_ISDIR(sbuf.st_mode)) { - /* Path is a directory (region) */ - do_zonedir(path, tzentries, info); - } - else if (S_ISLNK(sbuf.st_mode)) { - /* Path is a symlink (alias) */ - char link1024, *alias; - ssize_t llen; - - /* Isolate tzid in path */ - if ((llen = readlink(path, link, sizeof(link))) < 0) continue; - linkllen-4 = '\0'; /* Trim ".ics" */ - for (tzid = link; !strncmp(tzid, "../", 3); tzid += 3); - - /* Isolate alias in path */ - pathplen-4 = '\0'; /* Trim ".ics" */ - alias = path + strlen(config_dir) + strlen("zoneinfo") + 2; - - if (verbose) printf("\tLINK: %s -> %s\n", alias, tzid); - - /* Create hash entry for alias */ - if (!(zi = hash_lookup(alias, tzentries))) { - zi = xzmalloc(sizeof(struct zoneinfo)); - hash_insert(alias, zi, tzentries); - } - zi->type = ZI_LINK; - appendstrlist(&zi->data, tzid); - - /* Create/update hash entry for tzid */ - if (!(zi = hash_lookup(tzid, tzentries))) { - zi = xzmalloc(sizeof(struct zoneinfo)); - hash_insert(tzid, zi, tzentries); - } - zi->type = ZI_ZONE; - appendstrlist(&zi->data, alias); - } - else if (S_ISREG(sbuf.st_mode)) { - /* Path is a regular file (zone) */ - int fd; - const char *base = NULL; - unsigned long len = 0; - icalcomponent *ical, *comp; - icalproperty *prop; - - /* Parse the iCalendar file for important properties */ - if ((fd = open(path, O_RDONLY)) == -1) continue; - map_refresh(fd, 1, &base, &len, MAP_UNKNOWN_LEN, path, NULL); - close(fd); - - ical = icalparser_parse_string(base); - map_free(&base, &len); - - comp = icalcomponent_get_first_component(ical, - ICAL_VTIMEZONE_COMPONENT); - prop = icalcomponent_get_first_property(comp, ICAL_TZID_PROPERTY); - tzid = (char *) icalproperty_get_value_as_string(prop); - - if (verbose) printf("\tZONE: %s\n", tzid); - - /* Create/update hash entry for tzid */ - if (!(zi = hash_lookup(tzid, tzentries))) { - zi = xzmalloc(sizeof(struct zoneinfo)); - hash_insert(tzid, zi, tzentries); - } - zi->type = ZI_ZONE; - prop = icalcomponent_get_first_property(comp, - ICAL_LASTMODIFIED_PROPERTY); - zi->dtstamp = icaltime_as_timet(icalproperty_get_lastmodified(prop)); - - icalcomponent_free(ical); - - /* Check overall lastmod */ - if (zi->dtstamp > info->dtstamp) info->dtstamp = zi->dtstamp; - } - else { - fprintf(stderr, "unknown path type %s\n", path); - } - } - - closedir(dirp); -} - - -/* Free a malloc'd struct zoneinfo */ -void free_zoneinfo(void *data) -{ - struct zoneinfo *zi = (struct zoneinfo *) data; - - freestrlist(zi->data); - free(zi); -} - - -/* Store a struct zoneinfo into zoneinfo.db using the given txn */ -void store_zoneinfo(const char *tzid, void *data, void *rock) -{ - struct zoneinfo *zi = (struct zoneinfo *) data; - struct txn **tid = (struct txn **) rock; - - zoneinfo_store(tzid, zi, tid); -} - - -/* - * Cleanly shut down and exit - */ -void shut_down(int code) __attribute__((noreturn)); -void shut_down(int code) -{ - in_shutdown = 1; - - exit(code); -}
View file
cyrus-imapd-2.5.tar.gz/imap/cyr_dbtool.c
Changed
@@ -63,14 +63,11 @@ #include "global.h" #include "mailbox.h" #include "util.h" -#include "retry.h" #include "xmalloc.h" #define STACKSIZE 64000 static char stackSTACKSIZE+1; -int outfd; - static struct db *db = NULL; static int read_key_value(char **keyptr, size_t *keylen, char **valptr, size_t *vallen) { @@ -111,16 +108,7 @@ const char *key, size_t keylen, const char *data, size_t datalen) { - struct iovec io4; - io0.iov_base = (char *)key; - io0.iov_len = keylen; - io1.iov_base = "\t"; - io1.iov_len = 1; - io2.iov_base = (char *)data; - io2.iov_len = datalen; - io3.iov_base = "\n"; - io3.iov_len = 1; - retry_writev(outfd, io, 4); + printf("%.*s\t%.*s\n", (int)keylen, key, (int)datalen, data); return 0; } @@ -246,9 +234,9 @@ int use_stdin = 0; int db_flags = 0; struct txn *tid = NULL; - struct txn **tidp = NULL; + struct txn **tidp = &tid; - while ((opt = getopt(argc, argv, "C:ntT")) != EOF) { + while ((opt = getopt(argc, argv, "C:nt")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; @@ -256,12 +244,8 @@ case 'n': /* create new */ db_flags |= CYRUSDB_CREATE; break; - case 't': /* legacy - now the default, but don't break existing users */ + case 't': tidp = NULL; - break; - case 'T': - tidp = &tid; - break; } } @@ -306,8 +290,6 @@ exit(EC_OSERR); } - outfd = fileno(stdout); - cyrus_init(alt_config, "cyr_dbtool", 0, 0); r = cyrusdb_open(argvoptind+1, fname, db_flags, &db);
View file
cyrus-imapd-2.5.tar.gz/imap/cyr_expire.c
Changed
@@ -58,14 +58,12 @@ #include "annotate.h" #include "duplicate.h" #include "exitcodes.h" -#include "imap_err.h" +#include "imap/imap_err.h" #include "global.h" #include "hash.h" -#include "imap_err.h" #include "libcyr_cfg.h" #include "mboxevent.h" #include "mboxlist.h" -#include "conversations.h" #include "util.h" #include "xmalloc.h" #include "strarray.h" @@ -73,8 +71,6 @@ /* global state */ static volatile sig_atomic_t sigquit = 0; static int verbose = 0; -static int keep_flagged = 1; -static size_t max_archive_size = 0; /* current namespace */ static struct namespace expire_namespace; @@ -100,14 +96,6 @@ unsigned long userflags_expunged; }; -struct conversations_rock { - struct hash_table seen; - time_t expire_mark; - unsigned long databases_seen; - unsigned long msgids_seen; - unsigned long msgids_expired; -}; - struct delete_rock { time_t delete_mark; strarray_t to_delete; @@ -219,85 +207,6 @@ return 0; /* always keep the message */ } -static unsigned archive_cb(struct mailbox *mailbox, - struct index_record *record, - void *rock) -{ - time_t cutoff = *((time_t *)rock); - - /* never pull messages back from the archives */ - if (record->system_flags & FLAG_ARCHIVED) - return 1; - - /* always archive big messages */ - if (max_archive_size && max_archive_size <= record->size) - return 1; - - /* archive everything in DELETED mailboxes */ - if (mboxname_isdeletedmailbox(mailbox->name, NULL)) - return 1; - - /* Calendar and Addressbook are small files and need to be hot */ - if (mailbox->mbtype & MBTYPE_ADDRESSBOOK) - return 0; - if (mailbox->mbtype & MBTYPE_CALENDAR) - return 0; - - /* don't archive flagged messages - XXX, optional? */ - if (keep_flagged && (record->system_flags & FLAG_FLAGGED)) - return 0; - - /* archive all other old messages */ - if (record->internaldate < cutoff) - return 1; - - /* and don't archive anything else! */ - return 0; -} - -static int archive(void *rock, - const char *key, size_t keylen, - const char *data __attribute__((unused)), - size_t datalen __attribute__((unused))) -{ - char *name = xstrndup(key, keylen); - int r; - mbentry_t *mbentry = NULL; - struct mailbox *mailbox = NULL; - - if (sigquit) - return 1; - - /* Skip mailboxes with errors */ - r = mboxlist_lookup(name, &mbentry, NULL); - if (r == IMAP_MAILBOX_NONEXISTENT) - goto done; - if (r) { - if (verbose) - printf("error looking up %s: %s\n", name, error_message(r)); - goto done; - } - - if (mbentry->mbtype & MBTYPE_REMOTE) - goto done; - - r = mailbox_open_iwl(name, &mailbox); - if (r) { - /* mailbox corrupt/nonexistent -- skip it */ - syslog(LOG_WARNING, "unable to open mailbox %s: %s", - name, error_message(r)); - goto done; - } - - mailbox_archive(mailbox, archive_cb, rock); - -done: - mboxlist_entry_free(&mbentry); - mailbox_close(&mailbox); - - /* move on to the next mailbox regardless of errors */ - return 0; -} /* * mboxlist_findall() callback function to: @@ -305,12 +214,9 @@ * - build a hash table of mailboxes in which we expired messages, * - and perform a cleanup of expunged messages */ -static int expire(void *rock, - const char *key, size_t keylen, - const char *data __attribute__((unused)), - size_t datalen __attribute__((unused))) +static int expire(char *name, int matchlen __attribute__((unused)), + int maycreate __attribute__((unused)), void *rock) { - char *name = xstrndup(key, keylen); mbentry_t *mbentry = NULL; struct expire_rock *erock = (struct expire_rock *) rock; char *buf; @@ -334,7 +240,7 @@ if (verbose) { printf("error looking up %s: %s\n", name, error_message(r)); } - syslog(LOG_ERR, "IOERROR: error looking up %s: %s\n", name, error_message(r)); + syslog(LOG_ERR, "error looking up %s: %s\n", name, error_message(r)); return 0; /* still keep going */ } @@ -428,13 +334,12 @@ return 0; } -static int delete(void *rock, - const char *key, size_t keylen, - const char *data __attribute__((unused)), - size_t datalen __attribute__((unused))) +static int delete(char *name, + int matchlen __attribute__((unused)), + int maycreate __attribute__((unused)), + void *rock) { mbentry_t *mbentry = NULL; - char *name = xstrndup(key, keylen); struct delete_rock *drock = (struct delete_rock *) rock; int r; time_t timestamp; @@ -471,40 +376,6 @@ return(0); } -static int expire_conversations(void *rock, - const char *key, size_t keylen, - const char *data __attribute__((unused)), - size_t datalen __attribute__((unused))) -{ - char *name = xstrndup(key, keylen); - struct conversations_rock *crock = (struct conversations_rock *)rock; - char *filename = conversations_getmboxpath(name); - struct conversations_state *state = NULL; - unsigned int nseen = 0, ndeleted = 0; - - if (!filename) goto out; - - if (hash_lookup(filename, &crock->seen)) - goto out; - hash_insert(filename, (void *)1, &crock->seen); - - if (verbose) - fprintf(stderr, "Pruning conversations from db %s\n", filename); - - if (!conversations_open_path(filename, &state)) { - conversations_prune(state, crock->expire_mark, &nseen, &ndeleted); - conversations_commit(&state); - } - - crock->databases_seen++; - crock->msgids_seen += nseen; - crock->msgids_expired += ndeleted; - -out: - free(filename); - return 0; -} - static void sighandler (int sig __attribute((unused))) { sigquit = 1; @@ -517,17 +388,12 @@ int opt, r = 0; int do_expunge = 1; /* gnb:TODO bool */ int expunge_seconds = -1; - int archive_seconds = -1; int delete_seconds = -1; int expire_seconds = 0; - int cid_expire_seconds; - int do_cid_expire = -1; char *alt_config = NULL; - const char *find_prefix = NULL; - const char *do_user = NULL; + const char *find_prefix = "*"; struct expire_rock erock; struct delete_rock drock; - struct conversations_rock crock; struct sigaction action; if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { @@ -539,20 +405,13 @@ construct_hash_table(&erock.table, 10000, 1); memset(&drock, 0, sizeof(drock)); strarray_init(&drock.to_delete); - memset(&crock, 0, sizeof(crock)); - construct_hash_table(&crock.seen, 100, 1); - while ((opt = getopt(argc, argv, "C:D:E:X:A:p:u:vaxtcFS:")) != EOF) { + while ((opt = getopt(argc, argv, "C:D:E:X:p:vaxt")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; break; - case 'A': - if (archive_seconds >= 0) usage(); - if (!parse_duration(optarg, &archive_seconds)) usage(); - break; - case 'D': if (delete_seconds >= 0) usage(); if (!parse_duration(optarg, &delete_seconds)) usage(); @@ -568,22 +427,10 @@ if (!parse_duration(optarg, &expunge_seconds)) usage(); break; - case 'F': - keep_flagged = 0; - break; - - case 'S': - max_archive_size = atoi(optarg); /* bytes, yo */ - break; - case 'p': find_prefix = optarg; break; - case 'u': - do_user = optarg; - break; - case 'v': verbose++; break; @@ -601,11 +448,6 @@ erock.do_userflags = 1; break; - case 'c': - if (!do_cid_expire) usage(); - do_cid_expire = 0; - break; - default: usage(); break; @@ -615,7 +457,6 @@ if (!expire_seconds && delete_seconds == -1 && expunge_seconds == -1 && - archive_seconds == -1 && !erock.do_userflags) usage(); @@ -632,9 +473,6 @@ cyrus_init(alt_config, "cyr_expire", 0, 0); global_sasl_init(1, 0, NULL); - if (do_cid_expire < 0) - do_cid_expire = config_getswitch(IMAPOPT_CONVERSATIONS); - annotate_init(NULL, NULL); annotatemore_open(); @@ -662,15 +500,6 @@ exit(1); } - if (archive_seconds >= 0) { - time_t archive_mark = time(0) - archive_seconds; - if (do_user) - mboxlist_allusermbox(do_user, archive, &archive_mark, /*include_deleted*/1); - else - mboxlist_allmbox(find_prefix, archive, &archive_mark, /*include_deleted*/1); - /* XXX - add syslog? */ - } - if (do_expunge && (expunge_seconds >= 0 || expire_seconds || erock.do_userflags)) { /* xxx better way to determine a size for this table? */ @@ -690,10 +519,7 @@ } } - if (do_user) - mboxlist_allusermbox(do_user, expire, &erock, /*include_deleted*/1); - else - mboxlist_allmbox(find_prefix, expire, &erock, /*include_deleted*/1); + mboxlist_findall(NULL, find_prefix, 1, 0, 0, expire, &erock); syslog(LOG_NOTICE, "Expired %lu and expunged %lu out of %lu " "messages from %lu mailboxes", @@ -720,37 +546,6 @@ goto finish; } - if (do_cid_expire) { - cid_expire_seconds = config_getint(IMAPOPT_CONVERSATIONS_EXPIRE_DAYS) * 86400; - crock.expire_mark = time(0) - cid_expire_seconds; - - if (verbose) - fprintf(stderr, - "Removing conversation entries older than %0.2f days\n", - (double)(cid_expire_seconds/86400)); - - if (do_user) - mboxlist_allusermbox(do_user, expire_conversations, &crock, /*include_deleted*/1); - else - mboxlist_allmbox(find_prefix, expire_conversations, &crock, /*include_deleted*/1); - - syslog(LOG_NOTICE, "Expired %lu entries of %lu entries seen " - "in %lu conversation databases", - crock.msgids_expired, - crock.msgids_seen, - crock.databases_seen); - if (verbose) - fprintf(stderr, "Expired %lu entries of %lu entries seen " - "in %lu conversation databases\n", - crock.msgids_expired, - crock.msgids_seen, - crock.databases_seen); - } - - if (sigquit) { - goto finish; - } - if ((delete_seconds >= 0) && mboxlist_delayed_delete_isenabled() && config_getstring(IMAPOPT_DELETEDPREFIX)) { int count = 0; @@ -764,10 +559,7 @@ drock.delete_mark = time(0) - delete_seconds; - if (do_user) - mboxlist_allusermbox(do_user, delete, &drock, /*include_deleted*/1); - else - mboxlist_allmbox(find_prefix, delete, &drock, /*include_deleted*/1); + mboxlist_findall(NULL, find_prefix, 1, 0, 0, delete, &drock); for (i = 0 ; i < drock.to_delete.count ; i++) { char *name = drock.to_delete.datai; @@ -801,7 +593,6 @@ finish: free_hash_table(&erock.table, free); - free_hash_table(&crock.seen, NULL); strarray_fini(&drock.to_delete); quotadb_close();
View file
cyrus-imapd-2.5.tar.gz/imap/cyr_info.c
Changed
@@ -188,7 +188,6 @@ static int known_overflowkey(const char *key) { - const char *match; /* any partition is OK (XXX: are there name restrictions to check?) */ if (!strncmp(key, "partition-", 10)) return 1; @@ -198,12 +197,6 @@ return 1; } - match = strstr(key, "searchpartition-"); - if (match) { - if (config_getoverflowstring(match+6, NULL)) - return 1; - } - return 0; } @@ -280,42 +273,6 @@ } } -static void do_reid(const char *mboxname) -{ - struct mailbox *mailbox = NULL; - mbentry_t *mbentry = NULL; - int r; - - annotate_init(NULL, NULL); - annotatemore_open(); - - mboxlist_init(0); - mboxlist_open(NULL); - - r = mailbox_open_iwl(mboxname, &mailbox); - if (r) return; - - mailbox_make_uniqueid(mailbox); - - r = mboxlist_lookup(mboxname, &mbentry, NULL); - if (r) return; - - free(mbentry->uniqueid); - mbentry->uniqueid = xstrdup(mailbox->uniqueid); - - mboxlist_update(mbentry, 0); - - mailbox_close(&mailbox); - - mboxlist_close(); - mboxlist_done(); - - annotatemore_close(); - annotate_done(); - - printf("did reid %s\n", mboxname); -} - int main(int argc, char *argv) { extern char *optarg; @@ -360,11 +317,6 @@ do_conf(1); else if (!strcmp(argvoptind, "lint")) do_lint(); - else if (!strcmp(argvoptind, "reid")) { - if (optind + 1 >= argc) - usage(); - do_reid(argvoptind+1); - } else usage();
View file
cyrus-imapd-2.5.tar.gz/imap/cyr_sphinxmgr.c
Deleted
@@ -1,1380 +0,0 @@ -/* cyr_sphinxmgr.c - daemon for managing Sphinx index daemons - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <sys/types.h> -#include <syslog.h> -#include <sys/stat.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <stdlib.h> -#include <errno.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <signal.h> -#include <fcntl.h> -#include <sys/poll.h> - -#include "mboxname.h" -#include "mboxlist.h" -#include "imap/imap_err.h" -#include "global.h" -#include "retry.h" -#include "command.h" -#include "xstrlcat.h" -#include "xstrlcpy.h" -#include "xmalloc.h" -#include "hash.h" -#include "exitcodes.h" - -/* Various locations, relative to the Sphinx base directory */ -#define SOCKET_PATH "/searchd.sock" -#define SPHINX_CONFIG "/sphinx.conf" -#define SPHINX_PIDFILE "/searchd.pid" -#define SEARCHD "/usr/bin/searchd" - -#define STOPWAIT_DELAY_MS 50 /* 50 millisec */ -#define STOPWAIT_MAX_TRIES (20*1000/STOPWAIT_DELAY_MS) /* 20 sec */ - -extern int optind; -extern char *optarg; - -static int verbose = 0; -static int server_sock; -static int sphinx_timeout; -static int max_children; -static int num_started; /* number of children in STARTED state */ -static const char *syslog_prefix; - -typedef struct indexd indexd_t; -struct indexd { - /* There is no visible STARTING state because - * starting searchd is synchronous */ - enum { STOPPED, STARTED, STOPPING } state; - char *basedir; /* also used as the key */ - char *socketpath; - time_t started; /* for debugging */ - time_t used; - time_t stopped; /* when entered STOPPING state; for debugging */ - struct indexd *prev; - struct indexd *next; -}; -static struct hash_table itable; - -static struct indexd indexroot; - -static void shut_down(int code) __attribute__((noreturn)); -static int indexd_is_running(indexd_t *id); - -void fatal(const char *msg, int err) -{ - syslog(LOG_ERR, "Fatal error %s, exiting", msg); - shut_down(err); -} - -static void indexd_free(indexd_t *id) -{ - free(id->basedir); - free(id->socketpath); - free(id); -} - -static void indexd_set_state(indexd_t *id, unsigned state) -{ - if (id->state != state) { - if (id->state == STARTED) - num_started--; - - id->state = state; - - if (id->state == STARTED) - num_started++; - } -} - -static int indexd_setup_tree(indexd_t *id) -{ - static const char * const tobuild = { - "", - "/binlog", - NULL - }; - const char * const *dp; - char *path = NULL; - int r; - - if (verbose > 1) - syslog(LOG_INFO, "setting up tree"); - for (dp = tobuild ; *dp ; dp++) { - free(path); - path = strconcat(id->basedir, *dp, "/filename", (char *)NULL); - r = cyrus_mkdir(path, 0700); - if (r < 0 && errno != EEXIST) { - syslog(LOG_ERR, "IOERROR: unable to mkdir %s: %m", path); - r = IMAP_IOERROR; - goto out; - } - } - r = 0; - -out: - free(path); - return r; -} - -static int indexd_setup_config(indexd_t *id) -{ - static const char config = - "index rt\n" - "{\n" - " type = rt\n" - " source = fmindex\n" - " path = $sphinxdir/rt\n" - " morphology = stem_en\n" - " charset_type = utf-8\n" - " charset_table = 0..9, A..Z->a..z, _, a..z, \\\n" - /* Support for Cyrillic from - * http://sphinxsearch.com/wiki/doku.php?id=charset_tables#cyrillic */ - " U+0400->U+0435, U+0401->U+0435, U+0402->U+0452, U+0452, \\\n" - " U+0403->U+0433, U+0404->U+0454, U+0454, U+0405->U+0455, \\\n" - " U+0455, U+0406->U+0456, U+0407->U+0456, U+0457->U+0456, \\\n" - " U+0456, U+0408..U+040B->U+0458..U+045B, U+0458..U+045B, \\\n" - " U+040C->U+043A, U+040D->U+0438, U+040E->U+0443, U+040F->U+045F, \\\n" - " U+045F, U+0450->U+0435, U+0451->U+0435, U+0453->U+0433, \\\n" - " U+045C->U+043A, U+045D->U+0438, U+045E->U+0443, U+0460->U+0461, \\\n" - " U+0461, U+0462->U+0463, U+0463, U+0464->U+0465, U+0465, \\\n" - " U+0466->U+0467, U+0467, U+0468->U+0469, U+0469, U+046A->U+046B, \\\n" - " U+046B, U+046C->U+046D, U+046D, U+046E->U+046F, U+046F, \\\n" - " U+0470->U+0471, U+0471, U+0472->U+0473, U+0473, U+0474->U+0475, \\\n" - " U+0476->U+0475, U+0477->U+0475, U+0475, U+0478->U+0479, U+0479, \\\n" - " U+047A->U+047B, U+047B, U+047C->U+047D, U+047D, U+047E->U+047F, \\\n" - " U+047F, U+0480->U+0481, U+0481, U+048A->U+0438, U+048B->U+0438, \\\n" - " U+048C->U+044C, U+048D->U+044C, U+048E->U+0440, U+048F->U+0440, \\\n" - " U+0490->U+0433, U+0491->U+0433, U+0490->U+0433, U+0491->U+0433, \\\n" - " U+0492->U+0433, U+0493->U+0433, U+0494->U+0433, U+0495->U+0433, \\\n" - " U+0496->U+0436, U+0497->U+0436, U+0498->U+0437, U+0499->U+0437, \\\n" - " U+049A->U+043A, U+049B->U+043A, U+049C->U+043A, U+049D->U+043A, \\\n" - " U+049E->U+043A, U+049F->U+043A, U+04A0->U+043A, U+04A1->U+043A, \\\n" - " U+04A2->U+043D, U+04A3->U+043D, U+04A4->U+043D, U+04A5->U+043D, \\\n" - " U+04A6->U+043F, U+04A7->U+043F, U+04A8->U+04A9, U+04A9, \\\n" - " U+04AA->U+0441, U+04AB->U+0441, U+04AC->U+0442, U+04AD->U+0442, \\\n" - " U+04AE->U+0443, U+04AF->U+0443, U+04B0->U+0443, U+04B1->U+0443, \\\n" - " U+04B2->U+0445, U+04B3->U+0445, U+04B4->U+04B5, U+04B5, \\\n" - " U+04B6->U+0447, U+04B7->U+0447, U+04B8->U+0447, U+04B9->U+0447, \\\n" - " U+04BA->U+04BB, U+04BB, U+04BC->U+04BD, U+04BE->U+04BD, \\\n" - " U+04BF->U+04BD, U+04BD, U+04C0->U+04CF, U+04CF, U+04C1->U+0436, \\\n" - " U+04C2->U+0436, U+04C3->U+043A, U+04C4->U+043A, U+04C5->U+043B, \\\n" - " U+04C6->U+043B, U+04C7->U+043D, U+04C8->U+043D, U+04C9->U+043D, \\\n" - " U+04CA->U+043D, U+04CB->U+0447, U+04CC->U+0447, U+04CD->U+043C, \\\n" - " U+04CE->U+043C, U+04D0->U+0430, U+04D1->U+0430, U+04D2->U+0430, \\\n" - " U+04D3->U+0430, U+04D4->U+00E6, U+04D5->U+00E6, U+04D6->U+0435, \\\n" - " U+04D7->U+0435, U+04D8->U+04D9, U+04DA->U+04D9, U+04DB->U+04D9, \\\n" - " U+04D9, U+04DC->U+0436, U+04DD->U+0436, U+04DE->U+0437, \\\n" - " U+04DF->U+0437, U+04E0->U+04E1, U+04E1, U+04E2->U+0438, \\\n" - " U+04E3->U+0438, U+04E4->U+0438, U+04E5->U+0438, U+04E6->U+043E, \\\n" - " U+04E7->U+043E, U+04E8->U+043E, U+04E9->U+043E, U+04EA->U+043E, \\\n" - " U+04EB->U+043E, U+04EC->U+044D, U+04ED->U+044D, U+04EE->U+0443, \\\n" - " U+04EF->U+0443, U+04F0->U+0443, U+04F1->U+0443, U+04F2->U+0443, \\\n" - " U+04F3->U+0443, U+04F4->U+0447, U+04F5->U+0447, U+04F6->U+0433, \\\n" - " U+04F7->U+0433, U+04F8->U+044B, U+04F9->U+044B, U+04FA->U+0433, \\\n" - " U+04FB->U+0433, U+04FC->U+0445, U+04FD->U+0445, U+04FE->U+0445, \\\n" - " U+04FF->U+0445, U+0410..U+0418->U+0430..U+0438, U+0419->U+0438, \\\n" - " U+0430..U+0438, U+041A..U+042F->U+043A..U+044F, U+043A..U+044F,\\\n" - /* and this one was missing dammit */ - " U+0418->U+0438, U+0439->U+0438, \\\n" - /* Sumerian cuneiform which is fun for testing but seems - * to be broken in Spinx. */ - /* - " U+12000..U+1237F, \\\n" - */ - /* Support for Chinese/Japanese/Korean, from - * http://sphinxsearch.com/wiki/doku.php?id=charset_tables#cjk */ - " U+F900->U+8C48, U+F901->U+66F4, U+F902->U+8ECA, U+F903->U+8CC8, \\\n" - " U+F904->U+6ED1, U+F905->U+4E32, U+F906->U+53E5, U+F907->U+9F9C, \\\n" - " U+F908->U+9F9C, U+F909->U+5951, U+F90A->U+91D1, U+F90B->U+5587, \\\n" - " U+F90C->U+5948, U+F90D->U+61F6, U+F90E->U+7669, U+F90F->U+7F85, \\\n" - " U+F910->U+863F, U+F911->U+87BA, U+F912->U+88F8, U+F913->U+908F, \\\n" - " U+F914->U+6A02, U+F915->U+6D1B, U+F916->U+70D9, U+F917->U+73DE, \\\n" - " U+F918->U+843D, U+F919->U+916A, U+F91A->U+99F1, U+F91B->U+4E82, \\\n" - " U+F91C->U+5375, U+F91D->U+6B04, U+F91E->U+721B, U+F91F->U+862D, \\\n" - " U+F920->U+9E1E, U+F921->U+5D50, U+F922->U+6FEB, U+F923->U+85CD, \\\n" - " U+F924->U+8964, U+F925->U+62C9, U+F926->U+81D8, U+F927->U+881F, \\\n" - " U+F928->U+5ECA, U+F929->U+6717, U+F92A->U+6D6A, U+F92B->U+72FC, \\\n" - " U+F92C->U+90CE, U+F92D->U+4F86, U+F92E->U+51B7, U+F92F->U+52DE, \\\n" - " U+F930->U+64C4, U+F931->U+6AD3, U+F932->U+7210, U+F933->U+76E7, \\\n" - " U+F934->U+8001, U+F935->U+8606, U+F936->U+865C, U+F937->U+8DEF, \\\n" - " U+F938->U+9732, U+F939->U+9B6F, U+F93A->U+9DFA, U+F93B->U+788C, \\\n" - " U+F93C->U+797F, U+F93D->U+7DA0, U+F93E->U+83C9, U+F93F->U+9304, \\\n" - " U+F940->U+9E7F, U+F941->U+8AD6, U+F942->U+58DF, U+F943->U+5F04, \\\n" - " U+F944->U+7C60, U+F945->U+807E, U+F946->U+7262, U+F947->U+78CA, \\\n" - " U+F948->U+8CC2, U+F949->U+96F7, U+F94A->U+58D8, U+F94B->U+5C62, \\\n" - " U+F94C->U+6A13, U+F94D->U+6DDA, U+F94E->U+6F0F, U+F94F->U+7D2F, \\\n" - " U+F950->U+7E37, U+F951->U+964B, U+F952->U+52D2, U+F953->U+808B, \\\n" - " U+F954->U+51DC, U+F955->U+51CC, U+F956->U+7A1C, U+F957->U+7DBE, \\\n" - " U+F958->U+83F1, U+F959->U+9675, U+F95A->U+8B80, U+F95B->U+62CF, \\\n" - " U+F95C->U+6A02, U+F95D->U+8AFE, U+F95E->U+4E39, U+F95F->U+5BE7, \\\n" - " U+F960->U+6012, U+F961->U+7387, U+F962->U+7570, U+F963->U+5317, \\\n" - " U+F964->U+78FB, U+F965->U+4FBF, U+F966->U+5FA9, U+F967->U+4E0D, \\\n" - " U+F968->U+6CCC, U+F969->U+6578, U+F96A->U+7D22, U+F96B->U+53C3, \\\n" - " U+F96C->U+585E, U+F96D->U+7701, U+F96E->U+8449, U+F96F->U+8AAA, \\\n" - " U+F970->U+6BBA, U+F971->U+8FB0, U+F972->U+6C88, U+F973->U+62FE, \\\n" - " U+F974->U+82E5, U+F975->U+63A0, U+F976->U+7565, U+F977->U+4EAE, \\\n" - " U+F978->U+5169, U+F979->U+51C9, U+F97A->U+6881, U+F97B->U+7CE7, \\\n" - " U+F97C->U+826F, U+F97D->U+8AD2, U+F97E->U+91CF, U+F97F->U+52F5, \\\n" - " U+F980->U+5442, U+F981->U+5973, U+F982->U+5EEC, U+F983->U+65C5, \\\n" - " U+F984->U+6FFE, U+F985->U+792A, U+F986->U+95AD, U+F987->U+9A6A, \\\n" - " U+F988->U+9E97, U+F989->U+9ECE, U+F98A->U+529B, U+F98B->U+66C6, \\\n" - " U+F98C->U+6B77, U+F98D->U+8F62, U+F98E->U+5E74, U+F98F->U+6190, \\\n" - " U+F990->U+6200, U+F991->U+649A, U+F992->U+6F23, U+F993->U+7149, \\\n" - " U+F994->U+7489, U+F995->U+79CA, U+F996->U+7DF4, U+F997->U+806F, \\\n" - " U+F998->U+8F26, U+F999->U+84EE, U+F99A->U+9023, U+F99B->U+934A, \\\n" - " U+F99C->U+5217, U+F99D->U+52A3, U+F99E->U+54BD, U+F99F->U+70C8, \\\n" - " U+F9A0->U+88C2, U+F9A1->U+8AAA, U+F9A2->U+5EC9, U+F9A3->U+5FF5, \\\n" - " U+F9A4->U+637B, U+F9A5->U+6BAE, U+F9A6->U+7C3E, U+F9A7->U+7375, \\\n" - " U+F9A8->U+4EE4, U+F9A9->U+56F9, U+F9AA->U+5BE7, U+F9AB->U+5DBA, \\\n" - " U+F9AC->U+601C, U+F9AD->U+73B2, U+F9AE->U+7469, U+F9AF->U+7F9A, \\\n" - " U+F9B0->U+8046, U+F9B1->U+9234, U+F9B2->U+96F6, U+F9B3->U+9748, \\\n" - " U+F9B4->U+9818, U+F9B5->U+4F8B, U+F9B6->U+79AE, U+F9B7->U+91B4, \\\n" - " U+F9B8->U+96B8, U+F9B9->U+60E1, U+F9BA->U+4E86, U+F9BB->U+50DA, \\\n" - " U+F9BC->U+5BEE, U+F9BD->U+5C3F, U+F9BE->U+6599, U+F9BF->U+6A02, \\\n" - " U+F9C0->U+71CE, U+F9C1->U+7642, U+F9C2->U+84FC, U+F9C3->U+907C, \\\n" - " U+F9C4->U+9F8D, U+F9C5->U+6688, U+F9C6->U+962E, U+F9C7->U+5289, \\\n" - " U+F9C8->U+677B, U+F9C9->U+67F3, U+F9CA->U+6D41, U+F9CB->U+6E9C, \\\n" - " U+F9CC->U+7409, U+F9CD->U+7559, U+F9CE->U+786B, U+F9CF->U+7D10, \\\n" - " U+F9D0->U+985E, U+F9D1->U+516D, U+F9D2->U+622E, U+F9D3->U+9678, \\\n" - " U+F9D4->U+502B, U+F9D5->U+5D19, U+F9D6->U+6DEA, U+F9D7->U+8F2A, \\\n" - " U+F9D8->U+5F8B, U+F9D9->U+6144, U+F9DA->U+6817, U+F9DB->U+7387, \\\n" - " U+F9DC->U+9686, U+F9DD->U+5229, U+F9DE->U+540F, U+F9DF->U+5C65, \\\n" - " U+F9E0->U+6613, U+F9E1->U+674E, U+F9E2->U+68A8, U+F9E3->U+6CE5, \\\n" - " U+F9E4->U+7406, U+F9E5->U+75E2, U+F9E6->U+7F79, U+F9E7->U+88CF, \\\n" - " U+F9E8->U+88E1, U+F9E9->U+91CC, U+F9EA->U+96E2, U+F9EB->U+533F, \\\n" - " U+F9EC->U+6EBA, U+F9ED->U+541D, U+F9EE->U+71D0, U+F9EF->U+7498, \\\n" - " U+F9F0->U+85FA, U+F9F1->U+96A3, U+F9F2->U+9C57, U+F9F3->U+9E9F, \\\n" - " U+F9F4->U+6797, U+F9F5->U+6DCB, U+F9F6->U+81E8, U+F9F7->U+7ACB, \\\n" - " U+F9F8->U+7B20, U+F9F9->U+7C92, U+F9FA->U+72C0, U+F9FB->U+7099, \\\n" - " U+F9FC->U+8B58, U+F9FD->U+4EC0, U+F9FE->U+8336, U+F9FF->U+523A, \\\n" - " U+FA00->U+5207, U+FA01->U+5EA6, U+FA02->U+62D3, U+FA03->U+7CD6, \\\n" - " U+FA04->U+5B85, U+FA05->U+6D1E, U+FA06->U+66B4, U+FA07->U+8F3B, \\\n" - " U+FA08->U+884C, U+FA09->U+964D, U+FA0A->U+898B, U+FA0B->U+5ED3, \\\n" - " U+FA0C->U+5140, U+FA0D->U+55C0, U+FA10->U+585A, U+FA12->U+6674, \\\n" - " U+FA15->U+51DE, U+FA16->U+732A, U+FA17->U+76CA, U+FA18->U+793C, \\\n" - " U+FA19->U+795E, U+FA1A->U+7965, U+FA1B->U+798F, U+FA1C->U+9756, \\\n" - " U+FA1D->U+7CBE, U+FA1E->U+7FBD, U+FA20->U+8612, U+FA22->U+8AF8, \\\n" - " U+FA25->U+9038, U+FA26->U+90FD, U+FA2A->U+98EF, U+FA2B->U+98FC, \\\n" - " U+FA2C->U+9928, U+FA2D->U+9DB4, U+FA30->U+4FAE, U+FA31->U+50E7, \\\n" - " U+FA32->U+514D, U+FA33->U+52C9, U+FA34->U+52E4, U+FA35->U+5351, \\\n" - " U+FA36->U+559D, U+FA37->U+5606, U+FA38->U+5668, U+FA39->U+5840, \\\n" - " U+FA3A->U+58A8, U+FA3B->U+5C64, U+FA3C->U+5C6E, U+FA3D->U+6094, \\\n" - " U+FA3E->U+6168, U+FA3F->U+618E, U+FA40->U+61F2, U+FA41->U+654F, \\\n" - " U+FA42->U+65E2, U+FA43->U+6691, U+FA44->U+6885, U+FA45->U+6D77, \\\n" - " U+FA46->U+6E1A, U+FA47->U+6F22, U+FA48->U+716E, U+FA49->U+722B, \\\n" - " U+FA4A->U+7422, U+FA4B->U+7891, U+FA4C->U+793E, U+FA4D->U+7949, \\\n" - " U+FA4E->U+7948, U+FA4F->U+7950, U+FA50->U+7956, U+FA51->U+795D, \\\n" - " U+FA52->U+798D, U+FA53->U+798E, U+FA54->U+7A40, U+FA55->U+7A81, \\\n" - " U+FA56->U+7BC0, U+FA57->U+7DF4, U+FA58->U+7E09, U+FA59->U+7E41, \\\n" - " U+FA5A->U+7F72, U+FA5B->U+8005, U+FA5C->U+81ED, U+FA5D->U+8279, \\\n" - " U+FA5E->U+8279, U+FA5F->U+8457, U+FA60->U+8910, U+FA61->U+8996, \\\n" - " U+FA62->U+8B01, U+FA63->U+8B39, U+FA64->U+8CD3, U+FA65->U+8D08, \\\n" - " U+FA66->U+8FB6, U+FA67->U+9038, U+FA68->U+96E3, U+FA69->U+97FF, \\\n" - " U+FA6A->U+983B, U+FA70->U+4E26, U+FA71->U+51B5, U+FA72->U+5168, \\\n" - " U+FA73->U+4F80, U+FA74->U+5145, U+FA75->U+5180, U+FA76->U+52C7, \\\n" - " U+FA77->U+52FA, U+FA78->U+559D, U+FA79->U+5555, U+FA7A->U+5599, \\\n" - " U+FA7B->U+55E2, U+FA7C->U+585A, U+FA7D->U+58B3, U+FA7E->U+5944, \\\n" - " U+FA7F->U+5954, U+FA80->U+5A62, U+FA81->U+5B28, U+FA82->U+5ED2, \\\n" - " U+FA83->U+5ED9, U+FA84->U+5F69, U+FA85->U+5FAD, U+FA86->U+60D8, \\\n" - " U+FA87->U+614E, U+FA88->U+6108, U+FA89->U+618E, U+FA8A->U+6160, \\\n" - " U+FA8B->U+61F2, U+FA8C->U+6234, U+FA8D->U+63C4, U+FA8E->U+641C, \\\n" - " U+FA8F->U+6452, U+FA90->U+6556, U+FA91->U+6674, U+FA92->U+6717, \\\n" - " U+FA93->U+671B, U+FA94->U+6756, U+FA95->U+6B79, U+FA96->U+6BBA, \\\n" - " U+FA97->U+6D41, U+FA98->U+6EDB, U+FA99->U+6ECB, U+FA9A->U+6F22, \\\n" - " U+FA9B->U+701E, U+FA9C->U+716E, U+FA9D->U+77A7, U+FA9E->U+7235, \\\n" - " U+FA9F->U+72AF, U+FAA0->U+732A, U+FAA1->U+7471, U+FAA2->U+7506, \\\n" - " U+FAA3->U+753B, U+FAA4->U+761D, U+FAA5->U+761F, U+FAA6->U+76CA, \\\n" - " U+FAA7->U+76DB, U+FAA8->U+76F4, U+FAA9->U+774A, U+FAAA->U+7740, \\\n" - " U+FAAB->U+78CC, U+FAAC->U+7AB1, U+FAAD->U+7BC0, U+FAAE->U+7C7B, \\\n" - " U+FAAF->U+7D5B, U+FAB0->U+7DF4, U+FAB1->U+7F3E, U+FAB2->U+8005, \\\n" - " U+FAB3->U+8352, U+FAB4->U+83EF, U+FAB5->U+8779, U+FAB6->U+8941, \\\n" - " U+FAB7->U+8986, U+FAB8->U+8996, U+FAB9->U+8ABF, U+FABA->U+8AF8, \\\n" - " U+FABB->U+8ACB, U+FABC->U+8B01, U+FABD->U+8AFE, U+FABE->U+8AED, \\\n" - " U+FABF->U+8B39, U+FAC0->U+8B8A, U+FAC1->U+8D08, U+FAC2->U+8F38, \\\n" - " U+FAC3->U+9072, U+FAC4->U+9199, U+FAC5->U+9276, U+FAC6->U+967C, \\\n" - " U+FAC7->U+96E3, U+FAC8->U+9756, U+FAC9->U+97DB, U+FACA->U+97FF, \\\n" - " U+FACB->U+980B, U+FACC->U+983B, U+FACD->U+9B12, U+FACE->U+9F9C, \\\n" - " U+FACF->U+2284A, U+FAD0->U+22844, U+FAD1->U+233D5, U+FAD2->U+3B9D, \\\n" - " U+FAD3->U+4018, U+FAD4->U+4039, U+FAD5->U+25249, U+FAD6->U+25CD0, \\\n" - " U+FAD7->U+27ED3, U+FAD8->U+9F43, U+FAD9->U+9F8E, U+2F800->U+4E3D, \\\n" - " U+2F801->U+4E38, U+2F802->U+4E41, U+2F803->U+20122, U+2F804->U+4F60, \\\n" - " U+2F805->U+4FAE, U+2F806->U+4FBB, U+2F807->U+5002, U+2F808->U+507A, \\\n" - " U+2F809->U+5099, U+2F80A->U+50E7, U+2F80B->U+50CF, U+2F80C->U+349E, \\\n" - " U+2F80D->U+2063A, U+2F80E->U+514D, U+2F80F->U+5154, U+2F810->U+5164, \\\n" - " U+2F811->U+5177, U+2F812->U+2051C, U+2F813->U+34B9, U+2F814->U+5167, \\\n" - " U+2F815->U+518D, U+2F816->U+2054B, U+2F817->U+5197, U+2F818->U+51A4, \\\n" - " U+2F819->U+4ECC, U+2F81A->U+51AC, U+2F81B->U+51B5, U+2F81C->U+291DF, \\\n" - " U+2F81D->U+51F5, U+2F81E->U+5203, U+2F81F->U+34DF, U+2F820->U+523B, \\\n" - " U+2F821->U+5246, U+2F822->U+5272, U+2F823->U+5277, U+2F824->U+3515, \\\n" - " U+2F825->U+52C7, U+2F826->U+52C9, U+2F827->U+52E4, U+2F828->U+52FA, \\\n" - " U+2F829->U+5305, U+2F82A->U+5306, U+2F82B->U+5317, U+2F82C->U+5349, \\\n" - " U+2F82D->U+5351, U+2F82E->U+535A, U+2F82F->U+5373, U+2F830->U+537D, \\\n" - " U+2F831->U+537F, U+2F832->U+537F, U+2F833->U+537F, U+2F834->U+20A2C, \\\n" - " U+2F835->U+7070, U+2F836->U+53CA, U+2F837->U+53DF, U+2F838->U+20B63, \\\n" - " U+2F839->U+53EB, U+2F83A->U+53F1, U+2F83B->U+5406, U+2F83C->U+549E, \\\n" - " U+2F83D->U+5438, U+2F83E->U+5448, U+2F83F->U+5468, U+2F840->U+54A2, \\\n" - " U+2F841->U+54F6, U+2F842->U+5510, U+2F843->U+5553, U+2F844->U+5563, \\\n" - " U+2F845->U+5584, U+2F846->U+5584, U+2F847->U+5599, U+2F848->U+55AB, \\\n" - " U+2F849->U+55B3, U+2F84A->U+55C2, U+2F84B->U+5716, U+2F84C->U+5606, \\\n" - " U+2F84D->U+5717, U+2F84E->U+5651, U+2F84F->U+5674, U+2F850->U+5207, \\\n" - " U+2F851->U+58EE, U+2F852->U+57CE, U+2F853->U+57F4, U+2F854->U+580D, \\\n" - " U+2F855->U+578B, U+2F856->U+5832, U+2F857->U+5831, U+2F858->U+58AC, \\\n" - " U+2F859->U+214E4, U+2F85A->U+58F2, U+2F85B->U+58F7, U+2F85C->U+5906, \\\n" - " U+2F85D->U+591A, U+2F85E->U+5922, U+2F85F->U+5962, U+2F860->U+216A8, \\\n" - " U+2F861->U+216EA, U+2F862->U+59EC, U+2F863->U+5A1B, U+2F864->U+5A27, \\\n" - " U+2F865->U+59D8, U+2F866->U+5A66, U+2F867->U+36EE, U+2F868->U+36FC, \\\n" - " U+2F869->U+5B08, U+2F86A->U+5B3E, U+2F86B->U+5B3E, U+2F86C->U+219C8, \\\n" - " U+2F86D->U+5BC3, U+2F86E->U+5BD8, U+2F86F->U+5BE7, U+2F870->U+5BF3, \\\n" - " U+2F871->U+21B18, U+2F872->U+5BFF, U+2F873->U+5C06, U+2F874->U+5F53, \\\n" - " U+2F875->U+5C22, U+2F876->U+3781, U+2F877->U+5C60, U+2F878->U+5C6E, \\\n" - " U+2F879->U+5CC0, U+2F87A->U+5C8D, U+2F87B->U+21DE4, U+2F87C->U+5D43, \\\n" - " U+2F87D->U+21DE6, U+2F87E->U+5D6E, U+2F87F->U+5D6B, U+2F880->U+5D7C, \\\n" - " U+2F881->U+5DE1, U+2F882->U+5DE2, U+2F883->U+382F, U+2F884->U+5DFD, \\\n" - " U+2F885->U+5E28, U+2F886->U+5E3D, U+2F887->U+5E69, U+2F888->U+3862, \\\n" - " U+2F889->U+22183, U+2F88A->U+387C, U+2F88B->U+5EB0, U+2F88C->U+5EB3, \\\n" - " U+2F88D->U+5EB6, U+2F88E->U+5ECA, U+2F88F->U+2A392, U+2F890->U+5EFE, \\\n" - " U+2F891->U+22331, U+2F892->U+22331, U+2F893->U+8201, U+2F894->U+5F22, \\\n" - " U+2F895->U+5F22, U+2F896->U+38C7, U+2F897->U+232B8, U+2F898->U+261DA, \\\n" - " U+2F899->U+5F62, U+2F89A->U+5F6B, U+2F89B->U+38E3, U+2F89C->U+5F9A, \\\n" - " U+2F89D->U+5FCD, U+2F89E->U+5FD7, U+2F89F->U+5FF9, U+2F8A0->U+6081, \\\n" - " U+2F8A1->U+393A, U+2F8A2->U+391C, U+2F8A3->U+6094, U+2F8A4->U+226D4, \\\n" - " U+2F8A5->U+60C7, U+2F8A6->U+6148, U+2F8A7->U+614C, U+2F8A8->U+614E, \\\n" - " U+2F8A9->U+614C, U+2F8AA->U+617A, U+2F8AB->U+618E, U+2F8AC->U+61B2, \\\n" - " U+2F8AD->U+61A4, U+2F8AE->U+61AF, U+2F8AF->U+61DE, U+2F8B0->U+61F2, \\\n" - " U+2F8B1->U+61F6, U+2F8B2->U+6210, U+2F8B3->U+621B, U+2F8B4->U+625D, \\\n" - " U+2F8B5->U+62B1, U+2F8B6->U+62D4, U+2F8B7->U+6350, U+2F8B8->U+22B0C, \\\n" - " U+2F8B9->U+633D, U+2F8BA->U+62FC, U+2F8BB->U+6368, U+2F8BC->U+6383, \\\n" - " U+2F8BD->U+63E4, U+2F8BE->U+22BF1, U+2F8BF->U+6422, U+2F8C0->U+63C5, \\\n" - " U+2F8C1->U+63A9, U+2F8C2->U+3A2E, U+2F8C3->U+6469, U+2F8C4->U+647E, \\\n" - " U+2F8C5->U+649D, U+2F8C6->U+6477, U+2F8C7->U+3A6C, U+2F8C8->U+654F, \\\n" - " U+2F8C9->U+656C, U+2F8CA->U+2300A, U+2F8CB->U+65E3, U+2F8CC->U+66F8, \\\n" - " U+2F8CD->U+6649, U+2F8CE->U+3B19, U+2F8CF->U+6691, U+2F8D0->U+3B08, \\\n" - " U+2F8D1->U+3AE4, U+2F8D2->U+5192, U+2F8D3->U+5195, U+2F8D4->U+6700, \\\n" - " U+2F8D5->U+669C, U+2F8D6->U+80AD, U+2F8D7->U+43D9, U+2F8D8->U+6717, \\\n" - " U+2F8D9->U+671B, U+2F8DA->U+6721, U+2F8DB->U+675E, U+2F8DC->U+6753, \\\n" - " U+2F8DD->U+233C3, U+2F8DE->U+3B49, U+2F8DF->U+67FA, U+2F8E0->U+6785, \\\n" - " U+2F8E1->U+6852, U+2F8E2->U+6885, U+2F8E3->U+2346D, U+2F8E4->U+688E, \\\n" - " U+2F8E5->U+681F, U+2F8E6->U+6914, U+2F8E7->U+3B9D, U+2F8E8->U+6942, \\\n" - " U+2F8E9->U+69A3, U+2F8EA->U+69EA, U+2F8EB->U+6AA8, U+2F8EC->U+236A3, \\\n" - " U+2F8ED->U+6ADB, U+2F8EE->U+3C18, U+2F8EF->U+6B21, U+2F8F0->U+238A7, \\\n" - " U+2F8F1->U+6B54, U+2F8F2->U+3C4E, U+2F8F3->U+6B72, U+2F8F4->U+6B9F, \\\n" - " U+2F8F5->U+6BBA, U+2F8F6->U+6BBB, U+2F8F7->U+23A8D, U+2F8F8->U+21D0B, \\\n" - " U+2F8F9->U+23AFA, U+2F8FA->U+6C4E, U+2F8FB->U+23CBC, U+2F8FC->U+6CBF, \\\n" - " U+2F8FD->U+6CCD, U+2F8FE->U+6C67, U+2F8FF->U+6D16, U+2F900->U+6D3E, \\\n" - " U+2F901->U+6D77, U+2F902->U+6D41, U+2F903->U+6D69, U+2F904->U+6D78, \\\n" - " U+2F905->U+6D85, U+2F906->U+23D1E, U+2F907->U+6D34, U+2F908->U+6E2F, \\\n" - " U+2F909->U+6E6E, U+2F90A->U+3D33, U+2F90B->U+6ECB, U+2F90C->U+6EC7, \\\n" - " U+2F90D->U+23ED1, U+2F90E->U+6DF9, U+2F90F->U+6F6E, U+2F910->U+23F5E, \\\n" - " U+2F911->U+23F8E, U+2F912->U+6FC6, U+2F913->U+7039, U+2F914->U+701E, \\\n" - " U+2F915->U+701B, U+2F916->U+3D96, U+2F917->U+704A, U+2F918->U+707D, \\\n" - " U+2F919->U+7077, U+2F91A->U+70AD, U+2F91B->U+20525, U+2F91C->U+7145, \\\n" - " U+2F91D->U+24263, U+2F91E->U+719C, U+2F91F->U+243AB, U+2F920->U+7228, \\\n" - " U+2F921->U+7235, U+2F922->U+7250, U+2F923->U+24608, U+2F924->U+7280, \\\n" - " U+2F925->U+7295, U+2F926->U+24735, U+2F927->U+24814, U+2F928->U+737A, \\\n" - " U+2F929->U+738B, U+2F92A->U+3EAC, U+2F92B->U+73A5, U+2F92C->U+3EB8, \\\n" - " U+2F92D->U+3EB8, U+2F92E->U+7447, U+2F92F->U+745C, U+2F930->U+7471, \\\n" - " U+2F931->U+7485, U+2F932->U+74CA, U+2F933->U+3F1B, U+2F934->U+7524, \\\n" - " U+2F935->U+24C36, U+2F936->U+753E, U+2F937->U+24C92, U+2F938->U+7570, \\\n" - " U+2F939->U+2219F, U+2F93A->U+7610, U+2F93B->U+24FA1, U+2F93C->U+24FB8, \\\n" - " U+2F93D->U+25044, U+2F93E->U+3FFC, U+2F93F->U+4008, U+2F940->U+76F4, \\\n" - " U+2F941->U+250F3, U+2F942->U+250F2, U+2F943->U+25119, U+2F944->U+25133, \\\n" - " U+2F945->U+771E, U+2F946->U+771F, U+2F947->U+771F, U+2F948->U+774A, \\\n" - " U+2F949->U+4039, U+2F94A->U+778B, U+2F94B->U+4046, U+2F94C->U+4096, \\\n" - " U+2F94D->U+2541D, U+2F94E->U+784E, U+2F94F->U+788C, U+2F950->U+78CC, \\\n" - " U+2F951->U+40E3, U+2F952->U+25626, U+2F953->U+7956, U+2F954->U+2569A, \\\n" - " U+2F955->U+256C5, U+2F956->U+798F, U+2F957->U+79EB, U+2F958->U+412F, \\\n" - " U+2F959->U+7A40, U+2F95A->U+7A4A, U+2F95B->U+7A4F, U+2F95C->U+2597C, \\\n" - " U+2F95D->U+25AA7, U+2F95E->U+25AA7, U+2F95F->U+7AEE, U+2F960->U+4202, \\\n" - " U+2F961->U+25BAB, U+2F962->U+7BC6, U+2F963->U+7BC9, U+2F964->U+4227, \\\n" - " U+2F965->U+25C80, U+2F966->U+7CD2, U+2F967->U+42A0, U+2F968->U+7CE8, \\\n" - " U+2F969->U+7CE3, U+2F96A->U+7D00, U+2F96B->U+25F86, U+2F96C->U+7D63, \\\n" - " U+2F96D->U+4301, U+2F96E->U+7DC7, U+2F96F->U+7E02, U+2F970->U+7E45, \\\n" - " U+2F971->U+4334, U+2F972->U+26228, U+2F973->U+26247, U+2F974->U+4359, \\\n" - " U+2F975->U+262D9, U+2F976->U+7F7A, U+2F977->U+2633E, U+2F978->U+7F95, \\\n" - " U+2F979->U+7FFA, U+2F97A->U+8005, U+2F97B->U+264DA, U+2F97C->U+26523, \\\n" - " U+2F97D->U+8060, U+2F97E->U+265A8, U+2F97F->U+8070, U+2F980->U+2335F, \\\n" - " U+2F981->U+43D5, U+2F982->U+80B2, U+2F983->U+8103, U+2F984->U+440B, \\\n" - " U+2F985->U+813E, U+2F986->U+5AB5, U+2F987->U+267A7, U+2F988->U+267B5, \\\n" - " U+2F989->U+23393, U+2F98A->U+2339C, U+2F98B->U+8201, U+2F98C->U+8204, \\\n" - " U+2F98D->U+8F9E, U+2F98E->U+446B, U+2F98F->U+8291, U+2F990->U+828B, \\\n" - " U+2F991->U+829D, U+2F992->U+52B3, U+2F993->U+82B1, U+2F994->U+82B3, \\\n" - " U+2F995->U+82BD, U+2F996->U+82E6, U+2F997->U+26B3C, U+2F998->U+82E5, \\\n" - " U+2F999->U+831D, U+2F99A->U+8363, U+2F99B->U+83AD, U+2F99C->U+8323, \\\n" - " U+2F99D->U+83BD, U+2F99E->U+83E7, U+2F99F->U+8457, U+2F9A0->U+8353, \\\n" - " U+2F9A1->U+83CA, U+2F9A2->U+83CC, U+2F9A3->U+83DC, U+2F9A4->U+26C36, \\\n" - " U+2F9A5->U+26D6B, U+2F9A6->U+26CD5, U+2F9A7->U+452B, U+2F9A8->U+84F1, \\\n" - " U+2F9A9->U+84F3, U+2F9AA->U+8516, U+2F9AB->U+273CA, U+2F9AC->U+8564, \\\n" - " U+2F9AD->U+26F2C, U+2F9AE->U+455D, U+2F9AF->U+4561, U+2F9B0->U+26FB1, \\\n" - " U+2F9B1->U+270D2, U+2F9B2->U+456B, U+2F9B3->U+8650, U+2F9B4->U+865C, \\\n" - " U+2F9B5->U+8667, U+2F9B6->U+8669, U+2F9B7->U+86A9, U+2F9B8->U+8688, \\\n" - " U+2F9B9->U+870E, U+2F9BA->U+86E2, U+2F9BB->U+8779, U+2F9BC->U+8728, \\\n" - " U+2F9BD->U+876B, U+2F9BE->U+8786, U+2F9BF->U+45D7, U+2F9C0->U+87E1, \\\n" - " U+2F9C1->U+8801, U+2F9C2->U+45F9, U+2F9C3->U+8860, U+2F9C4->U+8863, \\\n" - " U+2F9C5->U+27667, U+2F9C6->U+88D7, U+2F9C7->U+88DE, U+2F9C8->U+4635, \\\n" - " U+2F9C9->U+88FA, U+2F9CA->U+34BB, U+2F9CB->U+278AE, U+2F9CC->U+27966, \\\n" - " U+2F9CD->U+46BE, U+2F9CE->U+46C7, U+2F9CF->U+8AA0, U+2F9D0->U+8AED, \\\n" - " U+2F9D1->U+8B8A, U+2F9D2->U+8C55, U+2F9D3->U+27CA8, U+2F9D4->U+8CAB, \\\n" - " U+2F9D5->U+8CC1, U+2F9D6->U+8D1B, U+2F9D7->U+8D77, U+2F9D8->U+27F2F, \\\n" - " U+2F9D9->U+20804, U+2F9DA->U+8DCB, U+2F9DB->U+8DBC, U+2F9DC->U+8DF0, \\\n" - " U+2F9DD->U+208DE, U+2F9DE->U+8ED4, U+2F9DF->U+8F38, U+2F9E0->U+285D2, \\\n" - " U+2F9E1->U+285ED, U+2F9E2->U+9094, U+2F9E3->U+90F1, U+2F9E4->U+9111, \\\n" - " U+2F9E5->U+2872E, U+2F9E6->U+911B, U+2F9E7->U+9238, U+2F9E8->U+92D7, \\\n" - " U+2F9E9->U+92D8, U+2F9EA->U+927C, U+2F9EB->U+93F9, U+2F9EC->U+9415, \\\n" - " U+2F9ED->U+28BFA, U+2F9EE->U+958B, U+2F9EF->U+4995, U+2F9F0->U+95B7, \\\n" - " U+2F9F1->U+28D77, U+2F9F2->U+49E6, U+2F9F3->U+96C3, U+2F9F4->U+5DB2, \\\n" - " U+2F9F5->U+9723, U+2F9F6->U+29145, U+2F9F7->U+2921A, U+2F9F8->U+4A6E, \\\n" - " U+2F9F9->U+4A76, U+2F9FA->U+97E0, U+2F9FB->U+2940A, U+2F9FC->U+4AB2, \\\n" - " U+2F9FD->U+29496, U+2F9FE->U+980B, U+2F9FF->U+980B, U+2FA00->U+9829, \\\n" - " U+2FA01->U+295B6, U+2FA02->U+98E2, U+2FA03->U+4B33, U+2FA04->U+9929, \\\n" - " U+2FA05->U+99A7, U+2FA06->U+99C2, U+2FA07->U+99FE, U+2FA08->U+4BCE, \\\n" - " U+2FA09->U+29B30, U+2FA0A->U+9B12, U+2FA0B->U+9C40, U+2FA0C->U+9CFD, \\\n" - " U+2FA0D->U+4CCE, U+2FA0E->U+4CED, U+2FA0F->U+9D67, U+2FA10->U+2A0CE, \\\n" - " U+2FA11->U+4CF8, U+2FA12->U+2A105, U+2FA13->U+2A20E, U+2FA14->U+2A291, \\\n" - " U+2FA15->U+9EBB, U+2FA16->U+4D56, U+2FA17->U+9EF9, U+2FA18->U+9EFE, \\\n" - " U+2FA19->U+9F05, U+2FA1A->U+9F0F, U+2FA1B->U+9F16, U+2FA1C->U+9F3B, \\\n" - " U+2FA1D->U+2A600, U+2F00->U+4E00, U+2F01->U+4E28, U+2F02->U+4E36, \\\n" - " U+2F03->U+4E3F, U+2F04->U+4E59, U+2F05->U+4E85, U+2F06->U+4E8C, \\\n" - " U+2F07->U+4EA0, U+2F08->U+4EBA, U+2F09->U+513F, U+2F0A->U+5165, \\\n" - " U+2F0B->U+516B, U+2F0C->U+5182, U+2F0D->U+5196, U+2F0E->U+51AB, \\\n" - " U+2F0F->U+51E0, U+2F10->U+51F5, U+2F11->U+5200, U+2F12->U+529B, \\\n" - " U+2F13->U+52F9, U+2F14->U+5315, U+2F15->U+531A, U+2F16->U+5338, \\\n" - " U+2F17->U+5341, U+2F18->U+535C, U+2F19->U+5369, U+2F1A->U+5382, \\\n" - " U+2F1B->U+53B6, U+2F1C->U+53C8, U+2F1D->U+53E3, U+2F1E->U+56D7, \\\n" - " U+2F1F->U+571F, U+2F20->U+58EB, U+2F21->U+5902, U+2F22->U+590A, \\\n" - " U+2F23->U+5915, U+2F24->U+5927, U+2F25->U+5973, U+2F26->U+5B50, \\\n" - " U+2F27->U+5B80, U+2F28->U+5BF8, U+2F29->U+5C0F, U+2F2A->U+5C22, \\\n" - " U+2F2B->U+5C38, U+2F2C->U+5C6E, U+2F2D->U+5C71, U+2F2E->U+5DDB, \\\n" - " U+2F2F->U+5DE5, U+2F30->U+5DF1, U+2F31->U+5DFE, U+2F32->U+5E72, \\\n" - " U+2F33->U+5E7A, U+2F34->U+5E7F, U+2F35->U+5EF4, U+2F36->U+5EFE, \\\n" - " U+2F37->U+5F0B, U+2F38->U+5F13, U+2F39->U+5F50, U+2F3A->U+5F61, \\\n" - " U+2F3B->U+5F73, U+2F3C->U+5FC3, U+2F3D->U+6208, U+2F3E->U+6236, \\\n" - " U+2F3F->U+624B, U+2F40->U+652F, U+2F41->U+6534, U+2F42->U+6587, \\\n" - " U+2F43->U+6597, U+2F44->U+65A4, U+2F45->U+65B9, U+2F46->U+65E0, \\\n" - " U+2F47->U+65E5, U+2F48->U+66F0, U+2F49->U+6708, U+2F4A->U+6728, \\\n" - " U+2F4B->U+6B20, U+2F4C->U+6B62, U+2F4D->U+6B79, U+2F4E->U+6BB3, \\\n" - " U+2F4F->U+6BCB, U+2F50->U+6BD4, U+2F51->U+6BDB, U+2F52->U+6C0F, \\\n" - " U+2F53->U+6C14, U+2F54->U+6C34, U+2F55->U+706B, U+2F56->U+722A, \\\n" - " U+2F57->U+7236, U+2F58->U+723B, U+2F59->U+723F, U+2F5A->U+7247, \\\n" - " U+2F5B->U+7259, U+2F5C->U+725B, U+2F5D->U+72AC, U+2F5E->U+7384, \\\n" - " U+2F5F->U+7389, U+2F60->U+74DC, U+2F61->U+74E6, U+2F62->U+7518, \\\n" - " U+2F63->U+751F, U+2F64->U+7528, U+2F65->U+7530, U+2F66->U+758B, \\\n" - " U+2F67->U+7592, U+2F68->U+7676, U+2F69->U+767D, U+2F6A->U+76AE, \\\n" - " U+2F6B->U+76BF, U+2F6C->U+76EE, U+2F6D->U+77DB, U+2F6E->U+77E2, \\\n" - " U+2F6F->U+77F3, U+2F70->U+793A, U+2F71->U+79B8, U+2F72->U+79BE, \\\n" - " U+2F73->U+7A74, U+2F74->U+7ACB, U+2F75->U+7AF9, U+2F76->U+7C73, \\\n" - " U+2F77->U+7CF8, U+2F78->U+7F36, U+2F79->U+7F51, U+2F7A->U+7F8A, \\\n" - " U+2F7B->U+7FBD, U+2F7C->U+8001, U+2F7D->U+800C, U+2F7E->U+8012, \\\n" - " U+2F7F->U+8033, U+2F80->U+807F, U+2F81->U+8089, U+2F82->U+81E3, \\\n" - " U+2F83->U+81EA, U+2F84->U+81F3, U+2F85->U+81FC, U+2F86->U+820C, \\\n" - " U+2F87->U+821B, U+2F88->U+821F, U+2F89->U+826E, U+2F8A->U+8272, \\\n" - " U+2F8B->U+8278, U+2F8C->U+864D, U+2F8D->U+866B, U+2F8E->U+8840, \\\n" - " U+2F8F->U+884C, U+2F90->U+8863, U+2F91->U+897E, U+2F92->U+898B, \\\n" - " U+2F93->U+89D2, U+2F94->U+8A00, U+2F95->U+8C37, U+2F96->U+8C46, \\\n" - " U+2F97->U+8C55, U+2F98->U+8C78, U+2F99->U+8C9D, U+2F9A->U+8D64, \\\n" - " U+2F9B->U+8D70, U+2F9C->U+8DB3, U+2F9D->U+8EAB, U+2F9E->U+8ECA, \\\n" - " U+2F9F->U+8F9B, U+2FA0->U+8FB0, U+2FA1->U+8FB5, U+2FA2->U+9091, \\\n" - " U+2FA3->U+9149, U+2FA4->U+91C6, U+2FA5->U+91CC, U+2FA6->U+91D1, \\\n" - " U+2FA7->U+9577, U+2FA8->U+9580, U+2FA9->U+961C, U+2FAA->U+96B6, \\\n" - " U+2FAB->U+96B9, U+2FAC->U+96E8, U+2FAD->U+9751, U+2FAE->U+975E, \\\n" - " U+2FAF->U+9762, U+2FB0->U+9769, U+2FB1->U+97CB, U+2FB2->U+97ED, \\\n" - " U+2FB3->U+97F3, U+2FB4->U+9801, U+2FB5->U+98A8, U+2FB6->U+98DB, \\\n" - " U+2FB7->U+98DF, U+2FB8->U+9996, U+2FB9->U+9999, U+2FBA->U+99AC, \\\n" - " U+2FBB->U+9AA8, U+2FBC->U+9AD8, U+2FBD->U+9ADF, U+2FBE->U+9B25, \\\n" - " U+2FBF->U+9B2F, U+2FC0->U+9B32, U+2FC1->U+9B3C, U+2FC2->U+9B5A, \\\n" - " U+2FC3->U+9CE5, U+2FC4->U+9E75, U+2FC5->U+9E7F, U+2FC6->U+9EA5, \\\n" - " U+2FC7->U+9EBB, U+2FC8->U+9EC3, U+2FC9->U+9ECD, U+2FCA->U+9ED1, \\\n" - " U+2FCB->U+9EF9, U+2FCC->U+9EFD, U+2FCD->U+9F0E, U+2FCE->U+9F13, \\\n" - " U+2FCF->U+9F20, U+2FD0->U+9F3B, U+2FD1->U+9F4A, U+2FD2->U+9F52, \\\n" - " U+2FD3->U+9F8D, U+2FD4->U+9F9C, U+2FD5->U+9FA0, U+3042->U+3041, \\\n" - " U+3044->U+3043, U+3046->U+3045, U+3048->U+3047, U+304A->U+3049, \\\n" - " U+304C->U+304B, U+304E->U+304D, U+3050->U+304F, U+3052->U+3051, \\\n" - " U+3054->U+3053, U+3056->U+3055, U+3058->U+3057, U+305A->U+3059, \\\n" - " U+305C->U+305B, U+305E->U+305D, U+3060->U+305F, U+3062->U+3061, \\\n" - " U+3064->U+3063, U+3065->U+3063, U+3067->U+3066, U+3069->U+3068, \\\n" - " U+3070->U+306F, U+3071->U+306F, U+3073->U+3072, U+3074->U+3072, \\\n" - " U+3076->U+3075, U+3077->U+3075, U+3079->U+3078, U+307A->U+3078, \\\n" - " U+307C->U+307B, U+307D->U+307B, U+3084->U+3083, U+3086->U+3085, \\\n" - " U+3088->U+3087, U+308F->U+308E, U+3094->U+3046, U+3095->U+304B, \\\n" - " U+3096->U+3051, U+30A2->U+30A1, U+30A4->U+30A3, U+30A6->U+30A5, \\\n" - " U+30A8->U+30A7, U+30AA->U+30A9, U+30AC->U+30AB, U+30AE->U+30AD, \\\n" - " U+30B0->U+30AF, U+30B2->U+30B1, U+30B4->U+30B3, U+30B6->U+30B5, \\\n" - " U+30B8->U+30B7, U+30BA->U+30B9, U+30BC->U+30BB, U+30BE->U+30BD, \\\n" - " U+30C0->U+30BF, U+30C2->U+30C1, U+30C5->U+30C4, U+30C7->U+30C6, \\\n" - " U+30C9->U+30C8, U+30D0->U+30CF, U+30D1->U+30CF, U+30D3->U+30D2, \\\n" - " U+30D4->U+30D2, U+30D6->U+30D5, U+30D7->U+30D5, U+30D9->U+30D8, \\\n" - " U+30DA->U+30D8, U+30DC->U+30DB, U+30DD->U+30DB, U+30E4->U+30E3, \\\n" - " U+30E6->U+30E5, U+30E8->U+30E7, U+30EF->U+30EE, U+30F4->U+30A6, \\\n" - " U+30AB->U+30F5, U+30B1->U+30F6, U+30F7->U+30EF, U+30F8->U+30F0, \\\n" - " U+30F9->U+30F1, U+30FA->U+30F2, U+30AF->U+31F0, U+30B7->U+31F1, \\\n" - " U+30B9->U+31F2, U+30C8->U+31F3, U+30CC->U+31F4, U+30CF->U+31F5, \\\n" - " U+30D2->U+31F6, U+30D5->U+31F7, U+30D8->U+31F8, U+30DB->U+31F9, \\\n" - " U+30E0->U+31FA, U+30E9->U+31FB, U+30EA->U+31FC, U+30EB->U+31FD, \\\n" - " U+30EC->U+31FE, U+30ED->U+31FF, U+FF66->U+30F2, U+FF67->U+30A1, \\\n" - " U+FF68->U+30A3, U+FF69->U+30A5, U+FF6A->U+30A7, U+FF6B->U+30A9, \\\n" - " U+FF6C->U+30E3, U+FF6D->U+30E5, U+FF6E->U+30E7, U+FF6F->U+30C3, \\\n" - " U+FF71->U+30A1, U+FF72->U+30A3, U+FF73->U+30A5, U+FF74->U+30A7, \\\n" - " U+FF75->U+30A9, U+FF76->U+30AB, U+FF77->U+30AD, U+FF78->U+30AF, \\\n" - " U+FF79->U+30B1, U+FF7A->U+30B3, U+FF7B->U+30B5, U+FF7C->U+30B7, \\\n" - " U+FF7D->U+30B9, U+FF7E->U+30BB, U+FF7F->U+30BD, U+FF80->U+30BF, \\\n" - " U+FF81->U+30C1, U+FF82->U+30C3, U+FF83->U+30C6, U+FF84->U+30C8, \\\n" - " U+FF85->U+30CA, U+FF86->U+30CB, U+FF87->U+30CC, U+FF88->U+30CD, \\\n" - " U+FF89->U+30CE, U+FF8A->U+30CF, U+FF8B->U+30D2, U+FF8C->U+30D5, \\\n" - " U+FF8D->U+30D8, U+FF8E->U+30DB, U+FF8F->U+30DE, U+FF90->U+30DF, \\\n" - " U+FF91->U+30E0, U+FF92->U+30E1, U+FF93->U+30E2, U+FF94->U+30E3, \\\n" - " U+FF95->U+30E5, U+FF96->U+30E7, U+FF97->U+30E9, U+FF98->U+30EA, \\\n" - " U+FF99->U+30EB, U+FF9A->U+30EC, U+FF9B->U+30ED, U+FF9C->U+30EF, \\\n" - " U+FF9D->U+30F3, U+FFA0->U+3164, U+FFA1->U+3131, U+FFA2->U+3132, \\\n" - " U+FFA3->U+3133, U+FFA4->U+3134, U+FFA5->U+3135, U+FFA6->U+3136, \\\n" - " U+FFA7->U+3137, U+FFA8->U+3138, U+FFA9->U+3139, U+FFAA->U+313A, \\\n" - " U+FFAB->U+313B, U+FFAC->U+313C, U+FFAD->U+313D, U+FFAE->U+313E, \\\n" - " U+FFAF->U+313F, U+FFB0->U+3140, U+FFB1->U+3141, U+FFB2->U+3142, \\\n" - " U+FFB3->U+3143, U+FFB4->U+3144, U+FFB5->U+3145, U+FFB6->U+3146, \\\n" - " U+FFB7->U+3147, U+FFB8->U+3148, U+FFB9->U+3149, U+FFBA->U+314A, \\\n" - " U+FFBB->U+314B, U+FFBC->U+314C, U+FFBD->U+314D, U+FFBE->U+314E, \\\n" - " U+FFC2->U+314F, U+FFC3->U+3150, U+FFC4->U+3151, U+FFC5->U+3152, \\\n" - " U+FFC6->U+3153, U+FFC7->U+3154, U+FFCA->U+3155, U+FFCB->U+3156, \\\n" - " U+FFCC->U+3157, U+FFCD->U+3158, U+FFCE->U+3159, U+FFCF->U+315A, \\\n" - " U+FFD2->U+315B, U+FFD3->U+315C, U+FFD4->U+315D, U+FFD5->U+315E, \\\n" - " U+FFD6->U+315F, U+FFD7->U+3160, U+FFDA->U+3161, U+FFDB->U+3162, \\\n" - " U+FFDC->U+3163, U+3131->U+1100, U+3132->U+1101, U+3133->U+11AA, \\\n" - " U+3134->U+1102, U+3135->U+11AC, U+3136->U+11AD, U+3137->U+1103, \\\n" - " U+3138->U+1104, U+3139->U+1105, U+313A->U+11B0, U+313B->U+11B1, \\\n" - " U+313C->U+11B2, U+313D->U+11B3, U+313E->U+11B4, U+313F->U+11B5, \\\n" - " U+3140->U+111A, U+3141->U+1106, U+3142->U+1107, U+3143->U+1108, \\\n" - " U+3144->U+1121, U+3145->U+1109, U+3146->U+110A, U+3147->U+110B, \\\n" - " U+3148->U+110C, U+3149->U+110D, U+314A->U+110E, U+314B->U+110F, \\\n" - " U+314C->U+1110, U+314D->U+1111, U+314E->U+1112, U+314F->U+1161, \\\n" - " U+3150->U+1162, U+3151->U+1163, U+3152->U+1164, U+3153->U+1165, \\\n" - " U+3154->U+1166, U+3155->U+1167, U+3156->U+1168, U+3157->U+1169, \\\n" - " U+3158->U+116A, U+3159->U+116B, U+315A->U+116C, U+315B->U+116D, \\\n" - " U+315C->U+116E, U+315D->U+116F, U+315E->U+1170, U+315F->U+1171, \\\n" - " U+3160->U+1172, U+3161->U+1173, U+3162->U+1174, U+3163->U+1175, \\\n" - " U+3165->U+1114, U+3166->U+1115, U+3167->U+11C7, U+3168->U+11C8, \\\n" - " U+3169->U+11CC, U+316A->U+11CE, U+316B->U+11D3, U+316C->U+11D7, \\\n" - " U+316D->U+11D9, U+316E->U+111C, U+316F->U+11DD, U+3170->U+11DF, \\\n" - " U+3171->U+111D, U+3172->U+111E, U+3173->U+1120, U+3174->U+1122, \\\n" - " U+3175->U+1123, U+3176->U+1127, U+3177->U+1129, U+3178->U+112B, \\\n" - " U+3179->U+112C, U+317A->U+112D, U+317B->U+112E, U+317C->U+112F, \\\n" - " U+317D->U+1132, U+317E->U+1136, U+317F->U+1140, U+3180->U+1147, \\\n" - " U+3181->U+114C, U+3182->U+11F1, U+3183->U+11F2, U+3184->U+1157, \\\n" - " U+3185->U+1158, U+3186->U+1159, U+3187->U+1184, U+3188->U+1185, \\\n" - " U+3189->U+1188, U+318A->U+1191, U+318B->U+1192, U+318C->U+1194, \\\n" - " U+318D->U+119E, U+318E->U+11A1, U+A490->U+A408, U+A491->U+A1B9, \\\n" - " U+4E00..U+9FBB, U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, \\\n" - " U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, \\\n" - " U+FA29, U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, \\\n" - " U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, U+3059, \\\n" - " U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, \\\n" - " U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, U+3085, U+3087, \\\n" - " U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, \\\n" - " U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, U+30BD, U+30BF, U+30C1, U+30C3, \\\n" - " U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, \\\n" - " U+30E2, U+30E3, U+30E5, U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, \\\n" - " U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, \\\n" - " U+31F9, U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, \\\n" - " U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, \\\n" - " U+A492..U+A4C6\n" - " ngram_chars = \\\n" - /* Support for Chinese/Japanese/Korean, from - * http://sphinxsearch.com/wiki/doku.php?id=charset_tables#cjk */ - " U+4E00..U+9FBB, U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, \\\n" - " U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, \\\n" - " U+FA29, U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, \\\n" - " U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, U+3059, \\\n" - " U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, \\\n" - " U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, U+3085, U+3087, \\\n" - " U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, \\\n" - " U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, U+30BD, U+30BF, U+30C1, U+30C3, \\\n" - " U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, \\\n" - " U+30E2, U+30E3, U+30E5, U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, \\\n" - " U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, \\\n" - " U+31F9, U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, \\\n" - " U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, \\\n" - " U+A492..U+A4C6\n" - " ngram_len = 1\n" - "\n" - " rt_attr_string = cyrusid\n" - " rt_field = header_from\n" - " rt_field = header_to\n" - " rt_field = header_cc\n" - " rt_field = header_bcc\n" - " rt_field = header_subject\n" - " rt_field = headers\n" - " rt_field = body\n" - " preopen = 0\n" - "}\n" - "\n" - "searchd\n" - "{\n" - " listen = $sphinxsock:mysql41\n" - " log = syslog\n" - " pid_file = $sphinxdir" SPHINX_PIDFILE "\n" - " binlog_path = $sphinxdir/binlog\n" - " compat_sphinxql_magics = 0\n" - " workers = threads\n" - " max_matches = " SPHINX_MAX_MATCHES "\n" - " preopen_indexes = 0\n" - "}\n" - "\n" - "source fmindex\n" - "{\n" - " type = xmlpipe2\n" - " xmlpipe_command = cat $sphinxdir/fmindex.xml\n" - " xmlpipe_attr_string = cyrusid\n" - " xmlpipe_field = header_from\n" - " xmlpipe_field = header_to\n" - " xmlpipe_field = header_cc\n" - " xmlpipe_field = header_bcc\n" - " xmlpipe_field = header_subject\n" - " xmlpipe_field = headers\n" - " xmlpipe_field = body\n" - "}\n"; - char *sphinx_config = NULL; - int fd = -1; - struct buf buf = BUF_INITIALIZER; - int r; - - sphinx_config = strconcat(id->basedir, SPHINX_CONFIG, (char *)NULL); - if (verbose > 1) - syslog(LOG_INFO, "setting up config \"%s\"", sphinx_config); - -/* the searchd.log entry changed, so force a rewrite of the config file */ -#if 0 - struct stat sb; - if (stat(sphinx_config, &sb) == 0 && - S_ISREG(sb.st_mode) && - sb.st_size > 0) { - r = 0; - goto out; /* a non-zero file already exists */ - } -#endif - - if (verbose) - syslog(LOG_INFO, "Sphinx writing config file %s", sphinx_config); - - fd = open(sphinx_config, O_WRONLY|O_CREAT|O_TRUNC, 0600); - if (fd < 0) { - syslog(LOG_ERR, "IOERROR: unable to open %s for writing: %m", - sphinx_config); - r = IMAP_IOERROR; - goto out; - } - - buf_init_ro_cstr(&buf, config); - buf_replace_all(&buf, "$sphinxsock", id->socketpath); - buf_replace_all(&buf, "$sphinxdir", id->basedir); - - r = retry_write(fd, buf.s, buf.len); - if (r < 0) { - syslog(LOG_ERR, "IOERROR: error writing %s: %m", sphinx_config); - r = IMAP_IOERROR; - goto out; - } - r = 0; - -out: - if (fd >= 0) close(fd); - free(sphinx_config); - buf_free(&buf); - return r; -} - -/* Wait until the searchd finishes shutting down. - * Returns 0 if all went well, or a system error code. */ -static int indexd_stopwait(indexd_t *id) -{ - int tries = 0; - - while (tries++ < STOPWAIT_MAX_TRIES) { - if (indexd_is_running(id) != 0) - return 0; - poll(NULL, 0, STOPWAIT_DELAY_MS); - } - return ETIMEDOUT; -} - -static int indexd_start(indexd_t *id) -{ - char *config_file = NULL; - int r; - - assert(id->state == STOPPED); - - if (verbose) - syslog(LOG_INFO, "Sphinx starting searchd %s", id->basedir); - - config_file = strconcat(id->basedir, SPHINX_CONFIG, (char *)NULL); - r = run_command(SEARCHD, "--config", config_file, - "--syslog-prefix", syslog_prefix, (char *)NULL); - if (r) goto out; - - id->started = time(NULL); - indexd_set_state(id, STARTED); - r = 0; - -out: - free(config_file); - return r; -} - -static int indexd_stop(indexd_t *id) -{ - char *config_file = NULL; - int r; - - if (id->state != STARTED) return 0; /* nothing to do */ - - if (verbose) - syslog(LOG_INFO, "Sphinx stopping searchd %s", - id->basedir); - - config_file = strconcat(id->basedir, SPHINX_CONFIG, (char *)NULL); - r = run_command(SEARCHD, "--config", config_file, - "--syslog-prefix", syslog_prefix, - "--stop", (char *)NULL); - if (r) goto out; - - unlink(id->socketpath); - indexd_set_state(id, STOPPING); - id->stopped = time(NULL); - - r = 0; - -out: - free(config_file); - return r; -} - -/* Returns in *basedir and *sockname, two new strings which must be free()d */ -static int sphinx_paths_from_mboxname(const char *mboxname, - char **basedirp, - char **socknamep) -{ - char *confkey = NULL; - const char *root; - struct mboxlist_entry *mbentry = NULL; - char *basedir = NULL; - struct mboxname_parts parts; - char *sockname = NULL; - char c2, d2; - int r; - - mboxname_init_parts(&parts); - - r = mboxlist_lookup(mboxname, &mbentry, /*tid*/NULL); - if (r) goto out; - if (mbentry->mbtype & MBTYPE_REMOTE) { - r = IMAP_PARTITION_UNKNOWN; - goto out; - } - - confkey = strconcat("sphinxpartition-", mbentry->partition, NULL); - root = config_getoverflowstring(confkey, NULL); - if (!root) { - r = IMAP_PARTITION_UNKNOWN; - goto out; - } - - r = mboxname_to_parts(mboxname, &parts); - if (r) goto out; - if (!parts.userid) { - r = IMAP_PARTITION_UNKNOWN; - goto out; - } - - if (parts.domain) - basedir = strconcat(root, - FNAME_DOMAINDIR, - dir_hash_b(parts.domain, config_fulldirhash, d), - "/", parts.domain, - FNAME_USERDIR, - dir_hash_b(parts.userid, config_fulldirhash, c), - "/", parts.userid, - (char *)NULL); - else - basedir = strconcat(root, - FNAME_USERDIR, - dir_hash_b(parts.userid, config_fulldirhash, c), - "/", parts.userid, - (char *)NULL); - - if (parts.domain) - sockname = strconcat(config_dir, - "/socket/sphinx.", - parts.userid, - "@", - parts.domain, - (char *)NULL); - else - sockname = strconcat(config_dir, - "/socket/sphinx.", - parts.userid, - (char *)NULL); - r = 0; - -out: - if (r) { - free(basedir); - free(sockname); - } - else { - *basedirp = basedir; - *socknamep = sockname; - } - free(confkey); - mboxname_free_parts(&parts); - mboxlist_entry_free(&mbentry); - return r; -} - -static void indexd_detach(indexd_t *id) -{ - if (id->state != STOPPED) - syslog(LOG_ERR, "IOERROR: internal error, state=%d in " - "indexd_detach for basedir %s", - id->state, id->basedir); - - /* unstitch */ - id->prev->next = id->next; - id->next->prev = id->prev; - hash_del(id->basedir, &itable); -} - -/* Checks to see if the searchd is still running. - * Returns 0 if still running or a system error code */ -static int indexd_is_running(indexd_t *id) -{ - char *pidfile = strconcat(id->basedir, SPHINX_PIDFILE, (char *)NULL); - int r; - struct stat sb; - - do { - r = stat(pidfile, &sb); - } while (r < 0 && errno == EINTR); - - if (r < 0) { - r = errno; - if (errno != ENOENT) - syslog(LOG_ERR, "cannot stat %s: %m", pidfile); - } - - /* TODO: could also read the pidfile send the process named there a - * signal 0 to see if it's still alive. But that would be a lot of - * work to detect and hide abnormal shutdowns, which don't seem to - * happen; sphinx seems pretty good about cleaning up after itself. - * So just check for the existance of the pidfile. */ - - free(pidfile); - return r; -} - -#define ID_NONE 0 -#define ID_SETUP 1 -#define ID_START 2 -static int indexd_get(const char *mboxname, indexd_t **idp, int action) -{ - indexd_t *id; - char *basedir = NULL; - char *socketpath = NULL; - int r; - - r = sphinx_paths_from_mboxname(mboxname, &basedir, &socketpath); - if (r) return r; - - id = (indexd_t *)hash_lookup(basedir, &itable); - - if (id && id->state == STOPPING) { - syslog(LOG_NOTICE, "searchd %s is still shutting down, " - "waiting to start a new one", - id->basedir); - r = indexd_stopwait(id); - if (r) { - syslog(LOG_ERR, "IOERROR: failed to wait for " - "searchd %s to shut down: %s", - id->basedir, error_message(r)); - return r; - } - indexd_set_state(id, STOPPED); - indexd_detach(id); - indexd_free(id); - id = NULL; - } - - if (id && indexd_is_running(id) != 0) { - syslog(LOG_NOTICE, "searchd %s is no longer running, forgetting", - id->basedir); - indexd_set_state(id, STOPPED); - indexd_detach(id); - indexd_free(id); - id = NULL; - } - - if (!id) { - if (action == ID_NONE) { - r = IMAP_NOTFOUND; - goto out; - } - if (action == ID_START && max_children && num_started >= max_children) { - /* stop the oldest STARTED child */ - indexd_t *oldest; - for (oldest = indexroot.next ; - oldest != &indexroot && oldest->state != STARTED ; - oldest = oldest->next) - ; - if (oldest->state == STARTED) { - syslog(LOG_NOTICE, "hit limit %d, killing oldest %s", - max_children, oldest->basedir); - if (indexd_is_running(oldest) != 0 || indexd_stop(oldest) != 0) { - /* not running or failed to initiate shut down */ - indexd_set_state(oldest, STOPPED); - indexd_detach(oldest); - indexd_free(oldest); - } - /* will be detached and freed later */ - } - } - id = xzmalloc(sizeof(*id)); - id->basedir = basedir; - basedir = NULL; - id->socketpath = socketpath; - socketpath = NULL; - - r = indexd_setup_tree(id); - if (r) { - indexd_free(id); - return r; - } - - r = indexd_setup_config(id); - if (r) { - indexd_free(id); - return r; - } - - if (action == ID_START) { - r = indexd_start(id); - if (r) { - indexd_free(id); - return r; - } - } - - hash_insert(id->basedir, id, &itable); - id->prev = indexroot.prev; - id->next = &indexroot; - id->prev->next = id; - id->next->prev = id; - } - else { - /* remove from the list */ - id->prev->next = id->next; - id->next->prev = id->prev; - /* move to the end */ - id->prev = indexroot.prev; - id->next = &indexroot; - id->prev->next = id; - id->next->prev = id; - } - - id->used = time(NULL); - *idp = id; - r = 0; -out: - free(basedir); - free(socketpath); - return r; -} - -static void expire_indexd(const char *key __attribute__((unused)), - void *data, void *rock) -{ - indexd_t *id = (indexd_t *)data; - time_t now = *(time_t *)rock; - - switch (id->state) { - case STARTED: - if (now > id->used + sphinx_timeout) { - if (verbose) - syslog(LOG_INFO, "searchd %s has been idle for %d sec", - id->basedir, (int)(now - id->used)); - if (indexd_is_running(id) != 0 || indexd_stop(id) != 0) { - /* not running or failed to initiate shut down */ - indexd_set_state(id, STOPPED); - indexd_detach(id); - indexd_free(id); - } - /* or, will be detached and freed later */ - } - break; - case STOPPING: - if (indexd_is_running(id) != 0) { - if (verbose) - syslog(LOG_INFO, "searchd %s finished shutting down after %d sec", - id->basedir, - (int)(now - id->stopped)); - indexd_set_state(id, STOPPED); - indexd_detach(id); - indexd_free(id); - } - break; - case STOPPED: - abort(); - } -} - -static int create_server_socket(void) -{ - const char *sockname = config_getstring(IMAPOPT_SPHINXMGR_SOCKET); - struct sockaddr_un asun; - int r; - int s; - - s = socket(PF_UNIX, SOCK_STREAM, 0); - if (s < 0) { - perror("socket(PF_UNIX)"); - shut_down(1); - } - - r = unlink(sockname); - if (r < 0 && errno != ENOENT) { - perror(sockname); - shut_down(1); - } - - memset(&asun, 0, sizeof(asun)); - asun.sun_family = AF_UNIX; - strlcpy(asun.sun_path, sockname, sizeof(asun.sun_path)); - - r = bind(s, (struct sockaddr *)&asun, sizeof(asun)); - if (r < 0) { - perror(sockname); - shut_down(1); - } - - r = listen(s, 100); - if (r < 0) { - perror("listen"); - shut_down(1); - } - - return s; -} - -/* - * Command is: GETSOCK <internal-mboxname> - */ -static int handle_getsock(char *mboxname, char *reply, size_t maxreply) -{ - indexd_t *id = NULL; - int r; - - if (!mboxname || !*mboxname) return IMAP_PROTOCOL_BAD_PARAMETERS; - - r = indexd_get(mboxname, &id, ID_START); - if (!r) - snprintf(reply, maxreply, "%s", id->socketpath); - return r; -} - -/* - * Command is: GETCONF <internal-mboxname> - */ -static int handle_getconf(char *mboxname, char *reply, size_t maxreply) -{ - indexd_t *id = NULL; - int r; - - if (!mboxname || !*mboxname) return IMAP_PROTOCOL_BAD_PARAMETERS; - - r = indexd_get(mboxname, &id, ID_SETUP); - if (!r) - snprintf(reply, maxreply, "%s%s", id->basedir, SPHINX_CONFIG); - if (id->state == STOPPED) { - /* This index_t was just created to return the basedir to us */ - indexd_detach(id); - indexd_free(id); - } - return r; -} - -/* - * Command is: STOP <internal-mboxname> - */ -static int handle_stop(char *mboxname, - char *reply __attribute__((unused)), - size_t maxreply __attribute__((unused))) -{ - indexd_t *id = NULL; - int r; - - if (!mboxname || !*mboxname) return IMAP_PROTOCOL_BAD_PARAMETERS; - - r = indexd_get(mboxname, &id, ID_NONE); - if (!r) - indexd_stop(id); - return r; -} - -static void process_command(int ss) -{ - int s; - int r; - int i; - char *cmd; - char *arg; - int (*handler)(char *arg, char *reply, size_t maxreply) = NULL; - char buf1024; - static const char sep = " \t\r\n"; - - s = accept(ss, NULL, NULL); - if (s < 0) { - syslog(LOG_ERR, "accept(): %m"); - return; - } - - r = read(s, buf, sizeof(buf)-1); - if (r < 0) { - syslog(LOG_ERR, "read(): %m"); - goto out; - } - if (r == 0) { - /* what, eof already? whatever */ - goto out; - } - bufr = '\0'; - - /* trim trailing CR or LF from the line */ - while (r > 0 && (bufr-1 == '\r' || bufr-1 == '\n')) - buf--r = '\0'; - - /* split into command and argument, preserving whitespace - * inside the argument */ - cmd = buf; - i = strcspn(buf, sep); - if (i <= 0 || i >= r) { - syslog(LOG_ERR, "Malformed command received, ignoring"); - goto out; - } - while (i < r && isspace(bufi)) - bufi++ = '\0'; - - ucase(cmd); - arg = buf+i; - - if (verbose > 1) - syslog(LOG_INFO, "parsed command, cmd=\"%s\" arg=\"%s\"", cmd, arg); - - if (!strcmp(cmd, "GETSOCK")) - handler = handle_getsock; - if (!strcmp(cmd, "GETCONF")) - handler = handle_getconf; - else if (!strcmp(cmd, "STOP")) - handler = handle_stop; - - if (handler) { - snprintf(buf, sizeof(buf), "OK "); - r = handler(arg, buf+strlen(buf), sizeof(buf)-strlen(buf)-2); - if (!r) - strlcat(buf, "\r\n", sizeof(buf)); - else - snprintf(buf, sizeof(buf), "NO %s\r\n", error_message(r)); - } - else - snprintf(buf, sizeof(buf), "BAD Unrecognized command\r\n"); - - if (verbose > 1) - syslog(LOG_INFO, "sending reply \"%s\"", buf); - - retry_write(s, buf, strlen(buf)); - - /* we always shut down the connection after one command */ -out: - close(s); -} - -static void kill_indexd(const char *key __attribute__((unused)), - void *data, - void *rock __attribute__((unused))) -{ - indexd_t *id = (indexd_t *)data; - - indexd_stop(id); -} - -static void shut_down(int code) -{ - if (server_sock >= 0) close(server_sock); - hash_enumerate(&itable, kill_indexd, NULL); - - /* mboxlist might not have been opened yet, but that's harmless */ - mboxlist_close(); - mboxlist_done(); - - cyrus_done(); - - exit(code); -} - -int main(int argc, char **argv) -{ - char *p = NULL; - int opt; - struct pollfd pfd; - char *alt_config = NULL; - int background = 1; - int init_flags = CYRUSINIT_PERROR; - - p = getenv("CYRUS_VERBOSE"); - if (p) verbose = atoi(p) + 1; - - while ((opt = getopt(argc, argv, "C:fv")) != EOF) { - switch (opt) { - case 'C': /* alt config file */ - alt_config = optarg; - break; - case 'f': /* run in foreground for debugging */ - background = 0; - break; - case 'v': /* be more verbose */ - verbose++; - break; - default: - fprintf(stderr, "invalid argument\n"); - exit(EC_USAGE); - break; - } - } - - cyrus_init(alt_config, "cyr_sphinxmgr", 0, 0); - - syslog_prefix = config_getstring(IMAPOPT_SYSLOG_PREFIX); - if (!syslog_prefix) - syslog_prefix = "cyrus"; - - /* Set inactivity timer (convert from minutes to seconds) */ - sphinx_timeout = config_getint(IMAPOPT_SPHINXMGR_TIMEOUT); - max_children = config_getint(IMAPOPT_SPHINXMGR_MAX_CHILDREN); - - signals_add_handlers(0); - signals_set_shutdown(shut_down); - - /* create idle table */ - construct_hash_table(&itable, 1024, 1); - - /* initialise the linked list */ - indexroot.prev = &indexroot; - indexroot.next = &indexroot; - - server_sock = create_server_socket(); - - /* fork unless we were given the -f option */ - if (background) { - pid_t pid; - int nullfd; - - nullfd = open("/dev/null", O_RDWR, 0); - if (nullfd < 0) { - perror("/dev/null"); - exit(1); - } - dup2(nullfd, 0); - dup2(nullfd, 1); - dup2(nullfd, 2); - close(nullfd); - init_flags &= ~CYRUSINIT_PERROR; - - pid = fork(); - if (pid == -1) { - perror("fork"); - exit(1); - } - - if (pid) - exit(0);/* parent */ - /* child */ - } - - mboxlist_init(0); - mboxlist_open(NULL); - - syslog(LOG_INFO, "cyr_sphinxmgr running"); - - for (;;) { - int n; - time_t now = time(NULL); - - signals_poll(); - - /* check for shutdown file */ - if (shutdown_file(NULL, 0)) - shut_down(0); - - hash_enumerate(&itable, expire_indexd, &now); - - memset(&pfd, 0, sizeof(pfd)); - pfd.fd = server_sock; - pfd.events = POLLIN; - - n = poll(&pfd, 1, /*1 second timeout*/1000); - if (n < 0) { - if (errno == EAGAIN || errno == EINTR) continue; - syslog(LOG_ERR, "poll(): %m"); - fatal("poll failed", EC_TEMPFAIL); - } - - if (n > 0 && (pfd.revents & POLLIN)) - process_command(server_sock); - } - - shut_down(0); -} -
View file
cyrus-imapd-2.5.tar.gz/imap/cyr_virusscan.c
Changed
@@ -424,7 +424,7 @@ const char *virname; int r = 0; - fname = mailbox_record_fname(mailbox, record); + fname = mailbox_message_fname(mailbox, record->uid); if ((r = engine.scanfile(engine.state, fname, &virname))) { if (verbose) {
View file
cyrus-imapd-2.5.tar.gz/imap/cyrdump.c
Changed
@@ -153,14 +153,6 @@ (long) getpid(), (long) time(NULL), (long) rand()); } -static search_expr_t *systemflag_match(int flag) -{ - search_expr_t *e = search_expr_new(NULL, SEOP_MATCH); - e->attr = search_attr_find("systemflags"); - e->value.u = flag; - return e; -} - static int dump_me(char *name, int matchlen __attribute__((unused)), int maycreate __attribute__((unused)), void *rock) { @@ -174,7 +166,6 @@ unsigned *uids; unsigned *uidseq; int i, n, numuids; - unsigned msgno; r = index_open(name, NULL, &state); if (r) { @@ -204,29 +195,28 @@ printf("\n"); memset(&searchargs, 0, sizeof(struct searchargs)); - searchargs.root = search_expr_new(NULL, SEOP_TRUE); numuids = index_getuidsequence(state, &searchargs, &uids); print_seq("uidlist", NULL, uids, numuids); printf("\n"); printf(" <flags>\n"); - searchargs.root = systemflag_match(FLAG_ANSWERED); + searchargs.system_flags_set = FLAG_ANSWERED; n = index_getuidsequence(state, &searchargs, &uidseq); print_seq("flag", "name=\"\\Answered\" user=\"*\"", uidseq, n); if (uidseq) free(uidseq); - searchargs.root = systemflag_match(FLAG_DELETED); + searchargs.system_flags_set = FLAG_DELETED; n = index_getuidsequence(state, &searchargs, &uidseq); print_seq("flag", "name=\"\\Deleted\" user=\"*\"", uidseq, n); if (uidseq) free(uidseq); - searchargs.root = systemflag_match(FLAG_DRAFT); + searchargs.system_flags_set = FLAG_DRAFT; n = index_getuidsequence(state, &searchargs, &uidseq); print_seq("flag", "name=\"\\Draft\" user=\"*\"", uidseq, n); if (uidseq) free(uidseq); - searchargs.root = systemflag_match(FLAG_FLAGGED); + searchargs.system_flags_set = FLAG_FLAGGED; n = index_getuidsequence(state, &searchargs, &uidseq); print_seq("flag", "name=\"\\Flagged\" user=\"*\"", uidseq, n); if (uidseq) free(uidseq); @@ -235,46 +225,31 @@ printf("</imapdump>\n"); - i = 0; - while (uidsi < irec->incruid && i < numuids) { - /* already dumped this message */ - /* xxx could do binary search to get to the first - undumped uid */ - i++; - } - - for (msgno = 1; msgno <= state->exists; msgno++) { - struct index_map *im = &state->mapmsgno-1; - struct buf msg = BUF_INITIALIZER; - struct index_record record; + for (i = 0; i < numuids; i++) { + const char *base; + size_t len; - while (im->uid > uidsi && i < numuids) - i++; - if (i >= numuids) - break; - - if (im->uid < uidsi) - continue; - - /* got a match */ - i++; - if (mailbox_read_index_record(state->mailbox, im->recno, &record)) + if (uidsi < irec->incruid) { + /* already dumped this message */ + /* xxx could do binary search to get to the first + undumped uid */ continue; + } printf("\n--%s\n", boundary); printf("Content-Type: message/rfc822\n"); printf("Content-ID: %d\n", uidsi); printf("\n"); - r = mailbox_map_record(state->mailbox, &record, &msg); + r = mailbox_map_message(state->mailbox, uidsi, &base, &len); if (r) { if (verbose) { - printf("error mapping message %u: %s\n", record.uid, + printf("error mapping message %d: %s\n", uidsi, error_message(r)); } break; } - fwrite(msg.s, 1, msg.len, stdout); - buf_free(&msg); + fwrite(base, 1, len, stdout); + mailbox_unmap_message(state->mailbox, uidsi, &base, &len); } printf("\n--%s--\n", boundary);
View file
cyrus-imapd-2.5.tar.gz/imap/dav_db.c
Deleted
@@ -1,280 +0,0 @@ -/* dav_db.c -- implementation of per-user DAV database - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#include <stdlib.h> -#include <syslog.h> -#include <string.h> -#include <errno.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <sys/stat.h> -#include <sys/types.h> - -#include "assert.h" -#include "cyrusdb.h" -#include "dav_db.h" -#include "global.h" -#include "util.h" -#include "xmalloc.h" - -#define FNAME_DAVSUFFIX ".dav" /* per-user DAV DB extension */ - -struct open_davdb { - sqlite3 *db; - char *path; - unsigned refcount; - struct open_davdb *next; -}; - -static struct open_davdb *open_davdbs; - -static int dbinit = 0; - -EXPORTED int dav_init(void) -{ - if (!dbinit++) { -#if SQLITE_VERSION_NUMBER >= 3006000 - sqlite3_initialize(); -#endif - } - - assert(!open_davdbs); - - return 0; -} - - -EXPORTED int dav_done(void) -{ - if (--dbinit) { -#if SQLITE_VERSION_NUMBER >= 3006000 - sqlite3_shutdown(); -#endif - } - - /* XXX - report the problems? */ - assert(!open_davdbs); - - return 0; -} - - -static void dav_debug(void *fname, const char *sql) -{ - syslog(LOG_DEBUG, "dav_exec(%s): %s", (const char *) fname, sql); -} - -static void free_dav_open(struct open_davdb *open) -{ - free(open->path); - free(open); -} - -/* Open DAV DB corresponding in file */ -EXPORTED sqlite3 *dav_open(const char *fname, const char *cmds) -{ - int rc = SQLITE_OK; - struct stat sbuf; - struct open_davdb *open; - - for (open = open_davdbs; open; open = open->next) { - if (!strcmp(open->path, fname)) { - /* already open! */ - open->refcount++; - goto docmds; - } - } - - open = xzmalloc(sizeof(struct open_davdb)); - open->path = xstrdup(fname); - - rc = stat(open->path, &sbuf); - if (rc == -1 && errno == ENOENT) { - rc = cyrus_mkdir(open->path, 0755); - } - -#if SQLITE_VERSION_NUMBER >= 3006000 - rc = sqlite3_open_v2(open->path, &open->db, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); -#else - rc = sqlite3_open(open->path, &open->db); -#endif - if (rc != SQLITE_OK) { - syslog(LOG_ERR, "dav_open(%s) open: %s", - open->path, open->db ? sqlite3_errmsg(open->db) : "failed"); - sqlite3_close(open->db); - free_dav_open(open); - return NULL; - } - else { -#if SQLITE_VERSION_NUMBER >= 3006000 - sqlite3_extended_result_codes(open->db, 1); -#endif - sqlite3_trace(open->db, dav_debug, open->path); - } - - /* stitch on up */ - open->refcount = 1; - open->next = open_davdbs; - open_davdbs = open; - - docmds: - if (cmds) { - rc = sqlite3_exec(open->db, cmds, NULL, NULL, NULL); - if (rc != SQLITE_OK) { - /* XXX - fatal? */ - syslog(LOG_ERR, "dav_open(%s) cmds: %s", - open->path, sqlite3_errmsg(open->db)); - } - } - - return open->db; -} - - -/* Close DAV DB */ -EXPORTED int dav_close(sqlite3 *davdb) -{ - int rc, r = 0; - struct open_davdb *open, *prev = NULL; - - if (!davdb) return 0; - - for (open = open_davdbs; open; open = open->next) { - if (davdb == open->db) { - if (--open->refcount) return 0; /* still in use */ - if (prev) - prev->next = open->next; - else - open_davdbs = open->next; - break; - } - prev = open; - } - - assert(open); - - rc = sqlite3_close(open->db); - if (rc != SQLITE_OK) { - syslog(LOG_ERR, "dav_close(%s): %s", open->path, sqlite3_errmsg(open->db)); - r = CYRUSDB_INTERNAL; - } - - free_dav_open(open); - - return r; -} - - -EXPORTED int dav_exec(sqlite3 *davdb, const char *cmd, struct bind_val bval, - int (*cb)(sqlite3_stmt *stmt, void *rock), void *rock, - sqlite3_stmt **stmt) -{ - int rc, r = 0; - - if (!*stmt) { - /* prepare new statement */ -#if SQLITE_VERSION_NUMBER >= 3006000 - rc = sqlite3_prepare_v2(davdb, cmd, -1, stmt, NULL); -#else - rc = sqlite3_prepare(davdb, cmd, -1, stmt, NULL); -#endif - if (rc != SQLITE_OK) { - syslog(LOG_ERR, "dav_exec() prepare: %s", sqlite3_errmsg(davdb)); - return CYRUSDB_INTERNAL; - } - } - - /* bind values */ - for (; bval && bval->name; bval++) { - int cidx = sqlite3_bind_parameter_index(*stmt, bval->name); - - switch (bval->type) { - case SQLITE_INTEGER: - sqlite3_bind_int(*stmt, cidx, bval->val.i); - break; - - case SQLITE_TEXT: - sqlite3_bind_text(*stmt, cidx, bval->val.s, -1, NULL); - break; - } - } - - /* execute and process the results */ - while ((rc = sqlite3_step(*stmt)) == SQLITE_ROW) { - if (cb && (r = cb(*stmt, rock))) break; - } - - /* reset statement and clear all bindings */ - sqlite3_reset(*stmt); -#if SQLITE_VERSION_NUMBER >= 3006000 - sqlite3_clear_bindings(*stmt); -#endif - - if (!r && rc != SQLITE_DONE) { - syslog(LOG_ERR, "dav_exec() step: %s", sqlite3_errmsg(davdb)); - r = CYRUSDB_INTERNAL; - } - - return r; -} - - -EXPORTED int dav_delete(struct mailbox *mailbox) -{ - struct buf fname = BUF_INITIALIZER; - int r = 0; - - dav_getpath(&fname, mailbox); - if (unlink(buf_cstring(&fname)) && errno != ENOENT) { - syslog(LOG_ERR, "dav_db: error unlinking %s: %m", buf_cstring(&fname)); - r = CYRUSDB_INTERNAL; - } - - buf_free(&fname); - - return r; -}
View file
cyrus-imapd-2.5.tar.gz/imap/dav_db.h
Deleted
@@ -1,92 +0,0 @@ -/* dav_db.h -- abstract interface for per-user DAV database - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef DAV_DB_H -#define DAV_DB_H - -#include <sqlite3.h> -#include "dav_util.h" - -struct dav_data { - unsigned rowid; - time_t creationdate; - const char *mailbox; - const char *resource; - uint32_t imap_uid; /* zero (0) until URL is mapped */ - const char *lock_token; - const char *lock_owner; - const char *lock_ownerid; - time_t lock_expire; -}; - -struct bind_val { - const char *name; - int type; - union { - int i; - const char *s; - } val; -}; - -/* prepare for DAV operations in this process */ -int dav_init(void); - -/* done with all DAV operations for this process */ -int dav_done(void); - -/* get a database handle corresponding to mailbox */ -sqlite3 *dav_open(const char *fname, const char *cmds); - -/* close this handle */ -int dav_close(sqlite3 *davdb); - -/* execute 'cmd' and process results with 'cb' - 'cmd' is prepared as 'stmt' with 'bval' as bound values */ -int dav_exec(sqlite3 *davdb, const char *cmd, struct bind_val bval, - int (*cb)(sqlite3_stmt *stmt, void *rock), void *rock, - sqlite3_stmt **stmt); - -/* delete database corresponding to mailbox */ -int dav_delete(struct mailbox *mailbox); - -#endif /* DAV_DB_H */
View file
cyrus-imapd-2.5.tar.gz/imap/dav_reconstruct.c
Deleted
@@ -1,221 +0,0 @@ -/* dav_reconstruct.c - (re)build DAV DB for a user - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <syslog.h> -#include <time.h> - -#include <libical/ical.h> - -#include "annotate.h" -#include "caldav_db.h" -#include "carddav_db.h" -#include "exitcodes.h" -#include "global.h" -#include "http_dav.h" -#include "imap_err.h" -#include "mailbox.h" -#include "message.h" -#include "message_guid.h" -#include "mboxname.h" -#include "mboxlist.h" -#include "util.h" -#include "xmalloc.h" -#include "xstrlcat.h" - -extern int optind; -extern char *optarg; - -/* current namespace */ -static struct namespace recon_namespace; - -/* config.c stuff */ -const int config_need_data = 0; - -/* forward declarations */ -static int do_reconstruct(void *rock, - const char *key, - size_t keylen, - const char *data, - size_t datalen); -void usage(void); -void shut_down(int code); - -static int code = 0; - -int main(int argc, char **argv) -{ - int opt, r; - char *alt_config = NULL, *userid; - struct buf fnamebuf = BUF_INITIALIZER; - - if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { - fatal("must run as the Cyrus user", EC_USAGE); - } - - /* Ensure we're up-to-date on the index file format */ - assert(INDEX_HEADER_SIZE == (OFFSET_HEADER_CRC+4)); - assert(INDEX_RECORD_SIZE == (OFFSET_RECORD_CRC+4)); - - while ((opt = getopt(argc, argv, "C:")) != EOF) { - switch (opt) { - case 'C': /* alt config file */ - alt_config = optarg; - break; - - default: - usage(); - } - } - - cyrus_init(alt_config, "dav_reconstruct", 0, 0); - - /* Set namespace -- force standard (internal) */ - if ((r = mboxname_init_namespace(&recon_namespace, 1)) != 0) { - syslog(LOG_ERR, "%s", error_message(r)); - fatal(error_message(r), EC_CONFIG); - } - - mboxlist_init(0); - mboxlist_open(NULL); - - signals_set_shutdown(&shut_down); - signals_add_handlers(0); - - if (optind == argc) usage(); - - userid = argvoptind; - - printf("Reconstructing DAV DB for %s...\n", userid); - caldav_init(); - carddav_init(); - - /* remove existing database entirely */ - /* XXX - build a new file and rename into place? */ - dav_getpath_byuserid(&fnamebuf, userid); - if (buf_len(&fnamebuf)) - unlink(buf_cstring(&fnamebuf)); - - struct caldav_alarm_db *alarmdb = caldav_alarm_open(); - - caldav_alarm_delete_user(alarmdb, userid); - - mboxlist_allusermbox(userid, do_reconstruct, NULL, 0); - - caldav_alarm_close(alarmdb); - - carddav_done(); - caldav_done(); - - mboxlist_close(); - mboxlist_done(); - - buf_free(&fnamebuf); - - exit(code); -} - - -void usage(void) -{ - fprintf(stderr, - "usage: dav_reconstruct -C <alt_config> userid\n"); - exit(EC_USAGE); -} - -/* - * mboxlist_findall() callback function to create DAV DB entries for a mailbox - */ -static int do_reconstruct(void *rock __attribute__((unused)), - const char *key, - size_t keylen, - const char *data, - size_t datalen) -{ - int r = 0; - char ext_name_bufMAX_MAILBOX_PATH+1; - mbentry_t *mbentry = NULL; - struct mailbox *mailbox = NULL; - - signals_poll(); - - r = mboxlist_parse_entry(&mbentry, key, keylen, data, datalen); - if (r) return 0; - - /* Convert internal name to external */ - (*recon_namespace.mboxname_toexternal)(&recon_namespace, mbentry->name, - "cyrus", ext_name_buf); - - if (mbentry->mbtype & (MBTYPE_CALENDAR|MBTYPE_ADDRESSBOOK)) { - printf("Inserting DAV DB entries for %s...\n", ext_name_buf); - - /* Open/lock header */ - r = mailbox_open_irl(mbentry->name, &mailbox); - if (!r) r = mailbox_add_dav(mailbox); - mailbox_close(&mailbox); - } - - mboxlist_entry_free(&mbentry); - return r; -} - -/* - * Cleanly shut down and exit - */ -void shut_down(int code) __attribute__((noreturn)); -void shut_down(int code) -{ - in_shutdown = 1; - - mboxlist_close(); - mboxlist_done(); - caldav_done(); - exit(code); -}
View file
cyrus-imapd-2.5.tar.gz/imap/dav_util.c
Deleted
@@ -1,84 +0,0 @@ -/* dav_util.c -- utility functions for dealing with DAV database - * - * Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#include <string.h> - -#include "dav_util.h" -#include "global.h" -#include "mailbox.h" -#include "mboxname.h" -#include "util.h" - -/* Create filename corresponding to DAV DB for mailbox */ -EXPORTED void dav_getpath(struct buf *fname, struct mailbox *mailbox) -{ - const char *userid; - - userid = mboxname_to_userid(mailbox->name); - - if (userid) dav_getpath_byuserid(fname, userid); - else buf_setcstr(fname, mailbox_meta_fname(mailbox, META_DAV)); -} - -/* Create filename corresponding to DAV DB for userid */ -EXPORTED void dav_getpath_byuserid(struct buf *fname, const char *userid) -{ - char c, *domain; - - buf_reset(fname); - if (config_virtdomains && (domain = strchr(userid, '@'))) { - char d = (char) dir_hash_c(domain+1, config_fulldirhash); - *domain = '\0'; /* split user@domain */ - c = (char) dir_hash_c(userid, config_fulldirhash); - buf_printf(fname, "%s%s%c/%s%s%c/%s%s", config_dir, FNAME_DOMAINDIR, d, - domain+1, FNAME_USERDIR, c, userid, FNAME_DAVSUFFIX); - *domain = '@'; /* reassemble user@domain */ - } - else { - c = (char) dir_hash_c(userid, config_fulldirhash); - buf_printf(fname, "%s%s%c/%s%s", config_dir, FNAME_USERDIR, c, userid, - FNAME_DAVSUFFIX); - } -}
View file
cyrus-imapd-2.5.tar.gz/imap/dav_util.h
Deleted
@@ -1,58 +0,0 @@ -/* dav_util.h -- utility functions for dealing with DAV database - * - * Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef DAV_UTIL_H -#define DAV_UTIL_H - -#include "mailbox.h" -#include "util.h" - -#define FNAME_DAVSUFFIX ".dav" /* per-user DAV DB extension */ - -/* Create filename corresponding to DAV DB for mailbox */ -void dav_getpath(struct buf *fname, struct mailbox *mailbox); - -/* Create filename corresponding to DAV DB for userid */ -void dav_getpath_byuserid(struct buf *fname, const char *userid); - -#endif /* DAV_UTIL_H */
View file
cyrus-imapd-2.5.tar.gz/imap/dlist.c
Changed
@@ -135,14 +135,11 @@ /* XXX - these two functions should be out in append.c or reserve.c * or something more general */ -EXPORTED const char *dlist_reserve_path(const char *part, int isarchive, - struct message_guid *guid) +EXPORTED const char *dlist_reserve_path(const char *part, struct message_guid *guid) { static char bufMAX_MAILBOX_PATH; - const char *base = isarchive ? config_archivepartitiondir(part) - : config_partitiondir(part); snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu/%s", - base, (unsigned long)getpid(), + config_partitiondir(part), (unsigned long)getpid(), message_guid_encode(guid)); /* gotta make sure we can create files */ if (cyrus_mkdir(buf, 0755)) { @@ -162,7 +159,7 @@ int r = 0, n; /* XXX - write to a temporary file then move in to place! */ - *fname = dlist_reserve_path(part, /*isarchive*/0, guid); + *fname = dlist_reserve_path(part, guid); /* remove any duplicates if they're still here */ unlink(*fname); @@ -386,7 +383,7 @@ dl->type = DL_NIL; } -EXPORTED void dlist_makemap(struct dlist *dl, const char *val, size_t len) +void dlist_makemap(struct dlist *dl, const char *val, size_t len) { if (!dl) return; _dlist_clean(dl); @@ -419,7 +416,7 @@ return dl; } -EXPORTED struct dlist *dlist_newpklist(struct dlist *parent, const char *name) +struct dlist *dlist_newpklist(struct dlist *parent, const char *name) { struct dlist *dl = dlist_child(parent, name); dl->type = DL_ATOMLIST; @@ -462,7 +459,7 @@ return dl; } -EXPORTED struct dlist *dlist_sethex64(struct dlist *parent, const char *name, bit64 val) +struct dlist *dlist_sethex64(struct dlist *parent, const char *name, bit64 val) { struct dlist *dl = dlist_child(parent, name); dlist_makehex64(dl, val); @@ -941,7 +938,7 @@ return EOF; } -EXPORTED char dlist_parse_asatomlist(struct dlist **dlp, int parsekey, +char dlist_parse_asatomlist(struct dlist **dlp, int parsekey, struct protstream *in) { char c = dlist_parse(dlp, parsekey, in); @@ -1283,7 +1280,7 @@ return 1; } -EXPORTED int dlist_isatomlist(const struct dlist *dl) +int dlist_isatomlist(const struct dlist *dl) { if (!dl) return 0; return (dl->type == DL_ATOMLIST); @@ -1317,7 +1314,7 @@ /* XXX - these ones aren't const, because they can change * things... */ -EXPORTED int dlist_ishex64(struct dlist *dl) +int dlist_ishex64(struct dlist *dl) { bit64 tmp; @@ -1389,7 +1386,7 @@ return dlist_todate(child, valp); } -EXPORTED int dlist_gethex64(struct dlist *parent, const char *name, bit64 *valp) +int dlist_gethex64(struct dlist *parent, const char *name, bit64 *valp) { struct dlist *child = dlist_getchild(parent, name); return dlist_tohex64(child, valp);
View file
cyrus-imapd-2.5.tar.gz/imap/dlist.h
Changed
@@ -96,7 +96,7 @@ char *part; /* so what if we're big! */ }; -const char *dlist_reserve_path(const char *part, int isarchive, struct message_guid *guid); +const char *dlist_reserve_path(const char *part, struct message_guid *guid); /* set fields */ void dlist_makeatom(struct dlist *dl, const char *val); @@ -165,6 +165,10 @@ const char *part, struct message_guid *guid, size_t size, const char *fname); +/* special number and string readers - return 0 and "" if nothing */ +bit64 dlist_childvaln(struct dlist *parent, const char *name); +const char *dlist_childvalcstring(struct dlist *parent, const char *name); + struct dlist *dlist_updateatom(struct dlist *parent, const char *name, const char *val); struct dlist *dlist_updateflag(struct dlist *parent, const char *name,
View file
cyrus-imapd-2.5.tar.gz/imap/global.c
Changed
@@ -100,8 +100,6 @@ EXPORTED const char *config_ptscache_db; EXPORTED const char *config_statuscache_db; HIDDEN const char *config_userdeny_db; -EXPORTED const char *config_zoneinfo_db; -EXPORTED const char *config_conversations_db; EXPORTED int charset_flags; static char session_id_bufMAX_SESSIONID_SIZE; @@ -248,10 +246,6 @@ break; } - if (config_getswitch(IMAPOPT_SEARCH_SKIPHTML)) - charset_flags |= CHARSET_SKIPHTML; - - if (!cyrus_init_nodb) { /* lookup the database backends */ config_mboxlist_db = config_getstring(IMAPOPT_MBOXLIST_DB); @@ -265,8 +259,6 @@ config_ptscache_db = config_getstring(IMAPOPT_PTSCACHE_DB); config_statuscache_db = config_getstring(IMAPOPT_STATUSCACHE_DB); config_userdeny_db = config_getstring(IMAPOPT_USERDENY_DB); - config_zoneinfo_db = config_getstring(IMAPOPT_ZONEINFO_DB); - config_conversations_db = config_getstring(IMAPOPT_CONVERSATIONS_DB); /* configure libcyrus as needed */ libcyrus_config_setstring(CYRUSOPT_CONFIG_DIR, config_dir); @@ -1041,10 +1033,3 @@ return buf_cstring(&clientbuf); } - -EXPORTED int cmd_cancelled() -{ - if (signals_cancelled()) - return IMAP_CANCELLED; - return 0; -}
View file
cyrus-imapd-2.5.tar.gz/imap/global.h
Changed
@@ -49,14 +49,10 @@ #include "prot.h" #include "mboxname.h" #include "signals.h" -#include "imapparse.h" #include "util.h" #define MAX_SESSIONID_SIZE 256 -/* This is a string because we only ever use it as a string */ -#define SPHINX_MAX_MATCHES "1000000" - /* Flags for cyrus_init() */ enum { CYRUSINIT_NODB = (1<<0), @@ -122,6 +118,47 @@ int *userisproxyadmin; }; +/* imap parsing functions (imapparse.c) */ +int getword(struct protstream *in, struct buf *buf); + +/* Flags for getxstring() */ +/* IMAP_BIN_ASTRING is an IMAP_ASTRING that does not perform the + * does-not-contain-a-NULL check (in the case of a literal) */ +enum getxstring_flags { + GXS_ATOM = (1<<0), /* result may be a bare atom */ + GXS_QUOTED = (1<<1), /* result may be "quoted" */ + GXS_LITERAL = (1<<2), /* result may be {N}literal */ + GXS_NIL = (1<<3), /* result may be the special atom NIL */ + GXS_BINARY = (1<<4), /* result may contain embedded NULs */ + + IMAP_ASTRING = GXS_ATOM|GXS_QUOTED|GXS_LITERAL, + IMAP_BIN_ASTRING = IMAP_ASTRING|GXS_BINARY, + IMAP_NSTRING = GXS_NIL|GXS_QUOTED|GXS_LITERAL, + IMAP_BIN_NSTRING = IMAP_NSTRING|GXS_BINARY, + IMAP_QSTRING = GXS_QUOTED, + IMAP_STRING = GXS_QUOTED|GXS_LITERAL, + + /* note: there's some consistency issues here... the special + * value "NIL" must be quoted to get returned as a string */ + IMAP_NASTRING = GXS_NIL|GXS_ATOM|GXS_QUOTED|GXS_LITERAL, +}; + +int getxstring(struct protstream *pin, struct protstream *pout, + struct buf *buf, enum getxstring_flags); +#define getastring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_ASTRING) +#define getbastring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_BIN_ASTRING) +#define getnstring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_NSTRING) +#define getbnstring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_BIN_NSTRING) +#define getqstring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_QSTRING) +#define getstring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_STRING) +#define getnastring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_NASTRING) +int getint32(struct protstream *pin, int *num); +int getsint32(struct protstream *pin, int *num); +int getuint32(struct protstream *pin, unsigned int *num); +int getmodseq(struct protstream *pin, modseq_t *num); + +void eatline(struct protstream *pin, int c); + /* Misc utils */ extern int shutdown_file(char *buf, int size); extern char *find_free_partition(unsigned long *tavail); @@ -146,8 +183,6 @@ extern const char *config_ptscache_db; extern const char *config_statuscache_db; extern const char *config_userdeny_db; -extern const char *config_zoneinfo_db; -extern const char *config_conversations_db; extern int charset_flags; /* Session ID */ @@ -158,6 +193,4 @@ /* Capability suppression */ extern int capa_is_disabled(const char *str); -extern int cmd_cancelled(); - #endif /* INCLUDED_GLOBAL_H */
View file
cyrus-imapd-2.5.tar.gz/imap/hammer_cyrusdb.c
Deleted
@@ -1,203 +0,0 @@ -/* hammer_cyrusdb.c - tool to harass a cyrusdb file - * - * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any other legal - * details, please contact - * Office of Technology Transfer - * Carnegie Mellon University - * 5000 Forbes Avenue - * Pittsburgh, PA 15213-3890 - * (412) 268-4387, fax: (412) 268-7395 - * tech-transfer@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ -/* - * $Id: hammer_cyrusdb.c,v 1.4 2007/09/28 02:27:46 murch Exp $ - */ - -#include <config.h> - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <errno.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/uio.h> -#include <fcntl.h> -#include <ctype.h> -#include <syslog.h> - -#include <sys/ipc.h> -#include <sys/msg.h> - -#include "acl.h" -#include "assert.h" -#include "auth.h" -#include "cyrusdb.h" -#include "exitcodes.h" -#include "glob.h" -#include "imap_err.h" -#include "global.h" -#include "mailbox.h" -#include "util.h" -#include "xmalloc.h" - -const int config_need_data = 0; -struct cyrusdb_backend *OLDDB = NULL; - -void hammer(struct db *db) -{ - int c; - for (c = 0;c < 10000; c++) { /* should be enough! */ - struct txn *tid = NULL; - struct txn **tp; - char key100; - char value100; - int klen, vlen, i, r = 0; - int rop; - - /* protect against silly things */ - - tp = (rand() % 2) ? &tid : NULL; - klen = 1 + (rand() % 6); - for (i = 0; i < klen; i++) { - keyi = 'A' + (rand() % 26); - } - keyklen = '\0'; - vlen = rand() % 20; - for (i = 0; i < vlen; i++) { - valuei = 'a' + (rand() % 26); - } - valuevlen = '\0'; - rop = rand() % 1000; - if (rop >= 999) { - if (!r) r = cyrusdb_store(db, key, klen, value, vlen, tp); - /* forget to commit */ - } - else if (rop >= 800) { - if (!r) r = cyrusdb_store(db, key, klen, value, vlen, tp); - if (!r) r = cyrusdb_delete(db, key, klen, tp, 0); - if (!r) r = cyrusdb_store(db, key, klen, value, vlen, tp); - if (!r && tp) cyrusdb_commit(db, *tp); - } - else if (rop >= 700) { - if (!r) r = cyrusdb_delete(db, key, klen, tp, 0); - if (!r && tp) cyrusdb_commit(db, *tp); - } - else if (rop >= 600) { /* will fail */ - if (!r) r = cyrusdb_store(db, key, klen, value, vlen, tp); - if (!r) r = cyrusdb_create(db, key, klen, value, vlen, tp); - } - else if (rop > 200) { - if (!r) r = cyrusdb_store(db, key, klen, value, vlen, tp); - keyklen-1 = 'a'; - if (!r) r = cyrusdb_create(db, key, klen, value, vlen, tp); - keyklen-1 = 'b'; - if (!r) r = cyrusdb_create(db, key, klen, value, vlen, tp); - keyklen-1 = 'd'; - if (!r) r = cyrusdb_create(db, key, klen, value, vlen, tp); - keyklen-1 = 'c'; - if (!r) r = cyrusdb_create(db, key, klen, value, vlen, tp); - if (!r && tp) cyrusdb_commit(db, *tp); - } - else { - if (!r) r = cyrusdb_store(db, key, klen, value, vlen, tp); - keyklen-1 = 'a'; - if (!r) r = cyrusdb_create(db, key, klen, value, vlen, tp); - keyklen-1 = 'b'; - if (!r) r = cyrusdb_create(db, key, klen, value, vlen, tp); - keyklen-1 = 'd'; - if (!r) r = cyrusdb_create(db, key, klen, value, vlen, tp); - keyklen-1 = 'c'; - if (!r) r = cyrusdb_create(db, key, klen, value, vlen, tp); - if (!r && tp) cyrusdb_abort(db, *tp); - } - - } - - -} - -int main(int argc, char *argv) -{ - struct db *db; - const char *dbfile; - const char *dbtype = "twoskip"; - int opt, r; - char *alt_config = NULL; - int db_flags = 0; - - while ((opt = getopt(argc, argv, "C:n")) != EOF) { - switch (opt) { - case 'C': /* alt config file */ - alt_config = optarg; - break; - case 'n': /* create new */ - db_flags |= CYRUSDB_CREATE; - break; - } - } - - if((argc - optind) < 1) { - fprintf(stderr, "Usage: %s -C altconfig <dbfile> <dbtype>\n", argv0); - - exit(-1); - } - - dbfile = argvoptind; - - if((argc - optind) > 1) - dbtype = argvoptind+1; - - if (dbfile0 != '/') { - printf("\nSorry, you cannot use this tool with relative path names.\n" - "This is because some database backends (mainly berkeley) do not\n" - "always do what you would expect with them.\n" - "\nPlease use absolute pathnames instead.\n\n"); - exit(EC_OSERR); - } - - cyrus_init(alt_config, "hammer_cyrusdb", 0, 0); - - r = cyrusdb_open(dbtype, dbfile, db_flags, &db); - if (r) fatal("can't open database", EC_TEMPFAIL); - - hammer(db); - - cyrus_done(); - - return 0; -} -
View file
cyrus-imapd-2.5.tar.gz/imap/http_caldav.c
Deleted
@@ -1,5467 +0,0 @@ -/* http_caldav.c -- Routines for handling CalDAV collections in httpd - * - * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ -/* - * TODO: - * - * - Make proxying more robust. Currently depends on calendar collections - * residing on same server as user's INBOX. Doesn't handle global/shared - * calendars. - * - Support COPY/MOVE on collections - * - Add more required properties - * - GET/HEAD on collections (iCalendar stream of resources) - * - calendar-query REPORT (handle partial retrieval, prop-filter, timezone?) - * - free-busy-query REPORT (check ACL and transp on all calendars) - * - sync-collection REPORT - need to handle Depth infinity? - */ - -#include <config.h> - -#include <syslog.h> - -#include <libical/ical.h> -#include <libxml/tree.h> -#include <libxml/uri.h> -#include <sys/types.h> -#include <sys/wait.h> - -#include "acl.h" -#include "append.h" -#include "caldav_db.h" -#include "exitcodes.h" -#include "global.h" -#include "hash.h" -#include "httpd.h" -#include "http_caldav_sched.h" -#include "http_dav.h" -#include "http_err.h" -#include "http_proxy.h" -#include "imap_err.h" -#include "index.h" -#include "jcal.h" -#include "xcal.h" -#include "mailbox.h" -#include "mboxlist.h" -#include "md5.h" -#include "message.h" -#include "message_guid.h" -#include "proxy.h" -#include "times.h" -#include "smtpclient.h" -#include "spool.h" -#include "strhash.h" -#include "stristr.h" -#include "tok.h" -#include "util.h" -#include "version.h" -#include "xmalloc.h" -#include "xstrlcat.h" -#include "xstrlcpy.h" - -#define NEW_STAG (1<<8) /* Make sure we skip over PREFER bits */ - -struct busytime { - struct icalperiodtype *busy; - unsigned len; - unsigned alloc; -}; - -struct calquery_filter { - unsigned comp; - struct icaltimetype start; - struct icaltimetype end; - unsigned check_transp; - unsigned save_busytime; - struct busytime busytime; /* array of found busytime periods */ -}; - -static unsigned config_allowsched = IMAP_ENUM_CALDAV_ALLOWSCHEDULING_OFF; -static struct caldav_db *auth_caldavdb = NULL; -static time_t compile_time; - -static struct caldav_db *my_caldav_open(struct mailbox *mailbox); -static void my_caldav_close(struct caldav_db *caldavdb); -static void my_caldav_init(struct buf *serverinfo); -static void my_caldav_auth(const char *userid); -static void my_caldav_reset(void); -static void my_caldav_shutdown(void); - -static int caldav_parse_path(const char *path, - struct request_target_t *tgt, const char **errstr); - -static int caldav_check_precond(struct transaction_t *txn, const void *data, - const char *etag, time_t lastmod); - -static int caldav_acl(struct transaction_t *txn, xmlNodePtr priv, int *rights); -static int caldav_copy(struct transaction_t *txn, - struct mailbox *src_mbox, struct index_record *src_rec, - struct mailbox *dest_mbox, const char *dest_rsrc, - struct caldav_db *dest_davdb, - unsigned overwrite, unsigned flags); -static int caldav_delete_sched(struct transaction_t *txn, - struct mailbox *mailbox, - struct index_record *record, void *data); -static int meth_get(struct transaction_t *txn, void *params); -static int caldav_post(struct transaction_t *txn); -static int caldav_put(struct transaction_t *txn, - struct mime_type_t *mime, - struct mailbox *mailbox, - struct caldav_db *caldavdb, - unsigned flags); - -static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_restype(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_reportset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_caldata(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_calcompset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int proppatch_calcompset(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, void *rock); -static int propfind_suppcaldata(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_schedtag(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_caltransp(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int proppatch_caltransp(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, void *rock); -static int propfind_tz_avail(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int proppatch_timezone(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, void *rock); -static int proppatch_availability(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, void *rock); - -static int report_cal_query(struct transaction_t *txn, xmlNodePtr inroot, - struct propfind_ctx *fctx); -static int report_cal_multiget(struct transaction_t *txn, xmlNodePtr inroot, - struct propfind_ctx *fctx); -static int report_fb_query(struct transaction_t *txn, xmlNodePtr inroot, - struct propfind_ctx *fctx); - -static int store_resource(struct transaction_t *txn, icalcomponent *ical, - struct mailbox *mailbox, const char *resource, - struct caldav_db *caldavdb, int overwrite, - unsigned flags); - -static void sched_request(const char *organizer, struct sched_param *sparam, - icalcomponent *oldical, icalcomponent *newical, - const char *att_update); -static void sched_reply(const char *userid, - icalcomponent *oldical, icalcomponent *newical); - -static const char *begin_icalendar(struct buf *buf); -static void end_icalendar(struct buf *buf); - -static struct mime_type_t caldav_mime_types = { - /* First item MUST be the default type and storage format */ - { "text/calendar; charset=utf-8", "2.0", "ics", "ifb", - (char* (*)(void *)) &icalcomponent_as_ical_string_r, - (void * (*)(const char*)) &icalparser_parse_string, - (void (*)(void *)) &icalcomponent_free, &begin_icalendar, &end_icalendar - }, - { "application/calendar+xml; charset=utf-8", NULL, "xcs", "xfb", - (char* (*)(void *)) &icalcomponent_as_xcal_string, - (void * (*)(const char*)) &xcal_string_as_icalcomponent, - NULL, &begin_xcal, &end_xcal - }, -#ifdef WITH_JSON - { "application/calendar+json; charset=utf-8", NULL, "jcs", "jfb", - (char* (*)(void *)) &icalcomponent_as_jcal_string, - (void * (*)(const char*)) &jcal_string_as_icalcomponent, - NULL, &begin_jcal, &end_jcal - }, -#endif - { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } -}; - -/* Array of known "live" properties */ -static const struct prop_entry caldav_props = { - - /* WebDAV (RFC 4918) properties */ - { "creationdate", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_creationdate, NULL, NULL }, - { "displayname", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_fromdb, proppatch_todb, NULL }, - { "getcontentlanguage", NS_DAV, PROP_ALLPROP | PROP_RESOURCE, - propfind_fromhdr, NULL, "Content-Language" }, - { "getcontentlength", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_getlength, NULL, NULL }, - { "getcontenttype", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_getcontenttype, NULL, "Content-Type" }, - { "getetag", NS_DAV, PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_getetag, NULL, NULL }, - { "getlastmodified", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_getlastmod, NULL, NULL }, - { "lockdiscovery", NS_DAV, PROP_ALLPROP | PROP_RESOURCE, - propfind_lockdisc, NULL, NULL }, - { "resourcetype", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_restype, proppatch_restype, "calendar" }, - { "supportedlock", NS_DAV, PROP_ALLPROP | PROP_RESOURCE, - propfind_suplock, NULL, NULL }, - - /* WebDAV Versioning (RFC 3253) properties */ - { "supported-report-set", NS_DAV, PROP_COLLECTION, - propfind_reportset, NULL, NULL }, - - /* WebDAV ACL (RFC 3744) properties */ - { "owner", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_owner, NULL, NULL }, - { "group", NS_DAV, 0, NULL, NULL, NULL }, - { "supported-privilege-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_supprivset, NULL, NULL }, - { "current-user-privilege-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_curprivset, NULL, NULL }, - { "acl", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_acl, NULL, NULL }, - { "acl-restrictions", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_aclrestrict, NULL, NULL }, - { "inherited-acl-set", NS_DAV, 0, NULL, NULL, NULL }, - { "principal-collection-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_princolset, NULL, NULL }, - - /* WebDAV Quota (RFC 4331) properties */ - { "quota-available-bytes", NS_DAV, PROP_COLLECTION, - propfind_quota, NULL, NULL }, - { "quota-used-bytes", NS_DAV, PROP_COLLECTION, - propfind_quota, NULL, NULL }, - - /* WebDAV Current Principal (RFC 5397) properties */ - { "current-user-principal", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_curprin, NULL, NULL }, - - /* WebDAV POST (RFC 5995) properties */ - { "add-member", NS_DAV, PROP_COLLECTION, - propfind_addmember, NULL, NULL }, - - /* WebDAV Sync (RFC 6578) properties */ - { "sync-token", NS_DAV, PROP_COLLECTION, - propfind_sync_token, NULL, NULL }, - - /* CalDAV (RFC 4791) properties */ - { "calendar-data", NS_CALDAV, - PROP_RESOURCE | PROP_PRESCREEN | PROP_NEEDPROP, - propfind_caldata, NULL, NULL }, - { "calendar-description", NS_CALDAV, PROP_COLLECTION, - propfind_fromdb, proppatch_todb, NULL }, - { "calendar-timezone", NS_CALDAV, - PROP_COLLECTION | PROP_PRESCREEN | PROP_NEEDPROP, - propfind_tz_avail, proppatch_timezone, NULL }, - { "supported-calendar-component-set", NS_CALDAV, PROP_COLLECTION, - propfind_calcompset, proppatch_calcompset, NULL }, - { "supported-calendar-data", NS_CALDAV, PROP_COLLECTION, - propfind_suppcaldata, NULL, NULL }, - { "max-resource-size", NS_CALDAV, 0, NULL, NULL, NULL }, - { "min-date-time", NS_CALDAV, 0, NULL, NULL, NULL }, - { "max-date-time", NS_CALDAV, 0, NULL, NULL, NULL }, - { "max-instances", NS_CALDAV, 0, NULL, NULL, NULL }, - { "max-attendees-per-instance", NS_CALDAV, 0, NULL, NULL, NULL }, - - /* CalDAV Scheduling (RFC 6638) properties */ - { "schedule-tag", NS_CALDAV, PROP_RESOURCE, - propfind_schedtag, NULL, NULL }, - { "schedule-default-calendar-URL", NS_CALDAV, PROP_COLLECTION, - propfind_calurl, NULL, SCHED_DEFAULT }, - { "schedule-calendar-transp", NS_CALDAV, PROP_COLLECTION, - propfind_caltransp, proppatch_caltransp, NULL }, - - /* Calendar Availability (draft-daboo-calendar-availability) properties */ - { "calendar-availability", NS_CALDAV, - PROP_COLLECTION | PROP_PRESCREEN | PROP_NEEDPROP, - propfind_tz_avail, proppatch_availability, NULL }, - - /* Apple Calendar Server properties */ - { "getctag", NS_CS, PROP_ALLPROP | PROP_COLLECTION, - propfind_sync_token, NULL, NULL }, - - { NULL, 0, 0, NULL, NULL, NULL } -}; - - -static struct meth_params caldav_params = { - caldav_mime_types, - &caldav_parse_path, - &caldav_check_precond, - { (db_open_proc_t) &my_caldav_open, - (db_close_proc_t) &my_caldav_close, - (db_lookup_proc_t) &caldav_lookup_resource, - (db_foreach_proc_t) &caldav_foreach, - (db_write_proc_t) &caldav_write, - (db_delete_proc_t) &caldav_delete, - (db_delmbox_proc_t) &caldav_delmbox }, - &caldav_acl, - (copy_proc_t) &caldav_copy, - &caldav_delete_sched, - { MBTYPE_CALENDAR, "mkcalendar", "mkcalendar-response", NS_CALDAV }, - &caldav_post, - { CALDAV_SUPP_DATA, (put_proc_t) &caldav_put }, - caldav_props, - { { "calendar-query", &report_cal_query, DACL_READ, - REPORT_NEED_MBOX | REPORT_MULTISTATUS }, - { "calendar-multiget", &report_cal_multiget, DACL_READ, - REPORT_NEED_MBOX | REPORT_MULTISTATUS }, - { "free-busy-query", &report_fb_query, DACL_READFB, - REPORT_NEED_MBOX }, - { "sync-collection", &report_sync_col, DACL_READ, - REPORT_NEED_MBOX | REPORT_MULTISTATUS | REPORT_NEED_PROPS }, - { NULL, NULL, 0, 0 } } -}; - - -/* Namespace for CalDAV collections */ -struct namespace_t namespace_calendar = { - URL_NS_CALENDAR, 0, "/dav/calendars", "/.well-known/caldav", 1 /* auth */, - MBTYPE_CALENDAR, - (ALLOW_READ | ALLOW_POST | ALLOW_WRITE | ALLOW_DELETE | -#ifdef HAVE_VAVAILABILITY - ALLOW_CAL_AVAIL | -#endif - ALLOW_DAV | ALLOW_WRITECOL | ALLOW_CAL ), - &my_caldav_init, &my_caldav_auth, my_caldav_reset, &my_caldav_shutdown, - { - { &meth_acl, &caldav_params }, /* ACL */ - { &meth_copy, &caldav_params }, /* COPY */ - { &meth_delete, &caldav_params }, /* DELETE */ - { &meth_get, &caldav_params }, /* GET */ - { &meth_get, &caldav_params }, /* HEAD */ - { &meth_lock, &caldav_params }, /* LOCK */ - { &meth_mkcol, &caldav_params }, /* MKCALENDAR */ - { &meth_mkcol, &caldav_params }, /* MKCOL */ - { &meth_copy, &caldav_params }, /* MOVE */ - { &meth_options, &caldav_parse_path }, /* OPTIONS */ - { &meth_post, &caldav_params }, /* POST */ - { &meth_propfind, &caldav_params }, /* PROPFIND */ - { &meth_proppatch, &caldav_params }, /* PROPPATCH */ - { &meth_put, &caldav_params }, /* PUT */ - { &meth_report, &caldav_params }, /* REPORT */ - { &meth_trace, &caldav_parse_path }, /* TRACE */ - { &meth_unlock, &caldav_params } /* UNLOCK */ - } -}; - - -static struct caldav_db *my_caldav_open(struct mailbox *mailbox) -{ - if (httpd_userid && mboxname_userownsmailbox(httpd_userid, mailbox->name)) { - return auth_caldavdb; - } - else { - return caldav_open_mailbox(mailbox, CALDAV_CREATE); - } -} - - -static void my_caldav_close(struct caldav_db *caldavdb) -{ - if (caldavdb && (caldavdb != auth_caldavdb)) caldav_close(caldavdb); -} - - -static void my_caldav_init(struct buf *serverinfo) -{ - namespace_calendar.enabled = - config_httpmodules & IMAP_ENUM_HTTPMODULES_CALDAV; - - if (!namespace_calendar.enabled) return; - - if (!config_getstring(IMAPOPT_CALENDARPREFIX)) { - fatal("Required 'calendarprefix' option is not set", EC_CONFIG); - } - - caldav_init(); - - if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) { - buf_printf(serverinfo, " libical/%s", ICAL_VERSION); -#ifdef WITH_JSON - buf_printf(serverinfo, " Jansson/%s", JANSSON_VERSION); -#endif - } - - config_allowsched = config_getenum(IMAPOPT_CALDAV_ALLOWSCHEDULING); - if (config_allowsched) { - namespace_calendar.allow |= ALLOW_CAL_SCHED; - - /* Need to set this to parse CalDAV Scheduling parameters */ - ical_set_unknown_token_handling_setting(ICAL_ASSUME_IANA_TOKEN); - } - - compile_time = calc_compile_time(__TIME__, __DATE__); -} - - -static void my_caldav_auth(const char *userid) -{ - const char *mailboxname; - int r; - - /* Generate mailboxname of calendar-home-set */ - mailboxname = caldav_mboxname(userid, NULL); - - if (httpd_userisadmin || - global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) { - /* admin or proxy from frontend - won't have DAV database */ - return; - } - else if (config_mupdate_server && !config_getstring(IMAPOPT_PROXYSERVERS)) { - /* proxy-only server - won't have DAV database */ - } - else { - /* Open CalDAV DB for 'userid' */ - my_caldav_reset(); - auth_caldavdb = caldav_open_userid(userid, CALDAV_CREATE); - if (!auth_caldavdb) fatal("Unable to open CalDAV DB", EC_IOERR); - } - - /* Auto-provision calendars for 'userid' */ - - /* calendar-home-set */ - r = mboxlist_lookup(mailboxname, NULL, NULL); - if (r == IMAP_MAILBOX_NONEXISTENT) { - if (config_mupdate_server) { - /* Find location of INBOX */ - const char *inboxname = mboxname_user_mbox(userid, NULL); - mbentry_t *mbentry = NULL; - - r = http_mlookup(inboxname, &mbentry, NULL); - if (!r && mbentry->server) { - proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - return; - } - mboxlist_entry_free(&mbentry); - } - - /* will have been overwritten */ - mailboxname = caldav_mboxname(userid, NULL); - - /* Create locally */ - r = mboxlist_createmailbox(mailboxname, MBTYPE_CALENDAR, - NULL, 0, - userid, httpd_authstate, - 0, 0, 0, 0, NULL); - } -} - - -static void my_caldav_reset(void) -{ - if (auth_caldavdb) caldav_close(auth_caldavdb); - auth_caldavdb = NULL; -} - - -static void my_caldav_shutdown(void) -{ - caldav_done(); -} - - -/* Parse request-target path in CalDAV namespace */ -static int caldav_parse_path(const char *path, - struct request_target_t *tgt, const char **errstr) -{ - char *p; - size_t len; - struct mboxname_parts parts; - struct buf boxbuf = BUF_INITIALIZER; - - /* Make a working copy of target path */ - strlcpy(tgt->path, path, sizeof(tgt->path)); - tgt->tail = tgt->path + strlen(tgt->path); - - p = tgt->path; - - /* Sanity check namespace */ - len = strlen(namespace_calendar.prefix); - if (strlen(p) < len || - strncmp(namespace_calendar.prefix, p, len) || - (pathlen && pathlen != '/')) { - *errstr = "Namespace mismatch request target path"; - return HTTP_FORBIDDEN; - } - - tgt->prefix = namespace_calendar.prefix; - - /* Default to bare-bones Allow bits for toplevel collections */ - tgt->allow &= ~(ALLOW_POST|ALLOW_WRITE|ALLOW_DELETE); - - /* Skip namespace */ - p += len; - if (!*p || !*++p) return 0; - - /* Check if we're in user space */ - len = strcspn(p, "/"); - if (!strncmp(p, "user", len)) { - p += len; - if (!*p || !*++p) return 0; - - /* Get user id */ - len = strcspn(p, "/"); - tgt->user = p; - tgt->userlen = len; - - p += len; - if (!*p || !*++p) { - /* Make sure calendar-home-set is terminated with '/' */ - if (p-1 != '/') *p++ = '/'; - goto done; - } - - len = strcspn(p, "/"); - } - - /* Get collection */ - tgt->collection = p; - tgt->collen = len; - - p += len; - if (!*p || !*++p) { - /* Make sure collection is terminated with '/' */ - if (p-1 != '/') *p++ = '/'; - goto done; - } - - /* Get resource */ - len = strcspn(p, "/"); - tgt->resource = p; - tgt->reslen = len; - - p += len; - - if (*p) { -// *errstr = "Too many segments in request target path"; - return HTTP_NOT_FOUND; - } - - done: - /* Set proper Allow bits and flags based on path components */ - if (tgt->collection) { - if (tgt->resource) { - tgt->allow &= ~ALLOW_WRITECOL; - tgt->allow |= (ALLOW_WRITE|ALLOW_DELETE); - } - else if (!strcmp(tgt->collection, SCHED_INBOX)) - tgt->flags = TGT_SCHED_INBOX; - else if (!strcmp(tgt->collection, SCHED_OUTBOX)) { - tgt->flags = TGT_SCHED_OUTBOX; - tgt->allow |= ALLOW_POST; - } - else - tgt->allow |= (ALLOW_POST|ALLOW_DELETE); - } - else if (tgt->user) tgt->allow |= ALLOW_DELETE; - - /* Create mailbox name from the parsed path */ - - mboxname_init_parts(&parts); - - if (tgt->user && tgt->userlen) { - /* holy "avoid copying" batman */ - char *userid = xstrndup(tgt->user, tgt->userlen); - mboxname_userid_to_parts(userid, &parts); - free(userid); - } - - buf_setcstr(&boxbuf, config_getstring(IMAPOPT_CALENDARPREFIX)); - if (tgt->collen) { - buf_putc(&boxbuf, '.'); - buf_appendmap(&boxbuf, tgt->collection, tgt->collen); - } - parts.box = buf_release(&boxbuf); - - /* XXX - hack to allow @domain parts for non-domain-split users */ - if (httpd_extradomain) { - //free(parts.domain); - XXX - fix when converting to real parts - parts.domain = NULL; - } - - mboxname_parts_to_internal(&parts, tgt->mboxname); - - mboxname_free_parts(&parts); - - return 0; -} - - -/* Check headers for any preconditions */ -static int caldav_check_precond(struct transaction_t *txn, const void *data, - const char *etag, time_t lastmod) -{ - const struct caldav_data *cdata = (const struct caldav_data *) data; - const char *stag = cdata ? cdata->sched_tag : NULL; - const char **hdr; - int precond; - - /* Do normal WebDAV/HTTP checks (primarily for lock-token via If header) */ - precond = check_precond(txn, data, etag, lastmod); - if (!(precond == HTTP_OK || precond == HTTP_PARTIAL)) return precond; - - /* Per RFC 6638, check Schedule-Tag */ - if ((hdr = spool_getheader(txn->req_hdrs, "If-Schedule-Tag-Match"))) { - /* Special case for Apple 'If-Schedule-Tag-Match:' with no value - * and also no schedule tag on the record - let that match */ - if (cdata && !stag && !hdr00) return precond; - if (etagcmp(hdr0, stag)) return HTTP_PRECOND_FAILED; - } - - if (txn->meth == METH_GET || txn->meth == METH_HEAD) { - /* Fill in Schedule-Tag for successful GET/HEAD */ - txn->resp_body.stag = stag; - } - - return precond; -} - - -static int caldav_acl(struct transaction_t *txn, xmlNodePtr priv, int *rights) -{ - if (!xmlStrcmp(priv->ns->href, BAD_CAST XML_NS_CALDAV)) { - /* CalDAV privileges */ - switch (txn->req_tgt.flags) { - case TGT_SCHED_INBOX: - if (!xmlStrcmp(priv->name, BAD_CAST "schedule-deliver")) - *rights |= DACL_SCHED; - else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-deliver-invite")) - *rights |= DACL_INVITE; - else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-deliver-reply")) - *rights |= DACL_REPLY; - else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-query-freebusy")) - *rights |= DACL_SCHEDFB; - else { - /* DAV:not-supported-privilege */ - txn->error.precond = DAV_SUPP_PRIV; - } - break; - case TGT_SCHED_OUTBOX: - if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send")) - *rights |= DACL_SCHED; - else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send-invite")) - *rights |= DACL_INVITE; - else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send-reply")) - *rights |= DACL_REPLY; - else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send-freebusy")) - *rights |= DACL_SCHEDFB; - else { - /* DAV:not-supported-privilege */ - txn->error.precond = DAV_SUPP_PRIV; - } - break; - default: - if (!xmlStrcmp(priv->name, BAD_CAST "read-free-busy")) - *rights |= DACL_READFB; - else { - /* DAV:not-supported-privilege */ - txn->error.precond = DAV_SUPP_PRIV; - } - break; - } - - /* Done processing this priv */ - return 1; - } - else if (!xmlStrcmp(priv->ns->href, BAD_CAST XML_NS_DAV)) { - /* WebDAV privileges */ - if (!xmlStrcmp(priv->name, BAD_CAST "all")) { - switch (txn->req_tgt.flags) { - case TGT_SCHED_INBOX: - /* DAV:all aggregates CALDAV:schedule-deliver */ - *rights |= DACL_SCHED; - break; - case TGT_SCHED_OUTBOX: - /* DAV:all aggregates CALDAV:schedule-send */ - *rights |= DACL_SCHED; - break; - default: - /* DAV:all aggregates CALDAV:read-free-busy */ - *rights |= DACL_READFB; - break; - } - } - else if (!xmlStrcmp(priv->name, BAD_CAST "read")) { - switch (txn->req_tgt.flags) { - case TGT_SCHED_INBOX: - case TGT_SCHED_OUTBOX: - break; - default: - /* DAV:read aggregates CALDAV:read-free-busy */ - *rights |= DACL_READFB; - break; - } - } - } - - /* Process this priv in meth_acl() */ - return 0; -} - - -/* Perform a COPY/MOVE request - * - * preconditions: - * CALDAV:supported-calendar-data - * CALDAV:valid-calendar-data - * CALDAV:valid-calendar-object-resource - * CALDAV:supported-calendar-component - * CALDAV:no-uid-conflict (DAV:href) - * CALDAV:calendar-collection-location-ok - * CALDAV:max-resource-size - * CALDAV:min-date-time - * CALDAV:max-date-time - * CALDAV:max-instances - * CALDAV:max-attendees-per-instance - */ -static int caldav_copy(struct transaction_t *txn, - struct mailbox *src_mbox, struct index_record *src_rec, - struct mailbox *dest_mbox, const char *dest_rsrc, - struct caldav_db *dest_davdb, - unsigned overwrite, unsigned flags) -{ - int r; - - const char *organizer = NULL; - struct buf msg_buf = BUF_INITIALIZER; - icalcomponent *ical, *comp; - icalproperty *prop; - - /* Load message containing the resource and parse iCal data */ - r = mailbox_map_record(src_mbox, src_rec, &msg_buf); - if (r) return r; - ical = icalparser_parse_string(buf_base(&msg_buf) + src_rec->header_size); - buf_free(&msg_buf); - - if (!ical) { - txn->error.precond = CALDAV_VALID_DATA; - return HTTP_FORBIDDEN; - } - - /* Finished our initial read of source mailbox */ - mailbox_unlock_index(src_mbox, NULL); - - if (namespace_calendar.allow & ALLOW_CAL_SCHED) { - comp = icalcomponent_get_first_real_component(ical); - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - if (prop) organizer = icalproperty_get_organizer(prop); - if (organizer) flags |= NEW_STAG; - } - - /* Store source resource at destination */ - r = store_resource(txn, ical, dest_mbox, dest_rsrc, dest_davdb, - overwrite, flags); - - icalcomponent_free(ical); - - return r; -} - - -/* Perform scheduling actions for a DELETE request */ -static int caldav_delete_sched(struct transaction_t *txn, - struct mailbox *mailbox, - struct index_record *record, void *data) -{ - struct caldav_data *cdata = (struct caldav_data *) data; - int r = 0; - - if (!(namespace_calendar.allow & ALLOW_CAL_SCHED)) return 0; - - if (!record) { - /* XXX DELETE collection - check all resources for sched objects */ - } - else if (cdata->sched_tag) { - /* Scheduling object resource */ - const char *userid, *organizer, **hdr; - struct buf msg_buf = BUF_INITIALIZER; - icalcomponent *ical, *comp; - icalproperty *prop; - struct sched_param sparam; - - /* Load message containing the resource and parse iCal data */ - r = mailbox_map_record(mailbox, record, &msg_buf); - if (r) return r; - ical = icalparser_parse_string(buf_base(&msg_buf) + record->header_size); - buf_free(&msg_buf); - - if (!ical) { - syslog(LOG_ERR, - "meth_delete: failed to parse iCalendar object %s:%u", - txn->req_tgt.mboxname, record->uid); - return HTTP_SERVER_ERROR; - } - - /* Construct userid corresponding to mailbox */ - userid = mboxname_to_userid(txn->req_tgt.mboxname); - - /* Grab the organizer */ - comp = icalcomponent_get_first_real_component(ical); - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - organizer = icalproperty_get_organizer(prop); - - r = caladdress_lookup(organizer, &sparam); - if (r == HTTP_NOT_FOUND) { - r = 0; - goto done; - } - if (r) { - syslog(LOG_ERR, - "meth_delete: failed to process scheduling message in %s" - " (org=%s, att=%s)", - txn->req_tgt.mboxname, organizer, userid); - txn->error.desc = "Failed to lookup organizer address\r\n"; - r = HTTP_SERVER_ERROR; - goto done; - } - - if (!strcmp(sparam.userid, userid)) { - /* Organizer scheduling object resource */ - sched_request(organizer, &sparam, ical, NULL, 0); - } - else if (!(hdr = spool_getheader(txn->req_hdrs, "Schedule-Reply")) || - strcmp(hdr0, "F")) { - /* Attendee scheduling object resource */ - sched_reply(userid, ical, NULL); - } - - done: - icalcomponent_free(ical); - } - - return r; -} - -static const char *begin_icalendar(struct buf *buf) -{ - /* Begin iCalendar stream */ - buf_setcstr(buf, "BEGIN:VCALENDAR\r\n"); - buf_printf(buf, "PRODID:-//CyrusIMAP.org/Cyrus %s//EN\r\n", - cyrus_version()); - buf_appendcstr(buf, "VERSION:2.0\r\n"); - - return ""; -} - -static void end_icalendar(struct buf *buf) -{ - /* End iCalendar stream */ - buf_setcstr(buf, "END:VCALENDAR\r\n"); -} - -static int dump_calendar(struct transaction_t *txn, struct meth_params *gparams) -{ - int ret = 0, r, precond; - struct resp_body_t *resp_body = &txn->resp_body; - struct buf *buf = &resp_body->payload; - struct mailbox *mailbox = NULL; - static char etag33; - uint32_t recno; - struct index_record record; - struct hash_table tzid_table; - static const char *displayname_annot = - ANNOT_NS "<" XML_NS_DAV ">displayname"; - struct buf attrib = BUF_INITIALIZER; - const char **hdr, *sep; - struct mime_type_t *mime = NULL; - - /* Check requested MIME type: - 1st entry in caldav_mime_types array MUST be default MIME type */ - if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) - mime = get_accept_type(hdr, caldav_mime_types); - else mime = caldav_mime_types; - if (!mime) return HTTP_NOT_ACCEPTABLE; - - /* Open mailbox for reading */ - r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Check any preconditions */ - sprintf(etag, "%u-%u-%u", - mailbox->i.uidvalidity, mailbox->i.last_uid, mailbox->i.exists); - precond = gparams->check_precond(txn, NULL, etag, mailbox->index_mtime); - - switch (precond) { - case HTTP_OK: - case HTTP_NOT_MODIFIED: - /* Fill in ETag, Last-Modified, Expires, and Cache-Control */ - txn->resp_body.etag = etag; - txn->resp_body.lastmod = mailbox->index_mtime; - txn->resp_body.maxage = 3600; /* 1 hr */ - txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; /* don't use stale data */ - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - ret = precond; - goto done; - } - - /* Setup for chunked response */ - txn->flags.te |= TE_CHUNKED; - txn->flags.vary |= VARY_ACCEPT; - txn->resp_body.type = mime->content_type; - - /* Set filename of resource */ - r = annotatemore_lookupmask(mailbox->name, displayname_annot, httpd_userid, &attrib); - /* fall back to last part of mailbox name */ - if (r || !attrib.len) buf_setcstr(&attrib, strrchr(mailbox->name, '.') + 1); - - buf_reset(&txn->buf); - buf_printf(&txn->buf, "%s.%s", buf_cstring(&attrib), mime->file_ext); - txn->resp_body.fname = buf_cstring(&txn->buf); - - /* Short-circuit for HEAD request */ - if (txn->meth == METH_HEAD) { - response_header(HTTP_OK, txn); - return 0; - } - - /* iCalendar data in response should not be transformed */ - txn->flags.cc |= CC_NOTRANSFORM; - - /* Create hash table for TZIDs */ - construct_hash_table(&tzid_table, 10, 1); - - /* Begin (converted) iCalendar stream */ - sep = mime->begin_stream(buf); - write_body(HTTP_OK, txn, buf_cstring(buf), buf_len(buf)); - - for (r = 0, recno = 1; recno <= mailbox->i.num_records; recno++) { - struct buf msg_buf = BUF_INITIALIZER; - icalcomponent *ical; - - if (mailbox_read_index_record(mailbox, recno, &record)) continue; - - if (record.system_flags & (FLAG_EXPUNGED | FLAG_DELETED)) continue; - - /* Map and parse existing iCalendar resource */ - if (mailbox_map_record(mailbox, &record, &msg_buf)) continue; - ical = icalparser_parse_string(buf_base(&msg_buf) + record.header_size); - buf_free(&msg_buf); - - if (ical) { - icalcomponent *comp; - - for (comp = icalcomponent_get_first_component(ical, - ICAL_ANY_COMPONENT); - comp; - comp = icalcomponent_get_next_component(ical, - ICAL_ANY_COMPONENT)) { - char *cal_str; - icalcomponent_kind kind = icalcomponent_isa(comp); - - /* Don't duplicate any TZIDs in our iCalendar */ - if (kind == ICAL_VTIMEZONE_COMPONENT) { - icalproperty *prop = - icalcomponent_get_first_property(comp, - ICAL_TZID_PROPERTY); - const char *tzid = icalproperty_get_tzid(prop); - - if (hash_lookup(tzid, &tzid_table)) continue; - else hash_insert(tzid, (void *)0xDEADBEEF, &tzid_table); - } - - /* Include this component in our iCalendar */ - if (r++ && *sep) { - /* Add separator, if necessary */ - buf_reset(buf); - buf_printf_markup(buf, 0, sep); - write_body(0, txn, buf_cstring(buf), buf_len(buf)); - } - cal_str = mime->to_string(comp); - write_body(0, txn, cal_str, strlen(cal_str)); - free(cal_str); - } - - icalcomponent_free(ical); - } - } - - free_hash_table(&tzid_table, NULL); - - /* End (converted) iCalendar stream */ - mime->end_stream(buf); - write_body(0, txn, buf_cstring(buf), buf_len(buf)); - - /* End of output */ - write_body(0, txn, NULL, 0); - - done: - mailbox_close(&mailbox); - buf_free(&attrib); - - return ret; -} - - -/* - * mboxlist_findall() callback function to list calendars - */ -static int list_cb(char *name, - int matchlen __attribute__((unused)), - int maycreate __attribute__((unused)), - void *rock) -{ - struct transaction_t *txn = (struct transaction_t *) rock; - struct buf *body = &txn->resp_body.payload; - struct buf *url = &txn->buf; - static size_t inboxlen = 0; - static size_t outboxlen = 0; - int rights; - char *shortname; - mbentry_t *mbentry = NULL; - size_t len; - int r; - static const char *displayname_annot = - ANNOT_NS "<" XML_NS_DAV ">displayname"; - struct buf displayname = BUF_INITIALIZER; - - if (!inboxlen) inboxlen = strlen(SCHED_INBOX) - 1; - if (!outboxlen) outboxlen = strlen(SCHED_OUTBOX) - 1; - - shortname = strrchr(name, '.') + 1; - len = strlen(shortname); - - /* Don't list scheduling Inbox/Outbox */ - if ((len == inboxlen && !strncmp(shortname, SCHED_INBOX, inboxlen)) || - (len == outboxlen && !strncmp(shortname, SCHED_OUTBOX, outboxlen))) - goto done; - - /* Don't list deleted mailboxes */ - if (mboxname_isdeletedmailbox(name, 0)) goto done; - - /* Lookup the mailbox and make sure its readable */ - r = http_mlookup(name, &mbentry, NULL); - if (r) goto done; - - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if ((rights & DACL_READ) != DACL_READ) - goto done; - - /* Send a body chunk once in a while */ - if (buf_len(body) > PROT_BUFSIZE) { - write_body(0, txn, buf_cstring(body), buf_len(body)); - buf_reset(body); - } - - /* Lookup DAV:displayname */ - r = annotatemore_lookupmask(name, displayname_annot, httpd_userid, &displayname); - /* fall back to the last part of the mailbox name */ - if (r || !displayname.len) buf_setcstr(&displayname, shortname); - - /* Add available calendar with link */ - len = buf_len(url); - buf_printf_markup(body, 3, "<li><a href=\"%s%s\">%s</a></li>", - buf_cstring(url), shortname, buf_cstring(&displayname)); - -done: - buf_free(&displayname); - mboxlist_entry_free(&mbentry); - - return 0; -} - - -/* Create a HTML document listing all calendars available to the user */ -static int list_calendars(struct transaction_t *txn, - struct meth_params *gparams) -{ - int ret = 0, precond; - time_t lastmod = compile_time; - char mboxlistMAX_MAILBOX_PATH+1; - struct stat sbuf; - static char etag63; - unsigned level = 0; - struct buf *body = &txn->resp_body.payload; - const char *proto = NULL, *host = NULL; - - /* stat() mailboxes.db for Last-Modified and ETag */ - snprintf(mboxlist, MAX_MAILBOX_PATH, "%s%s", config_dir, FNAME_MBOXLIST); - stat(mboxlist, &sbuf); - lastmod = MAX(compile_time, sbuf.st_mtime); - sprintf(etag, "%ld-%ld-%ld", compile_time, sbuf.st_mtime, sbuf.st_size); - - /* Check any preconditions */ - precond = gparams->check_precond(txn, NULL, etag, lastmod); - - switch (precond) { - case HTTP_OK: - case HTTP_NOT_MODIFIED: - /* Fill in ETag, Last-Modified, and Expires */ - txn->resp_body.etag = etag; - txn->resp_body.lastmod = lastmod; - txn->resp_body.maxage = 86400; /* 24 hrs */ - txn->flags.cc |= CC_MAXAGE; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - ret = precond; - goto done; - } - - /* Setup for chunked response */ - txn->flags.te |= TE_CHUNKED; - txn->resp_body.type = "text/html; charset=utf-8"; - - /* Short-circuit for HEAD request */ - if (txn->meth == METH_HEAD) { - response_header(HTTP_OK, txn); - goto done; - } - - /* Send HTML header */ - buf_reset(body); - buf_printf_markup(body, level, HTML_DOCTYPE); - buf_printf_markup(body, level++, "<html>"); - buf_printf_markup(body, level++, "<head>"); - buf_printf_markup(body, level, "<title>%s</title>", "Available Calendars"); - buf_printf_markup(body, --level, "</head>"); - buf_printf_markup(body, level++, "<body>"); - buf_printf_markup(body, level, "<h2>%s</h2>", "Available Calendars"); - buf_printf_markup(body, level++, "<ul>"); - write_body(HTTP_OK, txn, buf_cstring(body), buf_len(body)); - buf_reset(body); - - /* Create base URL for calendars */ - http_proto_host(txn->req_hdrs, &proto, &host); - buf_reset(&txn->buf); - buf_printf(&txn->buf, "%s://%s%s", proto, host, txn->req_tgt.path); - - /* Generate list of calendars */ - strlcat(txn->req_tgt.mboxname, ".%", sizeof(txn->req_tgt.mboxname)); - - mboxlist_findall(NULL, txn->req_tgt.mboxname, 1, httpd_userid, - httpd_authstate, list_cb, txn); - - if (buf_len(body)) write_body(0, txn, buf_cstring(body), buf_len(body)); - - /* Finish HTML */ - buf_reset(body); - buf_printf_markup(body, --level, "</ul>"); - buf_printf_markup(body, --level, "</body>"); - buf_printf_markup(body, --level, "</html>"); - write_body(0, txn, buf_cstring(body), buf_len(body)); - - /* End of output */ - write_body(0, txn, NULL, 0); - - done: - return ret; -} - - -/* Perform a GET/HEAD request on a CalDAV resource */ -static int meth_get(struct transaction_t *txn, void *params) -{ - struct meth_params *gparams = (struct meth_params *) params; - int r, rights; - mbentry_t *mbentry = NULL; - - /* Parse the path */ - if ((r = gparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) return r; - - /* GET an individual resource */ - if (txn->req_tgt.resource) return meth_get_dav(txn, gparams); - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Check ACL for current user */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if ((rights & DACL_READ) != DACL_READ) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = DACL_READ; - mboxlist_entry_free(&mbentry); - return HTTP_FORBIDDEN; - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - - /* Get an entire calendar collection */ - if (txn->req_tgt.collection) return dump_calendar(txn, gparams); - - /* GET a list of calendars under calendar-home-set */ - else return list_calendars(txn, gparams); -} - - -/* Perform a busy time request, if necessary */ -static int caldav_post(struct transaction_t *txn) -{ - int ret = 0, r, rights; - char orgidMAX_MAILBOX_NAME+1 = ""; - mbentry_t *mbentry = NULL; - const char **hdr; - struct mime_type_t *mime = NULL; - icalcomponent *ical = NULL, *comp; - icalcomponent_kind kind = 0; - icalproperty_method meth = 0; - icalproperty *prop = NULL; - const char *uid = NULL, *organizer = NULL; - struct sched_param sparam; - - if (!(namespace_calendar.allow & ALLOW_CAL_SCHED) || !txn->req_tgt.flags) { - /* POST to regular calendar collection */ - return HTTP_CONTINUE; - } - else if (txn->req_tgt.flags == TGT_SCHED_INBOX) { - /* Don't allow POST to schedule-inbox */ - return HTTP_NOT_ALLOWED; - } - - /* POST to schedule-outbox */ - - /* Check Content-Type */ - if ((hdr = spool_getheader(txn->req_hdrs, "Content-Type"))) { - for (mime = caldav_mime_types; mime->content_type; mime++) { - if (is_mediatype(mime->content_type, hdr0)) break; - } - } - if (!mime || !mime->content_type) { - txn->error.precond = CALDAV_SUPP_DATA; - return HTTP_BAD_REQUEST; - } - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Get rights for current user */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - mboxlist_entry_free(&mbentry); - - /* Read body */ - txn->req_body.flags |= BODY_DECODE; - r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc); - if (r) { - txn->flags.conn = CONN_CLOSE; - return r; - } - - /* Make sure we have a body */ - if (!buf_len(&txn->req_body.payload)) { - txn->error.desc = "Missing request body\r\n"; - return HTTP_BAD_REQUEST; - } - - /* Parse the iCal data for important properties */ - ical = mime->from_string(buf_cstring(&txn->req_body.payload)); - if (!ical || !icalrestriction_check(ical)) { - txn->error.precond = CALDAV_VALID_DATA; - ret = HTTP_BAD_REQUEST; - goto done; - } - - meth = icalcomponent_get_method(ical); - comp = icalcomponent_get_first_real_component(ical); - if (comp) { - uid = icalcomponent_get_uid(comp); - kind = icalcomponent_isa(comp); - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - } - - /* Check method preconditions */ - if (!meth || !uid || !prop) { - txn->error.precond = CALDAV_VALID_SCHED; - ret = HTTP_BAD_REQUEST; - goto done; - } - - /* Organizer MUST be local to use CalDAV Scheduling */ - organizer = icalproperty_get_organizer(prop); - if (organizer) { - if (!caladdress_lookup(organizer, &sparam) && - !(sparam.flags & SCHEDTYPE_REMOTE)) { - strlcpy(orgid, sparam.userid, sizeof(orgid)); - mboxname_hiersep_toexternal(&httpd_namespace, orgid, 0); - } - } - - if (strncmp(orgid, txn->req_tgt.user, txn->req_tgt.userlen)) { - txn->error.precond = CALDAV_VALID_ORGANIZER; - ret = HTTP_FORBIDDEN; - goto done; - } - - switch (kind) { - case ICAL_VFREEBUSY_COMPONENT: - if (meth == ICAL_METHOD_REQUEST) - if (!(rights & DACL_SCHEDFB)) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = DACL_SCHEDFB; - ret = HTTP_FORBIDDEN; - } - else ret = sched_busytime_query(txn, mime, ical); - else { - txn->error.precond = CALDAV_VALID_SCHED; - ret = HTTP_BAD_REQUEST; - } - break; - - default: - txn->error.precond = CALDAV_VALID_SCHED; - ret = HTTP_BAD_REQUEST; - } - - done: - if (ical) icalcomponent_free(ical); - - return ret; -} - - -static const char *get_icalrestriction_errstr(icalcomponent *ical) -{ - icalcomponent *comp; - - for (comp = icalcomponent_get_first_component(ical, ICAL_ANY_COMPONENT); - comp; - comp = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT)) { - icalproperty *prop = - icalcomponent_get_first_property(comp, ICAL_XLICERROR_PROPERTY); - if (prop) return icalproperty_get_xlicerror(prop); - } - - return NULL; -} - - -/* Perform a PUT request - * - * preconditions: - * CALDAV:valid-calendar-data - * CALDAV:valid-calendar-object-resource - * CALDAV:supported-calendar-component - * CALDAV:no-uid-conflict (DAV:href) - * CALDAV:max-resource-size - * CALDAV:min-date-time - * CALDAV:max-date-time - * CALDAV:max-instances - * CALDAV:max-attendees-per-instance - */ -static int caldav_put(struct transaction_t *txn, - struct mime_type_t *mime, - struct mailbox *mailbox, - struct caldav_db *davdb, - unsigned flags) -{ - int ret; - icalcomponent *ical = NULL, *comp, *nextcomp; - icalcomponent_kind kind; - icalproperty *prop; - const char *uid, *organizer = NULL; - - /* Parse and validate the iCal data */ - ical = mime->from_string(buf_cstring(&txn->req_body.payload)); - if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) { - txn->error.precond = CALDAV_VALID_DATA; - ret = HTTP_FORBIDDEN; - goto done; - } - else if (!icalrestriction_check(ical)) { - txn->error.precond = CALDAV_VALID_OBJECT; - if ((txn->error.desc = get_icalrestriction_errstr(ical))) { - buf_setcstr(&txn->buf, txn->error.desc); - txn->error.desc = buf_cstring(&txn->buf); - } - ret = HTTP_FORBIDDEN; - goto done; - } - - /* Make sure iCal UIDs and ORGANIZERs in all components are the same */ - comp = icalcomponent_get_first_real_component(ical); - kind = icalcomponent_isa(comp); - uid = icalcomponent_get_uid(comp); - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - if (prop) organizer = icalproperty_get_organizer(prop); - while ((nextcomp = - icalcomponent_get_next_component(ical, kind))) { - const char *nextuid = icalcomponent_get_uid(nextcomp); - - if (!nextuid || strcmp(uid, nextuid)) { - txn->error.precond = CALDAV_VALID_OBJECT; - ret = HTTP_FORBIDDEN; - goto done; - } - - if (organizer) { - const char *nextorg = NULL; - - prop = icalcomponent_get_first_property(nextcomp, - ICAL_ORGANIZER_PROPERTY); - if (prop) nextorg = icalproperty_get_organizer(prop); - if (!nextorg || strcmp(organizer, nextorg)) { - txn->error.precond = CALDAV_SAME_ORGANIZER; - ret = HTTP_FORBIDDEN; - goto done; - } - } - } - - switch (kind) { - case ICAL_VEVENT_COMPONENT: - case ICAL_VTODO_COMPONENT: - if ((namespace_calendar.allow & ALLOW_CAL_SCHED) && organizer && - icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY)) { - /* Scheduling object resource */ - const char *userid; - struct caldav_data *cdata; - struct sched_param sparam; - icalcomponent *oldical = NULL; - int r; - - /* Construct userid corresponding to mailbox */ - userid = mboxname_to_userid(txn->req_tgt.mboxname); - - /* Make sure iCal UID is unique for this user */ - caldav_lookup_uid(davdb, uid, 0, &cdata); - /* XXX Check errors */ - - if (cdata->dav.mailbox && - (strcmp(cdata->dav.mailbox, txn->req_tgt.mboxname) || - strcmp(cdata->dav.resource, txn->req_tgt.resource))) { - - buf_reset(&txn->buf); - buf_printf(&txn->buf, "%s/user/%s/%s/%s", - namespace_calendar.prefix, - userid, strrchr(cdata->dav.mailbox, '.')+1, - cdata->dav.resource); - txn->error.resource = buf_cstring(&txn->buf); - ret = HTTP_FORBIDDEN; - goto done; - } - - /* Lookup the organizer */ - r = caladdress_lookup(organizer, &sparam); - if (r == HTTP_NOT_FOUND) - break; /* not a local organiser? Just skip it */ - if (r) { - syslog(LOG_ERR, - "meth_put: failed to process scheduling message in %s" - " (org=%s)", - txn->req_tgt.mboxname, organizer); - txn->error.desc = "Failed to lookup organizer address\r\n"; - ret = HTTP_SERVER_ERROR; - goto done; - } - - if (cdata->dav.imap_uid) { - /* Update existing object */ - struct index_record record; - struct buf msg_buf = BUF_INITIALIZER; - - /* Load message containing the resource and parse iCal data */ - r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record, NULL); - if (!r) r = mailbox_map_record(mailbox, &record, &msg_buf); - if (r) { - txn->error.desc = "Failed to read record \r\n"; - ret = HTTP_SERVER_ERROR; - goto done; - } - oldical = icalparser_parse_string(buf_base(&msg_buf) + record.header_size); - buf_free(&msg_buf); - } - -#if 0 -FastMail hack - make configurable later - if (!strcmp(sparam.userid, userid)) { - /* Organizer scheduling object resource */ - sched_request(organizer, &sparam, oldical, ical, 0); - } - else { - /* Attendee scheduling object resource */ - sched_reply(userid, oldical, ical); - } -#endif - - if (oldical) icalcomponent_free(oldical); - - flags |= NEW_STAG; - } - break; - - default: - /* Nothing else to do */ - break; - } - - /* Store resource at target */ - ret = store_resource(txn, ical, mailbox, txn->req_tgt.resource, - davdb, OVERWRITE_CHECK, flags); - - if (flags & PREFER_REP) { - struct resp_body_t *resp_body = &txn->resp_body; - const char **hdr; - char *data; - - if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) - mime = get_accept_type(hdr, caldav_mime_types); - if (!mime) goto done; - - switch (ret) { - case HTTP_NO_CONTENT: - ret = HTTP_OK; - - case HTTP_CREATED: - /* Convert into requested MIME type */ - data = mime->to_string(ical); - - /* Fill in Content-Type, Content-Length */ - resp_body->type = mime->content_type; - resp_body->len = strlen(data); - - /* Fill in Content-Location */ - resp_body->loc = txn->req_tgt.path; - - /* Fill in Expires and Cache-Control */ - resp_body->maxage = 3600; /* 1 hr */ - txn->flags.cc = CC_MAXAGE - | CC_REVALIDATE /* don't use stale data */ - | CC_NOTRANSFORM; /* don't alter iCal data */ - - /* Output current representation */ - write_body(ret, txn, data, resp_body->len); - - free(data); - ret = 0; - break; - - default: - /* failure - do nothing */ - break; - } - } - - done: - if (ical) icalcomponent_free(ical); - - return ret; -} - - -/* Append a new busytime period to the busytime array */ -static void add_busytime(icalcomponent *comp, struct icaltime_span *span, - void *rock) -{ - struct busytime *busytime = (struct busytime *) rock; - int is_date = icaltime_is_date(icalcomponent_get_dtstart(comp)); - icaltimezone *utc = icaltimezone_get_utc_timezone(); - struct icalperiodtype *newp; - - /* Grow the array, if necessary */ - if (busytime->len == busytime->alloc) { - busytime->alloc += 100; /* XXX arbitrary */ - busytime->busy = xrealloc(busytime->busy, - busytime->alloc * - sizeof(struct icalperiodtype)); - } - - /* Add new busytime */ - newp = &busytime->busybusytime->len++; - newp->start = icaltime_from_timet_with_zone(span->start, is_date, utc); - newp->end = icaltime_from_timet_with_zone(span->end, is_date, utc); - newp->duration = icaldurationtype_null_duration(); -} - -static int tracking_busytime(struct calquery_filter *calfilter, icalcomponent *comp) -{ - /* only if we're checking transparency */ - if (calfilter->check_transp) { - icalproperty *prop = icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY); - if (prop && !strcasecmp(icalproperty_get_value_as_string(prop), "TRANSPARENT")) - return 0; - } - - return 1; -} - - -/* See if the current resource matches the specified filter - * (comp-type and/or time-range). Returns 1 if match, 0 otherwise. - */ -static int apply_calfilter(struct propfind_ctx *fctx, void *data) -{ - struct calquery_filter *calfilter = - (struct calquery_filter *) fctx->filter_crit; - struct caldav_data *cdata = (struct caldav_data *) data; - int match = 1; - - if (calfilter->comp) { - /* Perform CALDAV:comp-filter filtering */ - if (!(cdata->comp_type & calfilter->comp)) return 0; - } - - if (!icaltime_is_null_time(calfilter->start)) { - /* Perform CALDAV:time-range filtering */ - struct icaltimetype dtstart = icaltime_from_string(cdata->dtstart); - struct icaltimetype dtend = icaltime_from_string(cdata->dtend); - - if (icaltime_compare(dtend, calfilter->start) <= 0) { - /* Component is earlier than range */ - return 0; - } - else if (icaltime_compare(dtstart, calfilter->end) >= 0) { - /* Component is later than range */ - return 0; - } - else if (!cdata->recurring && !calfilter->save_busytime) { - /* Component is within range, non-recurring, - and we don't need to save busytime */ - return 1; - } - else if (config_getswitch(IMAPOPT_CALENDAR_GREEDYMATCH) && !calfilter->save_busytime) { - /* just assume recurrences will match, client doesn't - * care if it gets a bit more */ - return 1; - } - else { - /* Component is within range and recurring. - * Need to mmap() and parse iCalendar object - * to perform complete check of each recurrence. - */ - struct busytime *busytime = &calfilter->busytime; - icalcomponent *ical, *comp; - icalcomponent_kind kind; - icaltimezone *utc = icaltimezone_get_utc_timezone(); - icaltime_span rangespan; - unsigned firstr, lastr; - - /* XXX - error */ - if (mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf)) - return 0; - - ical = icalparser_parse_string(buf_base(&fctx->msg_buf) + - fctx->record->header_size); - - comp = icalcomponent_get_first_real_component(ical); - kind = icalcomponent_isa(comp); - - /* XXX This code assumes that the first VEVENT will contain - * the recurrence rule and the subsequent VEVENTs will - * be the overrides. Technically this doesn't have to be - * the case, but it appears to be true in practice. - */ - - /* Create a span for the given time-range */ - rangespan.start = - icaltime_as_timet_with_zone(calfilter->start, utc); - rangespan.end = - icaltime_as_timet_with_zone(calfilter->end, utc); - - /* Mark start of where recurrences will be added */ - firstr = busytime->len; - - /* we still need to check overriden recurrences, in case the - * outer event is TRANSPARENT, but a recurrence is OPAQUE. Ouch */ - if (tracking_busytime(calfilter, comp)) { - /* Add all recurring busytime in specified time-range */ - icalcomponent_foreach_recurrence(comp, - calfilter->start, calfilter->end, - add_busytime, busytime); - } - - /* Mark end of where recurrences were added */ - lastr = busytime->len; - - /* XXX Should we sort busytime array, so we can use bsearch()? */ - - /* Handle overridden recurrences */ - while ((comp = icalcomponent_get_next_component(ical, kind))) { - icalproperty *prop; - struct icaltimetype recurid; - icalparameter *param; - icaltime_span recurspan; - unsigned n; - - /* but if we're not tracking now, we can skip out immediately */ - if (!tracking_busytime(calfilter, comp)) - continue; - - /* The *_get_recurrenceid() functions don't appear - to deal with timezones properly, so we do it ourselves */ - prop = - icalcomponent_get_first_property(comp, - ICAL_RECURRENCEID_PROPERTY); - recurid = icalproperty_get_recurrenceid(prop); - param = - icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER); - - if (param) { - const char *tzid = icalparameter_get_tzid(param); - icaltimezone *tz = NULL; - - tz = icalcomponent_get_timezone(ical, tzid); - if (!tz) { - tz = icaltimezone_get_builtin_timezone_from_tzid(tzid); - } - if (tz) icaltime_set_timezone(&recurid, tz); - } - - recurid = - icaltime_convert_to_zone(recurid, - icaltimezone_get_utc_timezone()); - - /* Check if this overridden instance is in our array */ - /* XXX Should we replace this linear search with bsearch() */ - for (n = firstr; n < lastr; n++) { - if (!icaltime_compare(recurid, - busytime->busyn.start)) { - /* Remove the instance - by sliding all future instances into its place */ - /* XXX Doesn't handle the RANGE=THISANDFUTURE param */ - busytime->len--; - memmove(&busytime->busyn, &busytime->busyn+1, - sizeof(struct icalperiodtype) * - (busytime->len - n)); - lastr--; - - break; - } - } - - /* Check if the new instance is in our time-range */ - recurspan = icaltime_span_new(icalcomponent_get_dtstart(comp), - icalcomponent_get_dtend(comp), 1); - - if (icaltime_span_overlaps(&recurspan, &rangespan)) { - /* Add this instance to the array */ - add_busytime(comp, &recurspan, busytime); - } - } - - if (busytime->len == firstr) match = 0; - - if (!calfilter->save_busytime) busytime->len = 0; - - icalcomponent_free(ical); - } - } - - return match; -} - - -static int is_valid_timerange(const struct icaltimetype start, - const struct icaltimetype end) -{ - return (icaltime_is_valid_time(start) && icaltime_is_valid_time(end) && - !icaltime_is_date(start) && !icaltime_is_date(end) && - (icaltime_is_utc(start) || start.zone) && - (icaltime_is_utc(end) || end.zone)); -} - - -static int parse_comp_filter(xmlNodePtr root, struct calquery_filter *filter, - struct error_t *error) -{ - int ret = 0; - xmlNodePtr node; - - /* Parse elements of filter */ - for (node = root; node; node = node->next) { - if (node->type == XML_ELEMENT_NODE) { - if (!xmlStrcmp(node->name, BAD_CAST "comp-filter")) { - xmlChar *name = xmlGetProp(node, BAD_CAST "name"); - - if (!filter->comp) { - if (!xmlStrcmp(name, BAD_CAST "VCALENDAR")) - filter->comp = CAL_COMP_VCALENDAR; - else { - error->precond = CALDAV_VALID_FILTER; - ret = HTTP_FORBIDDEN; - } - } - else if (filter->comp == CAL_COMP_VCALENDAR) { - if (!xmlStrcmp(name, BAD_CAST "VCALENDAR") || - !xmlStrcmp(name, BAD_CAST "VALARM")) { - error->precond = CALDAV_VALID_FILTER; - ret = HTTP_FORBIDDEN; - } - else if (!xmlStrcmp(name, BAD_CAST "VEVENT")) - filter->comp |= CAL_COMP_VEVENT; - else if (!xmlStrcmp(name, BAD_CAST "VTODO")) - filter->comp |= CAL_COMP_VTODO; - else if (!xmlStrcmp(name, BAD_CAST "VJOURNAL")) - filter->comp |= CAL_COMP_VJOURNAL; - else if (!xmlStrcmp(name, BAD_CAST "VFREEBUSY")) - filter->comp |= CAL_COMP_VFREEBUSY; - else if (!xmlStrcmp(name, BAD_CAST "VTIMEZONE")) - filter->comp |= CAL_COMP_VTIMEZONE; - else if (!xmlStrcmp(name, BAD_CAST "VAVAILABILITY")) - filter->comp |= CAL_COMP_VAVAILABILITY; - else { - error->precond = CALDAV_SUPP_FILTER; - ret = HTTP_FORBIDDEN; - } - } - else if (filter->comp & (CAL_COMP_VEVENT | CAL_COMP_VTODO)) { - if (!xmlStrcmp(name, BAD_CAST "VALARM")) - filter->comp |= CAL_COMP_VALARM; - else { - error->precond = CALDAV_VALID_FILTER; - ret = HTTP_FORBIDDEN; - } - } - else { - error->precond = CALDAV_SUPP_FILTER; - ret = HTTP_FORBIDDEN; - } - - xmlFree(name); - - if (!ret) - ret = parse_comp_filter(node->children, filter, error); - if (ret) return ret; - } - else if (!xmlStrcmp(node->name, BAD_CAST "time-range")) { - xmlChar *start, *end; - - if (!(filter->comp & (CAL_COMP_VEVENT | CAL_COMP_VTODO))) { - error->precond = CALDAV_VALID_FILTER; - return HTTP_FORBIDDEN; - } - - start = xmlGetProp(node, BAD_CAST "start"); - if (start) { - filter->start = icaltime_from_string((char *) start); - xmlFree(start); - } - else { - filter->start = world_start; - } - - end = xmlGetProp(node, BAD_CAST "end"); - if (end) { - filter->end = icaltime_from_string((char *) end); - xmlFree(end); - } - else { - filter->end = world_end; - } - - if (!is_valid_timerange(filter->start, filter->end)) { - error->precond = CALDAV_VALID_FILTER; - return HTTP_FORBIDDEN; - } - } - else { - error->precond = CALDAV_SUPP_FILTER; - return HTTP_FORBIDDEN; - } - } - } - - return ret; -} - - -/* Callback to fetch DAV:getcontenttype */ -static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - buf_setcstr(&fctx->buf, "text/calendar; charset=utf-8"); - - if (fctx->data) { - struct caldav_data *cdata = (struct caldav_data *) fctx->data; - const char *comp = NULL; - - switch (cdata->comp_type) { - case CAL_COMP_VEVENT: comp = "VEVENT"; break; - case CAL_COMP_VTODO: comp = "VTODO"; break; - case CAL_COMP_VJOURNAL: comp = "VJOURNAL"; break; - case CAL_COMP_VFREEBUSY: comp = "VFREEBUSY"; break; - case CAL_COMP_VAVAILABILITY: comp = "VAVAILABILITY"; break; - } - - if (comp) buf_printf(&fctx->buf, "; component=%s", comp); - } - - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, BAD_CAST buf_cstring(&fctx->buf), 0); - - return 0; -} - - -/* Callback to fetch DAV:resourcetype */ -static int propfind_restype(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - if (!fctx->record) { - xmlNewChild(node, NULL, BAD_CAST "collection", NULL); - - if (fctx->req_tgt->collection) { - ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C"); - if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX)) { - xmlNewChild(node, fctx->nsNS_CALDAV, - BAD_CAST "schedule-inbox", NULL); - } - else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX)) { - xmlNewChild(node, fctx->nsNS_CALDAV, - BAD_CAST "schedule-outbox", NULL); - } - else { - xmlNewChild(node, fctx->nsNS_CALDAV, - BAD_CAST "calendar", NULL); - } - } - } - - return 0; -} - - -/* Callback to fetch DAV:supported-report-set */ -static int propfind_reportset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr s, r, top; - - top = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - if (fctx->req_tgt->collection && !fctx->req_tgt->resource) { - s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL); - r = xmlNewChild(s, NULL, BAD_CAST "report", NULL); - xmlNewChild(r, fctx->nsNS_DAV, BAD_CAST "sync-collection", NULL); - } - - ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C"); - - s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL); - r = xmlNewChild(s, NULL, BAD_CAST "report", NULL); - xmlNewChild(r, fctx->nsNS_CALDAV, BAD_CAST "calendar-query", NULL); - - s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL); - r = xmlNewChild(s, NULL, BAD_CAST "report", NULL); - xmlNewChild(r, fctx->nsNS_CALDAV, BAD_CAST "calendar-multiget", NULL); - - s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL); - r = xmlNewChild(s, NULL, BAD_CAST "report", NULL); - xmlNewChild(r, fctx->nsNS_CALDAV, BAD_CAST "free-busy-query", NULL); - - return 0; -} - - -/* Callback to prescreen/fetch CALDAV:calendar-data */ -static int propfind_caldata(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock) -{ - xmlNodePtr prop = (xmlNodePtr) rock; - const char *data = NULL; - unsigned long datalen = 0; - - if (propstat) { - if (!fctx->record) return HTTP_NOT_FOUND; - - if (!fctx->msg_buf.len) { - mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf); - } - if (!fctx->msg_buf.len) return HTTP_SERVER_ERROR; - - data = fctx->msg_buf.s + fctx->record->header_size; - datalen = fctx->record->size - fctx->record->header_size; - } - - return propfind_getdata(name, ns, fctx, propstat, prop, caldav_mime_types, - CALDAV_SUPP_DATA, data, datalen); -} - - -/* Callback to fetch CALDAV:calendar-home-set, - * CALDAV:schedule-inbox-URL, CALDAV:schedule-outbox-URL, - * and CALDAV:schedule-default-calendar-URL - */ -int propfind_calurl(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock) -{ - xmlNodePtr node; - const char *cal = (const char *) rock; - - if (!fctx->userid) return HTTP_NOT_FOUND; - - /* sched-def-cal-URL only defined on sched-inbox-URL */ - if (!xmlStrcmp(name, BAD_CAST "schedule-default-calendar-URL") && - (!fctx->req_tgt->collection || - strcmp(fctx->req_tgt->collection, SCHED_INBOX))) - return HTTP_NOT_FOUND; - - /* named calendars are only used for scheduling */ - if (cal && !(namespace_calendar.allow & ALLOW_CAL_SCHED)) - return HTTP_NOT_FOUND; - - node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - buf_reset(&fctx->buf); - if (strchr(fctx->userid, '@')) { - buf_printf(&fctx->buf, "%s/user/%s/%s", - namespace_calendar.prefix, fctx->userid, cal ? cal : ""); - } - else { - buf_printf(&fctx->buf, "%s/user/%s@%s/%s", - namespace_calendar.prefix, fctx->userid, - httpd_extradomain ? httpd_extradomain : config_servername, - cal ? cal : ""); - } - - xml_add_href(node, fctx->nsNS_DAV, buf_cstring(&fctx->buf)); - - return 0; -} - - -/* Callback to fetch CALDAV:supported-calendar-component-set */ -static const struct cal_comp_t { - const char *name; - unsigned long type; -} cal_comps = { - { "VEVENT", CAL_COMP_VEVENT }, - { "VTODO", CAL_COMP_VTODO }, - { "VJOURNAL", CAL_COMP_VJOURNAL }, - { "VFREEBUSY", CAL_COMP_VFREEBUSY }, -#ifdef HAVE_VAVAILABILITY - { "VAVAILABILITY", CAL_COMP_VAVAILABILITY }, -#endif -// { "VTIMEZONE", CAL_COMP_VTIMEZONE }, -// { "VALARM", CAL_COMP_VALARM }, - { NULL, 0 } -}; - -static int propfind_calcompset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - const char *prop_annot = ANNOT_NS "CALDAV:supported-calendar-component-set"; - struct buf attrib = BUF_INITIALIZER; - unsigned long types = 0; - xmlNodePtr set, node; - const struct cal_comp_t *comp; - int r = 0; - - if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND; - - r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot, httpd_userid, &attrib); - if (r) return HTTP_SERVER_ERROR; - - if (attrib.len) - types = strtoul(buf_cstring(&attrib), NULL, 10); - else - types = -1; /* ALL components types */ - - buf_free(&attrib); - - if (!types) return HTTP_NOT_FOUND; - - set = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - /* Create "comp" elements from the stored bitmask */ - for (comp = cal_comps; comp->name; comp++) { - if (types & comp->type) { - node = xmlNewChild(set, fctx->nsNS_CALDAV, - BAD_CAST "comp", NULL); - xmlNewProp(node, BAD_CAST "name", BAD_CAST comp->name); - } - } - - return 0; -} - - -/* Callback to write supported-calendar-component-set property */ -static int proppatch_calcompset(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - int r = 0; - unsigned precond = 0; - - if (set && (pctx->meth == METH_MKCOL || pctx->meth == METH_MKCALENDAR)) { - /* "Writeable" for MKCOL/MKCALENDAR only */ - xmlNodePtr cur; - unsigned long types = 0; - - /* Work through the given list of components */ - for (cur = prop->children; cur; cur = cur->next) { - xmlChar *name; - const struct cal_comp_t *comp; - - /* Make sure its a "comp" element with a "name" */ - if (cur->type != XML_ELEMENT_NODE) continue; - if (xmlStrcmp(cur->name, BAD_CAST "comp") || - !(name = xmlGetProp(cur, BAD_CAST "name"))) break; - - /* Make sure we have a valid component type */ - for (comp = cal_comps; - comp->name && xmlStrcmp(name, BAD_CAST comp->name); comp++); - xmlFree(name); - - if (comp->name) types |= comp->type; /* found match in our list */ - else break; /* no match - invalid type */ - } - - if (!cur) { - /* All component types are valid */ - const char *prop_annot = - ANNOT_NS "CALDAV:supported-calendar-component-set"; - annotate_state_t *astate = NULL; - - buf_reset(&pctx->buf); - buf_printf(&pctx->buf, "%lu", types); - - r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate); - if (!r) r = annotate_state_writemask(astate, prop_annot, httpd_userid, &pctx->buf); - - if (!r) { - xml_add_prop(HTTP_OK, pctx->nsNS_DAV, &propstatPROPSTAT_OK, - prop->name, prop->ns, NULL, 0); - } - else { - xml_add_prop(HTTP_SERVER_ERROR, pctx->nsNS_DAV, - &propstatPROPSTAT_ERROR, - prop->name, prop->ns, NULL, 0); - } - - return 0; - } - - /* Invalid component type */ - precond = CALDAV_SUPP_COMP; - } - else { - /* Protected property */ - precond = DAV_PROT_PROP; - } - - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, precond); - - *pctx->ret = HTTP_FORBIDDEN; - - return 0; -} - -/* Callback to fetch CALDAV:supported-calendar-data */ -static int propfind_suppcaldata(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node; - struct mime_type_t *mime; - - if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND; - - node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - for (mime = caldav_mime_types; mime->content_type; mime++) { - xmlNodePtr type = xmlNewChild(node, fctx->nsNS_CALDAV, - BAD_CAST "calendar-data", NULL); - - /* Trim any charset from content-type */ - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "%.*s", - (int) strcspn(mime->content_type, ";"), mime->content_type); - - xmlNewProp(type, BAD_CAST "content-type", - BAD_CAST buf_cstring(&fctx->buf)); - - if (mime->version) - xmlNewProp(type, BAD_CAST "version", BAD_CAST mime->version); - } - - return 0; -} - - -/* Callback to fetch CALDAV:schedule-tag */ -static int propfind_schedtag(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - struct caldav_data *cdata = (struct caldav_data *) fctx->data; - - if (!cdata->sched_tag) return HTTP_NOT_FOUND; - - /* add DQUOTEs */ - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "\"%s\"", cdata->sched_tag); - - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, BAD_CAST buf_cstring(&fctx->buf), 0); - - return 0; -} - - -/* Callback to fetch CALDAV:calendar-user-address-set */ -int propfind_caluseraddr(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node; - - if (!fctx->userid) return HTTP_NOT_FOUND; - - node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - /* XXX This needs to be done via an LDAP/DB lookup */ - buf_reset(&fctx->buf); - if (strchr(fctx->userid, '@')) { - buf_printf(&fctx->buf, "mailto:%s", fctx->userid); - } - else { - buf_printf(&fctx->buf, "mailto:%s@%s", fctx->userid, - httpd_extradomain ? httpd_extradomain : config_servername); - } - - xmlNewChild(node, fctx->nsNS_DAV, BAD_CAST "href", - BAD_CAST buf_cstring(&fctx->buf)); - - return 0; -} - - -/* Callback to fetch CALDAV:schedule-calendar-transp */ -static int propfind_caltransp(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - const char *prop_annot = ANNOT_NS "CALDAV:schedule-calendar-transp"; - struct buf attrib = BUF_INITIALIZER; - xmlNodePtr node; - int r = 0; - - if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND; - - r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot, httpd_userid, &attrib); - - if (r) return HTTP_SERVER_ERROR; - if (!attrib.len) return HTTP_NOT_FOUND; - - node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - xmlNewChild(node, fctx->nsNS_CALDAV, BAD_CAST buf_cstring(&attrib), NULL); - - buf_free(&attrib); - - return 0; -} - - -/* Callback to write schedule-calendar-transp property */ -static int proppatch_caltransp(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - int r; - - if (pctx->req_tgt->collection && !pctx->req_tgt->resource) { - const char *prop_annot = - ANNOT_NS "CALDAV:schedule-calendar-transp"; - annotate_state_t *astate = NULL; - - buf_reset(&pctx->buf); - - if (set) { - xmlNodePtr cur; - - /* Find the value */ - for (cur = prop->children; cur; cur = cur->next) { - - /* Make sure its a value we understand */ - if (cur->type != XML_ELEMENT_NODE) continue; - if (!xmlStrcmp(cur->name, BAD_CAST "opaque") || - !xmlStrcmp(cur->name, BAD_CAST "transparent")) { - buf_setcstr(&pctx->buf, (const char *)cur->name); - break; - } - else { - /* Unknown value */ - xml_add_prop(HTTP_CONFLICT, pctx->nsNS_DAV, - &propstatPROPSTAT_CONFLICT, - prop->name, prop->ns, NULL, 0); - - *pctx->ret = HTTP_FORBIDDEN; - - return 0; - } - } - } - - r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate); - if (!r) r = annotate_state_writemask(astate, prop_annot, httpd_userid, &pctx->buf); - if (!r) { - xml_add_prop(HTTP_OK, pctx->nsNS_DAV, - &propstatPROPSTAT_OK, prop->name, prop->ns, NULL, 0); - } - else { - xml_add_prop(HTTP_SERVER_ERROR, pctx->nsNS_DAV, - &propstatPROPSTAT_ERROR, - prop->name, prop->ns, NULL, 0); - } - } - else { - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, 0); - - *pctx->ret = HTTP_FORBIDDEN; - } - - return 0; -} - - -/* Callback to prescreen/fetch CALDAV:calendar-timezone/availability */ -static int propfind_tz_avail(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock) -{ - xmlNodePtr prop = (xmlNodePtr) rock; - struct buf attrib = BUF_INITIALIZER; - int ret = 0; - - if (propstat) { - int r = 0; - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, ANNOT_NS "<%s>%s", - (const char *) ns->href, name); - - if (fctx->mailbox && !fctx->record) { - r = annotatemore_lookupmask(fctx->mailbox->name, - buf_cstring(&fctx->buf), - httpd_userid, &attrib); - } - - if (r) { - ret = HTTP_SERVER_ERROR; - goto done; - } - - if (!attrib.len) { - ret = HTTP_NOT_FOUND; - goto done; - } - } - - buf_cstring(&attrib); /* ensure a valid pointer */ - - ret = propfind_getdata(name, ns, fctx, propstat, prop, caldav_mime_types, - CALDAV_SUPP_DATA, attrib.s, attrib.len); - -done: - buf_free(&attrib); - return ret; -} - - -/* Callback to write calendar-timezone property */ -static int proppatch_timezone(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - int r; - - if (pctx->req_tgt->collection && !pctx->req_tgt->resource) { - xmlChar *type, *ver = NULL, *freeme = NULL; - struct buf buf = BUF_INITIALIZER; - struct mime_type_t *mime; - icalcomponent *ical = NULL; - unsigned valid = 1; - - type = xmlGetProp(prop, BAD_CAST "content-type"); - if (type) ver = xmlGetProp(prop, BAD_CAST "version"); - - /* Check/find requested MIME type */ - for (mime = caldav_mime_types; type && mime->content_type; mime++) { - if (is_mediatype(mime->content_type, (const char *) type)) { - if (ver && - (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) { - continue; - } - break; - } - } - - if (!mime) { - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, - CALDAV_SUPP_DATA); - *pctx->ret = HTTP_FORBIDDEN; - valid = 0; - } - else if (!mime->content_type) { - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, - CALDAV_SUPP_DATA); - *pctx->ret = HTTP_FORBIDDEN; - valid = 0; - } - else if (set) { - freeme = xmlNodeGetContent(prop); - - /* Parse and validate the iCal data */ - ical = mime->from_string((const char *)freeme); - if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) { - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, - CALDAV_VALID_DATA); - *pctx->ret = HTTP_FORBIDDEN; - valid = 0; - } - else if (!icalcomponent_get_first_component(ical, - ICAL_VTIMEZONE_COMPONENT) - || icalcomponent_get_first_real_component(ical)) { - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, - CALDAV_VALID_OBJECT); - *pctx->ret = HTTP_FORBIDDEN; - valid = 0; - } - else if (mime != caldav_mime_types) { - buf_setcstr(&buf, icalcomponent_as_ical_string(ical)); - } - else { - buf_setcstr(&buf, (const char *)freeme); - } - } - - if (valid) { - annotate_state_t *astate = NULL; - - buf_reset(&pctx->buf); - buf_printf(&pctx->buf, ANNOT_NS "<%s>%s", - (const char *) prop->ns->href, prop->name); - - r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate); - if (!r) r = annotate_state_writemask(astate, buf_cstring(&pctx->buf), httpd_userid, &buf); - if (!r) { - xml_add_prop(HTTP_OK, pctx->nsNS_DAV, &propstatPROPSTAT_OK, - prop->name, prop->ns, NULL, 0); - } - else { - xml_add_prop(HTTP_SERVER_ERROR, pctx->nsNS_DAV, - &propstatPROPSTAT_ERROR, - prop->name, prop->ns, NULL, 0); - } - } - - if (ical) icalcomponent_free(ical); - if (freeme) xmlFree(freeme); - if (type) xmlFree(type); - if (ver) xmlFree(ver); - buf_free(&buf); - } - else { - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, prop->name, prop->ns, NULL, 0); - - *pctx->ret = HTTP_FORBIDDEN; - } - - return 0; -} - - -/* Callback to write calendar-availability property */ -static int proppatch_availability(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - /* XXX - fixme */ - if (pctx->req_tgt->collection && !pctx->req_tgt->resource) { - struct buf buf = BUF_INITIALIZER; - xmlChar *type, *ver = NULL, *freeme = NULL; - struct mime_type_t *mime; - icalcomponent *ical = NULL; - unsigned valid = 1; - - type = xmlGetProp(prop, BAD_CAST "content-type"); - if (type) ver = xmlGetProp(prop, BAD_CAST "version"); - - /* Check/find requested MIME type */ - for (mime = caldav_mime_types; type && mime->content_type; mime++) { - if (is_mediatype(mime->content_type, (const char *) type)) { - if (ver && - (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) { - continue; - } - break; - } - } - - if (!mime->content_type) { - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, - CALDAV_SUPP_DATA); - *pctx->ret = HTTP_FORBIDDEN; - valid = 0; - } - else if (set) { - freeme = xmlNodeGetContent(prop); - - /* Parse and validate the iCal data */ - ical = mime->from_string((const char *)freeme); - if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) { - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, - CALDAV_VALID_DATA); - *pctx->ret = HTTP_FORBIDDEN; - valid = 0; - } - else if (!icalcomponent_get_first_component(ical, - ICAL_VAVAILABILITY_COMPONENT)) { - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, - CALDAV_VALID_OBJECT); - *pctx->ret = HTTP_FORBIDDEN; - valid = 0; - } - else if (mime != caldav_mime_types) { - buf_setcstr(&buf, icalcomponent_as_ical_string(ical)); - } - else { - buf_setcstr(&buf, (const char *)freeme); - } - } - - if (valid) { - struct annotate_state *astate = NULL; - int r; - buf_reset(&pctx->buf); - buf_printf(&pctx->buf, ANNOT_NS "<%s>%s", - (const char *) prop->ns->href, prop->name); - - r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate); - r = annotate_state_writemask(astate, buf_cstring(&pctx->buf), httpd_userid, &buf); - if (!r) { - xml_add_prop(HTTP_OK, pctx->nsNS_DAV, &propstatPROPSTAT_OK, - prop->name, prop->ns, NULL, 0); - } - else { - xml_add_prop(HTTP_SERVER_ERROR, pctx->nsNS_DAV, - &propstatPROPSTAT_ERROR, - prop->name, prop->ns, NULL, 0); - } - } - - if (ical) icalcomponent_free(ical); - if (freeme) xmlFree(freeme); - if (type) xmlFree(type); - if (ver) xmlFree(ver); - buf_free(&buf); - } - else { - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, prop->name, prop->ns, NULL, 0); - - *pctx->ret = HTTP_FORBIDDEN; - } - - return 0; -} - - -static int report_cal_query(struct transaction_t *txn, - xmlNodePtr inroot, struct propfind_ctx *fctx) -{ - int ret = 0; - xmlNodePtr node; - struct calquery_filter calfilter; - - memset(&calfilter, 0, sizeof(struct calquery_filter)); - calfilter.save_busytime = 0; - - fctx->open_db = (db_open_proc_t) &my_caldav_open; - fctx->close_db = (db_close_proc_t) &my_caldav_close; - fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource; - fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach; - fctx->proc_by_resource = &propfind_by_resource; - - /* Parse children element of report */ - for (node = inroot->children; node; node = node->next) { - if (node->type == XML_ELEMENT_NODE) { - if (!xmlStrcmp(node->name, BAD_CAST "filter")) { - ret = parse_comp_filter(node->children, &calfilter, &txn->error); - if (ret) return ret; - else { - fctx->filter = apply_calfilter; - fctx->filter_crit = &calfilter; - } - } - else if (!xmlStrcmp(node->name, BAD_CAST "timezone")) { - xmlChar *tz = NULL; - icalcomponent *ical = NULL; - - syslog(LOG_WARNING, "REPORT calendar-query w/timezone"); - tz = xmlNodeGetContent(node); - ical = icalparser_parse_string((const char *) tz); - if (!ical || - (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT) || - !icalcomponent_get_first_component(ical, - ICAL_VTIMEZONE_COMPONENT) - || icalcomponent_get_first_real_component(ical)) { - txn->error.precond = CALDAV_VALID_DATA; - ret = HTTP_FORBIDDEN; - } - - if (tz) xmlFree(tz); - if (ical) icalcomponent_free(ical); - if (ret) return ret; - } - } - } - - if (fctx->depth > 0) { - /* Calendar collection(s) */ - if (txn->req_tgt.collection) { - /* Add response for target calendar collection */ - propfind_by_collection(txn->req_tgt.mboxname, 0, 0, fctx); - } - else { - /* Add responses for all contained calendar collections */ - strlcat(txn->req_tgt.mboxname, ".%", sizeof(txn->req_tgt.mboxname)); - mboxlist_findall(NULL, /* internal namespace */ - txn->req_tgt.mboxname, 1, httpd_userid, - httpd_authstate, propfind_by_collection, fctx); - } - - if (fctx->davdb) my_caldav_close(fctx->davdb); - - ret = *fctx->ret; - } - - /* RRULEs still populate busytime array */ - if (calfilter.busytime.busy) free(calfilter.busytime.busy); - - return ret; -} - - -static int report_cal_multiget(struct transaction_t *txn, - xmlNodePtr inroot, struct propfind_ctx *fctx) -{ - int r, ret = 0; - struct request_target_t tgt; - struct mailbox *mailbox = NULL; - xmlNodePtr node; - struct buf uri = BUF_INITIALIZER; - - memset(&tgt, 0, sizeof(struct request_target_t)); - tgt.namespace = URL_NS_CALENDAR; - - /* Get props for each href */ - for (node = inroot->children; node; node = node->next) { - if ((node->type == XML_ELEMENT_NODE) && - !xmlStrcmp(node->name, BAD_CAST "href")) { - xmlChar *href = xmlNodeListGetString(inroot->doc, node->children, 1); - int len = xmlStrlen(href); - struct caldav_data *cdata; - - buf_ensure(&uri, len); - xmlURIUnescapeString((const char *) href, len, uri.s); - xmlFree(href); - - /* Parse the path */ - if ((r = caldav_parse_path(uri.s, &tgt, &fctx->err->desc))) { - ret = r; - goto done; - } - - /* bogus HREFs, just ignore */ - if (!tgt.resource) - continue; - - fctx->req_tgt = &tgt; - - /* Check if we already have this mailbox open */ - if (!mailbox || strcmp(mailbox->name, tgt.mboxname)) { - if (mailbox) mailbox_close(&mailbox); - - /* Open mailbox for reading */ - r = mailbox_open_irl(tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - fctx->mailbox = mailbox; - } - - /* Open the DAV DB corresponding to the mailbox */ - fctx->davdb = my_caldav_open(fctx->mailbox); - - /* Find message UID for the resource */ - caldav_lookup_resource(fctx->davdb, - tgt.mboxname, tgt.resource, 0, &cdata); - cdata->dav.resource = tgt.resource; - /* XXX Check errors */ - - propfind_by_resource(fctx, cdata); - - my_caldav_close(fctx->davdb); - } - } - - done: - mailbox_close(&mailbox); - buf_free(&uri); - - return ret; -} - - - -/* caldav_foreach() callback to find busytime of a resource */ -static int busytime_by_resource(void *rock, void *data) -{ - struct propfind_ctx *fctx = (struct propfind_ctx *) rock; - struct dav_data *ddata = (struct dav_data *) data; - struct index_record record; - int r; - - if (!ddata->imap_uid) return 0; - - /* Fetch index record for the resource */ - r = mailbox_find_index_record(fctx->mailbox, ddata->imap_uid, &record, NULL); - if (r) return 0; - - fctx->record = &record; - (void) apply_calfilter(fctx, data); - - buf_free(&fctx->msg_buf); - fctx->record = NULL; - - return 0; -} - - -/* mboxlist_findall() callback to find busytime of a collection */ -static int busytime_by_collection(char *mboxname, int matchlen, - int maycreate, void *rock) -{ - struct propfind_ctx *fctx = (struct propfind_ctx *) rock; - struct calquery_filter *calfilter = - (struct calquery_filter *) fctx->filter_crit; - - if (calfilter && calfilter->check_transp) { - /* Check if the collection is marked as transparent */ - struct buf attrib = BUF_INITIALIZER; - const char *prop_annot = - ANNOT_NS "CALDAV:schedule-calendar-transp"; - - if (!annotatemore_lookupmask(mboxname, prop_annot, httpd_userid, &attrib)) { - if (!strcmp(buf_cstring(&attrib), "transparent")) { - buf_free(&attrib); - return 0; - } - buf_free(&attrib); - } - } - - return propfind_by_collection(mboxname, matchlen, maycreate, rock); -} - - -/* Compare start times of busytime period -- used for sorting */ -static int compare_busytime(const void *b1, const void *b2) -{ - struct icalperiodtype *a = (struct icalperiodtype *) b1; - struct icalperiodtype *b = (struct icalperiodtype *) b2; - - return icaltime_compare(a->start, b->start); -} - - -/* Create an iCalendar object containing busytime of all specified resources */ -static icalcomponent *busytime_query_local(struct transaction_t *txn, - struct propfind_ctx *fctx, - char mailboxname, - icalproperty_method method, - const char *uid, - const char *organizer, - const char *attendee) -{ - struct calquery_filter *calfilter = - (struct calquery_filter *) fctx->filter_crit; - struct busytime *busytime = &calfilter->busytime; - icalcomponent *cal = NULL; - - fctx->open_db = (db_open_proc_t) &my_caldav_open; - fctx->close_db = (db_close_proc_t) &my_caldav_close; - fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource; - fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach; - fctx->proc_by_resource = &busytime_by_resource; - - /* Gather up all of the busytime */ - if (fctx->depth > 0) { - /* Calendar collection(s) */ - - /* XXX Check DACL_READFB on all calendars */ - - if (txn->req_tgt.collection) { - /* Get busytime for target calendar collection */ - busytime_by_collection(mailboxname, 0, 0, fctx); - } - else { - /* Get busytime for all contained calendar collections */ - strlcat(mailboxname, ".%", MAX_MAILBOX_PATH); - mboxlist_findall(NULL, /* internal namespace */ - mailboxname, 1, httpd_userid, - httpd_authstate, busytime_by_collection, fctx); - } - - if (fctx->davdb) my_caldav_close(fctx->davdb); - } - - if (!*fctx->ret) { - struct buf prodid = BUF_INITIALIZER; - icalcomponent *fb; - icalproperty *prop; - time_t now = time(0); - unsigned n; - - /* Construct iCalendar object with VFREEBUSY component */ - buf_printf(&prodid, "-//CyrusIMAP.org/Cyrus %s//EN", cyrus_version()); - cal = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT, - icalproperty_new_version("2.0"), - icalproperty_new_prodid(buf_cstring(&prodid)), - 0); - buf_free(&prodid); - - if (method) icalcomponent_set_method(cal, method); - - fb = icalcomponent_vanew(ICAL_VFREEBUSY_COMPONENT, - icalproperty_new_dtstamp( - icaltime_from_timet_with_zone( - now, - 0, - icaltimezone_get_utc_timezone())), - icalproperty_new_dtstart(calfilter->start), - icalproperty_new_dtend(calfilter->end), - 0); - - if (uid) icalcomponent_set_uid(fb, uid); - if (organizer) { - prop = icalproperty_new_organizer(organizer); - icalcomponent_add_property(fb, prop); - } - if (attendee) { - prop = icalproperty_new_attendee(attendee); - icalcomponent_add_property(fb, prop); - } - - icalcomponent_add_component(cal, fb); - - /* Sort busytime periods by start time */ - qsort(busytime->busy, busytime->len, sizeof(struct icalperiodtype), - compare_busytime); - - /* Add busytime periods to VFREEBUSY component, coalescing as needed */ - for (n = 0; n < busytime->len; n++) { - if ((n+1 < busytime->len) && - icaltime_compare(busytime->busyn.end, - busytime->busyn+1.start) >= 0) { - /* Periods overlap -- coalesce into next busytime */ - memcpy(&busytime->busyn+1.start, &busytime->busyn.start, - sizeof(struct icaltimetype)); - if (icaltime_compare(busytime->busyn.end, - busytime->busyn+1.end) > 0) { - memcpy(&busytime->busyn+1.end, &busytime->busyn.end, - sizeof(struct icaltimetype)); - } - } - else { - icalproperty *busy = - icalproperty_new_freebusy(busytime->busyn); - - icalcomponent_add_property(fb, busy); - } - } - } - - return cal; -} - - -static int report_fb_query(struct transaction_t *txn, - xmlNodePtr inroot, struct propfind_ctx *fctx) -{ - int ret = 0; - const char **hdr; - struct mime_type_t *mime; - struct calquery_filter calfilter; - xmlNodePtr node; - icalcomponent *cal; - - /* Can not be run against a resource */ - if (txn->req_tgt.resource) return HTTP_FORBIDDEN; - - /* Check requested MIME type: - 1st entry in caldav_mime_types array MUST be default MIME type */ - if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) - mime = get_accept_type(hdr, caldav_mime_types); - else mime = caldav_mime_types; - if (!mime) return HTTP_NOT_ACCEPTABLE; - - memset(&calfilter, 0, sizeof(struct calquery_filter)); - calfilter.comp = CAL_COMP_VEVENT | CAL_COMP_VFREEBUSY; - calfilter.start = world_start; - calfilter.end = world_end; - calfilter.save_busytime = 1; - calfilter.check_transp = 1; - fctx->filter = apply_calfilter; - fctx->filter_crit = &calfilter; - - /* Parse children element of report */ - for (node = inroot->children; node; node = node->next) { - if (node->type == XML_ELEMENT_NODE) { - if (!xmlStrcmp(node->name, BAD_CAST "time-range")) { - xmlChar *start, *end; - - start = xmlGetProp(node, BAD_CAST "start"); - if (start) { - calfilter.start = icaltime_from_string((char *) start); - xmlFree(start); - } - - end = xmlGetProp(node, BAD_CAST "end"); - if (end) { - calfilter.end = icaltime_from_string((char *) end); - xmlFree(end); - } - - if (!is_valid_timerange(calfilter.start, calfilter.end)) { - return HTTP_BAD_REQUEST; - } - } - } - } - - cal = busytime_query_local(txn, fctx, txn->req_tgt.mboxname, - 0, NULL, NULL, NULL); - - if (calfilter.busytime.busy) free(calfilter.busytime.busy); - - if (cal) { - /* Output the iCalendar object as text/calendar */ - char *cal_str = mime->to_string(cal); - icalcomponent_free(cal); - - txn->resp_body.type = mime->content_type; - - /* iCalendar data in response should not be transformed */ - txn->flags.cc |= CC_NOTRANSFORM; - - write_body(HTTP_OK, txn, cal_str, strlen(cal_str)); - free(cal_str); - } - else ret = HTTP_NOT_FOUND; - - return ret; -} - - -/* Store the iCal data in the specified calendar/resource */ -static int store_resource(struct transaction_t *txn, icalcomponent *ical, - struct mailbox *mailbox, const char *resource, - struct caldav_db *caldavdb, int overwrite, - unsigned flags) -{ - int ret = HTTP_CREATED, r; - icalcomponent *comp; - icalcomponent_kind kind; - icalproperty_method meth; - icalproperty *prop; - unsigned mykind = 0; - char *header; - const char *organizer = NULL; - const char *prop_annot = ANNOT_NS "CALDAV:supported-calendar-component-set"; - struct buf attrib = BUF_INITIALIZER; - struct buf headbuf = BUF_INITIALIZER; - struct index_record oldrecord; - struct caldav_data *cdata; - quota_t qdiffsQUOTA_NUMRESOURCES = QUOTA_DIFFS_DONTCARE_INITIALIZER; - FILE *f = NULL; - struct stagemsg *stage; - const char *uid, *ics; - uint32_t expunge_uid = 0; - time_t now = time(NULL); - char datestr80; - struct appendstate as; - static char sched_tag64; - static unsigned store_count = 0; - - /* Check for supported component type */ - comp = icalcomponent_get_first_real_component(ical); - kind = icalcomponent_isa(comp); - switch (kind) { - case ICAL_VEVENT_COMPONENT: mykind = CAL_COMP_VEVENT; break; - case ICAL_VTODO_COMPONENT: mykind = CAL_COMP_VTODO; break; - case ICAL_VJOURNAL_COMPONENT: mykind = CAL_COMP_VJOURNAL; break; - case ICAL_VFREEBUSY_COMPONENT: mykind = CAL_COMP_VFREEBUSY; break; - case ICAL_VAVAILABILITY_COMPONENT: mykind = CAL_COMP_VAVAILABILITY; break; - default: - txn->error.precond = CALDAV_SUPP_COMP; - return HTTP_FORBIDDEN; - } - - if (!annotatemore_lookupmask(mailbox->name, prop_annot, httpd_userid, &attrib) - && attrib.len) { - unsigned long supp_comp = strtoul(buf_cstring(&attrib), NULL, 10); - - buf_free(&attrib); - - if (!(mykind & supp_comp)) { - txn->error.precond = CALDAV_SUPP_COMP; - return HTTP_FORBIDDEN; - } - } - - /* Find iCalendar UID for the current resource, if exists */ - uid = icalcomponent_get_uid(comp); - caldav_lookup_resource(caldavdb, - mailbox->name, resource, 0, &cdata); - if (cdata->ical_uid && strcmp(cdata->ical_uid, uid)) { - /* CALDAV:no-uid-conflict */ - txn->error.precond = CALDAV_UID_CONFLICT; - return HTTP_FORBIDDEN; - } - - /* XXX - theoretical race, but the mailbox is locked, so nothing - * else can ACTUALLY change it */ - if (cdata->dav.imap_uid) { - /* Fetch index record for the resource */ - r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, - &oldrecord, NULL); - if (r) return HTTP_SERVER_ERROR; - - if (overwrite == OVERWRITE_CHECK) { - /* Check any preconditions */ - const char *etag = message_guid_encode(&oldrecord.guid); - time_t lastmod = oldrecord.internaldate; - int precond = caldav_check_precond(txn, cdata, - etag, lastmod); - - if (precond != HTTP_OK) - return HTTP_PRECOND_FAILED; - } - - expunge_uid = cdata->dav.imap_uid; - } - - /* Check for existing iCalendar UID */ - caldav_lookup_uid(caldavdb, uid, 0, &cdata); - if (!(flags & NO_DUP_CHECK) && - cdata->dav.mailbox && !strcmp(cdata->dav.mailbox, mailbox->name) && - strcmp(cdata->dav.resource, resource)) { - /* CALDAV:no-uid-conflict */ - const char *owner = mboxname_to_userid(cdata->dav.mailbox); - - txn->error.precond = CALDAV_UID_CONFLICT; - buf_reset(&txn->buf); - buf_printf(&txn->buf, "%s/user/%s/%s/%s", - namespace_calendar.prefix, owner, - strrchr(cdata->dav.mailbox, '.')+1, cdata->dav.resource); - txn->error.resource = buf_cstring(&txn->buf); - return HTTP_FORBIDDEN; - } - - /* Prepare to stage the message */ - if (!(f = append_newstage(mailbox->name, now, 0, &stage))) { - syslog(LOG_ERR, "append_newstage(%s) failed", mailbox->name); - txn->error.desc = "append_newstage() failed\r\n"; - return HTTP_SERVER_ERROR; - } - - /* Remove all X-LIC-ERROR properties*/ - icalcomponent_strip_errors(ical); - - ics = icalcomponent_as_ical_string(ical); - - /* Create iMIP header for resource */ - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - if (prop) { - organizer = icalproperty_get_organizer(prop)+7; - header = charset_encode_mimeheader(organizer, 0); - fprintf(f, "From: %s\r\n", header); - free(header); - } - else if (strchr(proxy_userid, '@')) { - /* XXX This needs to be done via an LDAP/DB lookup */ - header = charset_encode_mimeheader(proxy_userid, 0); - fprintf(f, "From: %s\r\n", header); - free(header); - } - else { - buf_printf(&headbuf, "%s@%s", proxy_userid, config_servername); - header = charset_encode_mimeheader(buf_base(&headbuf), buf_len(&headbuf)); - fprintf(f, "From: %s\r\n", header); - free(header); - buf_reset(&headbuf); - } - - header = charset_encode_mimeheader(icalcomponent_get_summary(comp), 0); - fprintf(f, "Subject: %s\r\n", header); - free(header); - - time_to_rfc822(icaltime_as_timet_with_zone(icalcomponent_get_dtstamp(comp), - icaltimezone_get_utc_timezone()), - datestr, sizeof(datestr)); - fprintf(f, "Date: %s\r\n", datestr); - - /* XXX - validate uid for mime safety? */ - if (strchr(uid, '@')) { - fprintf(f, "Message-ID: <%s>\r\n", uid); - } - else { - fprintf(f, "Message-ID: <%s@%s>\r\n", uid, config_servername); - } - - fprintf(f, "Content-Type: text/calendar; charset=utf-8"); - if ((meth = icalcomponent_get_method(ical)) != ICAL_METHOD_NONE) { - fprintf(f, "; method=%s", icalproperty_method_to_string(meth)); - } - fprintf(f, "; component=%s\r\n", icalcomponent_kind_to_string(kind)); - - fprintf(f, "Content-Length: %u\r\n", (unsigned) strlen(ics)); - fprintf(f, "Content-Disposition: inline; filename=\"%s\"", resource); - if (organizer) { - const char *stag; - if (flags & NEW_STAG) { - sprintf(sched_tag, "%d-%ld-%u", getpid(), now, store_count++); - stag = sched_tag; - } - else stag = cdata->sched_tag; - if (stag) fprintf(f, ";\r\n\tschedule-tag=%s", stag); - } - fprintf(f, "\r\n"); - - /* XXX Check domain of data and use appropriate CTE */ - - fprintf(f, "MIME-Version: 1.0\r\n"); - fprintf(f, "\r\n"); - - /* Write the iCal data to the file */ - fprintf(f, "%s", ics); - qdiffsQUOTA_STORAGE = ftell(f); - - fclose(f); - - qdiffsQUOTA_MESSAGE = 1; - - /* Prepare to append the iMIP message to calendar mailbox */ - if ((r = append_setup_mbox(&as, mailbox, NULL, NULL, 0, qdiffs, 0, 0, EVENT_MESSAGE_NEW|EVENT_CALENDAR))) { - syslog(LOG_ERR, "append_setup(%s) failed: %s", - mailbox->name, error_message(r)); - ret = HTTP_SERVER_ERROR; - txn->error.desc = "append_setup() failed\r\n"; - } - else { - struct body *body = NULL; - - /* Append the iMIP file to the calendar mailbox */ - if ((r = append_fromstage(&as, &body, stage, now, NULL, 0, 0))) { - syslog(LOG_ERR, "append_fromstage() failed"); - ret = HTTP_SERVER_ERROR; - txn->error.desc = "append_fromstage() failed\r\n"; - } - if (body) { - message_free_body(body); - free(body); - } - - if (r) append_abort(&as); - else { - /* Commit the append to the calendar mailbox */ - if ((r = append_commit(&as))) { - syslog(LOG_ERR, "append_commit() failed"); - ret = HTTP_SERVER_ERROR; - txn->error.desc = "append_commit() failed\r\n"; - } - else { - /* append_commit() returns a write-locked index */ - struct index_record newrecord; - - /* Read index record for new message (always the last one) */ - mailbox_read_index_record(mailbox, mailbox->i.num_records, - &newrecord); - - if (expunge_uid) { - /* Now that we have the replacement message in place - and the mailbox locked, re-read the old record - and see if we should overwrite it. Either way, - one of our records will have to be expunged. - */ - int userflag; - - ret = HTTP_NO_CONTENT; - - /* Perform the actual expunge */ - r = mailbox_user_flag(mailbox, DFLAG_UNBIND, &userflag, 1); - if (!r) { - oldrecord.user_flagsuserflag/32 |= 1<<(userflag&31); - oldrecord.system_flags |= FLAG_EXPUNGED; - r = mailbox_rewrite_index_record(mailbox, &oldrecord); - } - if (r) { - syslog(LOG_ERR, "expunging record (%s) failed: %s", - mailbox->name, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - } - } - - if (!r) { - struct resp_body_t *resp_body = &txn->resp_body; - - if (cdata->organizer && (flags & NEW_STAG)) { - resp_body->stag = sched_tag; - } - - if ((flags & PREFER_REP) || !(flags & NEW_STAG)) { - /* Tell client about the new resource */ - resp_body->lastmod = newrecord.internaldate; - resp_body->etag = message_guid_encode(&newrecord.guid); - } - } - } - } - } - - append_removestage(stage); - - buf_free(&headbuf); - - return ret; -} - - -int caladdress_lookup(const char *addr, struct sched_param *param) -{ - const char *userid = addr; - int islocal = 1, found = 1; - - memset(param, 0, sizeof(struct sched_param)); - - if (!addr) return HTTP_NOT_FOUND; - - if (!strncasecmp(userid, "mailto:", 7)) userid += 7; - - /* XXX Do LDAP/DB/socket lookup to see if user is local */ - /* XXX Hack until real lookup stuff is written */ - - if (islocal) { - /* User is in a local domain */ - int r; - char mailboxnameMAX_MAILBOX_NAME; - mbentry_t *mbentry = NULL; - struct mboxname_parts parts; - - if (!found) return HTTP_NOT_FOUND; - else param->userid = xstrdupnull(userid); /* XXX - memleak */ - - /* Lookup user's cal-home-set to see if its on this server */ - - mboxname_userid_to_parts(userid, &parts); - parts.box = xstrdupnull(config_getstring(IMAPOPT_CALENDARPREFIX)); - mboxname_parts_to_internal(&parts, mailboxname); - - r = http_mlookup(mailboxname, &mbentry, NULL); - if (r) { - if (r != IMAP_MAILBOX_NONEXISTENT) - syslog(LOG_ERR, "mlookup(%s) failed: %s", - mailboxname, error_message(r)); - mboxname_free_parts(&parts); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - mboxname_free_parts(&parts); - param->server = xstrdupnull(mbentry->server); /* XXX - memory leak */ - mboxlist_entry_free(&mbentry); - - if (param->server) param->flags |= SCHEDTYPE_ISCHEDULE; - } - else { - /* User is outside of our domain(s) - - Do remote scheduling (default = iMIP) */ - param->flags |= SCHEDTYPE_REMOTE; - -#ifdef WITH_DKIM - /* Do iSchedule DNS SRV lookup */ - - /* XXX If success, set server, port, - and flags |= SCHEDTYPE_ISCHEDULE | SCHEDTYPE_SSL */ -#endif - } - - return 0; -} - - -/* Send an iMIP request for attendees in 'ical' */ -static int imip_send(icalcomponent *ical) -{ - icalcomponent *comp; - icalproperty *prop; - icalproperty_method meth; - icalcomponent_kind kind; - const char *argv8, *organizer, *subject; - FILE *sm; - pid_t pid; - int r; - time_t t = time(NULL); - char datestr80; - static unsigned send_count = 0; - - meth = icalcomponent_get_method(ical); - comp = icalcomponent_get_first_real_component(ical); - kind = icalcomponent_isa(comp); - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - organizer = icalproperty_get_organizer(prop) + 7; - - argv0 = "sendmail"; - argv1 = "-f"; - argv2 = organizer; - argv3 = "-i"; - argv4 = "-N"; - argv5 = "failure,delay"; - argv6 = "-t"; - argv7 = NULL; - pid = open_sendmail(argv, &sm); - - if (sm == NULL) return HTTP_UNAVAILABLE; - - /* Create iMIP message */ - fprintf(sm, "From: %s\r\n", organizer); - - for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY); - prop; - prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) { - fprintf(sm, "To: %s\r\n", icalproperty_get_attendee(prop) + 7); - } - - subject = icalcomponent_get_summary(comp); - if (!subject) { - fprintf(sm, "Subject: %s %s\r\n", icalcomponent_kind_to_string(kind), - icalproperty_method_to_string(meth)); - } - else fprintf(sm, "Subject: %s\r\n", subject); - - time_to_rfc822(t, datestr, sizeof(datestr)); - fprintf(sm, "Date: %s\r\n", datestr); - - fprintf(sm, "Message-ID: <cmu-httpd-%u-%ld-%u@%s>\r\n", - getpid(), t, send_count++, config_servername); - - fprintf(sm, "Content-Type: text/calendar; charset=utf-8"); - fprintf(sm, "; method=%s; component=%s \r\n", - icalproperty_method_to_string(meth), - icalcomponent_kind_to_string(kind)); - - fputs("Content-Disposition: inline\r\n", sm); - - fputs("MIME-Version: 1.0\r\n", sm); - fputs("\r\n", sm); - - fputs(icalcomponent_as_ical_string(ical), sm); - - fclose(sm); - - while (waitpid(pid, &r, 0) < 0); - - return r; -} - - -/* Add a <response> XML element for 'recipient' to 'root' */ -xmlNodePtr xml_add_schedresponse(xmlNodePtr root, xmlNsPtr dav_ns, - xmlChar *recipient, xmlChar *status) -{ - xmlNodePtr resp, recip; - - resp = xmlNewChild(root, NULL, BAD_CAST "response", NULL); - recip = xmlNewChild(resp, NULL, BAD_CAST "recipient", NULL); - - if (dav_ns) xmlNewChild(recip, dav_ns, BAD_CAST "href", recipient); - else xmlNodeAddContent(recip, recipient); - - if (status) - xmlNewChild(resp, NULL, BAD_CAST "request-status", status); - - return resp; -} - - -struct remote_rock { - struct transaction_t *txn; - icalcomponent *ical; - xmlNodePtr root; - xmlNsPtr *ns; -}; - -/* Send an iTIP busytime request to remote attendees via iMIP or iSchedule */ -static void busytime_query_remote(const char *server __attribute__((unused)), - void *data, void *rock) -{ - struct sched_param *remote = (struct sched_param *) data; - struct remote_rock *rrock = (struct remote_rock *) rock; - icalcomponent *comp; - struct proplist *list; - xmlNodePtr resp; - const char *status = NULL; - int r; - - comp = icalcomponent_get_first_real_component(rrock->ical); - - /* Add the attendees to the iTIP request */ - for (list = remote->props; list; list = list->next) { - icalcomponent_add_property(comp, list->prop); - } - - if (remote->flags == SCHEDTYPE_REMOTE) { - /* Use iMIP */ - - r = imip_send(rrock->ical); - - if (!r) status = REQSTAT_SENT; - else status = REQSTAT_TEMPFAIL; - } - else { - /* Use iSchedule */ - xmlNodePtr xml; - - r = isched_send(remote, NULL, rrock->ical, &xml); - if (r) status = REQSTAT_TEMPFAIL; - else if (xmlStrcmp(xml->name, BAD_CAST "schedule-response")) { - if (r) status = REQSTAT_TEMPFAIL; - } - else { - xmlNodePtr cur; - - /* Process each response element */ - for (cur = xml->children; cur; cur = cur->next) { - xmlNodePtr node; - xmlChar *recip = NULL, *status = NULL, *content = NULL; - - if (cur->type != XML_ELEMENT_NODE) continue; - - for (node = cur->children; node; node = node->next) { - if (node->type != XML_ELEMENT_NODE) continue; - - if (!xmlStrcmp(node->name, BAD_CAST "recipient")) - recip = xmlNodeGetContent(node); - else if (!xmlStrcmp(node->name, BAD_CAST "request-status")) - status = xmlNodeGetContent(node); - else if (!xmlStrcmp(node->name, BAD_CAST "calendar-data")) - content = xmlNodeGetContent(node); - } - - resp = - xml_add_schedresponse(rrock->root, - !(rrock->txn->req_tgt.allow & ALLOW_ISCHEDULE) ? - rrock->nsNS_DAV : NULL, - recip, status); - - xmlFree(status); - xmlFree(recip); - - if (content) { - xmlNodePtr cdata = - xmlNewTextChild(resp, NULL, - BAD_CAST "calendar-data", NULL); - xmlAddChild(cdata, - xmlNewCDataBlock(rrock->root->doc, - content, - xmlStrlen(content))); - xmlFree(content); - - /* iCal data in resp SHOULD NOT be transformed */ - rrock->txn->flags.cc |= CC_NOTRANSFORM; - } - } - - xmlFreeDoc(xml->doc); - } - } - - /* Report request-status (if necesary) - * Remove the attendees from the iTIP request and hash bucket - */ - for (list = remote->props; list; list = list->next) { - if (status) { - const char *attendee = icalproperty_get_attendee(list->prop); - xml_add_schedresponse(rrock->root, - !(rrock->txn->req_tgt.allow & ALLOW_ISCHEDULE) ? - rrock->nsNS_DAV : NULL, - BAD_CAST attendee, - BAD_CAST status); - } - - icalcomponent_remove_property(comp, list->prop); - icalproperty_free(list->prop); - } - - if (remote->server) free(remote->server); -} - - -static void free_sched_param(void *data) -{ - struct sched_param *sched_param = (struct sched_param *) data; - - if (sched_param) { - struct proplist *prop, *next; - - for (prop = sched_param->props; prop; prop = next) { - next = prop->next; - free(prop); - } - free(sched_param); - } -} - - -/* Perform a Busy Time query based on given VFREEBUSY component */ -/* NOTE: This function is destructive of 'ical' */ -int sched_busytime_query(struct transaction_t *txn, - struct mime_type_t *mime, icalcomponent *ical) -{ - int ret = 0; - static const char *calendarprefix = NULL; - icalcomponent *comp; - char mailboxnameMAX_MAILBOX_BUFFER; - icalproperty *prop = NULL, *next; - const char *uid = NULL, *organizer = NULL; - struct sched_param sparam; - struct auth_state *org_authstate = NULL; - xmlNodePtr root = NULL; - xmlNsPtr nsNUM_NAMESPACE; - struct propfind_ctx fctx; - struct calquery_filter calfilter; - struct hash_table remote_table; - struct sched_param *remote = NULL; - - if (!calendarprefix) { - calendarprefix = config_getstring(IMAPOPT_CALENDARPREFIX); - } - - comp = icalcomponent_get_first_real_component(ical); - uid = icalcomponent_get_uid(comp); - - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - organizer = icalproperty_get_organizer(prop); - - /* XXX Do we need to do more checks here? */ - if (caladdress_lookup(organizer, &sparam) || - (sparam.flags & SCHEDTYPE_REMOTE)) - org_authstate = auth_newstate("anonymous"); - else - org_authstate = auth_newstate(sparam.userid); - - /* Start construction of our schedule-response */ - if (!(root = - init_xml_response("schedule-response", - (txn->req_tgt.allow & ALLOW_ISCHEDULE) ? NS_ISCHED : - NS_CALDAV, NULL, ns))) { - ret = HTTP_SERVER_ERROR; - txn->error.desc = "Unable to create XML response\r\n"; - goto done; - } - - /* Populate our filter and propfind context for local attendees */ - memset(&calfilter, 0, sizeof(struct calquery_filter)); - calfilter.comp = CAL_COMP_VEVENT | CAL_COMP_VFREEBUSY; - calfilter.start = icalcomponent_get_dtstart(comp); - calfilter.end = icalcomponent_get_dtend(comp); - calfilter.check_transp = 1; - calfilter.save_busytime = 1; - - memset(&fctx, 0, sizeof(struct propfind_ctx)); - fctx.req_tgt = &txn->req_tgt; - fctx.depth = 2; - fctx.userid = proxy_userid; - fctx.userisadmin = httpd_userisadmin; - fctx.authstate = org_authstate; - fctx.reqd_privs = 0; /* handled by CALDAV:schedule-deliver on Inbox */ - fctx.filter = apply_calfilter; - fctx.filter_crit = &calfilter; - fctx.err = &txn->error; - fctx.ret = &ret; - fctx.fetcheddata = 0; - - /* Create hash table for any remote attendee servers */ - construct_hash_table(&remote_table, 10, 1); - - /* Process each attendee */ - for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY); - prop; - prop = next) { - const char *attendee; - int r; - - next = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY); - - /* Remove each attendee so we can add in only those - that reside on a given remote server later */ - icalcomponent_remove_property(comp, prop); - - /* Is attendee remote or local? */ - attendee = icalproperty_get_attendee(prop); - r = caladdress_lookup(attendee, &sparam); - - /* Don't allow scheduling of remote users via an iSchedule request */ - if ((sparam.flags & SCHEDTYPE_REMOTE) && - (txn->req_tgt.allow & ALLOW_ISCHEDULE)) { - r = HTTP_FORBIDDEN; - } - - if (r) { - xml_add_schedresponse(root, - !(txn->req_tgt.allow & ALLOW_ISCHEDULE) ? - nsNS_DAV : NULL, - BAD_CAST attendee, BAD_CAST REQSTAT_NOUSER); - - icalproperty_free(prop); - } - else if (sparam.flags) { - /* Remote attendee */ - struct proplist *newprop; - const char *key; - - if (sparam.flags == SCHEDTYPE_REMOTE) { - /* iMIP - collect attendees under empty key (no server) */ - key = ""; - } - else { - /* iSchedule - collect attendees by server */ - key = sparam.server; - } - - remote = hash_lookup(key, &remote_table); - if (!remote) { - /* New remote - add it to the hash table */ - remote = xzmalloc(sizeof(struct sched_param)); - if (sparam.server) remote->server = xstrdup(sparam.server); - remote->port = sparam.port; - remote->flags = sparam.flags; - hash_insert(key, remote, &remote_table); - } - newprop = xmalloc(sizeof(struct proplist)); - newprop->prop = prop; - newprop->next = remote->props; - remote->props = newprop; - } - else { - /* Local attendee on this server */ - xmlNodePtr resp; - const char *userid = sparam.userid; - mbentry_t *mbentry = NULL; - icalcomponent *busy = NULL; - - resp = - xml_add_schedresponse(root, - !(txn->req_tgt.allow & ALLOW_ISCHEDULE) ? - nsNS_DAV : NULL, - BAD_CAST attendee, NULL); - - /* XXX - BROKEN WITH DOMAIN SPLIT, POS */ - /* Check ACL of ORGANIZER on attendee's Scheduling Inbox */ - snprintf(mailboxname, sizeof(mailboxname), - "user.%s.%s.Inbox", userid, calendarprefix); - - r = mboxlist_lookup(mailboxname, &mbentry, NULL); - if (r) { - syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s", - mailboxname, error_message(r)); - xmlNewChild(resp, NULL, BAD_CAST "request-status", - BAD_CAST REQSTAT_REJECTED); - } - else { - /* Start query at attendee's calendar-home-set */ - snprintf(mailboxname, sizeof(mailboxname), - "user.%s.%s", userid, calendarprefix); - - fctx.req_tgt->collection = NULL; - calfilter.busytime.len = 0; - busy = busytime_query_local(txn, &fctx, mailboxname, - ICAL_METHOD_REPLY, uid, - organizer, attendee); - } - - if (busy) { - xmlNodePtr cdata; - char *fb_str = mime->to_string(busy); - icalcomponent_free(busy); - - xmlNewChild(resp, NULL, BAD_CAST "request-status", - BAD_CAST REQSTAT_SUCCESS); - - cdata = xmlNewTextChild(resp, NULL, - BAD_CAST "calendar-data", NULL); - - /* Trim any charset from content-type */ - buf_reset(&txn->buf); - buf_printf(&txn->buf, "%.*s", - (int) strcspn(mime->content_type, ";"), - mime->content_type); - - xmlNewProp(cdata, BAD_CAST "content-type", - BAD_CAST buf_cstring(&txn->buf)); - - if (mime->version) - xmlNewProp(cdata, BAD_CAST "version", - BAD_CAST mime->version); - - xmlAddChild(cdata, - xmlNewCDataBlock(root->doc, BAD_CAST fb_str, - strlen(fb_str))); - free(fb_str); - - /* iCalendar data in response should not be transformed */ - txn->flags.cc |= CC_NOTRANSFORM; - } - else { - xmlNewChild(resp, NULL, BAD_CAST "request-status", - BAD_CAST REQSTAT_NOUSER); - } - - icalproperty_free(prop); - } - } - - if (remote) { - struct remote_rock rrock = { txn, ical, root, ns }; - hash_enumerate(&remote_table, busytime_query_remote, &rrock); - } - free_hash_table(&remote_table, free_sched_param); - - /* Output the XML response */ - if (!ret) xml_response(HTTP_OK, txn, root->doc); - - done: - if (org_authstate) auth_freestate(org_authstate); - if (calfilter.busytime.busy) free(calfilter.busytime.busy); - if (root) xmlFreeDoc(root->doc); - - return ret; -} - - -static void free_sched_data(void *data) -{ - struct sched_data *sched_data = (struct sched_data *) data; - - if (sched_data) { - if (sched_data->itip) icalcomponent_free(sched_data->itip); - if (sched_data->force_send) free(sched_data->force_send); - free(sched_data); - } -} - - -#define SCHEDSTAT_PENDING "1.0" -#define SCHEDSTAT_SENT "1.1" -#define SCHEDSTAT_DELIVERED "1.2" -#define SCHEDSTAT_SUCCESS "2.0" -#define SCHEDSTAT_PARAM "2.3" -#define SCHEDSTAT_NOUSER "3.7" -#define SCHEDSTAT_NOPRIVS "3.8" -#define SCHEDSTAT_TEMPFAIL "5.1" -#define SCHEDSTAT_PERMFAIL "5.2" -#define SCHEDSTAT_REJECTED "5.3" - -/* Deliver scheduling object to a remote recipient */ -static void sched_deliver_remote(const char *recipient, - struct sched_param *sparam, - struct sched_data *sched_data) -{ - int r; - - if (sparam->flags == SCHEDTYPE_REMOTE) { - /* Use iMIP */ - r = imip_send(sched_data->itip); - if (!r) { - sched_data->status = - sched_data->ischedule ? REQSTAT_SENT : SCHEDSTAT_SENT; - } - else { - sched_data->status = sched_data->ischedule ? - REQSTAT_TEMPFAIL : SCHEDSTAT_TEMPFAIL; - } - } - else { - /* Use iSchedule */ - xmlNodePtr xml; - - r = isched_send(sparam, recipient, sched_data->itip, &xml); - if (r) { - sched_data->status = sched_data->ischedule ? - REQSTAT_TEMPFAIL : SCHEDSTAT_TEMPFAIL; - } - else if (xmlStrcmp(xml->name, BAD_CAST "schedule-response")) { - sched_data->status = sched_data->ischedule ? - REQSTAT_TEMPFAIL : SCHEDSTAT_TEMPFAIL; - } - else { - xmlNodePtr cur; - - /* Process each response element */ - for (cur = xml->children; cur; cur = cur->next) { - xmlNodePtr node; - xmlChar *recip = NULL, *status = NULL; - static char statbuf1024; - - if (cur->type != XML_ELEMENT_NODE) continue; - - for (node = cur->children; node; node = node->next) { - if (node->type != XML_ELEMENT_NODE) continue; - - if (!xmlStrcmp(node->name, BAD_CAST "recipient")) - recip = xmlNodeGetContent(node); - else if (!xmlStrcmp(node->name, - BAD_CAST "request-status")) - status = xmlNodeGetContent(node); - } - - if (!strncmp((const char *) status, "2.0", 3)) { - sched_data->status = sched_data->ischedule ? - REQSTAT_DELIVERED : SCHEDSTAT_DELIVERED; - } - else { - if (sched_data->ischedule) - strlcpy(statbuf, (const char *) status, sizeof(statbuf)); - else - strlcpy(statbuf, (const char *) status, 4); - - sched_data->status = statbuf; - } - - xmlFree(status); - xmlFree(recip); - } - } - } -} - - -static const char *deliver_merge_reply(icalcomponent *ical, - icalcomponent *reply) -{ - struct hash_table comp_table; - icalcomponent *comp, *itip; - icalcomponent_kind kind; - icalproperty *prop, *att; - icalparameter *param; - icalparameter_partstat partstat; - icalparameter_rsvp rsvp = ICAL_RSVP_NONE; - const char *recurid, *attendee = NULL, *req_stat = SCHEDSTAT_SUCCESS; - - /* Add each component of old object to hash table for comparison */ - construct_hash_table(&comp_table, 10, 1); - comp = icalcomponent_get_first_real_component(ical); - kind = icalcomponent_isa(comp); - do { - prop = - icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; - - hash_insert(recurid, comp, &comp_table); - - } while ((comp = icalcomponent_get_next_component(ical, kind))); - - /* Process each component in the iTIP reply */ - for (itip = icalcomponent_get_first_component(reply, kind); - itip; - itip = icalcomponent_get_next_component(reply, kind)) { - - /* Lookup this comp in the hash table */ - prop = - icalcomponent_get_first_property(itip, ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; - - comp = hash_lookup(recurid, &comp_table); - if (!comp) { - /* New recurrence overridden by attendee. - Create a new recurrence from master component. */ - comp = icalcomponent_new_clone(hash_lookup("", &comp_table)); - - /* Add RECURRENCE-ID */ - icalcomponent_add_property(comp, icalproperty_new_clone(prop)); - - /* Remove RRULE */ - prop = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY); - if (prop) { - icalcomponent_remove_property(comp, prop); - icalproperty_free(prop); - } - - /* Replace DTSTART, DTEND, SEQUENCE */ - prop = - icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY); - if (prop) { - icalcomponent_remove_property(comp, prop); - icalproperty_free(prop); - } - prop = - icalcomponent_get_first_property(itip, ICAL_DTSTART_PROPERTY); - if (prop) - icalcomponent_add_property(comp, icalproperty_new_clone(prop)); - - prop = - icalcomponent_get_first_property(comp, ICAL_DTEND_PROPERTY); - if (prop) { - icalcomponent_remove_property(comp, prop); - icalproperty_free(prop); - } - prop = - icalcomponent_get_first_property(itip, ICAL_DTEND_PROPERTY); - if (prop) - icalcomponent_add_property(comp, icalproperty_new_clone(prop)); - - prop = - icalcomponent_get_first_property(comp, ICAL_SEQUENCE_PROPERTY); - if (prop) { - icalcomponent_remove_property(comp, prop); - icalproperty_free(prop); - } - prop = - icalcomponent_get_first_property(itip, ICAL_SEQUENCE_PROPERTY); - if (prop) - icalcomponent_add_property(comp, icalproperty_new_clone(prop)); - - icalcomponent_add_component(ical, comp); - } - - /* Get the sending attendee */ - att = icalcomponent_get_first_property(itip, ICAL_ATTENDEE_PROPERTY); - attendee = icalproperty_get_attendee(att); - param = icalproperty_get_first_parameter(att, ICAL_PARTSTAT_PARAMETER); - partstat = icalparameter_get_partstat(param); - param = icalproperty_get_first_parameter(att, ICAL_RSVP_PARAMETER); - if (param) rsvp = icalparameter_get_rsvp(param); - - prop = - icalcomponent_get_first_property(itip, ICAL_REQUESTSTATUS_PROPERTY); - if (prop) { - struct icalreqstattype rq = icalproperty_get_requeststatus(prop); - req_stat = icalenum_reqstat_code(rq.code); - } - - /* Find matching attendee in existing object */ - for (prop = - icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY); - prop && strcmp(attendee, icalproperty_get_attendee(prop)); - prop = - icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)); - if (!prop) { - /* Attendee added themselves to this recurrence */ - prop = icalproperty_new_clone(att); - icalcomponent_add_property(comp, prop); - } - - /* Find and set PARTSTAT */ - param = icalproperty_get_first_parameter(prop, ICAL_PARTSTAT_PARAMETER); - if (!param) { - param = icalparameter_new(ICAL_PARTSTAT_PARAMETER); - icalproperty_add_parameter(prop, param); - } - icalparameter_set_partstat(param, partstat); - - /* Find and set RSVP */ - param = icalproperty_get_first_parameter(prop, ICAL_RSVP_PARAMETER); - if (param) icalproperty_remove_parameter_by_ref(prop, param); - if (rsvp != ICAL_RSVP_NONE) { - param = icalparameter_new(ICAL_RSVP_PARAMETER); - icalproperty_add_parameter(prop, param); - icalparameter_set_rsvp(param, rsvp); - } - - /* Find and set SCHEDULE-STATUS */ - for (param = - icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER); - param && strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-STATUS"); - param = - icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER)); - if (!param) { - param = icalparameter_new(ICAL_IANA_PARAMETER); - icalproperty_add_parameter(prop, param); - icalparameter_set_iana_name(param, "SCHEDULE-STATUS"); - } - icalparameter_set_iana_value(param, req_stat); - } - - free_hash_table(&comp_table, NULL); - - return attendee; -} - - -static int deliver_merge_request(const char *attendee, - icalcomponent *ical, icalcomponent *request) -{ - int deliver_inbox = 0; - struct hash_table comp_table; - icalcomponent *comp, *itip; - icalcomponent_kind kind = ICAL_NO_COMPONENT; - icalproperty *prop; - icalparameter *param; - const char *tzid, *recurid; - - /* Add each VTIMEZONE of old object to hash table for comparison */ - construct_hash_table(&comp_table, 10, 1); - for (comp = - icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT); - comp; - comp = - icalcomponent_get_next_component(ical, ICAL_VTIMEZONE_COMPONENT)) { - prop = icalcomponent_get_first_property(comp, ICAL_TZID_PROPERTY); - tzid = icalproperty_get_tzid(prop); - - hash_insert(tzid, comp, &comp_table); - } - - /* Process each VTIMEZONE in the iTIP request */ - for (itip = icalcomponent_get_first_component(request, - ICAL_VTIMEZONE_COMPONENT); - itip; - itip = icalcomponent_get_next_component(request, - ICAL_VTIMEZONE_COMPONENT)) { - /* Lookup this TZID in the hash table */ - prop = icalcomponent_get_first_property(itip, ICAL_TZID_PROPERTY); - tzid = icalproperty_get_tzid(prop); - - comp = hash_lookup(tzid, &comp_table); - if (comp) { - /* Remove component from old object */ - icalcomponent_remove_component(ical, comp); - icalcomponent_free(comp); - } - - /* Add new/modified component from iTIP request */ - icalcomponent_add_component(ical, icalcomponent_new_clone(itip)); - } - - free_hash_table(&comp_table, NULL); - - /* Add each component of old object to hash table for comparison */ - construct_hash_table(&comp_table, 10, 1); - comp = icalcomponent_get_first_real_component(ical); - if (comp) kind = icalcomponent_isa(comp); - for (; comp; comp = icalcomponent_get_next_component(ical, kind)) { - prop = - icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; - - hash_insert(recurid, comp, &comp_table); - } - - /* Process each component in the iTIP request */ - itip = icalcomponent_get_first_real_component(request); - if (kind == ICAL_NO_COMPONENT) kind = icalcomponent_isa(itip); - for (; itip; itip = icalcomponent_get_next_component(request, kind)) { - icalcomponent *new_comp = icalcomponent_new_clone(itip); - - /* Lookup this comp in the hash table */ - prop = - icalcomponent_get_first_property(itip, ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; - - comp = hash_lookup(recurid, &comp_table); - if (comp) { - int old_seq, new_seq; - - /* Check if this is something more than an update */ - /* XXX Probably need to check PARTSTAT=NEEDS-ACTION - and RSVP=TRUE as well */ - old_seq = icalcomponent_get_sequence(comp); - new_seq = icalcomponent_get_sequence(itip); - if (new_seq > old_seq) deliver_inbox = 1; - - /* Copy over any COMPLETED, PERCENT-COMPLETE, - or TRANSP properties */ - prop = - icalcomponent_get_first_property(comp, ICAL_COMPLETED_PROPERTY); - if (prop) { - icalcomponent_add_property(new_comp, - icalproperty_new_clone(prop)); - } - prop = - icalcomponent_get_first_property(comp, - ICAL_PERCENTCOMPLETE_PROPERTY); - if (prop) { - icalcomponent_add_property(new_comp, - icalproperty_new_clone(prop)); - } - prop = - icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY); - if (prop) { - icalcomponent_add_property(new_comp, - icalproperty_new_clone(prop)); - } - - /* Copy over any ORGANIZER;SCHEDULE-STATUS */ - /* XXX Do we only do this iff PARTSTAT!=NEEDS-ACTION */ - prop = - icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - for (param = icalproperty_get_first_parameter(prop, - ICAL_IANA_PARAMETER); - param; - param = icalproperty_get_next_parameter(prop, - ICAL_IANA_PARAMETER)) { - if (!strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-STATUS")) { - const char *sched_stat = - icalparameter_get_iana_value(param); - - prop = - icalcomponent_get_first_property(new_comp, - ICAL_ORGANIZER_PROPERTY); - param = icalparameter_new(ICAL_IANA_PARAMETER); - icalproperty_add_parameter(prop, param); - icalparameter_set_iana_name(param, - "SCHEDULE-STATUS"); - icalparameter_set_iana_value(param, sched_stat); - } - } - - /* Remove component from old object */ - icalcomponent_remove_component(ical, comp); - icalcomponent_free(comp); - } - else { - /* New component */ - deliver_inbox = 1; - } - - if (config_allowsched == IMAP_ENUM_CALDAV_ALLOWSCHEDULING_APPLE && - kind == ICAL_VEVENT_COMPONENT) { - /* Make VEVENT component transparent if recipient ATTENDEE - PARTSTAT=NEEDS-ACTION (for compatibility with CalendarServer) */ - for (prop = - icalcomponent_get_first_property(new_comp, - ICAL_ATTENDEE_PROPERTY); - prop && strcmp(icalproperty_get_attendee(prop), attendee); - prop = - icalcomponent_get_next_property(new_comp, - ICAL_ATTENDEE_PROPERTY)); - param = - icalproperty_get_first_parameter(prop, ICAL_PARTSTAT_PARAMETER); - if (icalparameter_get_partstat(param) == - ICAL_PARTSTAT_NEEDSACTION) { - prop = - icalcomponent_get_first_property(new_comp, - ICAL_TRANSP_PROPERTY); - if (prop) - icalproperty_set_transp(prop, ICAL_TRANSP_TRANSPARENT); - else { - prop = icalproperty_new_transp(ICAL_TRANSP_TRANSPARENT); - icalcomponent_add_property(new_comp, prop); - } - } - } - - /* Add new/modified component from iTIP request */ - icalcomponent_add_component(ical, new_comp); - } - - free_hash_table(&comp_table, NULL); - - return deliver_inbox; -} - - -/* Deliver scheduling object to local recipient */ -static void sched_deliver_local(const char *recipient, - struct sched_param *sparam, - struct sched_data *sched_data, - struct auth_state *authstate) -{ - int r = 0, rights, reqd_privs, deliver_inbox = 1; - const char *userid = sparam->userid, *attendee = NULL; - static struct buf resource = BUF_INITIALIZER; - static unsigned sched_count = 0; - const char *mailboxname = NULL; - mbentry_t *mbentry = NULL; - struct mailbox *mailbox = NULL, *inbox = NULL; - struct caldav_db *caldavdb = NULL; - struct caldav_data *cdata; - icalcomponent *ical = NULL; - icalproperty_method method; - icalcomponent_kind kind; - icalcomponent *comp; - icalproperty *prop; - struct transaction_t txn; - - /* Check ACL of sender on recipient's Scheduling Inbox */ - mailboxname = caldav_mboxname(userid, SCHED_INBOX); - r = mboxlist_lookup(mailboxname, &mbentry, NULL); - if (r) { - syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s", - mailboxname, error_message(r)); - sched_data->status = - sched_data->ischedule ? REQSTAT_REJECTED : SCHEDSTAT_REJECTED; - goto done; - } - - rights = httpd_myrights(authstate, mbentry->acl); - mboxlist_entry_free(&mbentry); - - reqd_privs = sched_data->is_reply ? DACL_REPLY : DACL_INVITE; - if (!(rights & reqd_privs)) { - sched_data->status = - sched_data->ischedule ? REQSTAT_NOPRIVS : SCHEDSTAT_NOPRIVS; - goto done; - } - - /* Open recipient's Inbox for reading */ - if ((r = mailbox_open_irl(mailboxname, &inbox))) { - syslog(LOG_ERR, "mailbox_open_irl(%s) failed: %s", - mailboxname, error_message(r)); - sched_data->status = - sched_data->ischedule ? REQSTAT_TEMPFAIL : SCHEDSTAT_TEMPFAIL; - goto done; - } - - /* Get METHOD of the iTIP message */ - method = icalcomponent_get_method(sched_data->itip); - - /* Search for iCal UID in recipient's calendars */ - caldavdb = caldav_open_userid(userid, CALDAV_CREATE); - if (!caldavdb) { - sched_data->status = - sched_data->ischedule ? REQSTAT_TEMPFAIL : SCHEDSTAT_TEMPFAIL; - goto done; - } - - caldav_lookup_uid(caldavdb, - icalcomponent_get_uid(sched_data->itip), 0, &cdata); - - if (cdata->dav.mailbox) { - mailboxname = cdata->dav.mailbox; - buf_setcstr(&resource, cdata->dav.resource); - } - else if (sched_data->is_reply) { - /* Can't find object belonging to organizer - ignore reply */ - sched_data->status = - sched_data->ischedule ? REQSTAT_PERMFAIL : SCHEDSTAT_PERMFAIL; - goto done; - } - else if (method == ICAL_METHOD_CANCEL) { - /* Can't find object belonging to attendee - we're done */ - sched_data->status = - sched_data->ischedule ? REQSTAT_SUCCESS : SCHEDSTAT_DELIVERED; - goto done; - } - else { - /* Can't find object belonging to attendee - use default calendar */ - mailboxname = caldav_mboxname(userid, SCHED_DEFAULT); - buf_reset(&resource); - buf_printf(&resource, "%s.ics", - icalcomponent_get_uid(sched_data->itip)); - - /* Create new attendee object */ - ical = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT, 0); - - /* Copy over VERSION property */ - prop = icalcomponent_get_first_property(sched_data->itip, - ICAL_VERSION_PROPERTY); - icalcomponent_add_property(ical, icalproperty_new_clone(prop)); - - /* Copy over PRODID property */ - prop = icalcomponent_get_first_property(sched_data->itip, - ICAL_PRODID_PROPERTY); - icalcomponent_add_property(ical, icalproperty_new_clone(prop)); - - /* Copy over any CALSCALE property */ - prop = icalcomponent_get_first_property(sched_data->itip, - ICAL_CALSCALE_PROPERTY); - if (prop) { - icalcomponent_add_property(ical, - icalproperty_new_clone(prop)); - } - } - - /* Open recipient's calendar for reading */ - r = mailbox_open_irl(mailboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "mailbox_open_irl(%s) failed: %s", - mailboxname, error_message(r)); - sched_data->status = - sched_data->ischedule ? REQSTAT_TEMPFAIL : SCHEDSTAT_TEMPFAIL; - goto done; - } - - if (cdata->dav.imap_uid) { - struct index_record record; - struct buf msg_buf = BUF_INITIALIZER; - - /* Load message containing the resource and parse iCal data */ - r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record, NULL); - if (!r) r = mailbox_map_record(mailbox, &record, &msg_buf); - if (r) { - syslog(LOG_ERR, "mailbox_map_record(%s, %u) failed: %s", - mailbox->name, record.uid, error_message(r)); - goto done; - } - ical = icalparser_parse_string(buf_base(&msg_buf) + record.header_size); - buf_free(&msg_buf); - } - - switch (method) { - case ICAL_METHOD_CANCEL: - /* Get component type */ - comp = icalcomponent_get_first_real_component(ical); - kind = icalcomponent_isa(comp); - - /* Set STATUS:CANCELLED on all components */ - do { - icalcomponent_set_status(comp, ICAL_STATUS_CANCELLED); - icalcomponent_set_sequence(comp, - icalcomponent_get_sequence(comp)+1); - } while ((comp = icalcomponent_get_next_component(ical, kind))); - - break; - - case ICAL_METHOD_REPLY: - attendee = deliver_merge_reply(ical, sched_data->itip); - - break; - - case ICAL_METHOD_REQUEST: - deliver_inbox = deliver_merge_request(recipient, - ical, sched_data->itip); - break; - - default: - /* Unknown METHOD -- ignore it */ - syslog(LOG_ERR, "Unknown iTIP method: %s", - icalenum_method_to_string(method)); - - sched_data->is_reply = 0; - goto inbox; - } - - /* Store the (updated) object in the recipients's calendar */ - mailbox_unlock_index(mailbox, NULL); - - r = store_resource(&txn, ical, mailbox, buf_cstring(&resource), - caldavdb, OVERWRITE_YES, NEW_STAG); - - if (r == HTTP_CREATED || r == HTTP_NO_CONTENT) { - sched_data->status = - sched_data->ischedule ? REQSTAT_SUCCESS : SCHEDSTAT_DELIVERED; - } - else { - syslog(LOG_ERR, "store_resource(%s) failed: %s (%s)", - mailbox->name, error_message(r), txn.error.resource); - sched_data->status = - sched_data->ischedule ? REQSTAT_TEMPFAIL : SCHEDSTAT_TEMPFAIL; - goto done; - } - - inbox: - if (deliver_inbox) { - /* Create a name for the new iTIP message resource */ - buf_reset(&resource); - buf_printf(&resource, "%x-%d-%ld-%u.ics", - strhash(icalcomponent_get_uid(sched_data->itip)), getpid(), - time(0), sched_count++); - - /* Store the message in the recipient's Inbox */ - mailbox_unlock_index(inbox, NULL); - - r = store_resource(&txn, sched_data->itip, inbox, - buf_cstring(&resource), caldavdb, OVERWRITE_NO, 0); - /* XXX What do we do if storing to Inbox fails? */ - } - - /* XXX Should this be a config option? - it might have perf implications */ - if (sched_data->is_reply) { - /* Send updates to attendees - skipping sender of reply */ - sched_request(recipient, sparam, NULL, ical, attendee); - } - - done: - if (ical) icalcomponent_free(ical); - mailbox_close(&inbox); - mailbox_close(&mailbox); - if (caldavdb) caldav_close(caldavdb); -} - - -/* Deliver scheduling object to recipient's Inbox */ -void sched_deliver(const char *recipient, void *data, void *rock) -{ - struct sched_data *sched_data = (struct sched_data *) data; - struct auth_state *authstate = (struct auth_state *) rock; - struct sched_param sparam; - - /* Check SCHEDULE-FORCE-SEND value */ - if (sched_data->force_send) { - const char *force = sched_data->is_reply ? "REPLY" : "REQUEST"; - - if (strcmp(sched_data->force_send, force)) { - sched_data->status = SCHEDSTAT_PARAM; - return; - } - } - - if (caladdress_lookup(recipient, &sparam)) { - sched_data->status = - sched_data->ischedule ? REQSTAT_NOUSER : SCHEDSTAT_NOUSER; - /* Unknown user */ - return; - } - - if (sparam.flags) { - /* Remote recipient */ - sched_deliver_remote(recipient, &sparam, sched_data); - } - else { - /* Local recipient */ - sched_deliver_local(recipient, &sparam, sched_data, authstate); - } -} - - -struct comp_data { - icalcomponent *comp; - icalparameter_partstat partstat; - int sequence; -}; - -static void free_comp_data(void *data) { - struct comp_data *comp_data = (struct comp_data *) data; - - if (comp_data) { - if (comp_data->comp) icalcomponent_free(comp_data->comp); - free(comp_data); - } -} - - -/* - * sched_request/reply() helper function - * - * Update DTSTAMP, remove VALARMs, - * optionally remove scheduling params from ORGANIZER - */ -static void clean_component(icalcomponent *comp, int clean_org) -{ - icalcomponent *alarm, *next; - icalproperty *prop; - icaltimezone *utc = icaltimezone_get_utc_timezone(); - time_t now = time(NULL); - - /* Replace DTSTAMP on component */ - prop = icalcomponent_get_first_property(comp, - ICAL_DTSTAMP_PROPERTY); - icalcomponent_remove_property(comp, prop); - icalproperty_free(prop); - prop = - icalproperty_new_dtstamp(icaltime_from_timet_with_zone(now, 0, utc)); - icalcomponent_add_property(comp, prop); - - /* Remove any VALARM components */ - for (alarm = icalcomponent_get_first_component(comp, ICAL_VALARM_COMPONENT); - alarm; alarm = next) { - next = icalcomponent_get_next_component(comp, ICAL_VALARM_COMPONENT); - icalcomponent_remove_component(comp, alarm); - icalcomponent_free(alarm); - } - - if (clean_org) { - icalparameter *param, *next; - - /* Grab the organizer */ - prop = icalcomponent_get_first_property(comp, - ICAL_ORGANIZER_PROPERTY); - - /* Remove CalDAV Scheduling parameters from organizer */ - for (param = - icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER); - param; param = next) { - next = icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER); - - if (!strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-AGENT")) { - icalproperty_remove_parameter_by_ref(prop, param); - } - else if (!strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-FORCE-SEND")) { - icalproperty_remove_parameter_by_ref(prop, param); - } - } - } -} - - -/* - * sched_request() helper function - * - * Add EXDATE to master component if attendee is excluded from recurrence - */ -struct exclude_rock { - unsigned ncomp; - icalcomponent *comp; -}; - -static void sched_exclude(const char *attendee __attribute__((unused)), - void *data, void *rock) -{ - struct sched_data *sched_data = (struct sched_data *) data; - struct exclude_rock *erock = (struct exclude_rock *) rock; - - if (!(sched_data->comp_mask & (1<<erock->ncomp))) { - icalproperty *recurid, *exdate; - struct icaltimetype exdt; - icalparameter *param; - - /* Fetch the RECURRENCE-ID and use it to create a new EXDATE */ - recurid = icalcomponent_get_first_property(erock->comp, - ICAL_RECURRENCEID_PROPERTY); - exdt = icalproperty_get_recurrenceid(recurid); - - exdate = icalproperty_new_exdate(exdt); - - /* Copy any parameters from RECURRENCE-ID to EXDATE */ - param = icalproperty_get_first_parameter(recurid, ICAL_TZID_PARAMETER); - if (param) { - icalproperty_add_parameter(exdate, icalparameter_new_clone(param)); - } - param = icalproperty_get_first_parameter(recurid, ICAL_VALUE_PARAMETER); - if (param) { - icalproperty_add_parameter(exdate, icalparameter_new_clone(param)); - } - /* XXX Need to handle RANGE parameter */ - - /* Add the EXDATE to the master component for this attendee */ - icalcomponent_add_property(sched_data->master, exdate); - } -} - - -/* - * sched_request() helper function - * - * Process all attendees in the given component and add a - * properly modified component to the attendee's iTIP request if necessary - */ -static void process_attendees(icalcomponent *comp, unsigned ncomp, - const char *organizer, const char *att_update, - struct hash_table *att_table, - icalcomponent *itip, unsigned needs_action) -{ - icalcomponent *copy; - icalproperty *prop; - icalparameter *param, *next; - - /* Strip SCHEDULE-STATUS from each attendee - and optionally set PROPSTAT=NEEDS-ACTION */ - for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY); - prop; - prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) { - const char *attendee = icalproperty_get_attendee(prop); - - /* Don't modify attendee == organizer */ - if (!strcmp(attendee, organizer)) continue; - - for (param = - icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER); - param; param = next) { - next = icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER); - - if (!strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-STATUS")) { - icalproperty_remove_parameter_by_ref(prop, param); - } - } - - if (needs_action) { - param = - icalproperty_get_first_parameter(prop, ICAL_PARTSTAT_PARAMETER); - if (!param) { - param = icalparameter_new(ICAL_PARTSTAT_PARAMETER); - icalproperty_add_parameter(prop, param); - } - icalparameter_set_partstat(param, ICAL_PARTSTAT_NEEDSACTION); - } - } - - /* Clone a working copy of the component */ - copy = icalcomponent_new_clone(comp); - - clean_component(copy, 0); - - /* Process each attendee */ - for (prop = icalcomponent_get_first_property(copy, ICAL_ATTENDEE_PROPERTY); - prop; - prop = icalcomponent_get_next_property(copy, ICAL_ATTENDEE_PROPERTY)) { - const char *attendee = icalproperty_get_attendee(prop); - unsigned do_sched = 1; - icalparameter *force_send = NULL; - - /* Don't schedule attendee == organizer */ - if (!strcmp(attendee, organizer)) continue; - - /* Don't send an update to the attendee that just sent a reply */ - if (att_update && !strcmp(attendee, att_update)) continue; - - /* Check CalDAV Scheduling parameters */ - for (param = - icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER); - param; param = next) { - next = icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER); - - if (!strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-AGENT")) { - do_sched = - !strcmp(icalparameter_get_iana_value(param), "SERVER"); - icalproperty_remove_parameter_by_ref(prop, param); - } - else if (!strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-FORCE-SEND")) { - force_send = param; - } - } - - /* Create/update iTIP request for this attendee */ - if (do_sched) { - struct sched_data *sched_data; - icalcomponent *new_comp; - - sched_data = hash_lookup(attendee, att_table); - if (!sched_data) { - /* New attendee - add it to the hash table */ - sched_data = xzmalloc(sizeof(struct sched_data)); - sched_data->itip = icalcomponent_new_clone(itip); - if (force_send) { - sched_data->force_send = - xstrdup(icalparameter_get_iana_value(force_send)); - } - hash_insert(attendee, sched_data, att_table); - } - new_comp = icalcomponent_new_clone(copy); - icalcomponent_add_component(sched_data->itip, new_comp); - sched_data->comp_mask |= (1 << ncomp); - - /* XXX We assume that the master component is always first */ - if (!ncomp) sched_data->master = new_comp; - } - - if (force_send) icalproperty_remove_parameter_by_ref(prop, force_send); - } - - /* XXX We assume that the master component is always first */ - if (ncomp) { - /* Handle attendees that are excluded from this recurrence */ - struct exclude_rock erock = { ncomp, copy }; - - hash_enumerate(att_table, sched_exclude, &erock); - } - - icalcomponent_free(copy); -} - - -/* - * sched_request() helper function - * - * Organizer removed this component, mark it as cancelled for all attendees - */ -struct cancel_rock { - const char *organizer; - struct hash_table *att_table; - icalcomponent *itip; -}; - -static void sched_cancel(const char *recurid __attribute__((unused)), - void *data, void *rock) -{ - struct comp_data *old_data = (struct comp_data *) data; - struct cancel_rock *crock = (struct cancel_rock *) rock; - - /* Deleting the object -- set STATUS to CANCELLED for component */ - icalcomponent_set_status(old_data->comp, ICAL_STATUS_CANCELLED); -// icalcomponent_set_sequence(old_data->comp, old_data->sequence+1); - - process_attendees(old_data->comp, 0, crock->organizer, NULL, - crock->att_table, crock->itip, 0); -} - - -/* - * Compare the properties of the given kind in two components. - * Returns 0 if equal, 1 otherwise. - * - * If the property exists in neither comp, then they are equal. - * If the property exists in only 1 comp, then they are not equal. - * if the property is RDATE or EXDATE, create an MD5 hash of all - * property strings for each component and compare the hashes. - * Otherwise compare the two property strings. - */ -static unsigned propcmp(icalcomponent *oldical, icalcomponent *newical, - icalproperty_kind kind) -{ - icalproperty *oldprop = icalcomponent_get_first_property(oldical, kind); - icalproperty *newprop = icalcomponent_get_first_property(newical, kind); - - if (!oldprop) return (newprop != NULL); - else if (!newprop) return 1; - else if ((kind == ICAL_RDATE_PROPERTY) || (kind == ICAL_EXDATE_PROPERTY)) { - MD5_CTX ctx; - const char *str; - unsigned char old_md5MD5_DIGEST_LENGTH, new_md5MD5_DIGEST_LENGTH; - - MD5Init(&ctx); - do { - str = icalproperty_get_value_as_string(oldprop); - MD5Update(&ctx, str, strlen(str)); - } while ((oldprop = icalcomponent_get_next_property(oldical, kind))); - - MD5Final(old_md5, &ctx); - - MD5Init(&ctx); - do { - str = icalproperty_get_value_as_string(newprop); - MD5Update(&ctx, str, strlen(str)); - } while ((newprop = icalcomponent_get_next_property(newical, kind))); - - MD5Final(new_md5, &ctx); - - return (memcmp(old_md5, new_md5, MD5_DIGEST_LENGTH) != 0); - } - else { - return (strcmp(icalproperty_get_value_as_string(oldprop), - icalproperty_get_value_as_string(newprop)) != 0); - } -} - - -/* Create and deliver an organizer scheduling request */ -static void sched_request(const char *organizer, struct sched_param *sparam, - icalcomponent *oldical, icalcomponent *newical, - const char *att_update) -{ - int r; - icalproperty_method method; - static struct buf prodid = BUF_INITIALIZER; - struct auth_state *authstate; - icalcomponent *ical, *req, *comp; - icalproperty *prop; - icalcomponent_kind kind; - struct hash_table att_table, comp_table; - const char *sched_stat = NULL, *recurid; - struct comp_data *old_data; - - /* Check what kind of action we are dealing with */ - if (!newical) { - /* Remove */ - ical = oldical; - method = ICAL_METHOD_CANCEL; - } - else { - /* Create / Modify */ - ical = newical; - method = ICAL_METHOD_REQUEST; - } - - if (!att_update) { - int rights = 0; - mbentry_t *mbentry = NULL; - /* Check ACL of auth'd user on userid's Scheduling Outbox */ - const char *outboxname = caldav_mboxname(sparam->userid, SCHED_OUTBOX); - - r = mboxlist_lookup(outboxname, &mbentry, NULL); - if (r) { - syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s", - outboxname, error_message(r)); - } - else { - rights = httpd_myrights(httpd_authstate, mbentry->acl); - mboxlist_entry_free(&mbentry); - } - - if (!(rights & DACL_INVITE)) { - /* DAV:need-privileges */ - sched_stat = SCHEDSTAT_NOPRIVS; - - goto done; - } - } - - /* Create a shell for our iTIP request objects */ - if (!buf_len(&prodid)) { - buf_printf(&prodid, "-//CyrusIMAP.org/Cyrus %s//EN", cyrus_version()); - } - - req = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT, - icalproperty_new_version("2.0"), - icalproperty_new_prodid(buf_cstring(&prodid)), - icalproperty_new_method(method), - 0); - - /* XXX Make sure SEQUENCE is incremented */ - - /* Copy over any CALSCALE property */ - prop = icalcomponent_get_first_property(ical, ICAL_CALSCALE_PROPERTY); - if (prop) { - icalcomponent_add_property(req, - icalproperty_new_clone(prop)); - } - - /* Copy over any VTIMEZONE components */ - for (comp = icalcomponent_get_first_component(ical, - ICAL_VTIMEZONE_COMPONENT); - comp; - comp = icalcomponent_get_next_component(ical, - ICAL_VTIMEZONE_COMPONENT)) { - icalcomponent_add_component(req, - icalcomponent_new_clone(comp)); - } - - comp = icalcomponent_get_first_real_component(ical); - kind = icalcomponent_isa(comp); - - /* Add each component of old object to hash table for comparison */ - construct_hash_table(&comp_table, 10, 1); - - if (!att_update && oldical) { - comp = icalcomponent_get_first_real_component(oldical); - - /* If the existing object isn't a scheduling object, - we don't need to compare components, treat them as new */ - if (icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY)) { - do { - old_data = xzmalloc(sizeof(struct comp_data)); - old_data->comp = comp; - old_data->sequence = icalcomponent_get_sequence(comp); - - prop = - icalcomponent_get_first_property(comp, - ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; - - hash_insert(recurid, old_data, &comp_table); - - } while ((comp = icalcomponent_get_next_component(oldical, kind))); - } - } - - /* Create hash table of attendees */ - construct_hash_table(&att_table, 10, 1); - - /* Process each component of new object */ - if (newical) { - unsigned ncomp = 0; - - comp = icalcomponent_get_first_real_component(newical); - do { - unsigned changed = 1, needs_action = 0; - - prop = icalcomponent_get_first_property(comp, - ICAL_RECURRENCEID_PROPERTY); - if (prop) recurid = icalproperty_get_value_as_string(prop); - else recurid = ""; - - old_data = hash_del(recurid, &comp_table); - - if (old_data) { - /* Per RFC 6638, Section 3.2.8: We need to compare - DTSTART, DTEND, DURATION, DUE, RRULE, RDATE, EXDATE */ - needs_action += propcmp(old_data->comp, comp, - ICAL_DTSTART_PROPERTY); - needs_action += propcmp(old_data->comp, comp, - ICAL_DTEND_PROPERTY); - needs_action += propcmp(old_data->comp, comp, - ICAL_DURATION_PROPERTY); - needs_action += propcmp(old_data->comp, comp, - ICAL_DUE_PROPERTY); - needs_action += propcmp(old_data->comp, comp, - ICAL_RRULE_PROPERTY); - needs_action += propcmp(old_data->comp, comp, - ICAL_RDATE_PROPERTY); - needs_action += propcmp(old_data->comp, comp, - ICAL_EXDATE_PROPERTY); - /* XXX Should we check STATUS here? */ - - if (old_data->sequence >= icalcomponent_get_sequence(comp)) { - /* Make sure SEQUENCE is set properly */ - if (!needs_action) changed = 0; - - icalcomponent_set_sequence(comp, - old_data->sequence + changed); - } - - free(old_data); - } - - if (changed) { - /* Process all attendees in created/modified components */ - process_attendees(comp, ncomp++, organizer, att_update, - &att_table, req, needs_action); - } - - } while ((comp = icalcomponent_get_next_component(newical, kind))); - } - - if (oldical) { - /* Cancel any components that have been left behind in the old obj */ - struct cancel_rock crock = { organizer, &att_table, req }; - - hash_enumerate(&comp_table, sched_cancel, &crock); - } - free_hash_table(&comp_table, free); - - icalcomponent_free(req); - - /* Attempt to deliver requests to attendees */ - /* XXX Do we need to do more checks here? */ - if (sparam->flags & SCHEDTYPE_REMOTE) - authstate = auth_newstate("anonymous"); - else - authstate = auth_newstate(sparam->userid); - - hash_enumerate(&att_table, sched_deliver, authstate); - auth_freestate(authstate); - - done: - if (newical) { - unsigned ncomp = 0; - - /* Set SCHEDULE-STATUS for each attendee in organizer object */ - comp = icalcomponent_get_first_real_component(newical); - kind = icalcomponent_isa(comp); - - do { - for (prop = - icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY); - prop; - prop = - icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) { - const char *attendee = icalproperty_get_attendee(prop); - const char *stat = NULL; - - /* Don't set status if attendee == organizer */ - if (!strcmp(attendee, organizer)) continue; - - if (sched_stat) stat = sched_stat; - else { - struct sched_data *sched_data; - - sched_data = hash_lookup(attendee, &att_table); - if (sched_data && (sched_data->comp_mask & (1 << ncomp))) - stat = sched_data->status; - } - - if (stat) { - icalparameter *param; - for (param = - icalproperty_get_first_parameter(prop, - ICAL_IANA_PARAMETER); - param && strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-STATUS"); - param = - icalproperty_get_next_parameter(prop, - ICAL_IANA_PARAMETER)); - if (!param) { - param = icalparameter_new(ICAL_IANA_PARAMETER); - icalproperty_add_parameter(prop, param); - icalparameter_set_iana_name(param, "SCHEDULE-STATUS"); - } - icalparameter_set_iana_value(param, stat); - } - } - - ncomp++; - } while ((comp = icalcomponent_get_next_component(newical, kind))); - } - - /* Cleanup */ - if (!sched_stat) free_hash_table(&att_table, free_sched_data); -} - - -/* - * sched_reply() helper function - * - * Remove all attendees from 'comp' other than the one corresponding to 'userid' - * - * Returns the new trimmed component (must be freed by caller) - * Optionally returns the 'attendee' property, his/her 'propstat', - * and the 'recurid' of the component - */ -static icalcomponent *trim_attendees(icalcomponent *comp, const char *userid, - icalproperty **attendee, - icalparameter_partstat *partstat, - const char **recurid) -{ - icalcomponent *copy; - icalproperty *prop, *nextprop, *myattendee = NULL; - - if (partstat) *partstat = ICAL_PARTSTAT_NONE; - - /* Clone a working copy of the component */ - copy = icalcomponent_new_clone(comp); - - /* Locate userid in the attendee list (stripping others) */ - for (prop = icalcomponent_get_first_property(copy, - ICAL_ATTENDEE_PROPERTY); - prop; - prop = nextprop) { - const char *att = icalproperty_get_attendee(prop); - struct sched_param sparam; - - nextprop = icalcomponent_get_next_property(copy, - ICAL_ATTENDEE_PROPERTY); - - if (!myattendee && - !caladdress_lookup(att, &sparam) && - !(sparam.flags & SCHEDTYPE_REMOTE) && - !strcmp(sparam.userid, userid)) { - /* Found it */ - myattendee = prop; - - if (partstat) { - /* Get the PARTSTAT */ - icalparameter *param = - icalproperty_get_first_parameter(myattendee, - ICAL_PARTSTAT_PARAMETER); - if (param) *partstat = icalparameter_get_partstat(param); - } - } - else { - /* Some other attendee, remove it */ - icalcomponent_remove_property(copy, prop); - icalproperty_free(prop); - } - } - - if (attendee) *attendee = myattendee; - - if (recurid) { - prop = icalcomponent_get_first_property(copy, - ICAL_RECURRENCEID_PROPERTY); - if (prop) *recurid = icalproperty_get_value_as_string(prop); - else *recurid = ""; - } - - return copy; -} - - -/* - * sched_reply() helper function - * - * Attendee removed this component, mark it as declined for the organizer. - */ -static void sched_decline(const char *recurid __attribute__((unused)), - void *data, void *rock) -{ - struct comp_data *old_data = (struct comp_data *) data; - icalcomponent *itip = (icalcomponent *) rock; - icalproperty *myattendee; - icalparameter *param; - - /* Don't send a decline for cancelled components */ - if (icalcomponent_get_status(old_data->comp) == ICAL_STATUS_CANCELLED) - return; - - myattendee = icalcomponent_get_first_property(old_data->comp, - ICAL_ATTENDEE_PROPERTY); - - param = - icalproperty_get_first_parameter(myattendee, - ICAL_PARTSTAT_PARAMETER); - if (!param) { - param = icalparameter_new(ICAL_PARTSTAT_PARAMETER); - icalproperty_add_parameter(myattendee, param); - } - icalparameter_set_partstat(param, ICAL_PARTSTAT_DECLINED); - - clean_component(old_data->comp, 1); - - icalcomponent_add_component(itip, old_data->comp); -} - - -/* Create and deliver an attendee scheduling reply */ -static void sched_reply(const char *userid, - icalcomponent *oldical, icalcomponent *newical) -{ - int r, rights = 0; - mbentry_t *mbentry = NULL; - const char *outboxname; - icalcomponent *ical; - static struct buf prodid = BUF_INITIALIZER; - struct sched_data *sched_data; - struct auth_state *authstate; - icalcomponent *comp; - icalproperty *prop; - icalparameter *param, *force_send = NULL; - icalcomponent_kind kind; - const char *organizer, *recurid; - struct hash_table comp_table; - struct comp_data *old_data; - - /* Check what kind of action we are dealing with */ - if (!newical) { - /* Remove */ - ical = oldical; - } - else { - /* Create / Modify */ - ical = newical; - } - - /* Check CalDAV Scheduling parameters on the organizer */ - comp = icalcomponent_get_first_real_component(ical); - kind = icalcomponent_isa(comp); - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - organizer = icalproperty_get_organizer(prop); - - for (param = icalproperty_get_first_parameter(prop, - ICAL_IANA_PARAMETER); - param; - param = icalproperty_get_next_parameter(prop, - ICAL_IANA_PARAMETER)) { - if (!strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-AGENT")) { - if (strcmp(icalparameter_get_iana_value(param), "SERVER")) { - /* We are not supposed to send replies to the organizer */ - return; - } - } - else if (!strcmp(icalparameter_get_iana_name(param), - "SCHEDULE-FORCE-SEND")) { - force_send = param; - } - } - - sched_data = xzmalloc(sizeof(struct sched_data)); - sched_data->is_reply = 1; - if (force_send) { - sched_data->force_send = - xstrdup(icalparameter_get_iana_value(force_send)); - } - - /* Check ACL of auth'd user on userid's Scheduling Outbox */ - outboxname = caldav_mboxname(userid, SCHED_OUTBOX); - - r = mboxlist_lookup(outboxname, &mbentry, NULL); - if (r) { - syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s", - outboxname, error_message(r)); - } - else { - rights = httpd_myrights(httpd_authstate, mbentry->acl); - mboxlist_entry_free(&mbentry); - } - - if (!(rights & DACL_REPLY)) { - /* DAV:need-privileges */ - if (newical) sched_data->status = SCHEDSTAT_NOPRIVS; - - goto done; - } - - /* Create our reply iCal object */ - if (!buf_len(&prodid)) { - buf_printf(&prodid, "-//CyrusIMAP.org/Cyrus %s//EN", cyrus_version()); - } - - sched_data->itip = - icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT, - icalproperty_new_version("2.0"), - icalproperty_new_prodid(buf_cstring(&prodid)), - icalproperty_new_method(ICAL_METHOD_REPLY), - 0); - - /* XXX Make sure SEQUENCE is incremented */ - - /* Copy over any CALSCALE property */ - prop = icalcomponent_get_first_property(ical, ICAL_CALSCALE_PROPERTY); - if (prop) { - icalcomponent_add_property(sched_data->itip, - icalproperty_new_clone(prop)); - } - - /* Copy over any VTIMEZONE components */ - for (comp = icalcomponent_get_first_component(ical, - ICAL_VTIMEZONE_COMPONENT); - comp; - comp = icalcomponent_get_next_component(ical, - ICAL_VTIMEZONE_COMPONENT)) { - icalcomponent_add_component(sched_data->itip, - icalcomponent_new_clone(comp)); - } - - /* Add each component of old object to hash table for comparison */ - construct_hash_table(&comp_table, 10, 1); - - if (oldical) { - comp = icalcomponent_get_first_real_component(oldical); - do { - old_data = xzmalloc(sizeof(struct comp_data)); - - old_data->comp = trim_attendees(comp, userid, NULL, - &old_data->partstat, &recurid); - - hash_insert(recurid, old_data, &comp_table); - - } while ((comp = icalcomponent_get_next_component(oldical, kind))); - } - - /* Process each component of new object */ - if (newical) { - unsigned ncomp = 0; - - comp = icalcomponent_get_first_real_component(newical); - do { - icalcomponent *copy; - icalproperty *myattendee; - icalparameter_partstat partstat; - int changed = 1; - - copy = trim_attendees(comp, userid, - &myattendee, &partstat, &recurid); - if (myattendee) { - /* Found our userid */ - old_data = hash_del(recurid, &comp_table); - - if (old_data) { - /* XXX Need to check EXDATE */ - - /* Compare PARTSTAT in the two components */ - if (old_data->partstat == partstat) { - changed = 0; - } - - free_comp_data(old_data); - } - } - else { - /* Our user isn't in this component */ - /* XXX Can this actually happen? */ - changed = 0; - } - - if (changed) { - clean_component(copy, 1); - - icalcomponent_add_component(sched_data->itip, copy); - sched_data->comp_mask |= (1 << ncomp); - } - else icalcomponent_free(copy); - - ncomp++; - } while ((comp = icalcomponent_get_next_component(newical, kind))); - } - - /* Decline any components that have been left behind in the old obj */ - hash_enumerate(&comp_table, sched_decline, sched_data->itip); - free_hash_table(&comp_table, free_comp_data); - - done: - if (sched_data->itip && - icalcomponent_get_first_real_component(sched_data->itip)) { - /* We built a reply object */ - - if (!sched_data->status) { - /* Attempt to deliver reply to organizer */ - authstate = auth_newstate(userid); - sched_deliver(organizer, sched_data, authstate); - auth_freestate(authstate); - } - - if (newical) { - unsigned ncomp = 0; - - /* Set SCHEDULE-STATUS for organizer in attendee object */ - comp = icalcomponent_get_first_real_component(newical); - do { - if (sched_data->comp_mask & (1 << ncomp)) { - prop = - icalcomponent_get_first_property(comp, - ICAL_ORGANIZER_PROPERTY); - param = icalparameter_new(ICAL_IANA_PARAMETER); - icalparameter_set_iana_name(param, "SCHEDULE-STATUS"); - icalparameter_set_iana_value(param, sched_data->status); - icalproperty_add_parameter(prop, param); - } - - ncomp++; - } while ((comp = icalcomponent_get_next_component(newical, kind))); - } - } - - /* Cleanup */ - free_sched_data(sched_data); -}
View file
cyrus-imapd-2.5.tar.gz/imap/http_caldav_sched.h
Deleted
@@ -1,110 +0,0 @@ -/* http_caldav_sched.h -- Routines for dealing with CALDAV scheduling in httpd - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef HTTP_CALDAV_SCHED_H -#define HTTP_CALDAV_SCHED_H - -#include <libical/ical.h> - -#ifdef WITH_DKIM -#include <dkim.h> - -#ifndef DKIM_CANON_ISCHEDULE -#undef WITH_DKIM -#endif - -#endif /* WITH_DKIM */ - -#include "http_dav.h" - - -#define REQSTAT_PENDING "1.0;Pending" -#define REQSTAT_SENT "1.1;Sent" -#define REQSTAT_DELIVERED "1.2;Delivered" -#define REQSTAT_SUCCESS "2.0;Success" -#define REQSTAT_NOUSER "3.7;Invalid calendar user" -#define REQSTAT_NOPRIVS "3.8;Noauthority" -#define REQSTAT_TEMPFAIL "5.1;Service unavailable" -#define REQSTAT_PERMFAIL "5.2;Invalid calendar service" -#define REQSTAT_REJECTED "5.3;No scheduling support for user" - -struct sched_data { - unsigned ischedule; - unsigned is_reply; - icalcomponent *itip; - icalcomponent *master; - unsigned comp_mask; - char *force_send; - const char *status; -}; - -/* Scheduling protocol flags */ -#define SCHEDTYPE_REMOTE (1<<0) -#define SCHEDTYPE_ISCHEDULE (1<<1) -#define SCHEDTYPE_SSL (1<<2) - -struct proplist { - icalproperty *prop; - struct proplist *next; -}; - -/* Each calendar user address has the following scheduling protocol params */ -struct sched_param { - char *userid; /* Userid corresponding to calendar address */ - char *server; /* Remote server user lives on */ - unsigned port; /* Remote server port, default = 80 */ - unsigned flags; /* Flags dictating protocol to use for scheduling */ - struct proplist *props; /* List of attendee iCal properties */ -}; - -extern int isched_send(struct sched_param *sparam, const char *recipient, - icalcomponent *ical, xmlNodePtr *xml); - -extern int sched_busytime_query(struct transaction_t *txn, - struct mime_type_t *mime, icalcomponent *comp); -extern void sched_deliver(const char *recipient, void *data, void *rock); -extern xmlNodePtr xml_add_schedresponse(xmlNodePtr root, xmlNsPtr dav_ns, - xmlChar *recipient, xmlChar *status); -extern int caladdress_lookup(const char *addr, struct sched_param *param); - -#endif /* HTTP_CALDAV_SCHED_H */
View file
cyrus-imapd-2.5.tar.gz/imap/http_carddav.c
Deleted
@@ -1,1129 +0,0 @@ -/* http_carddav.c -- Routines for handling CardDAV collections in httpd - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ -/* - * TODO: - * Support <filter> for addressbook-query Report - * - */ - -#include <config.h> - -#include <syslog.h> - -#include <libical/vcc.h> -#include <libxml/tree.h> -#include <libxml/uri.h> -#include <sys/types.h> -#include <sys/wait.h> - -#include "acl.h" -#include "append.h" -#include "carddav_db.h" -#include "exitcodes.h" -#include "global.h" -#include "hash.h" -#include "httpd.h" -#include "http_dav.h" -#include "http_err.h" -#include "http_proxy.h" -#include "imap_err.h" -#include "index.h" -#include "mailbox.h" -#include "mboxlist.h" -#include "message.h" -#include "message_guid.h" -#include "proxy.h" -#include "smtpclient.h" -#include "spool.h" -#include "stristr.h" -#include "times.h" -#include "util.h" -#include "version.h" -#include "xmalloc.h" -#include "xstrlcat.h" -#include "xstrlcpy.h" - -static struct carddav_db *auth_carddavdb = NULL; - -static struct carddav_db *my_carddav_open(struct mailbox *mailbox); -static void my_carddav_close(struct carddav_db *carddavdb); -static void my_carddav_init(struct buf *serverinfo); -static void my_carddav_auth(const char *userid); -static void my_carddav_reset(void); -static void my_carddav_shutdown(void); - -static int carddav_parse_path(const char *path, - struct request_target_t *tgt, const char **errstr); - -static int carddav_copy(struct transaction_t *txn, - struct mailbox *src_mbox, struct index_record *src_rec, - struct mailbox *dest_mbox, const char *dest_rsrc, - struct carddav_db *dest_davdb, - unsigned overwrite, unsigned flags); -static int carddav_put(struct transaction_t *txn, - struct mime_type_t *mime, - struct mailbox *mailbox, - struct carddav_db *carddavdb, - unsigned flags); -static VObject *vcard_string_as_vobject(const char *str) -{ - return Parse_MIME(str, strlen(str)); -} - -static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_restype(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_reportset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_addrdata(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_suppaddrdata(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); - -static int report_card_query(struct transaction_t *txn, xmlNodePtr inroot, - struct propfind_ctx *fctx); -static int report_card_multiget(struct transaction_t *txn, xmlNodePtr inroot, - struct propfind_ctx *fctx); - -static int store_resource(struct transaction_t *txn, VObject *vcard, - struct mailbox *mailbox, const char *resource, - struct carddav_db *carddavdb, int overwrite, - unsigned flags); - -static struct mime_type_t carddav_mime_types = { - /* First item MUST be the default type and storage format */ - { "text/vcard; charset=utf-8", "3.0", NULL, "vcf", NULL, - (void * (*)(const char*)) &vcard_string_as_vobject, - (void (*)(void *)) &cleanVObject, NULL, NULL - }, - { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } -}; - -/* Array of known "live" properties */ -static const struct prop_entry carddav_props = { - - /* WebDAV (RFC 4918) properties */ - { "creationdate", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_creationdate, NULL, NULL }, - { "displayname", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_fromdb, proppatch_todb, NULL }, - { "getcontentlanguage", NS_DAV, PROP_ALLPROP | PROP_RESOURCE, - propfind_fromhdr, NULL, "Content-Language" }, - { "getcontentlength", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_getlength, NULL, NULL }, - { "getcontenttype", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_getcontenttype, NULL, "Content-Type" }, - { "getetag", NS_DAV, PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_getetag, NULL, NULL }, - { "getlastmodified", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_getlastmod, NULL, NULL }, - { "lockdiscovery", NS_DAV, PROP_ALLPROP | PROP_RESOURCE, - propfind_lockdisc, NULL, NULL }, - { "resourcetype", NS_DAV, - PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, - propfind_restype, proppatch_restype, "addressbook" }, - { "supportedlock", NS_DAV, PROP_ALLPROP | PROP_RESOURCE, - propfind_suplock, NULL, NULL }, - - /* WebDAV Versioning (RFC 3253) properties */ - { "supported-report-set", NS_DAV, PROP_COLLECTION, - propfind_reportset, NULL, NULL }, - - /* WebDAV ACL (RFC 3744) properties */ - { "owner", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_owner, NULL, NULL }, - { "group", NS_DAV, 0, NULL, NULL, NULL }, - { "supported-privilege-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_supprivset, NULL, NULL }, - { "current-user-privilege-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_curprivset, NULL, NULL }, - { "acl", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_acl, NULL, NULL }, - { "acl-restrictions", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_aclrestrict, NULL, NULL }, - { "inherited-acl-set", NS_DAV, 0, NULL, NULL, NULL }, - { "principal-collection-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_princolset, NULL, NULL }, - - /* WebDAV Quota (RFC 4331) properties */ - { "quota-available-bytes", NS_DAV, PROP_COLLECTION, - propfind_quota, NULL, NULL }, - { "quota-used-bytes", NS_DAV, PROP_COLLECTION, - propfind_quota, NULL, NULL }, - - /* WebDAV Current Principal (RFC 5397) properties */ - { "current-user-principal", NS_DAV, PROP_COLLECTION | PROP_RESOURCE, - propfind_curprin, NULL, NULL }, - - /* WebDAV POST (RFC 5995) properties */ - { "add-member", NS_DAV, PROP_COLLECTION, - NULL, /* Until Apple Contacts is fixed */ NULL, NULL }, - - /* WebDAV Sync (RFC 6578) properties */ - { "sync-token", NS_DAV, PROP_COLLECTION, - propfind_sync_token, NULL, NULL }, - - /* CardDAV (RFC 6352) properties */ - { "address-data", NS_CARDDAV, - PROP_RESOURCE | PROP_PRESCREEN | PROP_NEEDPROP, - propfind_addrdata, NULL, NULL }, - { "addressbook-description", NS_CARDDAV, PROP_COLLECTION, - propfind_fromdb, proppatch_todb, NULL }, - { "supported-address-data", NS_CARDDAV, PROP_COLLECTION, - propfind_suppaddrdata, NULL, NULL }, - { "max-resource-size", NS_CARDDAV, 0, NULL, NULL, NULL }, - - /* Apple Calendar Server properties */ - { "getctag", NS_CS, PROP_ALLPROP | PROP_COLLECTION, - propfind_sync_token, NULL, NULL }, - - { NULL, 0, 0, NULL, NULL, NULL } -}; - -static struct meth_params carddav_params = { - carddav_mime_types, - &carddav_parse_path, - &check_precond, - { (db_open_proc_t) &my_carddav_open, - (db_close_proc_t) &my_carddav_close, - (db_lookup_proc_t) &carddav_lookup_resource, - (db_foreach_proc_t) &carddav_foreach, - (db_write_proc_t) &carddav_write, - (db_delete_proc_t) &carddav_delete, - (db_delmbox_proc_t) &carddav_delmbox }, - NULL, /* No ACL extensions */ - (copy_proc_t) &carddav_copy, - NULL, /* No special DELETE handling */ - { MBTYPE_ADDRESSBOOK, NULL, NULL, 0 }, /* No special MK* method */ - NULL, /* No special POST handling */ - { CARDDAV_SUPP_DATA, (put_proc_t) &carddav_put }, - carddav_props, - { { "addressbook-query", &report_card_query, DACL_READ, - REPORT_NEED_MBOX | REPORT_MULTISTATUS }, - { "addressbook-multiget", &report_card_multiget, DACL_READ, - REPORT_NEED_MBOX | REPORT_MULTISTATUS }, - { "sync-collection", &report_sync_col, DACL_READ, - REPORT_NEED_MBOX | REPORT_MULTISTATUS | REPORT_NEED_PROPS }, - { NULL, NULL, 0, 0 } } -}; - - -/* Namespace for Carddav collections */ -struct namespace_t namespace_addressbook = { - URL_NS_ADDRESSBOOK, 0, "/dav/addressbooks", "/.well-known/carddav", 1 /* auth */, - MBTYPE_ADDRESSBOOK, -#if 0 /* Until Apple Contacts fixes their add-member implementation */ - (ALLOW_READ | ALLOW_POST | ALLOW_WRITE | ALLOW_DELETE | - ALLOW_DAV | ALLOW_WRITECOL | ALLOW_CARD), -#else - (ALLOW_READ | ALLOW_WRITE | ALLOW_DELETE | - ALLOW_DAV | ALLOW_WRITECOL | ALLOW_CARD), -#endif - &my_carddav_init, &my_carddav_auth, my_carddav_reset, &my_carddav_shutdown, - { - { &meth_acl, &carddav_params }, /* ACL */ - { &meth_copy, &carddav_params }, /* COPY */ - { &meth_delete, &carddav_params }, /* DELETE */ - { &meth_get_dav, &carddav_params }, /* GET */ - { &meth_get_dav, &carddav_params }, /* HEAD */ - { &meth_lock, &carddav_params }, /* LOCK */ - { NULL, NULL }, /* MKCALENDAR */ - { &meth_mkcol, &carddav_params }, /* MKCOL */ - { &meth_copy, &carddav_params }, /* MOVE */ - { &meth_options, &carddav_parse_path }, /* OPTIONS */ -#if 0 /* Until Apple Contacts fixes their add-member implementation */ - { &meth_post, &carddav_params }, /* POST */ -#else - { NULL, NULL }, /* POST */ -#endif - { &meth_propfind, &carddav_params }, /* PROPFIND */ - { &meth_proppatch, &carddav_params }, /* PROPPATCH */ - { &meth_put, &carddav_params }, /* PUT */ - { &meth_report, &carddav_params }, /* REPORT */ - { &meth_trace, &carddav_parse_path }, /* TRACE */ - { &meth_unlock, &carddav_params } /* UNLOCK */ - } -}; - - -static struct carddav_db *my_carddav_open(struct mailbox *mailbox) -{ - if (httpd_userid && mboxname_userownsmailbox(httpd_userid, mailbox->name)) { - return auth_carddavdb; - } - else { - return carddav_open_mailbox(mailbox, CALDAV_CREATE); - } -} - - -static void my_carddav_close(struct carddav_db *carddavdb) -{ - if (carddavdb && (carddavdb != auth_carddavdb)) carddav_close(carddavdb); -} - - -static void my_carddav_init(struct buf *serverinfo) -{ - namespace_addressbook.enabled = - config_httpmodules & IMAP_ENUM_HTTPMODULES_CARDDAV; - - if (!namespace_addressbook.enabled) return; - - if (!config_getstring(IMAPOPT_ADDRESSBOOKPREFIX)) { - fatal("Required 'addressbookprefix' option is not set", EC_CONFIG); - } - - carddav_init(); - - if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON && - !strstr(buf_cstring(serverinfo), " libical/")) { - buf_printf(serverinfo, " libicalvcal/%s", ICAL_VERSION); - } -} - - -#define DEFAULT_ADDRBOOK "Default" - -static void my_carddav_auth(const char *userid) -{ - int r; - struct buf boxbuf = BUF_INITIALIZER; - const char *mailboxname; - - mailboxname = mboxname_user_mbox(userid, NULL); - - if (httpd_userisadmin || - global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) { - /* admin or proxy from frontend - won't have DAV database */ - return; - } - else if (config_mupdate_server && !config_getstring(IMAPOPT_PROXYSERVERS)) { - /* proxy-only server - won't have DAV databases */ - } - else { - /* Open CardDAV DB for 'userid' */ - my_carddav_reset(); - auth_carddavdb = carddav_open_userid(userid, CARDDAV_CREATE); - if (!auth_carddavdb) fatal("Unable to open CardDAV DB", EC_IOERR); - } - - buf_setcstr(&boxbuf, config_getstring(IMAPOPT_ADDRESSBOOKPREFIX)); - - mailboxname = mboxname_user_mbox(userid, buf_cstring(&boxbuf)); - - /* Auto-provision an addressbook for 'userid' */ - r = mboxlist_lookup(mailboxname, NULL, NULL); - if (r == IMAP_MAILBOX_NONEXISTENT) { - if (config_mupdate_server) { - /* Find location of INBOX */ - const char *inboxname = mboxname_user_mbox(userid, NULL); - mbentry_t *mbentry = NULL; - - r = http_mlookup(inboxname, &mbentry, NULL); - if (!r && mbentry->server) { - proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - return; - } - mboxlist_entry_free(&mbentry); - } - - mailboxname = mboxname_user_mbox(userid, buf_cstring(&boxbuf)); - - /* XXX - set rights */ - r = mboxlist_createmailbox(mailboxname, MBTYPE_ADDRESSBOOK, - NULL, 0, - userid, httpd_authstate, - 0, 0, 0, 0, NULL); - } -} - - -static void my_carddav_reset(void) -{ - if (auth_carddavdb) carddav_close(auth_carddavdb); - auth_carddavdb = NULL; -} - - -static void my_carddav_shutdown(void) -{ - carddav_done(); -} - - -/* Parse request-target path in CardDAV namespace */ -static int carddav_parse_path(const char *path, - struct request_target_t *tgt, const char **errstr) -{ - char *p; - size_t len; - struct mboxname_parts parts; - struct buf boxbuf = BUF_INITIALIZER; - - /* Make a working copy of target path */ - strlcpy(tgt->path, path, sizeof(tgt->path)); - tgt->tail = tgt->path + strlen(tgt->path); - - p = tgt->path; - - /* Sanity check namespace */ - len = strlen(namespace_addressbook.prefix); - if (strlen(p) < len || - strncmp(namespace_addressbook.prefix, p, len) || - (pathlen && pathlen != '/')) { - *errstr = "Namespace mismatch request target path"; - return HTTP_FORBIDDEN; - } - - tgt->prefix = namespace_addressbook.prefix; - - /* Default to bare-bones Allow bits for toplevel collections */ - tgt->allow &= ~(ALLOW_POST|ALLOW_WRITE|ALLOW_DELETE); - - /* Skip namespace */ - p += len; - if (!*p || !*++p) return 0; - - /* Check if we're in user space */ - len = strcspn(p, "/"); - if (!strncmp(p, "user", len)) { - p += len; - if (!*p || !*++p) return 0; - - /* Get user id */ - len = strcspn(p, "/"); - tgt->user = p; - tgt->userlen = len; - - p += len; - if (!*p || !*++p) goto done; - - len = strcspn(p, "/"); - } - - /* Get collection */ - tgt->collection = p; - tgt->collen = len; - - p += len; - if (!*p || !*++p) { - /* Make sure collection is terminated with '/' */ - if (p-1 != '/') *p++ = '/'; - goto done; - } - - /* Get resource */ - len = strcspn(p, "/"); - tgt->resource = p; - tgt->reslen = len; - - p += len; - - if (*p) { -// *errstr = "Too many segments in request target path"; - return HTTP_NOT_FOUND; - } - - done: - /* Set proper Allow bits based on path components */ - if (tgt->collection) { - if (tgt->resource) { - tgt->allow &= ~ALLOW_WRITECOL; - tgt->allow |= (ALLOW_WRITE|ALLOW_DELETE); - } -#if 0 /* Until Apple Contacts fixes their add-member implementation */ - else tgt->allow |= (ALLOW_POST|ALLOW_DELETE); -#else - else tgt->allow |= ALLOW_DELETE; -#endif - } - else if (tgt->user) tgt->allow |= ALLOW_DELETE; - - - /* Create mailbox name from the parsed path */ - - mboxname_init_parts(&parts); - - if (tgt->user && tgt->userlen) { - /* holy "avoid copying" batman */ - char *userid = xstrndup(tgt->user, tgt->userlen); - mboxname_userid_to_parts(userid, &parts); - free(userid); - } - - buf_setcstr(&boxbuf, config_getstring(IMAPOPT_ADDRESSBOOKPREFIX)); - if (tgt->collen) { - buf_putc(&boxbuf, '.'); - buf_appendmap(&boxbuf, tgt->collection, tgt->collen); - } - parts.box = buf_release(&boxbuf); - - /* XXX - hack to allow @domain parts for non-domain-split users */ - if (httpd_extradomain) { - //free(parts.domain); - XXX fix when converting to real parts - parts.domain = NULL; - } - - mboxname_parts_to_internal(&parts, tgt->mboxname); - - mboxname_free_parts(&parts); - - return 0; -} - - -/* Perform a COPY/MOVE request - * - * preconditions: - * CARDDAV:supported-address-data - * CARDDAV:valid-address-data - * CARDDAV:no-uid-conflict (DAV:href) - * CARDDAV:addressbook-collection-location-ok - * CARDDAV:max-resource-size - */ -static int carddav_copy(struct transaction_t *txn, - struct mailbox *src_mbox, struct index_record *src_rec, - struct mailbox *dest_mbox, const char *dest_rsrc, - struct carddav_db *dest_davdb, - unsigned overwrite, unsigned flags) -{ - int r; - struct buf msg_buf = BUF_INITIALIZER; - VObject *vcard; - - /* Load message containing the resource and parse vCard data */ - r = mailbox_map_record(src_mbox, src_rec, &msg_buf); - if (r) return r; - vcard = Parse_MIME(buf_base(&msg_buf) + src_rec->header_size, - src_rec->size - src_rec->header_size); - buf_free(&msg_buf); - - if (!vcard) { - txn->error.precond = CARDDAV_VALID_DATA; - return HTTP_FORBIDDEN; - } - - /* Finished our initial read of source mailbox */ - mailbox_unlock_index(src_mbox, NULL); - - /* Store source resource at destination */ - r = store_resource(txn, vcard, dest_mbox, dest_rsrc, dest_davdb, - overwrite, flags); - - cleanVObject(vcard); - cleanStrTbl(); - - return r; -} - - -/* Perform a PUT request - * - * preconditions: - * CARDDAV:valid-address-data - * CARDDAV:no-uid-conflict (DAV:href) - * CARDDAV:max-resource-size - */ -static int carddav_put(struct transaction_t *txn, - struct mime_type_t *mime, - struct mailbox *mailbox, - struct carddav_db *davdb, - unsigned flags) -{ - int ret; - VObject *vcard = NULL; - - /* Parse and validate the vCard data */ - vcard = mime->from_string(buf_cstring(&txn->req_body.payload)); - if (!vcard || strcmp(vObjectName(vcard), "VCARD")) { - txn->error.precond = CARDDAV_VALID_DATA; - ret = HTTP_FORBIDDEN; - goto done; - } - - /* Store resource at target */ - ret = store_resource(txn, vcard, mailbox, txn->req_tgt.resource, - davdb, OVERWRITE_CHECK, flags); - - if (flags & PREFER_REP) { - struct resp_body_t *resp_body = &txn->resp_body; - const char *data; - - switch (ret) { - case HTTP_NO_CONTENT: - ret = HTTP_OK; - - case HTTP_CREATED: - /* Use the request data */ - data = buf_cstring(&txn->req_body.payload); - - /* Fill in Content-Type, Content-Length */ - resp_body->type = mime->content_type; - resp_body->len = strlen(data); - - /* Fill in Content-Location */ - resp_body->loc = txn->req_tgt.path; - - /* Fill in Expires and Cache-Control */ - resp_body->maxage = 3600; /* 1 hr */ - txn->flags.cc = CC_MAXAGE - | CC_REVALIDATE /* don't use stale data */ - | CC_NOTRANSFORM; /* don't alter vCard data */ - - /* Output current representation */ - write_body(ret, txn, data, resp_body->len); - ret = 0; - break; - - default: - /* failure - do nothing */ - break; - } - } - - done: - if (vcard) { - cleanVObject(vcard); - cleanStrTbl(); - } - - return ret; -} - - -/* Callback to fetch DAV:getcontenttype */ -static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - buf_setcstr(&fctx->buf, "text/vcard; charset=utf-8"); - - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, BAD_CAST buf_cstring(&fctx->buf), 0); - - return 0; -} - - -/* Callback to fetch DAV:resourcetype */ -static int propfind_restype(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - if (!fctx->record) { - xmlNewChild(node, NULL, BAD_CAST "collection", NULL); - - if (fctx->req_tgt->collection) { - ensure_ns(fctx->ns, NS_CARDDAV, resp->parent, - XML_NS_CARDDAV, "C"); - xmlNewChild(node, fctx->nsNS_CARDDAV, - BAD_CAST "addressbook", NULL); - } - } - - return 0; -} - - -/* Callback to fetch DAV:supported-report-set */ -static int propfind_reportset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr s, r, top; - - top = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - if (fctx->req_tgt->collection && !fctx->req_tgt->resource) { - s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL); - r = xmlNewChild(s, NULL, BAD_CAST "report", NULL); - xmlNewChild(r, fctx->nsNS_DAV, BAD_CAST "sync-collection", NULL); - } - - ensure_ns(fctx->ns, NS_CARDDAV, resp->parent, XML_NS_CARDDAV, "C"); - - s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL); - r = xmlNewChild(s, NULL, BAD_CAST "report", NULL); - xmlNewChild(r, fctx->nsNS_CARDDAV, BAD_CAST "addressbook-query", NULL); - - s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL); - r = xmlNewChild(s, NULL, BAD_CAST "report", NULL); - xmlNewChild(r, fctx->nsNS_CARDDAV, BAD_CAST "addressbook-multiget", NULL); - - return 0; -} - - -/* Callback to prescreen/fetch CARDDAV:address-data */ -static int propfind_addrdata(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock) -{ - xmlNodePtr prop = (xmlNodePtr) rock; - const char *data = NULL; - size_t datalen = 0; - - if (propstat) { - if (!fctx->record) return HTTP_NOT_FOUND; - - if (!fctx->msg_buf.len) - mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf); - if (!fctx->msg_buf.len) return HTTP_SERVER_ERROR; - - data = fctx->msg_buf.s + fctx->record->header_size; - datalen = fctx->record->size - fctx->record->header_size; - } - - return propfind_getdata(name, ns, fctx, propstat, prop, carddav_mime_types, - CARDDAV_SUPP_DATA, data, datalen); -} - - -/* Callback to fetch CARDDAV:addressbook-home-set */ -int propfind_abookurl(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock) -{ - xmlNodePtr node; - const char *abook = (const char *) rock; - - if (!fctx->userid) return HTTP_NOT_FOUND; - - node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "%s/user/%s/%s", namespace_addressbook.prefix, - fctx->userid, abook ? abook : ""); - - xml_add_href(node, fctx->nsNS_DAV, buf_cstring(&fctx->buf)); - - return 0; -} - - -/* Callback to fetch CARDDAV:supported-address-data */ -static int propfind_suppaddrdata(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node; - struct mime_type_t *mime; - - if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND; - - node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - for (mime = carddav_mime_types; mime->content_type; mime++) { - xmlNodePtr type = xmlNewChild(node, fctx->nsNS_CARDDAV, - BAD_CAST "address-data-type", NULL); - - /* Trim any charset from content-type */ - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "%.*s", - (int) strcspn(mime->content_type, ";"), mime->content_type); - - xmlNewProp(type, BAD_CAST "content-type", - BAD_CAST buf_cstring(&fctx->buf)); - - if (mime->version) - xmlNewProp(type, BAD_CAST "version", BAD_CAST mime->version); - } - - buf_reset(&fctx->buf); - - return 0; -} - - -static int report_card_query(struct transaction_t *txn, - xmlNodePtr inroot, struct propfind_ctx *fctx) -{ - int ret = 0; - xmlNodePtr node; - - fctx->open_db = (db_open_proc_t) &my_carddav_open; - fctx->close_db = (db_close_proc_t) &my_carddav_close; - fctx->lookup_resource = (db_lookup_proc_t) &carddav_lookup_resource; - fctx->foreach_resource = (db_foreach_proc_t) &carddav_foreach; - fctx->proc_by_resource = &propfind_by_resource; - - /* Parse children element of report */ - for (node = inroot->children; node; node = node->next) { - if (node->type == XML_ELEMENT_NODE) { - if (!xmlStrcmp(node->name, BAD_CAST "filter")) { - txn->error.precond = CARDDAV_SUPP_FILTER; - return HTTP_FORBIDDEN; - } - } - } - - if (fctx->depth > 0) { - /* Calendar collection(s) */ - if (txn->req_tgt.collection) { - /* Add response for target calendar collection */ - propfind_by_collection(txn->req_tgt.mboxname, 0, 0, fctx); - } - else { - /* Add responses for all contained calendar collections */ - strlcat(txn->req_tgt.mboxname, ".%", sizeof(txn->req_tgt.mboxname)); - mboxlist_findall(NULL, /* internal namespace */ - txn->req_tgt.mboxname, 1, httpd_userid, - httpd_authstate, propfind_by_collection, fctx); - } - - if (fctx->davdb) my_carddav_close(fctx->davdb); - - ret = *fctx->ret; - } - - return ret; -} - - -static int report_card_multiget(struct transaction_t *txn, - xmlNodePtr inroot, struct propfind_ctx *fctx) -{ - int r, ret = 0; - struct request_target_t tgt; - struct mailbox *mailbox = NULL; - xmlNodePtr node; - struct buf uri = BUF_INITIALIZER; - - memset(&tgt, 0, sizeof(struct request_target_t)); - tgt.namespace = URL_NS_CALENDAR; - - /* Get props for each href */ - for (node = inroot->children; node; node = node->next) { - if ((node->type == XML_ELEMENT_NODE) && - !xmlStrcmp(node->name, BAD_CAST "href")) { - xmlChar *href = xmlNodeListGetString(inroot->doc, node->children, 1); - int len = xmlStrlen(href); - struct carddav_data *cdata; - - buf_ensure(&uri, len); - xmlURIUnescapeString((const char *) href, len, uri.s); - xmlFree(href); - - /* Parse the path */ - if ((r = carddav_parse_path(uri.s, &tgt, &fctx->err->desc))) { - ret = r; - goto done; - } - - fctx->req_tgt = &tgt; - - /* Check if we already have this mailbox open */ - if (!mailbox || strcmp(mailbox->name, tgt.mboxname)) { - if (mailbox) mailbox_unlock_index(mailbox, NULL); - - /* Open mailbox for reading */ - r = mailbox_open_irl(tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - fctx->mailbox = mailbox; - } - - /* Open the DAV DB corresponding to the mailbox */ - fctx->davdb = my_carddav_open(fctx->mailbox); - - /* Find message UID for the resource */ - carddav_lookup_resource(fctx->davdb, - tgt.mboxname, tgt.resource, 0, &cdata); - cdata->dav.resource = tgt.resource; - /* XXX Check errors */ - - propfind_by_resource(fctx, cdata); - - my_carddav_close(fctx->davdb); - } - } - - done: - mailbox_close(&mailbox); - buf_free(&uri); - - return ret; -} - - - -/* Store the vCard data in the specified addressbook/resource */ -static int store_resource(struct transaction_t *txn, VObject *vcard, - struct mailbox *mailbox, const char *resource, - struct carddav_db *carddavdb, int overwrite, - unsigned flags) -{ - int ret = HTTP_CREATED, r; - VObjectIterator i; - struct carddav_data *cdata; - FILE *f = NULL; - struct index_record oldrecord; - struct stagemsg *stage; - char *header; - const char *version = NULL, *uid = NULL, *fullname = NULL; - quota_t qdiffsQUOTA_NUMRESOURCES = QUOTA_DIFFS_DONTCARE_INITIALIZER; - uint32_t expunge_uid = 0; - time_t now = time(NULL); - char datestr80; - struct appendstate as; - - /* Fetch some important properties */ - initPropIterator(&i, vcard); - while (moreIteration(&i)) { - VObject *prop = nextVObject(&i); - const char *name = vObjectName(prop); - - if (!strcmp(name, "VERSION")) { - version = fakeCString(vObjectUStringZValue(prop)); - if (strcmp(version, "3.0")) { - txn->error.precond = CARDDAV_SUPP_DATA; - return HTTP_FORBIDDEN; - } - } - else if (!strcmp(name, "UID")) { - uid = fakeCString(vObjectUStringZValue(prop)); - } - else if (!strcmp(name, "FN")) { - fullname = fakeCString(vObjectUStringZValue(prop)); - } - } - - /* Sanity check data */ - if (!version || !uid || !fullname) { - txn->error.precond = CARDDAV_VALID_DATA; - return HTTP_FORBIDDEN; - } - - /* Check for existing vCard UID */ - carddav_lookup_uid(carddavdb, uid, 0, &cdata); - if (!(flags & NO_DUP_CHECK) && - cdata->dav.mailbox && !strcmp(cdata->dav.mailbox, mailbox->name) && - strcmp(cdata->dav.resource, resource)) { - /* CARDDAV:no-uid-conflict */ - const char *owner = mboxname_to_userid(cdata->dav.mailbox); - - txn->error.precond = CARDDAV_UID_CONFLICT; - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%s/user/%s/%s/%s", - namespace_addressbook.prefix, owner, - strrchr(cdata->dav.mailbox, '.')+1, cdata->dav.resource); - txn->error.resource = buf_cstring(&txn->buf); - return HTTP_FORBIDDEN; - } - - if (cdata->dav.imap_uid) { - /* Fetch index record for the resource */ - r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, - &oldrecord, NULL); - - if (overwrite == OVERWRITE_CHECK) { - /* Check any preconditions */ - const char *etag = message_guid_encode(&oldrecord.guid); - time_t lastmod = oldrecord.internaldate; - int precond = check_precond(txn, cdata, etag, lastmod); - - if (precond != HTTP_OK) - return HTTP_PRECOND_FAILED; - } - - expunge_uid = oldrecord.uid; - } - - /* Prepare to stage the message */ - if (!(f = append_newstage(mailbox->name, now, 0, &stage))) { - syslog(LOG_ERR, "append_newstage(%s) failed", mailbox->name); - txn->error.desc = "append_newstage() failed\r\n"; - return HTTP_SERVER_ERROR; - } - - /* Create iMIP header for resource */ - - /* XXX This needs to be done via an LDAP/DB lookup */ - header = charset_encode_mimeheader(proxy_userid, 0); - fprintf(f, "From: %s <>\r\n", header); - free(header); - - header = charset_encode_mimeheader(fullname, 0); - fprintf(f, "Subject: %s\r\n", header); - free(header); - - time_to_rfc822(now, datestr, sizeof(datestr)); - - fprintf(f, "Date: %s\r\n", datestr); - - fprintf(f, "Message-ID: <%s@%s>\r\n", uid, config_servername); - - fprintf(f, "Content-Type: text/vcard; charset=utf-8\r\n"); - - fprintf(f, "Content-Length: %u\r\n", (unsigned)buf_len(&txn->req_body.payload)); - fprintf(f, "Content-Disposition: inline; filename=\"%s\"\r\n", resource); - - /* XXX Check domain of data and use appropriate CTE */ - - fprintf(f, "MIME-Version: 1.0\r\n"); - fprintf(f, "\r\n"); - - /* Write the vCard data to the file */ - fprintf(f, "%s", buf_cstring(&txn->req_body.payload)); - - qdiffsQUOTA_STORAGE = ftell(f); - qdiffsQUOTA_MESSAGE = 1; - - fclose(f); - - /* Prepare to append the iMIP message to calendar mailbox */ - if ((r = append_setup_mbox(&as, mailbox, NULL, NULL, 0, qdiffs, 0, 0, EVENT_MESSAGE_NEW|EVENT_CALENDAR))) { - syslog(LOG_ERR, "append_setup(%s) failed: %s", - mailbox->name, error_message(r)); - ret = HTTP_SERVER_ERROR; - txn->error.desc = "append_setup() failed\r\n"; - } - else { - struct body *body = NULL; - - /* Append the iMIP file to the calendar mailbox */ - if ((r = append_fromstage(&as, &body, stage, now, NULL, 0, 0))) { - syslog(LOG_ERR, "append_fromstage() failed"); - ret = HTTP_SERVER_ERROR; - txn->error.desc = "append_fromstage() failed\r\n"; - } - if (body) { - message_free_body(body); - free(body); - } - - if (r) append_abort(&as); - else { - /* Commit the append to the calendar mailbox */ - r = append_commit(&as); - if (r) { - syslog(LOG_ERR, "append_commit() failed"); - ret = HTTP_SERVER_ERROR; - txn->error.desc = "append_commit() failed\r\n"; - } - else { - /* append_commit() returns a write-locked index */ - struct index_record newrecord; - - /* Read index record for new message (always the last one) */ - mailbox_read_index_record(mailbox, mailbox->i.num_records, - &newrecord); - - if (expunge_uid) { - /* Now that we have the replacement message in place - and the mailbox locked, re-read the old record - and see if we should overwrite it. Either way, - one of our records will have to be expunged. - */ - int userflag; - - ret = HTTP_NO_CONTENT; - - /* Perform the actual expunge */ - r = mailbox_user_flag(mailbox, DFLAG_UNBIND, &userflag, 1); - if (!r) { - oldrecord.user_flagsuserflag/32 |= 1<<(userflag&31); - oldrecord.system_flags |= FLAG_EXPUNGED; - r = mailbox_rewrite_index_record(mailbox, &oldrecord); - } - if (r) { - syslog(LOG_ERR, "expunging record (%s) failed: %s", - mailbox->name, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - } - } - - if (!r) { - struct resp_body_t *resp_body = &txn->resp_body; - - /* Tell client about the new resource */ - resp_body->lastmod = newrecord.internaldate; - resp_body->etag = message_guid_encode(&newrecord.guid); - } - } - } - } - - append_removestage(stage); - - return ret; -}
View file
cyrus-imapd-2.5.tar.gz/imap/http_dav.c
Deleted
@@ -1,5283 +0,0 @@ -/* http_dav.c -- Routines for dealing with DAV properties in httpd - * - * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ -/* - * TODO: - * - * - CALDAV:supported-calendar-component-set should be a bitmask in - * cyrus.index header Mailbox Options field - * - * - CALDAV:schedule-calendar-transp should be a flag in - * cyrus.index header (Mailbox Options) - * - * - DAV:creationdate sould be added to cyrus.header since it only - * gets set at creation time - * - * - Should add a last_metadata_update field to cyrus.index header - * for use in PROPFIND, PROPPATCH, and possibly REPORT. - * This would get updated any time a mailbox annotation, mailbox - * acl, or quota root limit is changed - * - * - Should we use cyrus.index header Format field to indicate - * CalDAV mailbox? - * - */ - - -#include "http_dav.h" -#include "annotate.h" -#include "acl.h" -#include "append.h" -#include "caldav_db.h" -#include "global.h" -#include "http_err.h" -#include "http_proxy.h" -#include "imap_err.h" -#include "index.h" -#include "proxy.h" -#include "times.h" -#include "syslog.h" -#include "strhash.h" -#include "tok.h" -#include "xmalloc.h" -#include "xstrlcat.h" -#include "xstrlcpy.h" - -#include <libxml/uri.h> - - -static const struct dav_namespace_t { - const char *href; - const char *prefix; -} known_namespaces = { - { XML_NS_DAV, "D" }, - { XML_NS_CALDAV, "C" }, - { XML_NS_CARDDAV, "C" }, - { XML_NS_ISCHED, NULL }, - { XML_NS_CS, "CS" }, - { XML_NS_CYRUS, "CY" }, - { XML_NS_USERFLAG, "UF" }, - { XML_NS_SYSFLAG, "SF" }, -}; - -/* PROPFIND modes */ -enum { - PROPFIND_NONE = 0, /* only used with REPORT */ - PROPFIND_ALL, - PROPFIND_NAME, - PROPFIND_PROP -}; - -static void my_dav_init(struct buf *serverinfo); - -static int prin_parse_path(const char *path, - struct request_target_t *tgt, const char **errstr); -static int propfind_restype(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_reportset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -static int propfind_principalurl(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); - -static int allprop_cb(const char *mailbox __attribute__((unused)), - uint32_t uid __attribute__((unused)), - const char *entry, - const char *userid, const struct buf *attrib, - void *rock); - -/* Array of known "live" properties */ -static const struct prop_entry dav_props = { - - /* WebDAV (RFC 4918) properties */ - { "creationdate", NS_DAV, PROP_ALLPROP, NULL, NULL, NULL }, - { "displayname", NS_DAV, PROP_ALLPROP, NULL, NULL, NULL }, - { "getcontentlanguage", NS_DAV, PROP_ALLPROP, NULL, NULL, NULL }, - { "getcontentlength", NS_DAV, PROP_ALLPROP | PROP_COLLECTION, - propfind_getlength, NULL, NULL }, - { "getcontenttype", NS_DAV, PROP_ALLPROP, NULL, NULL, NULL }, - { "getetag", NS_DAV, PROP_ALLPROP, NULL, NULL, NULL }, - { "getlastmodified", NS_DAV, PROP_ALLPROP, NULL, NULL, NULL }, - { "lockdiscovery", NS_DAV, PROP_ALLPROP | PROP_COLLECTION, - propfind_lockdisc, NULL, NULL }, - { "resourcetype", NS_DAV, PROP_ALLPROP | PROP_COLLECTION, - propfind_restype, NULL, NULL }, - { "supportedlock", NS_DAV, PROP_ALLPROP | PROP_COLLECTION, - propfind_suplock, NULL, NULL }, - - /* WebDAV Versioning (RFC 3253) properties */ - { "supported-report-set", NS_DAV, PROP_COLLECTION, - propfind_reportset, NULL, NULL }, - - /* WebDAV ACL (RFC 3744) properties */ - { "alternate-URI-set", NS_DAV, 0, NULL, NULL, NULL }, - { "principal-URL", NS_DAV, PROP_COLLECTION, - propfind_principalurl, NULL, NULL }, - { "group-member-set", NS_DAV, 0, NULL, NULL, NULL }, - { "group-membership", NS_DAV, 0, NULL, NULL, NULL }, - { "principal-collection-set", NS_DAV, PROP_COLLECTION, - propfind_princolset, NULL, NULL }, - - /* WebDAV Current Principal (RFC 5397) properties */ - { "current-user-principal", NS_DAV, PROP_COLLECTION, - propfind_curprin, NULL, NULL }, - - /* CalDAV (RFC 4791) properties */ - { "calendar-home-set", NS_CALDAV, PROP_COLLECTION, - propfind_calurl, NULL, NULL }, - - /* CalDAV Scheduling (RFC 6638) properties */ - { "schedule-inbox-URL", NS_CALDAV, PROP_COLLECTION, - propfind_calurl, NULL, SCHED_INBOX }, - { "schedule-outbox-URL", NS_CALDAV, PROP_COLLECTION, - propfind_calurl, NULL, SCHED_OUTBOX }, - { "calendar-user-address-set", NS_CALDAV, PROP_COLLECTION, - propfind_caluseraddr, NULL, NULL }, - { "calendar-user-type", NS_CALDAV, 0, NULL, NULL, NULL }, - - /* CardDAV (RFC 6352) properties */ - { "addressbook-home-set", NS_CARDDAV, PROP_COLLECTION, - propfind_abookurl, NULL, NULL }, - - /* Apple Calendar Server properties */ - { "getctag", NS_CS, PROP_ALLPROP, NULL, NULL, NULL }, - - { NULL, 0, 0, NULL, NULL, NULL } -}; - - -static struct meth_params princ_params = { - .parse_path = &prin_parse_path, - .lprops = dav_props -}; - -/* Namespace for WebDAV principals */ -struct namespace_t namespace_principal = { - URL_NS_PRINCIPAL, 0, "/dav/principals", NULL, 1 /* auth */, - /*mbtype */ 0, - ALLOW_READ | ALLOW_DAV, - &my_dav_init, NULL, NULL, NULL, - { - { NULL, NULL }, /* ACL */ - { NULL, NULL }, /* COPY */ - { NULL, NULL }, /* DELETE */ - { &meth_get_dav, &princ_params }, /* GET */ - { &meth_get_dav, &princ_params }, /* HEAD */ - { NULL, NULL }, /* LOCK */ - { NULL, NULL }, /* MKCALENDAR */ - { NULL, NULL }, /* MKCOL */ - { NULL, NULL }, /* MOVE */ - { &meth_options, NULL }, /* OPTIONS */ - { NULL, NULL }, /* POST */ - { &meth_propfind, &princ_params }, /* PROPFIND */ - { NULL, NULL }, /* PROPPATCH */ - { NULL, NULL }, /* PUT */ - { &meth_report, &princ_params }, /* REPORT */ - { &meth_trace, NULL }, /* TRACE */ - { NULL, NULL } /* UNLOCK */ - } -}; - - -static void my_dav_init(struct buf *serverinfo) -{ - if (config_httpmodules & IMAP_ENUM_HTTPMODULES_CALDAV) { - namespace_principal.enabled = 1; - namespace_principal.allow |= ALLOW_CAL; - if (config_getenum(IMAPOPT_CALDAV_ALLOWSCHEDULING)) - namespace_principal.allow |= ALLOW_CAL_SCHED; - } - if (config_httpmodules & IMAP_ENUM_HTTPMODULES_CARDDAV) { - namespace_principal.enabled = 1; - namespace_principal.allow |= ALLOW_CARD; - } - - if (!namespace_principal.enabled) return; - - if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) { - buf_printf(serverinfo, " SQLite/%s", sqlite3_libversion()); - } -} - - -/* Linked-list of properties for fetching */ -struct propfind_entry_list { - const xmlChar *name; /* Property name */ - xmlNsPtr ns; /* Property namespace */ - unsigned char flags; /* Flags for how/where prop apply */ - int (*get)(const xmlChar *name, /* Callback to fetch property */ - xmlNsPtr ns, struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); - void *rock; /* Add'l data to pass to callback */ - struct propfind_entry_list *next; -}; - - -/* Bitmask of privilege flags */ -enum { - PRIV_IMPLICIT = (1<<0), - PRIV_INBOX = (1<<1), - PRIV_OUTBOX = (1<<2) -}; - - -/* Array of precondition/postcondition errors */ -static const struct precond_t { - const char *name; /* Property name */ - unsigned ns; /* Index into known namespace array */ -} preconds = { - /* Placeholder for zero (no) precondition code */ - { NULL, 0 }, - - /* WebDAV (RFC 4918) preconditons */ - { "cannot-modify-protected-property", NS_DAV }, - { "lock-token-matches-request-uri", NS_DAV }, - { "lock-token-submitted", NS_DAV }, - { "no-conflicting-lock", NS_DAV }, - - /* WebDAV Versioning (RFC 3253) preconditions */ - { "supported-report", NS_DAV }, - { "resource-must-be-null", NS_DAV }, - - /* WebDAV ACL (RFC 3744) preconditions */ - { "need-privileges", NS_DAV }, - { "no-invert", NS_DAV }, - { "no-abstract", NS_DAV }, - { "not-supported-privilege", NS_DAV }, - { "recognized-principal", NS_DAV }, - - /* WebDAV Quota (RFC 4331) preconditions */ - { "quota-not-exceeded", NS_DAV }, - { "sufficient-disk-space", NS_DAV }, - - /* WebDAV Extended MKCOL (RFC 5689) preconditions */ - { "valid-resourcetype", NS_DAV }, - - /* WebDAV Sync (RFC 6578) preconditions */ - { "valid-sync-token", NS_DAV }, - { "number-of-matches-within-limits", NS_DAV }, - - /* CalDAV (RFC 4791) preconditions */ - { "supported-calendar-data", NS_CALDAV }, - { "valid-calendar-data", NS_CALDAV }, - { "valid-calendar-object-resource", NS_CALDAV }, - { "supported-calendar-component", NS_CALDAV }, - { "calendar-collection-location-ok", NS_CALDAV }, - { "no-uid-conflict", NS_CALDAV }, - { "supported-filter", NS_CALDAV }, - { "valid-filter", NS_CALDAV }, - - /* CalDAV Scheduling (RFC 6638) preconditions */ - { "valid-scheduling-message", NS_CALDAV }, - { "valid-organizer", NS_CALDAV }, - { "unique-scheduling-object-resource", NS_CALDAV }, - { "same-organizer-in-all-components", NS_CALDAV }, - { "allowed-organizer-scheduling-object-change", NS_CALDAV }, - { "allowed-attendee-scheduling-object-change", NS_CALDAV }, - - /* iSchedule (draft-desruisseaux-ischedule) preconditions */ - { "version-not-supported", NS_ISCHED }, - { "invalid-calendar-data-type", NS_ISCHED }, - { "invalid-calendar-data", NS_ISCHED }, - { "invalid-scheduling-message", NS_ISCHED }, - { "originator-missing", NS_ISCHED }, - { "too-many-originators", NS_ISCHED }, - { "originator-invalid", NS_ISCHED }, - { "originator-denied", NS_ISCHED }, - { "recipient-missing", NS_ISCHED }, - { "recipient-mismatch", NS_ISCHED }, - { "verification-failed", NS_ISCHED }, - - /* CardDAV (RFC 6352) preconditions */ - { "supported-address-data", NS_CARDDAV }, - { "valid-address-data", NS_CARDDAV }, - { "no-uid-conflict", NS_CARDDAV }, - { "addressbook-collection-location-ok", NS_CARDDAV }, - { "supported-filter", NS_CARDDAV } -}; - - -/* Parse request-target path in DAV principals namespace */ -static int prin_parse_path(const char *path, - struct request_target_t *tgt, const char **errstr) -{ - char *p; - size_t len; - - /* Make a working copy of target path */ - strlcpy(tgt->path, path, sizeof(tgt->path)); - tgt->tail = tgt->path + strlen(tgt->path); - - p = tgt->path; - - /* Sanity check namespace */ - len = strlen(namespace_principal.prefix); - if (strlen(p) < len || - strncmp(namespace_principal.prefix, p, len) || - (pathlen && pathlen != '/')) { - *errstr = "Namespace mismatch request target path"; - return HTTP_FORBIDDEN; - } - - /* Skip namespace */ - p += len; - if (!*p || !*++p) return 0; - - /* Check if we're in user space */ - len = strcspn(p, "/"); - if (!strncmp(p, "user", len)) { - p += len; - if (!*p || !*++p) return 0; - - /* Get user id */ - len = strcspn(p, "/"); - tgt->user = p; - tgt->userlen = len; - - p += len; - if (!*p || !*++p) return 0; - } - else return HTTP_NOT_FOUND; /* need to specify a userid */ - - if (*p) { -// *errstr = "Too many segments in request target path"; - return HTTP_NOT_FOUND; - } - - return 0; -} - - -unsigned get_preferences(struct transaction_t *txn) -{ - unsigned mask = 0, prefs = 0; - const char **hdr; - - /* Create a mask for preferences honored by method */ - switch (txn->meth) { - case METH_COPY: - case METH_MOVE: - case METH_POST: - case METH_PUT: - mask = PREFER_REP; - break; - - case METH_MKCALENDAR: - case METH_MKCOL: - case METH_PROPPATCH: - mask = PREFER_MIN; - break; - - case METH_PROPFIND: - case METH_REPORT: - mask = (PREFER_MIN | PREFER_NOROOT); - break; - } - - if (!mask) return 0; - else { - txn->flags.vary |= VARY_PREFER; - if (mask & PREFER_MIN) txn->flags.vary |= VARY_BRIEF; - } - - /* Check for Prefer header(s) */ - if ((hdr = spool_getheader(txn->req_hdrs, "Prefer"))) { - int i; - for (i = 0; hdri; i++) { - tok_t tok; - char *token; - - tok_init(&tok, hdri, ",\r\n", TOK_TRIMLEFT|TOK_TRIMRIGHT); - while ((token = tok_next(&tok))) { - if ((mask & PREFER_MIN) && - !strcmp(token, "return=minimal")) - prefs |= PREFER_MIN; - else if ((mask & PREFER_REP) && - !strcmp(token, "return=representation")) - prefs |= PREFER_REP; - else if ((mask & PREFER_NOROOT) && - !strcmp(token, "depth-noroot")) - prefs |= PREFER_NOROOT; - } - tok_fini(&tok); - } - - txn->resp_body.prefs = prefs; - } - - /* Check for Brief header */ - if ((mask & PREFER_MIN) && - (hdr = spool_getheader(txn->req_hdrs, "Brief")) && - !strcasecmp(hdr0, "t")) { - prefs |= PREFER_MIN; - } - - return prefs; -} - - -/* Check requested MIME type */ -struct mime_type_t *get_accept_type(const char **hdr, struct mime_type_t *types) -{ - struct mime_type_t *ret = NULL; - struct accept *e, *enc = parse_accept(hdr); - - for (e = enc; e && e->token; e++) { - if (!ret && e->qual > 0.0) { - struct mime_type_t *m; - - for (m = types; !ret && m->content_type; m++) { - if (is_mediatype(e->token, m->content_type)) ret = m; - } - } - - free(e->token); - } - if (enc) free(enc); - - return ret; -} - - -static int add_privs(int rights, unsigned flags, - xmlNodePtr parent, xmlNodePtr root, xmlNsPtr *ns); - - -/* Ensure that we have a given namespace. If it doesn't exist in what we - * parsed in the request, create it and attach to 'node'. - */ -int ensure_ns(xmlNsPtr *respNs, int ns, xmlNodePtr node, - const char *url, const char *prefix) -{ - if (!respNsns) - respNsns = xmlNewNs(node, BAD_CAST url, BAD_CAST prefix); - - /* XXX check for errors */ - return 0; -} - - -/* Add namespaces declared in the request to our root node and Ns array */ -static int xml_add_ns(xmlNodePtr req, xmlNsPtr *respNs, xmlNodePtr root) -{ - for (; req; req = req->next) { - if (req->type == XML_ELEMENT_NODE) { - xmlNsPtr nsDef; - - for (nsDef = req->nsDef; nsDef; nsDef = nsDef->next) { - if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_DAV)) - ensure_ns(respNs, NS_DAV, root, - (const char *) nsDef->href, - (const char *) nsDef->prefix); - else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_CALDAV)) - ensure_ns(respNs, NS_CALDAV, root, - (const char *) nsDef->href, - (const char *) nsDef->prefix); - else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_CARDDAV)) - ensure_ns(respNs, NS_CARDDAV, root, - (const char *) nsDef->href, - (const char *) nsDef->prefix); - else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_CS)) - ensure_ns(respNs, NS_CS, root, - (const char *) nsDef->href, - (const char *) nsDef->prefix); - else if (!xmlStrcmp(nsDef->href, BAD_CAST XML_NS_CYRUS)) - ensure_ns(respNs, NS_CYRUS, root, - (const char *) nsDef->href, - (const char *) nsDef->prefix); - else - xmlNewNs(root, nsDef->href, nsDef->prefix); - } - } - - xml_add_ns(req->children, respNs, root); - } - - /* XXX check for errors */ - return 0; -} - - -/* Initialize an XML tree for a property response */ -xmlNodePtr init_xml_response(const char *resp, int ns, - xmlNodePtr req, xmlNsPtr *respNs) -{ - /* Start construction of our XML response tree */ - xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); - xmlNodePtr root = NULL; - - if (!doc) return NULL; - if (!(root = xmlNewNode(NULL, BAD_CAST resp))) return NULL; - - xmlDocSetRootElement(doc, root); - - /* Add namespaces from request to our response, - * creating array of known namespaces that we can reference later. - */ - memset(respNs, 0, NUM_NAMESPACE * sizeof(xmlNsPtr)); - xml_add_ns(req, respNs, root); - - /* Set namespace of root node */ - ensure_ns(respNs, ns, root, - known_namespacesns.href, known_namespacesns.prefix); - xmlSetNs(root, respNsns); - - return root; -} - -xmlNodePtr xml_add_href(xmlNodePtr parent, xmlNsPtr ns, const char *href) -{ - xmlChar *uri = xmlURIEscapeStr(BAD_CAST href, BAD_CAST ":/"); - xmlNodePtr node = xmlNewChild(parent, ns, BAD_CAST "href", uri); - - free(uri); - return node; -} - -xmlNodePtr xml_add_error(xmlNodePtr root, struct error_t *err, - xmlNsPtr *avail_ns) -{ - xmlNsPtr nsNUM_NAMESPACE; - xmlNodePtr error, node; - const struct precond_t *precond = &precondserr->precond; - unsigned err_ns = NS_DAV; - const char *resp_desc = "responsedescription"; - - if (precond->ns == NS_ISCHED) { - err_ns = NS_ISCHED; - resp_desc = "response-description"; - } - - if (!root) { - error = root = init_xml_response("error", err_ns, NULL, ns); - avail_ns = ns; - } - else error = xmlNewChild(root, NULL, BAD_CAST "error", NULL); - - ensure_ns(avail_ns, precond->ns, root, known_namespacesprecond->ns.href, - known_namespacesprecond->ns.prefix); - node = xmlNewChild(error, avail_nsprecond->ns, - BAD_CAST precond->name, NULL); - - switch (err->precond) { - case DAV_NEED_PRIVS: - if (err->resource && err->rights) { - unsigned flags = 0; - size_t rlen = strlen(err->resource); - const char *p = err->resource + rlen; - - node = xmlNewChild(node, NULL, BAD_CAST "resource", NULL); - xml_add_href(node, NULL, err->resource); - - if (rlen > 6 && !strcmp(p-6, SCHED_INBOX)) - flags = PRIV_INBOX; - else if (rlen > 7 && !strcmp(p-7, SCHED_OUTBOX)) - flags = PRIV_OUTBOX; - - add_privs(err->rights, flags, node, root, avail_ns); - } - break; - - default: - if (err->resource) xml_add_href(node, avail_nsNS_DAV, err->resource); - break; - } - - if (err->desc) { - xmlNewTextChild(error, NULL, BAD_CAST resp_desc, BAD_CAST err->desc); - } - - return root; -} - - -void xml_add_lockdisc(xmlNodePtr node, const char *root, struct dav_data *data) -{ - time_t now = time(NULL); - - if (data->lock_expire > now) { - xmlNodePtr active, node1; - char tbuf30; /* "Second-" + long int + NUL */ - - active = xmlNewChild(node, NULL, BAD_CAST "activelock", NULL); - node1 = xmlNewChild(active, NULL, BAD_CAST "lockscope", NULL); - xmlNewChild(node1, NULL, BAD_CAST "exclusive", NULL); - - node1 = xmlNewChild(active, NULL, BAD_CAST "locktype", NULL); - xmlNewChild(node1, NULL, BAD_CAST "write", NULL); - - xmlNewChild(active, NULL, BAD_CAST "depth", BAD_CAST "0"); - - if (data->lock_owner) { - /* Last char of token signals href (1) or text (0) */ - if (data->lock_tokenstrlen(data->lock_token)-1 == '1') { - node1 = xmlNewChild(active, NULL, BAD_CAST "owner", NULL); - xml_add_href(node1, NULL, data->lock_owner); - } - else { - xmlNewTextChild(active, NULL, BAD_CAST "owner", - BAD_CAST data->lock_owner); - } - } - - snprintf(tbuf, sizeof(tbuf), "Second-%lu", data->lock_expire - now); - xmlNewChild(active, NULL, BAD_CAST "timeout", BAD_CAST tbuf); - - node1 = xmlNewChild(active, NULL, BAD_CAST "locktoken", NULL); - xml_add_href(node1, NULL, data->lock_token); - - node1 = xmlNewChild(active, NULL, BAD_CAST "lockroot", NULL); - xml_add_href(node1, NULL, root); - } -} - - -/* Add a property 'name', of namespace 'ns', with content 'content', - * and status code/string 'status' to propstat element 'stat'. - * 'stat' will be created as necessary. - */ -xmlNodePtr xml_add_prop(long status, xmlNsPtr davns, - struct propstat *propstat, - const xmlChar *name, xmlNsPtr ns, - xmlChar *content, - unsigned precond) -{ - xmlNodePtr newprop = NULL; - - if (!propstat->root) { - propstat->root = xmlNewNode(davns, BAD_CAST "propstat"); - xmlNewChild(propstat->root, NULL, BAD_CAST "prop", NULL); - } - - if (name) newprop = xmlNewTextChild(propstat->root->children, - ns, name, content); - propstat->status = status; - propstat->precond = precond; - - return newprop; -} - - -struct allprop_rock { - struct propfind_ctx *fctx; - struct propstat *propstat; -}; - -/* Add a response tree to 'root' for the specified href and - either error code or property list */ -static int xml_add_response(struct propfind_ctx *fctx, long code) -{ - xmlNodePtr resp; - - resp = xmlNewChild(fctx->root, NULL, BAD_CAST "response", NULL); - if (!resp) { - fctx->err->desc = "Unable to add response XML element"; - *fctx->ret = HTTP_SERVER_ERROR; - return HTTP_SERVER_ERROR; - } - xml_add_href(resp, NULL, fctx->req_tgt->path); - - if (code) { - xmlNewChild(resp, NULL, BAD_CAST "status", - BAD_CAST http_statusline(code)); - } - else { - struct propstat propstatNUM_PROPSTAT, *stat; - struct propfind_entry_list *e; - int i; - - memset(propstat, 0, NUM_PROPSTAT * sizeof(struct propstat)); - - /* Process each property in the linked list */ - for (e = fctx->elist; e; e = e->next) { - int r = HTTP_NOT_FOUND; - - if (e->get) { - r = 0; - - /* Pre-screen request based on prop flags */ - if (fctx->req_tgt->resource) { - if (!(e->flags & PROP_RESOURCE)) r = HTTP_NOT_FOUND; - } - else if (!(e->flags & PROP_COLLECTION)) r = HTTP_NOT_FOUND; - - if (!r) { - if (fctx->mode == PROPFIND_NAME) { - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, - e->name, e->ns, NULL, 0); - } - else { - r = e->get(e->name, e->ns, - fctx, resp, propstat, e->rock); - } - } - } - - switch (r) { - case 0: - case HTTP_OK: - /* Nothing to do - property handled in callback */ - break; - - case HTTP_UNAUTHORIZED: - xml_add_prop(HTTP_UNAUTHORIZED, fctx->nsNS_DAV, - &propstatPROPSTAT_UNAUTH, - e->name, e->ns, NULL, 0); - break; - - case HTTP_FORBIDDEN: - xml_add_prop(HTTP_FORBIDDEN, fctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, - e->name, e->ns, NULL, 0); - break; - - case HTTP_NOT_FOUND: - if (!(fctx->prefer & PREFER_MIN)) { - xml_add_prop(HTTP_NOT_FOUND, fctx->nsNS_DAV, - &propstatPROPSTAT_NOTFOUND, - e->name, e->ns, NULL, 0); - } - break; - - default: - xml_add_prop(r, fctx->nsNS_DAV, &propstatPROPSTAT_ERROR, - e->name, e->ns, NULL, 0); - break; - - } - } - - /* Process dead properties for allprop/propname */ - if (fctx->mailbox && !fctx->req_tgt->resource && - (fctx->mode == PROPFIND_ALL || fctx->mode == PROPFIND_NAME)) { - struct allprop_rock arock = { fctx, propstat }; - - annotatemore_findall(fctx->mailbox->name, 0, "*", allprop_cb, &arock); - } - - /* Check if we have any propstat elements */ - for (i = 0; i < NUM_PROPSTAT && !propstati.root; i++); - if (i == NUM_PROPSTAT) { - /* Add an empty propstat 200 */ - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, NULL, NULL, NULL, 0); - } - - /* Add status and optional error to the propstat elements - and then add them to response element */ - for (i = 0; i < NUM_PROPSTAT; i++) { - stat = &propstati; - - if (stat->root) { - xmlNewChild(stat->root, NULL, BAD_CAST "status", - BAD_CAST http_statusline(stat->status)); - if (stat->precond) { - struct error_t error = { NULL, stat->precond, NULL, 0 }; - xml_add_error(stat->root, &error, fctx->ns); - } - - xmlAddChild(resp, stat->root); - } - } - } - - return 0; -} - - -/* Helper function to prescreen/fetch resource data */ -int propfind_getdata(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - struct propstat propstat, xmlNodePtr prop, - struct mime_type_t *mime_types, int precond, - const char *data, unsigned long datalen) -{ - int ret = 0; - xmlChar *type, *ver = NULL; - struct mime_type_t *mime; - - type = xmlGetProp(prop, BAD_CAST "content-type"); - if (type) ver = xmlGetProp(prop, BAD_CAST "version"); - - /* Check/find requested MIME type */ - for (mime = mime_types; type && mime->content_type; mime++) { - if (is_mediatype((const char *) type, mime->content_type)) { - if (ver && - (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) { - continue; - } - break; - } - } - - if (!propstat) { - /* Prescreen "property" request */ - if (!mime->content_type) { - fctx->err->precond = precond; - ret = *fctx->ret = HTTP_FORBIDDEN; - } - } - else { - /* Add "property" */ - char *freeme = NULL; - - prop = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - if (mime != mime_types) { - /* Not the storage format - convert into requested MIME type */ - void *obj = mime_types->from_string(data); - - data = freeme = mime->to_string(obj); - datalen = strlen(data); - mime_types->free(obj); - } - - if (type) { - xmlSetProp(prop, BAD_CAST "content-type", type); - if (ver) xmlSetProp(prop, BAD_CAST "version", ver); - } - - xmlAddChild(prop, - xmlNewCDataBlock(fctx->root->doc, BAD_CAST data, datalen)); - - fctx->fetcheddata = 1; - - if (freeme) free(freeme); - } - - if (type) xmlFree(type); - if (ver) xmlFree(ver); - - return ret; -} - - -/* Callback to fetch DAV:creationdate */ -int propfind_creationdate(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - time_t t = 0; - char datestr21; - - if (fctx->data) { - struct dav_data *ddata = (struct dav_data *) fctx->data; - - t = ddata->creationdate; - } - else if (fctx->mailbox) { - struct stat sbuf; - - fstat(fctx->mailbox->header_fd, &sbuf); - - t = sbuf.st_ctime; - } - - if (!t) return HTTP_NOT_FOUND; - - rfc3339date_gen(datestr, sizeof(datestr), t); - - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, BAD_CAST datestr, 0); - - return 0; -} - - -/* Callback to fetch DAV:getcontentlength */ -int propfind_getlength(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - buf_reset(&fctx->buf); - - if (fctx->record) { - buf_printf(&fctx->buf, "%u", - fctx->record->size - fctx->record->header_size); - } - - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, BAD_CAST buf_cstring(&fctx->buf), 0); - - return 0; -} - - -/* Callback to fetch DAV:getetag */ -int propfind_getetag(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - if (fctx->req_tgt->resource && !fctx->record) return HTTP_NOT_FOUND; - if (!fctx->mailbox) return HTTP_NOT_FOUND; - - buf_reset(&fctx->buf); - - if (fctx->record) { - /* add DQUOTEs */ - buf_printf(&fctx->buf, "\"%s\"", - message_guid_encode(&fctx->record->guid)); - } - else { - buf_printf(&fctx->buf, "\"%u-%u-%u\"", fctx->mailbox->i.uidvalidity, - fctx->mailbox->i.last_uid, fctx->mailbox->i.exists); - } - - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, BAD_CAST buf_cstring(&fctx->buf), 0); - - return 0; -} - - -/* Callback to fetch DAV:getlastmodified */ -int propfind_getlastmod(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - if (!fctx->mailbox || - (fctx->req_tgt->resource && !fctx->record)) return HTTP_NOT_FOUND; - - buf_ensure(&fctx->buf, 30); - httpdate_gen(fctx->buf.s, fctx->buf.alloc, - fctx->record ? fctx->record->internaldate : - fctx->mailbox->index_mtime); - - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, BAD_CAST fctx->buf.s, 0); - - return 0; -} - - -/* Callback to fetch DAV:lockdiscovery */ -int propfind_lockdisc(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - if (fctx->mailbox && fctx->record) { - struct dav_data *ddata = (struct dav_data *) fctx->data; - - xml_add_lockdisc(node, fctx->req_tgt->path, ddata); - } - - return 0; -} - - -/* Callback to fetch DAV:resourcetype */ -static int propfind_restype(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - xmlNewChild(node, NULL, BAD_CAST "collection", NULL); - - return 0; -} - - -/* Callback to "write" resourcetype property */ -int proppatch_restype(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, - void *rock) -{ - const char *coltype = (const char *) rock; - unsigned precond = 0; - - if (set && (pctx->meth == METH_MKCOL || pctx->meth == METH_MKCALENDAR)) { - /* "Writeable" for MKCOL/MKCALENDAR only */ - xmlNodePtr cur; - - for (cur = prop->children; cur; cur = cur->next) { - if (cur->type != XML_ELEMENT_NODE) continue; - /* Make sure we have valid resourcetypes for the collection */ - if (xmlStrcmp(cur->name, BAD_CAST "collection") && - xmlStrcmp(cur->name, BAD_CAST coltype)) break; - } - - if (!cur) { - /* All resourcetypes are valid */ - xml_add_prop(HTTP_OK, pctx->nsNS_DAV, &propstatPROPSTAT_OK, - prop->name, prop->ns, NULL, 0); - - return 0; - } - - /* Invalid resourcetype */ - precond = DAV_VALID_RESTYPE; - } - else { - /* Protected property */ - precond = DAV_PROT_PROP; - } - - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, precond); - - *pctx->ret = HTTP_FORBIDDEN; - - return 0; -} - - -/* Callback to fetch DAV:supportedlock */ -int propfind_suplock(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - if (fctx->mailbox && fctx->record) { - xmlNodePtr entry = xmlNewChild(node, NULL, BAD_CAST "lockentry", NULL); - xmlNodePtr scope = xmlNewChild(entry, NULL, BAD_CAST "lockscope", NULL); - xmlNodePtr type = xmlNewChild(entry, NULL, BAD_CAST "locktype", NULL); - - xmlNewChild(scope, NULL, BAD_CAST "exclusive", NULL); - xmlNewChild(type, NULL, BAD_CAST "write", NULL); - } - - return 0; -} - - -/* Callback to fetch DAV:supported-report-set */ -static int propfind_reportset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - return 0; -} - - -/* Callback to fetch DAV:principalurl */ -static int propfind_principalurl(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - buf_reset(&fctx->buf); - if (fctx->req_tgt->user) { - buf_printf(&fctx->buf, "%s/user/%.*s/", - namespace_principal.prefix, - (int) fctx->req_tgt->userlen, fctx->req_tgt->user); - } - - xml_add_href(node, NULL, buf_cstring(&fctx->buf)); - - return 0; -} - - -/* Callback to fetch DAV:owner */ -int propfind_owner(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - if (fctx->req_tgt->user) { - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "%s/user/%.*s/", - namespace_principal.prefix, - (int) fctx->req_tgt->userlen, fctx->req_tgt->user); - - xml_add_href(node, NULL, buf_cstring(&fctx->buf)); - } - - return 0; -} - - -/* Add possibly 'abstract' supported-privilege 'priv_name', of namespace 'ns', - * with description 'desc_str' to node 'root'. For now, we alssume all - * descriptions are English. - */ -static xmlNodePtr add_suppriv(xmlNodePtr root, const char *priv_name, - xmlNsPtr ns, int abstract, const char *desc_str) -{ - xmlNodePtr supp, priv, desc; - - supp = xmlNewChild(root, NULL, BAD_CAST "supported-privilege", NULL); - priv = xmlNewChild(supp, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, ns, BAD_CAST priv_name, NULL); - if (abstract) xmlNewChild(supp, NULL, BAD_CAST "abstract", NULL); - desc = xmlNewChild(supp, NULL, BAD_CAST "description", BAD_CAST desc_str); - xmlNodeSetLang(desc, BAD_CAST "en"); - - return supp; -} - - -/* Callback to fetch DAV:supported-privilege-set */ -int propfind_supprivset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr set, all, agg, write; - unsigned tgt_flags = 0; - - set = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - all = add_suppriv(set, "all", NULL, 0, "Any operation"); - - agg = add_suppriv(all, "read", NULL, 0, "Read any object"); - add_suppriv(agg, "read-current-user-privilege-set", NULL, 1, - "Read current user privilege set"); - - if (fctx->req_tgt->namespace == URL_NS_CALENDAR) { - if (fctx->req_tgt->collection) { - ensure_ns(fctx->ns, NS_CALDAV, resp->parent, XML_NS_CALDAV, "C"); - - if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX)) - tgt_flags = TGT_SCHED_INBOX; - else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX)) - tgt_flags = TGT_SCHED_OUTBOX; - else { - add_suppriv(agg, "read-free-busy", fctx->nsNS_CALDAV, 0, - "Read free/busy time"); - } - } - } - - write = add_suppriv(all, "write", NULL, 0, "Write any object"); - add_suppriv(write, "write-content", NULL, 0, "Write resource content"); - add_suppriv(write, "write-properties", NULL, 0, "Write properties"); - - agg = add_suppriv(write, "bind", NULL, 0, "Add new member to collection"); - ensure_ns(fctx->ns, NS_CYRUS, resp->parent, XML_NS_CYRUS, "CY"); - add_suppriv(agg, "make-collection", fctx->nsNS_CYRUS, 0, - "Make new collection"); - add_suppriv(agg, "add-resource", fctx->nsNS_CYRUS, 0, - "Add new resource"); - - agg = add_suppriv(write, "unbind", NULL, 0, - "Remove member from collection"); - add_suppriv(agg, "remove-collection", fctx->nsNS_CYRUS, 0, - "Remove collection"); - add_suppriv(agg, "remove-resource", fctx->nsNS_CYRUS, 0, - "Remove resource"); - - agg = add_suppriv(all, "admin", fctx->nsNS_CYRUS, 0, - "Perform administrative operations"); - add_suppriv(agg, "read-acl", NULL, 1, "Read ACL"); - add_suppriv(agg, "write-acl", NULL, 1, "Write ACL"); - add_suppriv(agg, "unlock", NULL, 1, "Unlock resource"); - - if (tgt_flags == TGT_SCHED_INBOX) { - agg = add_suppriv(all, "schedule-deliver", fctx->nsNS_CALDAV, 0, - "Deliver scheduling messages"); - add_suppriv(agg, "schedule-deliver-invite", fctx->nsNS_CALDAV, 0, - "Deliver scheduling messages from Organizers"); - add_suppriv(agg, "schedule-deliver-reply", fctx->nsNS_CALDAV, 0, - "Deliver scheduling messages from Attendees"); - add_suppriv(agg, "schedule-query-freebusy", fctx->nsNS_CALDAV, 0, - "Accept free/busy requests"); - } - else if (tgt_flags == TGT_SCHED_OUTBOX) { - agg = add_suppriv(all, "schedule-send", fctx->nsNS_CALDAV, 0, - "Send scheduling messages"); - add_suppriv(agg, "schedule-send-invite", fctx->nsNS_CALDAV, 0, - "Send scheduling messages by Organizers"); - add_suppriv(agg, "schedule-send-reply", fctx->nsNS_CALDAV, 0, - "Send scheduling messages by Attendees"); - add_suppriv(agg, "schedule-send-freebusy", fctx->nsNS_CALDAV, 0, - "Submit free/busy requests"); - } - - return 0; -} - - -static int add_privs(int rights, unsigned flags, - xmlNodePtr parent, xmlNodePtr root, xmlNsPtr *ns) -{ - xmlNodePtr priv; - - if ((rights & DACL_ALL) == DACL_ALL && - /* DAV:all on CALDAV:schedule-in/outbox MUST include CALDAV:schedule */ - (!(flags & (PRIV_INBOX|PRIV_OUTBOX)) || - (rights & DACL_SCHED) == DACL_SCHED)) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, NULL, BAD_CAST "all", NULL); - } - if ((rights & DACL_READ) == DACL_READ) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, NULL, BAD_CAST "read", NULL); - if (flags & PRIV_IMPLICIT) rights |= DACL_READFB; - } - if ((rights & DACL_READFB) && - /* CALDAV:read-free-busy does not apply to CALDAV:schedule-in/outbox */ - !(flags & (PRIV_INBOX|PRIV_OUTBOX))) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - ensure_ns(ns, NS_CALDAV, root, XML_NS_CALDAV, "C"); - xmlNewChild(priv, nsNS_CALDAV, BAD_CAST "read-free-busy", NULL); - } - if ((rights & DACL_WRITE) == DACL_WRITE) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, NULL, BAD_CAST "write", NULL); - } - if (rights & DACL_WRITECONT) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, NULL, BAD_CAST "write-content", NULL); - } - if (rights & DACL_WRITEPROPS) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, NULL, BAD_CAST "write-properties", NULL); - } - - if (rights & (DACL_BIND|DACL_UNBIND|DACL_ADMIN)) { - ensure_ns(ns, NS_CYRUS, root, XML_NS_CYRUS, "CY"); - } - - if ((rights & DACL_BIND) == DACL_BIND) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, NULL, BAD_CAST "bind", NULL); - } - if (rights & DACL_MKCOL) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, nsNS_CYRUS, BAD_CAST "make-collection", NULL); - } - if (rights & DACL_ADDRSRC) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, nsNS_CYRUS, BAD_CAST "add-resource", NULL); - } - if ((rights & DACL_UNBIND) == DACL_UNBIND) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, NULL, BAD_CAST "unbind", NULL); - } - if (rights & DACL_RMCOL) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, nsNS_CYRUS, BAD_CAST "remove-collection", NULL); - } - if (rights & DACL_RMRSRC) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, nsNS_CYRUS, BAD_CAST "remove-resource", NULL); - } - if (rights & DACL_ADMIN) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - xmlNewChild(priv, nsNS_CYRUS, BAD_CAST "admin", NULL); - } - - if (rights & DACL_SCHED) { - ensure_ns(ns, NS_CALDAV, root, XML_NS_CALDAV, "C"); - } - if ((rights & DACL_SCHED) == DACL_SCHED) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - if (flags & PRIV_INBOX) - xmlNewChild(priv, nsNS_CALDAV, BAD_CAST "schedule-deliver", NULL); - else if (flags & PRIV_OUTBOX) - xmlNewChild(priv, nsNS_CALDAV, BAD_CAST "schedule-send", NULL); - } - if (rights & DACL_INVITE) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - if (flags & PRIV_INBOX) - xmlNewChild(priv, nsNS_CALDAV, - BAD_CAST "schedule-deliver-invite", NULL); - else if (flags & PRIV_OUTBOX) - xmlNewChild(priv, nsNS_CALDAV, - BAD_CAST "schedule-send-invite", NULL); - } - if (rights & DACL_REPLY) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - if (flags & PRIV_INBOX) - xmlNewChild(priv, nsNS_CALDAV, - BAD_CAST "schedule-deliver-reply", NULL); - else if (flags & PRIV_OUTBOX) - xmlNewChild(priv, nsNS_CALDAV, - BAD_CAST "schedule-send-reply", NULL); - } - if (rights & DACL_SCHEDFB) { - priv = xmlNewChild(parent, NULL, BAD_CAST "privilege", NULL); - if (flags & PRIV_INBOX) - xmlNewChild(priv, nsNS_CALDAV, - BAD_CAST "schedule-query-freebusy", NULL); - else if (flags & PRIV_OUTBOX) - xmlNewChild(priv, nsNS_CALDAV, - BAD_CAST "schedule-send-freebusy", NULL); - } - - return 0; -} - - -/* Callback to fetch DAV:current-user-privilege-set */ -int propfind_curprivset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - int rights; - unsigned flags = 0; - xmlNodePtr set; - - if (!fctx->mailbox) return HTTP_NOT_FOUND; - rights = httpd_myrights(fctx->authstate, fctx->mailbox->acl); - if ((rights & DACL_READ) != DACL_READ) { - return HTTP_UNAUTHORIZED; - } - - /* Add in implicit rights */ - if (fctx->userisadmin) { - rights |= DACL_ADMIN; - } - else if (mboxname_userownsmailbox(httpd_userid, fctx->mailbox->name)) { - rights |= config_implicitrights; - /* we always allow admin by the owner in DAV */ - rights |= DACL_ADMIN; - } - - /* Build the rest of the XML response */ - set = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - if (fctx->req_tgt->collection) { - if (fctx->req_tgt->namespace == URL_NS_CALENDAR) { - flags = PRIV_IMPLICIT; - - if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX)) - flags = PRIV_INBOX; - else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX)) - flags = PRIV_OUTBOX; - } - - add_privs(rights, flags, set, resp->parent, fctx->ns); - } - - return 0; -} - - -/* Callback to fetch DAV:acl */ -int propfind_acl(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr acl; - char *aclstr, *userid; - unsigned flags = 0; - - if (!fctx->mailbox) return HTTP_NOT_FOUND; - - /* owner has implicit admin rights */ - if (!mboxname_userownsmailbox(httpd_userid, fctx->mailbox->name)) { - int rights = httpd_myrights(fctx->authstate, fctx->mailbox->acl); - if (!(rights & DACL_ADMIN)) - return HTTP_UNAUTHORIZED; - } - - if (fctx->req_tgt->namespace == URL_NS_CALENDAR) { - flags = PRIV_IMPLICIT; - - if (fctx->req_tgt->collection) { - if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX)) - flags = PRIV_INBOX; - else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX)) - flags = PRIV_OUTBOX; - } - } - - /* Start the acl XML response */ - acl = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - /* Parse the ACL string (userid/rights pairs) */ - userid = aclstr = xstrdup(fctx->mailbox->acl); - - while (userid) { - int rights; - char *rightstr, *nextid; - xmlNodePtr ace, node; - int deny = 0; - - rightstr = strchr(userid, '\t'); - if (!rightstr) break; - *rightstr++ = '\0'; - - nextid = strchr(rightstr, '\t'); - if (!nextid) break; - *nextid++ = '\0'; - - /* Check for negative rights */ - /* XXX Does this correspond to DAV:deny? */ - if (*userid == '-') { - deny = 1; - userid++; - } - - rights = cyrus_acl_strtomask(rightstr); - - /* Add ace XML element for this userid/right pair */ - ace = xmlNewChild(acl, NULL, BAD_CAST "ace", NULL); - - /* XXX Need to check for groups. - */ - - node = xmlNewChild(ace, NULL, BAD_CAST "principal", NULL); - if (!strcmp(userid, fctx->userid)) - xmlNewChild(node, NULL, BAD_CAST "self", NULL); - else if (mboxname_userownsmailbox(userid, fctx->mailbox->name)) { - xmlNewChild(node, NULL, BAD_CAST "owner", NULL); - /* we always allow admin by the owner in DAV */ - rights |= DACL_ADMIN; - } - else if (!strcmp(userid, "anyone")) - xmlNewChild(node, NULL, BAD_CAST "authenticated", NULL); - /* XXX - well, it's better than a user called 'anonymous' - * Is there any IMAP equivalent to "unauthenticated"? - * Is there any DAV equivalent to "anonymous"? - */ - else if (!strcmp(userid, "anonymous")) - xmlNewChild(node, NULL, BAD_CAST "unauthenticated", NULL); - else { - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "%s/user/%s/", - namespace_principal.prefix, userid); - xml_add_href(node, NULL, buf_cstring(&fctx->buf)); - } - - node = xmlNewChild(ace, NULL, - BAD_CAST (deny ? "deny" : "grant"), NULL); - add_privs(rights, flags, node, resp->parent, fctx->ns); - - if (fctx->req_tgt->resource) { - node = xmlNewChild(ace, NULL, BAD_CAST "inherited", NULL); - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "%.*s", - (int)(fctx->req_tgt->resource - fctx->req_tgt->path), - fctx->req_tgt->path); - xml_add_href(node, NULL, buf_cstring(&fctx->buf)); - } - - userid = nextid; - } - - if (aclstr) free(aclstr); - - return 0; -} - - -/* Callback to fetch DAV:acl-restrictions */ -int propfind_aclrestrict(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - xmlNewChild(node, NULL, BAD_CAST "no-invert", NULL); - - return 0; -} - - -/* Callback to fetch DAV:principal-collection-set */ -EXPORTED int propfind_princolset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "%s/", namespace_principal.prefix); - xmlNewChild(node, NULL, BAD_CAST "href", BAD_CAST buf_cstring(&fctx->buf)); - - return 0; -} - - -/* Callback to fetch DAV:quota-available-bytes and DAV:quota-used-bytes */ -EXPORTED int propfind_quota(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - static char prevrootMAX_MAILBOX_BUFFER; - char foundrootMAX_MAILBOX_BUFFER, *qr = NULL; - - if (fctx->mailbox) { - /* Use the quotaroot as specified in mailbox header */ - qr = fctx->mailbox->quotaroot; - } - else { - /* Find the quotaroot governing this hierarchy */ - if (quota_findroot(foundroot, sizeof(foundroot), fctx->req_tgt->mboxname)) { - qr = foundroot; - } - } - - if (!qr) return HTTP_NOT_FOUND; - - if (!fctx->quota.root || - strcmp(fctx->quota.root, qr)) { - /* Different quotaroot - read it */ - - syslog(LOG_DEBUG, "reading quota for '%s'", qr); - - fctx->quota.root = strcpy(prevroot, qr); - - quota_read(&fctx->quota, NULL, 0); - } - - buf_reset(&fctx->buf); - if (!xmlStrcmp(name, BAD_CAST "quota-available-bytes")) { - /* Calculate limit in bytes and subtract usage */ - if (fctx->quota.limitsQUOTA_STORAGE < 0) { // unlimited - buf_printf(&fctx->buf, QUOTA_T_FMT, (quota_t)INT64_MAX); - } - else { - quota_t limit = fctx->quota.limitsQUOTA_STORAGE * quota_unitsQUOTA_STORAGE; - buf_printf(&fctx->buf, QUOTA_T_FMT, limit - fctx->quota.usedsQUOTA_STORAGE); - } - } - else if (fctx->record) { - /* Bytes used by resource */ - buf_printf(&fctx->buf, "%u", fctx->record->size); - } - else if (fctx->mailbox) { - /* Bytes used by calendar collection */ - buf_printf(&fctx->buf, QUOTA_T_FMT, - fctx->mailbox->i.quota_mailbox_used); - } - else { - /* Bytes used by entire hierarchy */ - buf_printf(&fctx->buf, QUOTA_T_FMT, fctx->quota.usedsQUOTA_STORAGE); - } - - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, BAD_CAST buf_cstring(&fctx->buf), 0); - - return 0; -} - - -/* Callback to fetch DAV:current-user-principal */ -EXPORTED int propfind_curprin(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, - &propstatPROPSTAT_OK, name, ns, NULL, 0); - - if (fctx->userid) { - buf_reset(&fctx->buf); - if (strchr(fctx->userid, '@')) { - buf_printf(&fctx->buf, "%s/user/%s/", - namespace_principal.prefix, fctx->userid); - } - else { - buf_printf(&fctx->buf, "%s/user/%s@%s/", - namespace_principal.prefix, fctx->userid, httpd_extradomain); - } - xml_add_href(node, NULL, buf_cstring(&fctx->buf)); - } - else { - xmlNewChild(node, NULL, BAD_CAST "unauthenticated", NULL); - } - - return 0; -} - - -/* Callback to fetch DAV:add-member */ -int propfind_addmember(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlNodePtr node; - int len; - - if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND; - - node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - - len = fctx->req_tgt->resource ? - (size_t) (fctx->req_tgt->resource - fctx->req_tgt->path) : - strlen(fctx->req_tgt->path); - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, "%.*s", len, fctx->req_tgt->path); - - xml_add_href(node, NULL, buf_cstring(&fctx->buf)); - - return 0; -} - - -/* Callback to fetch DAV:sync-token and CS:getctag */ -int propfind_sync_token(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - if (!fctx->mailbox || fctx->record) return HTTP_NOT_FOUND; - - /* not defined on the top-level collection either (aka #calendars) */ - if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND; - - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, XML_NS_CYRUS "sync/%u-" MODSEQ_FMT, - fctx->mailbox->i.uidvalidity, - fctx->mailbox->i.highestmodseq); - - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, BAD_CAST buf_cstring(&fctx->buf), 0); - - return 0; -} - - -/* Callback to fetch properties from resource header */ -int propfind_fromhdr(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock) -{ - const char *hdrname = (const char *) rock; - int r = HTTP_NOT_FOUND; - - if (fctx->record && - (mailbox_cached_header(hdrname) != BIT32_MAX) && - !mailbox_cacherecord(fctx->mailbox, fctx->record)) { - unsigned size; - struct protstream *stream; - hdrcache_t hdrs = NULL; - const char **hdr; - - size = cacheitem_size(fctx->record, CACHE_HEADERS); - stream = prot_readmap(cacheitem_base(fctx->record, - CACHE_HEADERS), size); - hdrs = spool_new_hdrcache(); - spool_fill_hdrcache(stream, NULL, hdrs, NULL); - prot_free(stream); - - if ((hdr = spool_getheader(hdrs, (const char *) hdrname))) { - xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, BAD_CAST hdr0, 0); - r = 0; - } - - spool_free_hdrcache(hdrs); - } - - return r; -} - -static struct flaggedresources { - const char *name; - int flag; -} fres = { - { "answered", FLAG_ANSWERED }, - { "flagged", FLAG_FLAGGED }, - { "seen", FLAG_SEEN }, - { NULL, 0 } /* last is always NULL */ -}; - -/* Callback to write a property to annotation DB */ -static int proppatch_toresource(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlChar *freeme = NULL; - annotate_state_t *astate = NULL; - struct buf value = BUF_INITIALIZER; - int r = 1; /* default to error */ - - /* flags only store "exists" */ - - if (!strcmp((const char *)prop->ns->href, XML_NS_SYSFLAG)) { - struct flaggedresources *frp; - int isset; - for (frp = fres; frp->name; frp++) { - if (strcasecmp((const char *)prop->name, frp->name)) continue; - r = 0; /* ok to do nothing */ - isset = pctx->record->system_flags & frp->flag; - if (set) { - if (isset) goto done; - pctx->record->system_flags |= frp->flag; - } - else { - if (!isset) goto done; - pctx->record->system_flags &= ~frp->flag; - } - r = mailbox_rewrite_index_record(pctx->mailbox, pctx->record); - goto done; - } - goto done; - } - - if (!strcmp((const char *)prop->ns->href, XML_NS_USERFLAG)) { - int userflag = 0; - int isset; - r = mailbox_user_flag(pctx->mailbox, (const char *)prop->name, &userflag, 1); - if (r) goto done; - isset = pctx->record->user_flagsuserflag/32 & (1<<userflag%31); - if (set) { - if (isset) goto done; - pctx->record->user_flagsuserflag/32 |= (1<<userflag%31); - } - else { - if (!isset) goto done; - pctx->record->user_flagsuserflag/32 &= ~(1<<userflag%31); - } - r = mailbox_rewrite_index_record(pctx->mailbox, pctx->record); - goto done; - } - - /* otherwise it's a database annotation */ - - buf_reset(&pctx->buf); - buf_printf(&pctx->buf, ANNOT_NS "<%s>%s", - (const char *) prop->ns->href, prop->name); - - if (set) { - freeme = xmlNodeGetContent(prop); - buf_init_ro_cstr(&value, (const char *)freeme); - } - - r = mailbox_get_annotate_state(pctx->mailbox, pctx->record->uid, &astate); - if (!r) r = annotate_state_writemask(astate, buf_cstring(&pctx->buf), httpd_userid, &value); - - done: - - if (!r) { - xml_add_prop(HTTP_OK, pctx->nsNS_DAV, &propstatPROPSTAT_OK, - prop->name, prop->ns, NULL, 0); - } - else { - xml_add_prop(HTTP_SERVER_ERROR, pctx->nsNS_DAV, - &propstatPROPSTAT_ERROR, prop->name, prop->ns, NULL, 0); - } - - if (freeme) xmlFree(freeme); - - return 0; -} - - -/* Callback to read a property from annotation DB */ -static int propfind_fromresource(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - struct buf attrib = BUF_INITIALIZER; - xmlNodePtr node; - int r = 0; /* default no error */ - - if (!strcmp((const char *)ns->href, XML_NS_SYSFLAG)) { - struct flaggedresources *frp; - int isset; - for (frp = fres; frp->name; frp++) { - if (strcasecmp((const char *)name, frp->name)) continue; - isset = fctx->record->system_flags & frp->flag; - if (isset) - buf_setcstr(&attrib, "1"); - goto done; - } - goto done; - } - - if (!strcmp((const char *)ns->href, XML_NS_USERFLAG)) { - int userflag = 0; - int isset; - r = mailbox_user_flag(fctx->mailbox, (const char *)name, &userflag, 0); - if (r) goto done; - isset = fctx->record->user_flagsuserflag/32 & (1<<userflag%31); - if (isset) - buf_setcstr(&attrib, "1"); - goto done; - } - - /* otherwise it's a DB annotation */ - - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, ANNOT_NS "<%s>%s", - (const char *) ns->href, name); - - r = annotatemore_msg_lookupmask(fctx->mailbox->name, fctx->record->uid, - buf_cstring(&fctx->buf), httpd_userid, &attrib); - -done: - if (r) return HTTP_SERVER_ERROR; - if (!buf_len(&attrib)) return HTTP_NOT_FOUND; - - node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - xmlAddChild(node, xmlNewCDataBlock(fctx->root->doc, - BAD_CAST buf_cstring(&attrib), buf_len(&attrib))); - - return 0; -} - - -/* Callback to read a property from annotation DB */ -int propfind_fromdb(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - xmlNodePtr resp __attribute__((unused)), - struct propstat propstat, - void *rock __attribute__((unused))) -{ - struct buf attrib = BUF_INITIALIZER; - xmlNodePtr node; - int r = 0; - - if (fctx->req_tgt->resource) - return propfind_fromresource(name, ns, fctx, NULL, propstat, NULL); - - buf_reset(&fctx->buf); - buf_printf(&fctx->buf, ANNOT_NS "<%s>%s", - (const char *) ns->href, name); - - if (fctx->mailbox && !fctx->record && - !(r = annotatemore_lookupmask(fctx->mailbox->name, buf_cstring(&fctx->buf), - httpd_userid, &attrib))) { - if (!buf_len(&attrib) && - !xmlStrcmp(name, BAD_CAST "displayname")) { - /* Special case empty displayname -- use last segment of path */ - buf_setcstr(&attrib, strrchr(fctx->mailbox->name, '.') + 1); - } - } - - if (r) return HTTP_SERVER_ERROR; - if (!buf_len(&attrib)) return HTTP_NOT_FOUND; - - node = xml_add_prop(HTTP_OK, fctx->nsNS_DAV, &propstatPROPSTAT_OK, - name, ns, NULL, 0); - xmlAddChild(node, xmlNewCDataBlock(fctx->root->doc, - BAD_CAST buf_cstring(&attrib), buf_len(&attrib))); - - return 0; -} - -/* Callback to write a property to annotation DB */ -int proppatch_todb(xmlNodePtr prop, unsigned set, - struct proppatch_ctx *pctx, - struct propstat propstat, - void *rock __attribute__((unused))) -{ - xmlChar *freeme = NULL; - annotate_state_t *astate = NULL; - struct buf value = BUF_INITIALIZER; - int r; - - if (pctx->req_tgt->resource) - return proppatch_toresource(prop, set, pctx, propstat, NULL); - - buf_reset(&pctx->buf); - buf_printf(&pctx->buf, ANNOT_NS "<%s>%s", - (const char *) prop->ns->href, prop->name); - - if (set) { - freeme = xmlNodeGetContent(prop); - buf_init_ro_cstr(&value, (const char *)freeme); - } - - r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate); - if (!r) r = annotate_state_writemask(astate, buf_cstring(&pctx->buf), httpd_userid, &value); - - if (!r) { - xml_add_prop(HTTP_OK, pctx->nsNS_DAV, &propstatPROPSTAT_OK, - prop->name, prop->ns, NULL, 0); - } - else { - xml_add_prop(HTTP_SERVER_ERROR, pctx->nsNS_DAV, - &propstatPROPSTAT_ERROR, prop->name, prop->ns, NULL, 0); - } - - if (freeme) xmlFree(freeme); - - return 0; -} - - -/* annotemore_findall callback for adding dead properties (allprop/propname) */ -static int allprop_cb(const char *mailbox __attribute__((unused)), - uint32_t uid __attribute__((unused)), - const char *entry, - const char *userid, const struct buf *attrib, - void *rock) -{ - struct allprop_rock *arock = (struct allprop_rock *) rock; - const struct prop_entry *pentry; - char *href, *name; - xmlNsPtr ns; - xmlNodePtr node; - - /* Make sure its a shared entry or the user's private one */ - if (*userid && strcmp(userid, arock->fctx->userid)) return 0; - - /* Split entry into namespace href and name ( <href>name ) */ - buf_setcstr(&arock->fctx->buf, entry + strlen(ANNOT_NS) + 1); - href = (char *) buf_cstring(&arock->fctx->buf); - if ((name = strchr(href, '>'))) *name++ = '\0'; - else if ((name = strchr(href, ':'))) *name++ = '\0'; - - /* Look for a match against live properties */ - for (pentry = arock->fctx->lprops; - pentry->name && - (strcmp(name, pentry->name) || - strcmp(href, known_namespacespentry->ns.href)); - pentry++); - - if (pentry->name && - (arock->fctx->mode == PROPFIND_ALL /* Skip all live properties */ - || (pentry->flags & PROP_ALLPROP))) /* Skip those already included */ - return 0; - - /* Look for an instance of this namespace in our response */ - ns = hash_lookup(href, arock->fctx->ns_table); - if (!ns) { - char prefix5; - snprintf(prefix, sizeof(prefix), "X%u", arock->fctx->prefix_count++); - ns = xmlNewNs(arock->fctx->root, BAD_CAST href, BAD_CAST prefix); - hash_insert(href, ns, arock->fctx->ns_table); - } - - /* Add the dead property to the response */ - node = xml_add_prop(HTTP_OK, arock->fctx->nsNS_DAV, - &arock->propstatPROPSTAT_OK, - BAD_CAST name, ns, NULL, 0); - - if (arock->fctx->mode == PROPFIND_ALL) { - xmlAddChild(node, xmlNewCDataBlock(arock->fctx->root->doc, - BAD_CAST attrib->s, attrib->len)); - } - - return 0; -} - - -static int prescreen_prop(const struct prop_entry *entry, - xmlNodePtr prop, - struct propfind_ctx *fctx) -{ - unsigned allowed = 1; - - if (fctx->req_tgt->resource && !(entry->flags & PROP_RESOURCE)) allowed = 0; - else if (entry->flags & PROP_PRESCREEN) { - void *rock = (entry->flags & PROP_NEEDPROP) ? prop : entry->rock; - - allowed = !entry->get(prop->name, NULL, fctx, NULL, NULL, rock); - } - - return allowed; -} - - -/* Parse the requested properties and create a linked list of fetch callbacks. - * The list gets reused for each href if Depth > 0 - */ -static int preload_proplist(xmlNodePtr proplist, struct propfind_ctx *fctx) -{ - int ret = 0; - xmlNodePtr prop; - const struct prop_entry *entry; - - if (fctx->mode == PROPFIND_ALL || fctx->mode == PROPFIND_NAME) { - xmlNsPtr nsDef; - - /* Add live properties for allprop/propname */ - for (entry = fctx->lprops; entry->name; entry++) { - if (entry->flags & PROP_ALLPROP) { - /* Pre-screen request based on prop flags */ - int allowed = prescreen_prop(entry, NULL, fctx); - - if (allowed || fctx->mode == PROP_ALLPROP) { - struct propfind_entry_list *nentry = - xzmalloc(sizeof(struct propfind_entry_list)); - - ensure_ns(fctx->ns, entry->ns, fctx->root, - known_namespacesentry->ns.href, - known_namespacesentry->ns.prefix); - - nentry->name = BAD_CAST entry->name; - nentry->ns = fctx->nsentry->ns; - if (allowed) { - nentry->flags = entry->flags; - nentry->get = entry->get; - nentry->rock = entry->rock; - } - nentry->next = fctx->elist; - fctx->elist = nentry; - } - } - } - - /* Add all namespaces attached to the response to our hash table */ - construct_hash_table(fctx->ns_table, 10, 1); - - for (nsDef = fctx->root->nsDef; nsDef; nsDef = nsDef->next) { - hash_insert((const char *) nsDef->href, nsDef, fctx->ns_table); - } - } - - /* Iterate through requested properties */ - for (prop = proplist; !*fctx->ret && prop; prop = prop->next) { - if (prop->type == XML_ELEMENT_NODE) { - struct propfind_entry_list *nentry; - - /* Look for a match against our known properties */ - for (entry = fctx->lprops; - entry->name && - (strcmp((const char *) prop->name, entry->name) || - strcmp((const char *) prop->ns->href, - known_namespacesentry->ns.href)); - entry++); - - /* Skip properties already included by allprop */ - if (fctx->mode == PROPFIND_ALL && (entry->flags & PROP_ALLPROP)) - continue; - - nentry = xzmalloc(sizeof(struct propfind_entry_list)); - nentry->name = prop->name; - nentry->ns = prop->ns; - if (entry->name) { - /* Found a match - Pre-screen request based on prop flags */ - if (prescreen_prop(entry, prop, fctx)) { - nentry->flags = entry->flags; - nentry->get = entry->get; - if (entry->flags & PROP_NEEDPROP) - nentry->rock = prop; - else - nentry->rock = entry->rock; - } - ret = *fctx->ret; - } - else { - /* No match, treat as a dead property. Need to look for both collections - * resources */ - nentry->flags = PROP_COLLECTION | PROP_RESOURCE; - nentry->get = propfind_fromdb; - } - nentry->next = fctx->elist; - fctx->elist = nentry; - } - } - - return ret; -} - - -/* Execute the given property patch instructions */ -static int do_proppatch(struct proppatch_ctx *pctx, xmlNodePtr instr) -{ - struct propstat propstatNUM_PROPSTAT; - int i; - - memset(propstat, 0, NUM_PROPSTAT * sizeof(struct propstat)); - - /* Iterate through propertyupdate children */ - for (; instr; instr = instr->next) { - if (instr->type == XML_ELEMENT_NODE) { - xmlNodePtr prop; - unsigned set = 0; - - if (!xmlStrcmp(instr->name, BAD_CAST "set")) set = 1; - else if ((pctx->meth == METH_PROPPATCH) && - !xmlStrcmp(instr->name, BAD_CAST "remove")) set = 0; - else { - syslog(LOG_INFO, "Unknown PROPPATCH instruction"); - pctx->err->desc = "Unknown PROPPATCH instruction"; - return HTTP_BAD_REQUEST; - } - - /* Find child element */ - for (prop = instr->children; - prop && prop->type != XML_ELEMENT_NODE; prop = prop->next); - if (!prop || xmlStrcmp(prop->name, BAD_CAST "prop")) { - pctx->err->desc = "Missing prop element"; - return HTTP_BAD_REQUEST; - } - - /* Iterate through requested properties */ - for (prop = prop->children; prop; prop = prop->next) { - if (prop->type == XML_ELEMENT_NODE) { - const struct prop_entry *entry; - - /* Look for a match against our known properties */ - for (entry = pctx->lprops; - entry->name && - (strcmp((const char *) prop->name, entry->name) || - strcmp((const char *) prop->ns->href, - known_namespacesentry->ns.href)); - entry++); - - if (entry->name) { - if (!entry->put) { - /* Protected property */ - xml_add_prop(HTTP_FORBIDDEN, pctx->nsNS_DAV, - &propstatPROPSTAT_FORBID, - prop->name, prop->ns, NULL, - DAV_PROT_PROP); - *pctx->ret = HTTP_FORBIDDEN; - } - else { - /* Write "live" property */ - entry->put(prop, set, pctx, propstat, entry->rock); - } - } - else { - /* Write "dead" property */ - proppatch_todb(prop, set, pctx, propstat, NULL); - } - } - } - } - } - - /* One or more of the properties failed */ - if (*pctx->ret && propstatPROPSTAT_OK.root) { - /* 200 status must become 424 */ - propstatPROPSTAT_FAILEDDEP.root = propstatPROPSTAT_OK.root; - propstatPROPSTAT_FAILEDDEP.status = HTTP_FAILED_DEP; - propstatPROPSTAT_OK.root = NULL; - } - - /* Add status and optional error to the propstat elements - and then add them to the response element */ - for (i = 0; i < NUM_PROPSTAT; i++) { - struct propstat *stat = &propstati; - - if (stat->root) { - xmlNewChild(stat->root, NULL, BAD_CAST "status", - BAD_CAST http_statusline(stat->status)); - if (stat->precond) { - struct error_t error = { NULL, stat->precond, NULL, 0 }; - xml_add_error(stat->root, &error, pctx->ns); - } - - xmlAddChild(pctx->root, stat->root); - } - } - - return 0; -} - - -/* Parse an XML body into a tree */ -int parse_xml_body(struct transaction_t *txn, xmlNodePtr *root) -{ - const char **hdr; - xmlParserCtxtPtr ctxt; - xmlDocPtr doc = NULL; - int r = 0; - - *root = NULL; - - /* Read body */ - txn->req_body.flags |= BODY_DECODE; - r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc); - if (r) { - txn->flags.conn = CONN_CLOSE; - return r; - } - - if (!buf_len(&txn->req_body.payload)) return 0; - - /* Check Content-Type */ - if (!(hdr = spool_getheader(txn->req_hdrs, "Content-Type")) || - (!is_mediatype("text/xml", hdr0) && - !is_mediatype("application/xml", hdr0))) { - txn->error.desc = "This method requires an XML body\r\n"; - return HTTP_BAD_MEDIATYPE; - } - - /* Parse the XML request */ - ctxt = xmlNewParserCtxt(); - if (ctxt) { - doc = xmlCtxtReadMemory(ctxt, buf_cstring(&txn->req_body.payload), - buf_len(&txn->req_body.payload), NULL, NULL, - XML_PARSE_NOWARNING); - xmlFreeParserCtxt(ctxt); - } - if (!doc) { - txn->error.desc = "Unable to parse XML body\r\n"; - return HTTP_BAD_REQUEST; - } - - /* Get the root element of the XML request */ - if (!(*root = xmlDocGetRootElement(doc))) { - txn->error.desc = "Missing root element in request\r\n"; - return HTTP_BAD_REQUEST; - } - - return 0; -} - - -/* Perform an ACL request - * - * preconditions: - * DAV:no-ace-conflict - * DAV:no-protected-ace-conflict - * DAV:no-inherited-ace-conflict - * DAV:limited-number-of-aces - * DAV:deny-before-grant - * DAV:grant-only - * DAV:no-invert - * DAV:no-abstract - * DAV:not-supported-privilege - * DAV:missing-required-principal - * DAV:recognized-principal - * DAV:allowed-principal - */ -int meth_acl(struct transaction_t *txn, void *params) -{ - struct meth_params *aparams = (struct meth_params *) params; - int ret = 0, r, rights; - xmlDocPtr indoc = NULL; - xmlNodePtr root, ace; - struct mailbox *mailbox = NULL; - mbentry_t *mbentry = NULL; - struct buf acl = BUF_INITIALIZER; - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Parse the path */ - if ((r = aparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) return r; - - /* Make sure method is allowed (only allowed on collections) */ - if (!(txn->req_tgt.allow & ALLOW_WRITECOL)) { - txn->error.desc = "ACLs can only be set on collections\r\n"; - syslog(LOG_DEBUG, "Tried to set ACL on non-collection"); - return HTTP_NOT_ALLOWED; - } - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - if (!mboxname_userownsmailbox(httpd_userid, mbentry->name)) { - /* Check ACL for current user */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if (!(rights & DACL_ADMIN)) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = DACL_ADMIN; - mboxlist_entry_free(&mbentry); - return HTTP_FORBIDDEN; - } - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - - /* Parse the ACL body */ - ret = parse_xml_body(txn, &root); - if (!ret && !root) { - txn->error.desc = "Missing request body\r\n"; - ret = HTTP_BAD_REQUEST; - } - if (ret) goto done; - - indoc = root->doc; - - /* Make sure its an DAV:acl element */ - if (xmlStrcmp(root->name, BAD_CAST "acl")) { - txn->error.desc = "Missing acl element in ACL request\r\n"; - ret = HTTP_BAD_REQUEST; - goto done; - } - - /* Parse the DAV:ace elements */ - for (ace = root->children; ace; ace = ace->next) { - if (ace->type == XML_ELEMENT_NODE) { - xmlNodePtr child = NULL, prin = NULL, privs = NULL; - const char *userid = NULL; - int deny = 0, rights = 0; - char rightstr100; - struct request_target_t tgt; - - for (child = ace->children; child; child = child->next) { - if (child->type == XML_ELEMENT_NODE) { - if (!xmlStrcmp(child->name, BAD_CAST "principal")) { - if (prin) { - txn->error.desc = "Multiple principals in ACE\r\n"; - ret = HTTP_BAD_REQUEST; - goto done; - } - - for (prin = child->children; prin && - prin->type != XML_ELEMENT_NODE; prin = prin->next); - } - else if (!xmlStrcmp(child->name, BAD_CAST "grant")) { - if (privs) { - txn->error.desc = "Multiple grant|deny in ACE\r\n"; - ret = HTTP_BAD_REQUEST; - goto done; - } - - for (privs = child->children; privs && - privs->type != XML_ELEMENT_NODE; privs = privs->next); - } - else if (!xmlStrcmp(child->name, BAD_CAST "deny")) { - if (privs) { - txn->error.desc = "Multiple grant|deny in ACE\r\n"; - ret = HTTP_BAD_REQUEST; - goto done; - } - - for (privs = child->children; privs && - privs->type != XML_ELEMENT_NODE; privs = privs->next); - deny = 1; - } - else if (!xmlStrcmp(child->name, BAD_CAST "invert")) { - /* DAV:no-invert */ - txn->error.precond = DAV_NO_INVERT; - ret = HTTP_FORBIDDEN; - goto done; - } - else { - txn->error.desc = "Unknown element in ACE\r\n"; - ret = HTTP_BAD_REQUEST; - goto done; - } - } - } - - if (!xmlStrcmp(prin->name, BAD_CAST "self")) { - userid = proxy_userid; - } - else if (!xmlStrcmp(prin->name, BAD_CAST "owner")) { - userid = mboxname_to_userid(mailbox->name); - } - else if (!xmlStrcmp(prin->name, BAD_CAST "authenticated")) { - userid = "anyone"; - } - else if (!xmlStrcmp(prin->name, BAD_CAST "href")) { - xmlChar *href = xmlNodeGetContent(prin); - xmlURIPtr uri; - const char *errstr = NULL; - size_t plen = strlen(namespace_principal.prefix); - - uri = parse_uri(METH_UNKNOWN, (const char *) href, 1, &errstr); - if (uri && - !strncmp(namespace_principal.prefix, uri->path, plen) && - uri->pathplen == '/') { - memset(&tgt, 0, sizeof(struct request_target_t)); - tgt.namespace = URL_NS_PRINCIPAL; - /* XXX: there is no doubt that this leaks memory */ - r = prin_parse_path(uri->path, &tgt, &errstr); - if (!r && tgt.user) userid = tgt.user; - } - if (uri) xmlFreeURI(uri); - xmlFree(href); - } - - if (!userid) { - /* DAV:recognized-principal */ - txn->error.precond = DAV_RECOG_PRINC; - ret = HTTP_FORBIDDEN; - goto done; - } - - for (; privs; privs = privs->next) { - if (privs->type == XML_ELEMENT_NODE) { - xmlNodePtr priv = privs->children; - for (; priv->type != XML_ELEMENT_NODE; priv = priv->next); - - if (aparams->acl_ext && - aparams->acl_ext(txn, priv, &rights)) { - /* Extension (CalDAV) privileges */ - if (txn->error.precond) { - ret = HTTP_FORBIDDEN; - goto done; - } - } - else if (!xmlStrcmp(priv->ns->href, - BAD_CAST XML_NS_DAV)) { - /* WebDAV privileges */ - if (!xmlStrcmp(priv->name, - BAD_CAST "all")) { - if (deny) - rights |= ACL_FULL; /* wipe EVERYTHING */ - else - rights |= DACL_ALL; - } - else if (!xmlStrcmp(priv->name, - BAD_CAST "read")) - rights |= DACL_READ; - else if (!xmlStrcmp(priv->name, - BAD_CAST "write")) - rights |= DACL_WRITE; - else if (!xmlStrcmp(priv->name, - BAD_CAST "write-content")) - rights |= DACL_WRITECONT; - else if (!xmlStrcmp(priv->name, - BAD_CAST "write-properties")) - rights |= DACL_WRITEPROPS; - else if (!xmlStrcmp(priv->name, - BAD_CAST "bind")) - rights |= DACL_BIND; - else if (!xmlStrcmp(priv->name, - BAD_CAST "unbind")) - rights |= DACL_UNBIND; - else if (!xmlStrcmp(priv->name, - BAD_CAST "read-current-user-privilege-set") - || !xmlStrcmp(priv->name, - BAD_CAST "read-acl") - || !xmlStrcmp(priv->name, - BAD_CAST "write-acl") - || !xmlStrcmp(priv->name, - BAD_CAST "unlock")) { - /* DAV:no-abstract */ - txn->error.precond = DAV_NO_ABSTRACT; - ret = HTTP_FORBIDDEN; - goto done; - } - else { - /* DAV:not-supported-privilege */ - txn->error.precond = DAV_SUPP_PRIV; - ret = HTTP_FORBIDDEN; - goto done; - } - } - else if (!xmlStrcmp(priv->ns->href, - BAD_CAST XML_NS_CALDAV)) { - if (!xmlStrcmp(priv->name, - BAD_CAST "read-free-busy")) - rights |= DACL_READFB; - else { - /* DAV:not-supported-privilege */ - txn->error.precond = DAV_SUPP_PRIV; - ret = HTTP_FORBIDDEN; - goto done; - } - } - else if (!xmlStrcmp(priv->ns->href, - BAD_CAST XML_NS_CYRUS)) { - /* Cyrus-specific privileges */ - if (!xmlStrcmp(priv->name, - BAD_CAST "make-collection")) - rights |= DACL_MKCOL; - else if (!xmlStrcmp(priv->name, - BAD_CAST "remove-collection")) - rights |= DACL_RMCOL; - else if (!xmlStrcmp(priv->name, - BAD_CAST "add-resource")) - rights |= DACL_ADDRSRC; - else if (!xmlStrcmp(priv->name, - BAD_CAST "remove-resource")) - rights |= DACL_RMRSRC; - else if (!xmlStrcmp(priv->name, - BAD_CAST "admin")) - rights |= DACL_ADMIN; - else { - /* DAV:not-supported-privilege */ - txn->error.precond = DAV_SUPP_PRIV; - ret = HTTP_FORBIDDEN; - goto done; - } - } - else { - /* DAV:not-supported-privilege */ - txn->error.precond = DAV_SUPP_PRIV; - ret = HTTP_FORBIDDEN; - goto done; - } - } - } - - /* gotta have something to do! */ - if (rights) { - cyrus_acl_masktostr(rights, rightstr); - buf_reset(&acl); - buf_printf(&acl, "%s%s", deny ? "-" : "+", rightstr); - - r = mboxlist_setacl(&httpd_namespace, txn->req_tgt.mboxname, userid, buf_cstring(&acl), - /*isadmin*/1, httpd_userid, httpd_authstate); - if (r) { - syslog(LOG_ERR, "mboxlist_setacl(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - } - } - } - - response_header(HTTP_OK, txn); - - done: - buf_free(&acl); - if (indoc) xmlFreeDoc(indoc); - - return ret; -} - - -/* Perform a COPY/MOVE request - * - * preconditions: - * *DAV:need-privileges - */ -int meth_copy(struct transaction_t *txn, void *params) -{ - struct meth_params *cparams = (struct meth_params *) params; - int ret = HTTP_CREATED, r, precond, rights, overwrite = OVERWRITE_YES; - const char **hdr; - xmlURIPtr dest_uri; - struct request_target_t dest_tgt; /* Parsed destination URL */ - struct backend *src_be = NULL, *dest_be = NULL; - struct mailbox *src_mbox = NULL, *dest_mbox = NULL; - mbentry_t *mbentry = NULL; - struct dav_data *ddata; - struct index_record src_rec; - const char *etag = NULL; - time_t lastmod = 0; - unsigned flags = 0; - void *src_davdb = NULL, *dest_davdb = NULL; - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Parse the source path */ - if ((r = cparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) return r; - - /* Make sure method is allowed (not allowed on collections yet) */ - if (!(txn->req_tgt.allow & ALLOW_WRITE)) return HTTP_NOT_ALLOWED; - - /* Check for mandatory Destination header */ - if (!(hdr = spool_getheader(txn->req_hdrs, "Destination"))) { - txn->error.desc = "Missing Destination header\r\n"; - return HTTP_BAD_REQUEST; - } - - /* Parse destination URI */ - if (!(dest_uri = parse_uri(METH_UNKNOWN, hdr0, 1, &txn->error.desc))) { - txn->error.desc = "Illegal Destination target URI"; - return HTTP_BAD_REQUEST; - } - - /* Make sure source and dest resources are NOT the same */ - if (!strcmp(txn->req_uri->path, dest_uri->path)) { - txn->error.desc = "Source and destination resources are the same\r\n"; - r = HTTP_FORBIDDEN; - } - - /* Parse the destination path */ - if (!r) { - r = cparams->parse_path(dest_uri->path, &dest_tgt, &txn->error.desc); - } - xmlFreeURI(dest_uri); - - if (r) return HTTP_FORBIDDEN; - - /* We don't yet handle COPY/MOVE on collections */ - if (!dest_tgt.resource) return HTTP_NOT_ALLOWED; - - /* Locate the source mailbox */ - if ((r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL))) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Check ACL for current user on source mailbox */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if (((rights & DACL_READ) != DACL_READ) || - ((txn->meth == METH_MOVE) && !(rights & DACL_RMRSRC))) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = - (rights & DACL_READ) != DACL_READ ? DACL_READ : DACL_RMRSRC; - mboxlist_entry_free(&mbentry); - return HTTP_FORBIDDEN; - } - - if (mbentry->server) { - /* Remote source mailbox */ - src_be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!src_be) return HTTP_UNAVAILABLE; - } - - mboxlist_entry_free(&mbentry); - - /* Locate the destination mailbox */ - r = http_mlookup(dest_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - dest_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Check ACL for current user on destination */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if (!(rights & DACL_ADDRSRC) || !(rights & DACL_WRITECONT)) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = dest_tgt.path; - txn->error.rights = - !(rights & DACL_ADDRSRC) ? DACL_ADDRSRC : DACL_WRITECONT; - mboxlist_entry_free(&mbentry); - return HTTP_FORBIDDEN; - } - - if (mbentry->server) { - /* Remote destination mailbox */ - dest_be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!dest_be) return HTTP_UNAVAILABLE; - } - - mboxlist_entry_free(&mbentry); - - if (src_be) { - /* Remote source mailbox */ - /* XXX Currently only supports standard Murder */ - - if (!dest_be) return HTTP_NOT_ALLOWED; - - /* Replace cached Destination header with just the absolute path */ - hdr = spool_getheader(txn->req_hdrs, "Destination"); - strcpy((char *) hdr0, dest_tgt.path); - - if (src_be == dest_be) { - /* Simply send the COPY to the backend */ - return http_pipe_req_resp(src_be, txn); - } - - /* This is the harder case: GET from source and PUT on destination */ - return http_proxy_copy(src_be, dest_be, txn); - } - - /* Local Mailbox */ - - /* Open dest mailbox for reading */ - r = mailbox_open_irl(dest_tgt.mboxname, &dest_mbox); - if (r) { - syslog(LOG_ERR, "mailbox_open_irl(%s) failed: %s", - dest_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Open the DAV DB corresponding to the dest mailbox */ - dest_davdb = cparams->davdb.open_db(dest_mbox); - - /* Find message UID for the dest resource, if exists */ - cparams->davdb.lookup_resource(dest_davdb, dest_tgt.mboxname, - dest_tgt.resource, 0, (void **) &ddata); - /* XXX Check errors */ - - /* Finished our initial read of dest mailbox */ - mailbox_unlock_index(dest_mbox, NULL); - - /* Check any preconditions on destination */ - if ((hdr = spool_getheader(txn->req_hdrs, "Overwrite")) && - !strcmp(hdr0, "F")) { - - if (ddata->rowid) { - /* Don't overwrite the destination resource */ - ret = HTTP_PRECOND_FAILED; - goto done; - } - overwrite = OVERWRITE_NO; - } - - /* Open source mailbox for reading */ - r = mailbox_open_irl(txn->req_tgt.mboxname, &src_mbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Open the DAV DB corresponding to the src mailbox */ - src_davdb = cparams->davdb.open_db(src_mbox); - - /* Find message UID for the source resource */ - cparams->davdb.lookup_resource(src_davdb, txn->req_tgt.mboxname, - txn->req_tgt.resource, 0, (void **) &ddata); - if (!ddata->rowid) { - ret = HTTP_NOT_FOUND; - goto done; - } - - if (ddata->imap_uid) { - /* Mapped URL - Fetch index record for the resource */ - r = mailbox_find_index_record(src_mbox, ddata->imap_uid, &src_rec, NULL); - if (r) { - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - etag = message_guid_encode(&src_rec.guid); - lastmod = src_rec.internaldate; - } - else { - /* Unmapped URL (empty resource) */ - etag = NULL_ETAG; - lastmod = ddata->creationdate; - } - - /* Check any preconditions on source */ - precond = check_precond(txn, (void **) ddata, etag, lastmod); - - switch (precond) { - case HTTP_OK: - break; - - case HTTP_LOCKED: - txn->error.precond = DAV_NEED_LOCK_TOKEN; - txn->error.resource = txn->req_tgt.path; - - default: - /* We failed a precondition - don't perform the request */ - ret = precond; - goto done; - } - - if (get_preferences(txn) & PREFER_REP) flags |= PREFER_REP; - if ((txn->meth == METH_MOVE) && (dest_mbox == src_mbox)) - flags |= NO_DUP_CHECK; - - r = mailbox_lock_index(dest_mbox, LOCK_EXCLUSIVE); - if (r) { - syslog(LOG_ERR, "relock index(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Parse, validate, and store the resource */ - ret = cparams->copy(txn, src_mbox, &src_rec, dest_mbox, dest_tgt.resource, - dest_davdb, overwrite, flags); - - /* we're done, no need to keep this */ - mailbox_unlock_index(dest_mbox, NULL); - - /* For MOVE, we need to delete the source resource */ - if ((txn->meth == METH_MOVE) && - (ret == HTTP_CREATED || ret == HTTP_NO_CONTENT)) { - /* Lock source mailbox */ - mailbox_lock_index(src_mbox, LOCK_EXCLUSIVE); - - /* Find message UID for the source resource */ - cparams->davdb.lookup_resource(src_davdb, txn->req_tgt.mboxname, - txn->req_tgt.resource, 0, (void **) &ddata); - /* XXX Check errors */ - - /* Fetch index record for the source resource */ - if (ddata->imap_uid && - !mailbox_find_index_record(src_mbox, ddata->imap_uid, &src_rec, NULL)) { - - /* Expunge the source message */ - src_rec.system_flags |= FLAG_EXPUNGED; - if ((r = mailbox_rewrite_index_record(src_mbox, &src_rec))) { - syslog(LOG_ERR, "expunging src record (%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - } - } - - done: - if (ret == HTTP_CREATED) { - /* Tell client where to find the new resource */ - txn->location = dest_tgt.path; - } - else { - /* Don't confuse client by providing ETag of Destination resource */ - txn->resp_body.etag = NULL; - } - - if (dest_davdb) cparams->davdb.close_db(dest_davdb); - mailbox_close(&dest_mbox); - if (src_davdb) cparams->davdb.close_db(src_davdb); - mailbox_close(&src_mbox); - - return ret; -} - - -/* Perform a DELETE request */ -int meth_delete(struct transaction_t *txn, void *params) -{ - struct meth_params *dparams = (struct meth_params *) params; - int ret = HTTP_NO_CONTENT, r = 0, precond, rights; - struct mboxevent *mboxevent = NULL; - struct mailbox *mailbox = NULL; - mbentry_t *mbentry = NULL; - struct dav_data *ddata; - struct index_record record; - const char *etag = NULL; - time_t lastmod = 0; - void *davdb = NULL; - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Parse the path */ - r = dparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc); - if (r) return r; - - /* Make sure method is allowed */ - if (!(txn->req_tgt.allow & ALLOW_DELETE)) return HTTP_NOT_ALLOWED; - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Check ACL for current user */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if ((txn->req_tgt.resource && !(rights & DACL_RMRSRC)) || - !(rights & DACL_RMCOL)) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = txn->req_tgt.resource ? DACL_RMRSRC : DACL_RMCOL; - mboxlist_entry_free(&mbentry); - return HTTP_FORBIDDEN; - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - - if (!txn->req_tgt.resource) { - /* DELETE collection */ - - /* Open mailbox for reading */ - r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - return HTTP_SERVER_ERROR; - } - - /* Do any special processing */ - if (dparams->delete) dparams->delete(txn, mailbox, NULL, NULL); - - mailbox_close(&mailbox); - - mboxevent = mboxevent_new(EVENT_MAILBOX_DELETE); - - /* XXX - delayed delete? */ - if (mboxlist_delayed_delete_isenabled()) { - r = mboxlist_delayed_deletemailbox(txn->req_tgt.mboxname, - httpd_userisadmin || httpd_userisproxyadmin, - httpd_userid, httpd_authstate, mboxevent, - /*checkack*/1, /*force*/0); - } - else { - r = mboxlist_deletemailbox(txn->req_tgt.mboxname, - httpd_userisadmin || httpd_userisproxyadmin, - httpd_userid, httpd_authstate, mboxevent, - /*checkack*/1, /*localonly*/0, /*force*/0); - } - if (r == IMAP_PERMISSION_DENIED) ret = HTTP_FORBIDDEN; - else if (r == IMAP_MAILBOX_NONEXISTENT) ret = HTTP_NOT_FOUND; - else if (r) ret = HTTP_SERVER_ERROR; - - goto done; - } - - /* DELETE resource */ - - /* Open mailbox for writing */ - r = mailbox_open_iwl(txn->req_tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Open the DAV DB corresponding to the mailbox */ - davdb = dparams->davdb.open_db(mailbox); - - /* Find message UID for the resource, if exists */ - dparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, - txn->req_tgt.resource, 0, (void **) &ddata); - if (!ddata->rowid) { - ret = HTTP_NOT_FOUND; - goto done; - } - - memset(&record, 0, sizeof(struct index_record)); - if (ddata->imap_uid) { - /* Mapped URL - Fetch index record for the resource */ - r = mailbox_find_index_record(mailbox, ddata->imap_uid, &record, NULL); - if (r) { - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - etag = message_guid_encode(&record.guid); - lastmod = record.internaldate; - } - else { - /* Unmapped URL (empty resource) */ - etag = NULL_ETAG; - lastmod = ddata->creationdate; - } - - /* Check any preconditions */ - precond = dparams->check_precond(txn, (void *) ddata, etag, lastmod); - - switch (precond) { - case HTTP_OK: - break; - - case HTTP_LOCKED: - txn->error.precond = DAV_NEED_LOCK_TOKEN; - txn->error.resource = txn->req_tgt.path; - - default: - /* We failed a precondition - don't perform the request */ - ret = precond; - goto done; - } - - if (record.uid) { - /* Expunge the resource */ - record.system_flags |= FLAG_EXPUNGED; - - mboxevent = mboxevent_new(EVENT_MESSAGE_EXPUNGE); - - r = mailbox_rewrite_index_record(mailbox, &record); - - if (r) { - syslog(LOG_ERR, "expunging record (%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - mboxevent_extract_record(mboxevent, mailbox, &record); - mboxevent_extract_mailbox(mboxevent, mailbox); - } - - /* Do any special processing */ - if (dparams->delete) dparams->delete(txn, mailbox, &record, ddata); - - done: - if (davdb) dparams->davdb.close_db(davdb); - mailbox_close(&mailbox); - - if (!r) - mboxevent_notify(mboxevent); - mboxevent_free(&mboxevent); - - return ret; -} - - -/* Perform a GET/HEAD request on a DAV resource */ -int meth_get_dav(struct transaction_t *txn, void *params) -{ - struct meth_params *gparams = (struct meth_params *) params; - const char **hdr; - struct mime_type_t *mime; - int ret = 0, r, precond, rights; - const char *data = NULL; - unsigned long datalen, offset; - struct buf msg_buf = BUF_INITIALIZER; - struct resp_body_t *resp_body = &txn->resp_body; - struct mailbox *mailbox = NULL; - mbentry_t *mbentry = NULL; - struct dav_data *ddata; - struct index_record record; - const char *etag = NULL; - time_t lastmod = 0; - void *davdb = NULL; - char *freeme = NULL; - - /* Parse the path */ - ret = gparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc); - if (ret) return ret; - - /* We don't handle GET on a collection (yet) */ - if (!txn->req_tgt.resource) return HTTP_NO_CONTENT; - - /* Check requested MIME type: - 1st entry in gparams->mime_types array MUST be default MIME type */ - if ((hdr = spool_getheader(txn->req_hdrs, "Accept"))) - mime = get_accept_type(hdr, gparams->mime_types); - else mime = gparams->mime_types; - if (!mime) return HTTP_NOT_ACCEPTABLE; - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Check ACL for current user */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if (!(rights & DACL_READ)) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = DACL_READ; - mboxlist_entry_free(&mbentry); - return HTTP_FORBIDDEN; - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - - /* Open mailbox for reading */ - r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - goto done; - } - - /* Open the DAV DB corresponding to the mailbox */ - davdb = gparams->davdb.open_db(mailbox); - - /* Find message UID for the resource */ - gparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, - txn->req_tgt.resource, 0, (void **) &ddata); - if (!ddata->rowid) { - ret = HTTP_NOT_FOUND; - goto done; - } - - memset(&record, 0, sizeof(struct index_record)); - if (ddata->imap_uid) { - /* Mapped URL - Fetch index record for the resource */ - r = mailbox_find_index_record(mailbox, ddata->imap_uid, &record, NULL); - if (r) { - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Resource length doesn't include RFC 5322 header */ - offset = record.header_size; - datalen = record.size - offset; - - txn->flags.ranges = 1; - etag = message_guid_encode(&record.guid); - lastmod = record.internaldate; - } - else { - /* Unmapped URL (empty resource) */ - offset = datalen = 0; - txn->flags.ranges = 0; - etag = NULL_ETAG; - lastmod = ddata->creationdate; - } - - /* Check any preconditions, including range request */ - precond = gparams->check_precond(txn, (void *) ddata, etag, lastmod); - - switch (precond) { - case HTTP_OK: - case HTTP_PARTIAL: - case HTTP_NOT_MODIFIED: - /* Fill in ETag, Last-Modified, Expires, and Cache-Control */ - resp_body->etag = etag; - resp_body->lastmod = lastmod; - resp_body->maxage = 3600; /* 1 hr */ - txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; /* don't use stale data */ - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - ret = precond; - goto done; - } - - if (record.uid) { - txn->flags.vary |= VARY_ACCEPT; - resp_body->type = mime->content_type; - - if (txn->meth == METH_GET) { - /* Load message containing the resource */ - r = mailbox_map_record(mailbox, &record, &msg_buf); - if (r) goto done; - - /* iCalendar data in response should not be transformed */ - txn->flags.cc |= CC_NOTRANSFORM; - - data = buf_base(&msg_buf) + offset; - - if (mime != gparams->mime_types) { - /* Not the storage format - convert into requested MIME type */ - void *obj = gparams->mime_types0.from_string(data); - - data = freeme = mime->to_string(obj); - datalen = strlen(data); - gparams->mime_types0.free(obj); - } - } - } - - write_body(precond, txn, data, datalen); - - buf_free(&msg_buf); - - done: - if (davdb) gparams->davdb.close_db(davdb); - if (r) { - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - } - mailbox_close(&mailbox); - free(freeme); - - return ret; -} - - -/* Perform a LOCK request - * - * preconditions: - * DAV:need-privileges - * DAV:no-conflicting-lock - * DAV:lock-token-submitted - */ -int meth_lock(struct transaction_t *txn, void *params) -{ - struct meth_params *lparams = (struct meth_params *) params; - int ret = HTTP_OK, r, precond, rights; - struct mailbox *mailbox = NULL; - mbentry_t *mbentry = NULL; - struct dav_data *ddata; - struct index_record oldrecord; - const char *etag; - time_t lastmod; - xmlDocPtr indoc = NULL, outdoc = NULL; - xmlNodePtr root = NULL; - xmlNsPtr nsNUM_NAMESPACE; - xmlChar *owner = NULL; - time_t now = time(NULL); - void *davdb = NULL; - - /* XXX We ignore Depth and Timeout header fields */ - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Parse the path */ - if ((r = lparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) return r; - - /* Make sure method is allowed (only allowed on resources) */ - if (!(txn->req_tgt.allow & ALLOW_WRITE)) return HTTP_NOT_ALLOWED; - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Check ACL for current user */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if (!(rights & DACL_WRITECONT) || !(rights & DACL_ADDRSRC)) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = - !(rights & DACL_WRITECONT) ? DACL_WRITECONT : DACL_ADDRSRC; - mboxlist_entry_free(&mbentry); - return HTTP_FORBIDDEN; - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - - /* Open mailbox for reading */ - r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Open the DAV DB corresponding to the mailbox */ - davdb = lparams->davdb.open_db(mailbox); - - /* Find message UID for the resource, if exists */ - lparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, - txn->req_tgt.resource, 1, (void *) &ddata); - - if (ddata->imap_uid) { - /* Locking existing resource */ - - /* Fetch index record for the resource */ - r = mailbox_find_index_record(mailbox, ddata->imap_uid, &oldrecord, NULL); - if (r) { - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - etag = message_guid_encode(&oldrecord.guid); - lastmod = oldrecord.internaldate; - } - else if (ddata->rowid) { - /* Unmapped URL (empty resource) */ - etag = NULL_ETAG; - lastmod = ddata->creationdate; - } - else { - /* New resource */ - etag = NULL; - lastmod = 0; - - ddata->creationdate = now; - ddata->mailbox = mailbox->name; - ddata->resource = txn->req_tgt.resource; - } - - /* Check any preconditions */ - precond = lparams->check_precond(txn, ddata, etag, lastmod); - - switch (precond) { - case HTTP_OK: - break; - - case HTTP_LOCKED: - if (strcmp(ddata->lock_ownerid, httpd_userid)) - txn->error.precond = DAV_LOCKED; - else - txn->error.precond = DAV_NEED_LOCK_TOKEN; - txn->error.resource = txn->req_tgt.path; - - default: - /* We failed a precondition - don't perform the request */ - ret = precond; - goto done; - } - - if (ddata->lock_expire <= now) { - /* Create new lock */ - xmlNodePtr node, sub; - unsigned owner_is_href = 0; - - /* Parse the required body */ - ret = parse_xml_body(txn, &root); - if (!ret && !root) { - txn->error.desc = "Missing request body"; - ret = HTTP_BAD_REQUEST; - } - if (ret) goto done; - - /* Check for correct root element */ - indoc = root->doc; - if (xmlStrcmp(root->name, BAD_CAST "lockinfo")) { - txn->error.desc = "Incorrect root element in XML request\r\n"; - ret = HTTP_BAD_MEDIATYPE; - goto done; - } - - /* Parse elements of lockinfo */ - for (node = root->children; node; node = node->next) { - if (node->type != XML_ELEMENT_NODE) continue; - - if (!xmlStrcmp(node->name, BAD_CAST "lockscope")) { - /* Find child element of lockscope */ - for (sub = node->children; - sub && sub->type != XML_ELEMENT_NODE; sub = sub->next); - /* Make sure its an exclusive element */ - if (!sub || xmlStrcmp(sub->name, BAD_CAST "exclusive")) { - txn->error.desc = "Only exclusive locks are supported"; - ret = HTTP_BAD_REQUEST; - goto done; - } - } - else if (!xmlStrcmp(node->name, BAD_CAST "locktype")) { - /* Find child element of locktype */ - for (sub = node->children; - sub && sub->type != XML_ELEMENT_NODE; sub = sub->next); - /* Make sure its a write element */ - if (!sub || xmlStrcmp(sub->name, BAD_CAST "write")) { - txn->error.desc = "Only write locks are supported"; - ret = HTTP_BAD_REQUEST; - goto done; - } - } - else if (!xmlStrcmp(node->name, BAD_CAST "owner")) { - /* Find child element of owner */ - for (sub = node->children; - sub && sub->type != XML_ELEMENT_NODE; sub = sub->next); - if (!sub) { - owner = xmlNodeGetContent(node); - } - /* Make sure its a href element */ - else if (xmlStrcmp(sub->name, BAD_CAST "href")) { - ret = HTTP_BAD_REQUEST; - goto done; - } - else { - owner_is_href = 1; - owner = xmlNodeGetContent(sub); - } - } - } - - ddata->lock_ownerid = httpd_userid; - if (owner) ddata->lock_owner = (const char *) owner; - - /* Construct lock-token */ - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, XML_NS_CYRUS "lock/%s-%x-%u", - mailbox->uniqueid, strhash(txn->req_tgt.resource), - owner_is_href); - - ddata->lock_token = buf_cstring(&txn->buf); - } - - /* Update lock expiration */ - ddata->lock_expire = now + 300; /* 5 min */ - - /* Start construction of our prop response */ - if (!(root = init_xml_response("prop", NS_DAV, root, ns))) { - ret = HTTP_SERVER_ERROR; - txn->error.desc = "Unable to create XML response\r\n"; - goto done; - } - - outdoc = root->doc; - root = xmlNewChild(root, NULL, BAD_CAST "lockdiscovery", NULL); - xml_add_lockdisc(root, txn->req_tgt.path, (struct dav_data *) ddata); - - lparams->davdb.write_resourceLOCKONLY(davdb, ddata, 1); - - txn->resp_body.lock = ddata->lock_token; - - if (!ddata->rowid) { - ret = HTTP_CREATED; - - /* Tell client about the new resource */ - txn->resp_body.etag = NULL_ETAG; - - /* Tell client where to find the new resource */ - txn->location = txn->req_tgt.path; - } - else ret = HTTP_OK; - - xml_response(ret, txn, outdoc); - ret = 0; - - done: - if (davdb) lparams->davdb.close_db(davdb); - mailbox_close(&mailbox); - if (outdoc) xmlFreeDoc(outdoc); - if (indoc) xmlFreeDoc(indoc); - if (owner) xmlFree(owner); - - return ret; -} - - -/* Perform a MKCOL/MKCALENDAR request */ -/* - * preconditions: - * DAV:resource-must-be-null - * DAV:need-privileges - * DAV:valid-resourcetype - * CALDAV:calendar-collection-location-ok - * CALDAV:valid-calendar-data (CALDAV:calendar-timezone) - */ -int meth_mkcol(struct transaction_t *txn, void *params) -{ - struct meth_params *mparams = (struct meth_params *) params; - int ret = 0, r = 0; - xmlDocPtr indoc = NULL, outdoc = NULL; - xmlNodePtr root = NULL, instr = NULL; - xmlNsPtr nsNUM_NAMESPACE; - char *partition = NULL; - struct proppatch_ctx pctx; - struct mailbox *mailbox = NULL; - - memset(&pctx, 0, sizeof(struct proppatch_ctx)); - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Parse the path */ - if ((r = mparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) { - txn->error.precond = CALDAV_LOCATION_OK; - return HTTP_FORBIDDEN; - } - - /* Make sure method is allowed (only allowed on home-set) */ - if (!(txn->req_tgt.allow & ALLOW_WRITECOL)) { - txn->error.precond = CALDAV_LOCATION_OK; - return HTTP_FORBIDDEN; - } - - /* Check if we are allowed to create the mailbox */ - r = mboxlist_createmailboxcheck(txn->req_tgt.mboxname, 0, NULL, - httpd_userisadmin || httpd_userisproxyadmin, - httpd_userid, httpd_authstate, - NULL, &partition, 0); - - if (r == IMAP_PERMISSION_DENIED) return HTTP_FORBIDDEN; - else if (r == IMAP_MAILBOX_EXISTS) { - txn->error.precond = DAV_RSRC_EXISTS; - return HTTP_FORBIDDEN; - } - else if (r) return HTTP_SERVER_ERROR; - - if (!config_partitiondir(partition)) { - /* Invalid partition, assume its a server (remote mailbox) */ - char *server = partition, *p; - struct backend *be; - - /* Trim remote partition */ - p = strchr(server, '!'); - if (p) *p++ = '\0'; - - be = proxy_findserver(server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - /* Local Mailbox */ - - /* Parse the MKCOL/MKCALENDAR body, if exists */ - ret = parse_xml_body(txn, &root); - if (ret) goto done; - - if (root) { - /* Check for correct root element */ - indoc = root->doc; - - if (txn->meth == METH_MKCOL) - r = xmlStrcmp(root->name, BAD_CAST "mkcol"); - else - r = xmlStrcmp(root->name, BAD_CAST mparams->mkcol.xml_req); - if (r) { - txn->error.desc = "Incorrect root element in XML request\r\n"; - return HTTP_BAD_MEDIATYPE; - } - - instr = root->children; - } - - /* Create the mailbox */ - r = mboxlist_createmailbox(txn->req_tgt.mboxname, mparams->mkcol.mbtype, - partition, - httpd_userisadmin || httpd_userisproxyadmin, - httpd_userid, httpd_authstate, - /*localonly*/0, /*forceuser*/0, - /*dbonly*/0, /*notify*/0, - &mailbox); - - if (instr && !r) { - /* Start construction of our mkcol/mkcalendar response */ - if (txn->meth == METH_MKCOL) - root = init_xml_response("mkcol-response", NS_DAV, root, ns); - else - root = init_xml_response(mparams->mkcol.xml_resp, - mparams->mkcol.xml_ns, root, ns); - if (!root) { - ret = HTTP_SERVER_ERROR; - txn->error.desc = "Unable to create XML response\r\n"; - goto done; - } - - outdoc = root->doc; - - /* Populate our proppatch context */ - pctx.req_tgt = &txn->req_tgt; - pctx.meth = txn->meth; - pctx.mailbox = mailbox; - pctx.lprops = mparams->lprops; - pctx.root = root; - pctx.ns = ns; - pctx.tid = NULL; - pctx.err = &txn->error; - pctx.ret = &r; - - /* Execute the property patch instructions */ - if (!r) ret = do_proppatch(&pctx, instr); - - if (ret || r) { - /* Something failed. Abort the txn and change the OK status */ - - if (!ret) { - /* Output the XML response */ - xml_response(HTTP_FORBIDDEN, txn, outdoc); - ret = 0; - } - - goto done; - } - } - - if (!r) ret = HTTP_CREATED; - else if (r == IMAP_PERMISSION_DENIED) ret = HTTP_FORBIDDEN; - else if (r == IMAP_MAILBOX_EXISTS) { - txn->error.precond = DAV_RSRC_EXISTS; - ret = HTTP_FORBIDDEN; - } - else if (r) { - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - } - - done: - buf_free(&pctx.buf); - mailbox_close(&mailbox); - - if (outdoc) xmlFreeDoc(outdoc); - if (indoc) xmlFreeDoc(indoc); - - return ret; -} - - -/* dav_foreach() callback to find props on a resource */ -int propfind_by_resource(void *rock, void *data) -{ - struct propfind_ctx *fctx = (struct propfind_ctx *) rock; - struct dav_data *ddata = (struct dav_data *) data; - struct index_record record; - char *p; - size_t len; - int r, ret = 0; - - /* Append resource name to URL path */ - if (!fctx->req_tgt->resource) { - len = strlen(fctx->req_tgt->path); - p = fctx->req_tgt->path + len; - } - else { - p = fctx->req_tgt->resource; - len = p - fctx->req_tgt->path; - } - - if (p-1 != '/') { - *p++ = '/'; - len++; - } - strlcpy(p, ddata->resource, MAX_MAILBOX_PATH - len); - fctx->req_tgt->resource = p; - fctx->req_tgt->reslen = strlen(p); - - fctx->data = data; - if (ddata->imap_uid && !fctx->record) { - /* Fetch index record for the resource */ - r = mailbox_find_index_record(fctx->mailbox, ddata->imap_uid, - &record, NULL); - /* XXX Check errors */ - - fctx->record = r ? NULL : &record; - } - - if (!ddata->imap_uid || !fctx->record) { - /* Add response for missing target */ - ret = xml_add_response(fctx, HTTP_NOT_FOUND); - } - else { - int add_it = 1; - - if (fctx->filter) add_it = fctx->filter(fctx, data); - - if (add_it) { - /* Add response for target */ - ret = xml_add_response(fctx, 0); - } - } - - buf_free(&fctx->msg_buf); - fctx->record = NULL; - fctx->data = NULL; - - return ret; -} - - -/* mboxlist_findall() callback to find props on a collection */ -int propfind_by_collection(char *mboxname, int matchlen, - int maycreate __attribute__((unused)), - void *rock) -{ - struct propfind_ctx *fctx = (struct propfind_ctx *) rock; - mbentry_t *mbentry = NULL; - struct buf writebuf = BUF_INITIALIZER; - struct mboxname_parts parts; - struct mailbox *mailbox = NULL; - char *p; - size_t len; - int r = 0, rights, root = 1; - - mboxname_init_parts(&parts); - - /* If this function is called outside of mboxlist_findall() - * with matchlen == 0, this is the root resource of the PROPFIND, - * otherwise it's just one of many found. Inbox and Outbox can't - * appear unless they are the root */ - if (matchlen) { - p = strrchr(mboxname, '.'); - if (!p) goto done; - p++; /* skip dot */ - if (!strncmp(p, SCHED_INBOX, strlen(SCHED_INBOX) - 1)) goto done; - if (!strncmp(p, SCHED_OUTBOX, strlen(SCHED_OUTBOX) - 1)) goto done; - /* and while we're at it, reject the fricking top-level folders too. - * XXX - this is evil and bad and wrong */ - if (*p == '#') goto done; - root = 0; - } - - /* Check ACL on mailbox for current user */ - r = mboxlist_lookup(mboxname, &mbentry, NULL); - if (r == IMAP_MAILBOX_NONEXISTENT) return 0; - if (r) { - syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s", - mboxname, error_message(r)); - fctx->err->desc = error_message(r); - *fctx->ret = HTTP_SERVER_ERROR; - goto done; - } - - if (fctx->req_tgt->mboxtype && !(mbentry->mbtype & fctx->req_tgt->mboxtype)) - goto done; - - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if ((rights & fctx->reqd_privs) != fctx->reqd_privs) goto done; - - /* Open mailbox for reading */ - if ((r = mailbox_open_irl(mboxname, &mailbox))) { - syslog(LOG_INFO, "mailbox_open_irl(%s) failed: %s", - mboxname, error_message(r)); - fctx->err->desc = error_message(r); - *fctx->ret = HTTP_SERVER_ERROR; - goto done; - } - - fctx->mailbox = mailbox; - fctx->record = NULL; - - if (!fctx->req_tgt->resource) { - size_t baselen; - mboxname_to_parts(mboxname, &parts); - - buf_reset(&writebuf); - buf_printf(&writebuf, "%s/user/", fctx->req_tgt->prefix); - baselen = writebuf.len; - - if (parts.userid) { - buf_printf(&writebuf, "%s@%s", parts.userid, parts.domain ? parts.domain - : httpd_extradomain ? httpd_extradomain : config_defdomain); - } - - /* in theory we should reposition the start too, but it won't change, because - * the prefix is fixed */ - fctx->req_tgt->userlen = writebuf.len - baselen; - - buf_putc(&writebuf, '/'); - - len = writebuf.len; - - /* one day this will just be the final element of 'boxes' hopefully */ - if (!parts.box) goto done; - p = strrchr(parts.box, '.'); - if (!p) goto done; - - /* OK, we're doing this mailbox */ - buf_appendcstr(&writebuf, p+1); - - /* don't forget the trailing slash */ - buf_putc(&writebuf, '/'); - - /* copy it all back into place... in theory we should check against - * 'last' and make sure it doesn't change from the original request. - * yay for micro-optimised memory usage... */ - strlcpy(fctx->req_tgt->path, buf_cstring(&writebuf), MAX_MAILBOX_PATH); - p = fctx->req_tgt->path + len; - fctx->req_tgt->collection = p; - fctx->req_tgt->collen = strlen(p) - 1; - - /* If not filtering by calendar resource, and not excluding root, - add response for collection */ - if (!fctx->filter && - (!root || (fctx->depth == 1) || !(fctx->prefer & PREFER_NOROOT)) && - (r = xml_add_response(fctx, 0))) goto done; - } - - if (fctx->depth > 1) { - /* Resource(s) */ - - /* open the DAV DB corresponding to the mailbox. Note we open the new one - * first before closing the old one, so we get refcounted retaining of the - * open database within a single user */ - sqlite3 *newdb = fctx->open_db(mailbox); - if (fctx->davdb) fctx->close_db(fctx->davdb); - fctx->davdb = newdb; - - if (fctx->req_tgt->resource) { - /* Add response for target resource */ - void *data; - - /* Find message UID for the resource */ - fctx->lookup_resource(fctx->davdb, - mboxname, fctx->req_tgt->resource, 0, &data); - /* XXX Check errors */ - - r = fctx->proc_by_resource(rock, data); - } - else { - /* Add responses for all contained resources */ - fctx->foreach_resource(fctx->davdb, mboxname, - fctx->proc_by_resource, rock); - - /* Started with NULL resource, end with NULL resource */ - fctx->req_tgt->resource = NULL; - fctx->req_tgt->reslen = 0; - } - } - - done: - buf_free(&writebuf); - mboxname_free_parts(&parts); - mboxlist_entry_free(&mbentry); - if (mailbox) mailbox_close(&mailbox); - - return r; -} - - -/* Perform a PROPFIND request */ -EXPORTED int meth_propfind(struct transaction_t *txn, void *params) -{ - struct meth_params *fparams = (struct meth_params *) params; - int ret = 0, r; - const char **hdr; - unsigned depth; - xmlDocPtr indoc = NULL, outdoc = NULL; - xmlNodePtr root, cur = NULL, props = NULL; - xmlNsPtr nsNUM_NAMESPACE; - struct hash_table ns_table = { 0, NULL, NULL }; - struct propfind_ctx fctx; - struct propfind_entry_list *elist = NULL; - - memset(&fctx, 0, sizeof(struct propfind_ctx)); - - /* Parse the path */ - if (fparams->parse_path) { - r = fparams->parse_path(txn->req_uri->path, &txn->req_tgt, &txn->error.desc); - if (r) return r; - } - - /* Make sure method is allowed */ - if (!(txn->req_tgt.allow & ALLOW_DAV)) - return HTTP_NOT_ALLOWED; - - /* Check Depth */ - hdr = spool_getheader(txn->req_hdrs, "Depth"); - if (!hdr || !strcmp(hdr0, "infinity")) { - depth = 3; - } - else if (!strcmp(hdr0, "1")) { - depth = 1; - } - else if (!strcmp(hdr0, "0")) { - depth = 0; - } - else { - txn->error.desc = "Illegal Depth value\r\n"; - return HTTP_BAD_REQUEST; - } - - if ((txn->req_tgt.namespace != URL_NS_PRINCIPAL) && txn->req_tgt.user - && txn->req_tgt.mboxname && txn->req_tgt.mboxname0) { - mbentry_t *mbentry = NULL; - int rights; - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Check ACL for current user */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if ((rights & DACL_READ) != DACL_READ) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = DACL_READ; - ret = HTTP_FORBIDDEN; - mboxlist_entry_free(&mbentry); - goto done; - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - } - - /* Principal or Local Mailbox */ - - /* Normalize depth so that: - * 0 = home-set collection, 1+ = calendar collection, 2+ = calendar resource, 3+ = infinity! - */ - if (txn->req_tgt.collection) depth++; - if (txn->req_tgt.resource) depth++; - - /* Parse the PROPFIND body, if exists */ - ret = parse_xml_body(txn, &root); - if (ret) goto done; - - if (!root) { - /* Empty request */ - fctx.mode = PROPFIND_ALL; - } - else { - indoc = root->doc; - - /* Make sure its a propfind element */ - if (xmlStrcmp(root->name, BAD_CAST "propfind")) { - txn->error.desc = "Missing propfind element in PROPFIND request\r\n"; - ret = HTTP_BAD_REQUEST; - goto done; - } - - /* Find child element of propfind */ - for (cur = root->children; - cur && cur->type != XML_ELEMENT_NODE; cur = cur->next); - - if (!cur) { - txn->error.desc = "Missing child node element in PROPFIND request\r\n"; - ret = HTTP_BAD_REQUEST; - goto done; - } - - /* Add propfind type to our header cache */ - spool_cache_header(xstrdup(":type"), xstrdup((const char *) cur->name), - txn->req_hdrs); - - /* Make sure its a known element */ - if (!xmlStrcmp(cur->name, BAD_CAST "allprop")) { - fctx.mode = PROPFIND_ALL; - } - else if (!xmlStrcmp(cur->name, BAD_CAST "propname")) { - fctx.mode = PROPFIND_NAME; - fctx.prefer = PREFER_MIN; /* Don't want 404 (Not Found) */ - } - else if (!xmlStrcmp(cur->name, BAD_CAST "prop")) { - fctx.mode = PROPFIND_PROP; - props = cur->children; - } - else { - ret = HTTP_BAD_REQUEST; - goto done; - } - - /* Check for extra elements */ - for (cur = cur->next; cur; cur = cur->next) { - if (cur->type == XML_ELEMENT_NODE) { - if ((fctx.mode == PROPFIND_ALL) && !props && - /* Check for 'include' element */ - !xmlStrcmp(cur->name, BAD_CAST "include")) { - props = cur->children; - } - else { - ret = HTTP_BAD_REQUEST; - goto done; - } - } - } - } - - /* Start construction of our multistatus response */ - root = init_xml_response("multistatus", NS_DAV, root, ns); - if (!root) { - ret = HTTP_SERVER_ERROR; - txn->error.desc = "Unable to create XML response\r\n"; - goto done; - } - - outdoc = root->doc; - - /* Populate our propfind context */ - fctx.req_tgt = &txn->req_tgt; - fctx.depth = depth; - fctx.prefer |= get_preferences(txn); - fctx.userid = proxy_userid; - fctx.userisadmin = httpd_userisadmin; - fctx.authstate = httpd_authstate; - fctx.mailbox = NULL; - fctx.record = NULL; - fctx.reqd_privs = DACL_READ; - fctx.filter = NULL; - fctx.filter_crit = NULL; - fctx.open_db = fparams->davdb.open_db; - fctx.close_db = fparams->davdb.close_db; - fctx.lookup_resource = fparams->davdb.lookup_resource; - fctx.foreach_resource = fparams->davdb.foreach_resource; - fctx.proc_by_resource = &propfind_by_resource; - fctx.elist = NULL; - fctx.lprops = fparams->lprops; - fctx.root = root; - fctx.ns = ns; - fctx.ns_table = &ns_table; - fctx.err = &txn->error; - fctx.ret = &ret; - fctx.fetcheddata = 0; - - /* Parse the list of properties and build a list of callbacks */ - preload_proplist(props, &fctx); - - /* Principal response for principal or home-set collection */ - if (!txn->req_tgt.collection && (!(fctx.prefer & PREFER_NOROOT) || !depth)) { - if (*txn->req_tgt.mboxname) { - /* Open mailbox for reading */ - if ((r = mailbox_open_irl(txn->req_tgt.mboxname, &fctx.mailbox))) { - syslog(LOG_INFO, "mailbox_open_irl(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - } - - xml_add_response(&fctx, 0); - mailbox_close(&fctx.mailbox); - } - - if (depth > 0) { - /* Calendar collection(s) */ - - if (txn->req_tgt.collection) { - /* Add response for target calendar collection */ - propfind_by_collection(txn->req_tgt.mboxname, 0, 0, &fctx); - } - else { - char *base = NULL; - /* Add responses for all contained calendar collections */ -#if 0 - XXX ALWAYS RETURN EVERYTHING EVEN OUTSIDE THIS MBOXNAME - (why? because Google does, and it gets us sharing for free) - if (txn->req_tgt.mboxname && txn->req_tgt.mboxname0) - base = strconcat(txn->req_tgt.mboxname, ".%", (const char *)NULL); - else { -#endif - struct mboxname_parts parts; - mboxname_userid_to_parts(httpd_userid, &parts); - if (parts.domain) - base = strconcat(parts.domain, "!user.*", (const char *)NULL); - else - base = xstrdup("user.*"); - mboxname_free_parts(&parts); -#if 0 - } -#endif - r = mboxlist_findall(NULL, /* internal namespace */ - base, 1, NULL, - httpd_authstate, propfind_by_collection, &fctx); - free(base); - } - - if (fctx.davdb) fctx.close_db(fctx.davdb); - - ret = *fctx.ret; - } - - /* Output the XML response */ - if (!ret) { - /* iCalendar data in response should not be transformed */ - if (fctx.fetcheddata) txn->flags.cc |= CC_NOTRANSFORM; - - xml_response(HTTP_MULTI_STATUS, txn, outdoc); - } - - done: - /* Free the entry list */ - elist = fctx.elist; - while (elist) { - struct propfind_entry_list *freeme = elist; - elist = elist->next; - free(freeme); - } - - buf_free(&fctx.buf); - - free_hash_table(&ns_table, NULL); - - if (outdoc) xmlFreeDoc(outdoc); - if (indoc) xmlFreeDoc(indoc); - - return ret; -} - - -/* Perform a PROPPATCH request - * - * preconditions: - * DAV:cannot-modify-protected-property - * CALDAV:valid-calendar-data (CALDAV:calendar-timezone) - */ -int meth_proppatch(struct transaction_t *txn, void *params) -{ - struct meth_params *pparams = (struct meth_params *) params; - int ret = 0, r = 0, rights; - xmlDocPtr indoc = NULL, outdoc = NULL; - xmlNodePtr root, instr, resp; - xmlNsPtr nsNUM_NAMESPACE; - struct mailbox *mailbox = NULL; - mbentry_t *mbentry = NULL; - struct proppatch_ctx pctx; - struct index_record record; - void *davdb = NULL; - - memset(&pctx, 0, sizeof(struct proppatch_ctx)); - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Parse the path */ - if ((r = pparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) return r; - - if (!txn->req_tgt.collection) { - txn->error.desc = "PROPPATCH requires a collection"; - return HTTP_NOT_ALLOWED; - } - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Check ACL for current user */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if (!(rights & DACL_WRITEPROPS)) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = DACL_WRITEPROPS; - mboxlist_entry_free(&mbentry); - return HTTP_FORBIDDEN; - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - - r = mailbox_open_iwl(txn->req_tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "IOERROR: failed to open mailbox %s for proppatch", txn->req_tgt.mboxname); - return HTTP_SERVER_ERROR; - } - - /* Parse the PROPPATCH body */ - ret = parse_xml_body(txn, &root); - if (!ret && !root) { - txn->error.desc = "Missing request body\r\n"; - ret = HTTP_BAD_REQUEST; - } - if (ret) goto done; - - indoc = root->doc; - - /* Make sure its a propertyupdate element */ - if (xmlStrcmp(root->name, BAD_CAST "propertyupdate")) { - txn->error.desc = - "Missing propertyupdate element in PROPPATCH request\r\n"; - ret = HTTP_BAD_REQUEST; - goto done; - } - instr = root->children; - - /* Start construction of our multistatus response */ - if (!(root = init_xml_response("multistatus", NS_DAV, root, ns))) { - txn->error.desc = "Unable to create XML response\r\n"; - ret = HTTP_SERVER_ERROR; - goto done; - } - - outdoc = root->doc; - - /* Add a response tree to 'root' for the specified href */ - resp = xmlNewChild(root, NULL, BAD_CAST "response", NULL); - if (!resp) syslog(LOG_ERR, "new child response failed"); - xmlNewChild(resp, NULL, BAD_CAST "href", BAD_CAST txn->req_tgt.path); - - /* Populate our proppatch context */ - pctx.req_tgt = &txn->req_tgt; - pctx.meth = txn->meth; - pctx.mailbox = mailbox; - pctx.record = NULL; - pctx.lprops = pparams->lprops; - pctx.root = resp; - pctx.ns = ns; - pctx.tid = NULL; - pctx.err = &txn->error; - pctx.ret = &r; - - if (txn->req_tgt.resource) { - struct dav_data *ddata; - /* gotta find the resource */ - /* Open the DAV DB corresponding to the mailbox */ - davdb = pparams->davdb.open_db(mailbox); - - /* Find message UID for the resource */ - pparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, - txn->req_tgt.resource, 0, (void **) &ddata); - if (!ddata->imap_uid) { - ret = HTTP_NOT_FOUND; - goto done; - } - - memset(&record, 0, sizeof(struct index_record)); - /* Mapped URL - Fetch index record for the resource */ - r = mailbox_find_index_record(mailbox, ddata->imap_uid, &record, NULL); - if (r) { - ret = HTTP_NOT_FOUND; - goto done; - } - - pctx.record = &record; - } - - /* Execute the property patch instructions */ - ret = do_proppatch(&pctx, instr); - - /* Output the XML response */ - if (!ret) { - if (get_preferences(txn) & PREFER_MIN) ret = HTTP_OK; - else xml_response(HTTP_MULTI_STATUS, txn, outdoc); - } - - done: - if (davdb) pparams->davdb.close_db(davdb); - mailbox_close(&mailbox); - buf_free(&pctx.buf); - - if (outdoc) xmlFreeDoc(outdoc); - if (indoc) xmlFreeDoc(indoc); - - return ret; -} - - -/* Perform a POST request */ -int meth_post(struct transaction_t *txn, void *params) -{ - struct meth_params *pparams = (struct meth_params *) params; - static unsigned post_count = 0; - int r, ret; - size_t len; - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Parse the path */ - if ((r = pparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) return r; - - /* Make sure method is allowed (only allowed on certain collections) */ - if (!(txn->req_tgt.allow & ALLOW_POST)) return HTTP_NOT_ALLOWED; - - /* Do any special processing */ - if (pparams->post) { - ret = pparams->post(txn); - if (ret != HTTP_CONTINUE) return ret; - } - - /* POST to regular collection */ - - /* Append a unique resource name to URL path and perform a PUT */ - len = strlen(txn->req_tgt.path); - txn->req_tgt.resource = txn->req_tgt.path + len; - txn->req_tgt.reslen = - snprintf(txn->req_tgt.resource, MAX_MAILBOX_PATH - len, - "%x-%d-%ld-%u.ics", - strhash(txn->req_tgt.path), getpid(), time(0), post_count++); - - /* Tell client where to find the new resource */ - txn->location = txn->req_tgt.path; - - ret = meth_put(txn, params); - - if (ret != HTTP_CREATED) txn->location = NULL; - - return ret; -} - - -/* Perform a PUT request - * - * preconditions: - * *DAV:supported-address-data - */ -int meth_put(struct transaction_t *txn, void *params) -{ - struct meth_params *pparams = (struct meth_params *) params; - int ret, r, precond, rights; - const char **hdr, *etag; - struct mime_type_t *mime = NULL; - struct mailbox *mailbox = NULL; - mbentry_t *mbentry = NULL; - struct dav_data *ddata; - struct index_record oldrecord; - quota_t qdiffsQUOTA_NUMRESOURCES = QUOTA_DIFFS_INITIALIZER; - time_t lastmod; - unsigned flags = 0; - void *davdb = NULL; - - if (txn->meth == METH_PUT) { - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Parse the path */ - if ((r = pparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) { - return HTTP_FORBIDDEN; - } - - /* Make sure method is allowed (only allowed on resources) */ - if (!(txn->req_tgt.allow & ALLOW_WRITE)) return HTTP_NOT_ALLOWED; - } - - /* Make sure Content-Range isn't specified */ - if (spool_getheader(txn->req_hdrs, "Content-Range")) - return HTTP_BAD_REQUEST; - - /* Check Content-Type */ - if ((hdr = spool_getheader(txn->req_hdrs, "Content-Type"))) { - for (mime = pparams->mime_types; mime->content_type; mime++) { - if (is_mediatype(mime->content_type, hdr0)) break; - } - } - if (!mime || !mime->content_type) { - txn->error.precond = pparams->put.supp_data_precond; - return HTTP_FORBIDDEN; - } - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Check ACL for current user */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if (!(rights & DACL_WRITECONT) || !(rights & DACL_ADDRSRC)) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = - !(rights & DACL_WRITECONT) ? DACL_WRITECONT : DACL_ADDRSRC; - mboxlist_entry_free(&mbentry); - return HTTP_FORBIDDEN; - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - - /* Open mailbox for reading */ - r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Open the DAV DB corresponding to the mailbox */ - davdb = pparams->davdb.open_db(mailbox); - - /* Find message UID for the resource, if exists */ - pparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, - txn->req_tgt.resource, 0, (void *) &ddata); - /* XXX Check errors */ - - if (ddata->imap_uid) { - /* Overwriting existing resource */ - - /* Fetch index record for the resource */ - r = mailbox_find_index_record(mailbox, ddata->imap_uid, &oldrecord, NULL); - if (r) { - syslog(LOG_ERR, "mailbox_find_index_record(%s, %u) failed: %s", - txn->req_tgt.mboxname, ddata->imap_uid, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - etag = message_guid_encode(&oldrecord.guid); - lastmod = oldrecord.internaldate; - } - else if (ddata->rowid) { - /* Unmapped URL (empty resource) */ - etag = NULL_ETAG; - lastmod = ddata->creationdate; - } - else { - /* New resource */ - etag = NULL; - lastmod = 0; - } - - /* Finished our initial read */ - mailbox_unlock_index(mailbox, NULL); - - /* Check any preconditions */ - precond = pparams->check_precond(txn, ddata, etag, lastmod); - - switch (precond) { - case HTTP_OK: - break; - - case HTTP_LOCKED: - txn->error.precond = DAV_NEED_LOCK_TOKEN; - txn->error.resource = txn->req_tgt.path; - - default: - /* We failed a precondition - don't perform the request */ - ret = precond; - goto done; - } - - /* Read body */ - txn->req_body.flags |= BODY_DECODE; - ret = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc); - if (ret) { - txn->flags.conn = CONN_CLOSE; - goto done; - } - - /* Make sure we have a body */ - qdiffsQUOTA_STORAGE = buf_len(&txn->req_body.payload); - if (!qdiffsQUOTA_STORAGE) { - txn->error.desc = "Missing request body\r\n"; - ret = HTTP_BAD_REQUEST; - goto done; - } - - /* Check if we can append a new message to mailbox */ - if ((r = append_check(txn->req_tgt.mboxname, - httpd_authstate, ACL_INSERT, qdiffs))) { - syslog(LOG_ERR, "append_check(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - if (get_preferences(txn) & PREFER_REP) flags |= PREFER_REP; - - r = mailbox_lock_index(mailbox, LOCK_EXCLUSIVE); - if (r) { - syslog(LOG_ERR, "relock index(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Parse, validate, and store the resource */ - ret = pparams->put.proc(txn, mime, mailbox, davdb, flags); - - done: - if (davdb) pparams->davdb.close_db(davdb); - mailbox_close(&mailbox); - - return ret; -} - - -/* Compare modseq in index maps -- used for sorting */ -static int map_modseq_cmp(const struct index_map *m1, - const struct index_map *m2) -{ - if (m1->modseq < m2->modseq) return -1; - if (m1->modseq > m2->modseq) return 1; - return 0; -} - - -int report_sync_col(struct transaction_t *txn, - xmlNodePtr inroot, struct propfind_ctx *fctx) -{ - int ret = 0, r, userflag, i; - struct mailbox *mailbox = NULL; - uint32_t uidvalidity = 0; - modseq_t syncmodseq = 0, basemodseq = 0, highestmodseq; - uint32_t limit = -1; - uint32_t recno; - uint32_t msgno; - uint32_t nresp = 0; - xmlNodePtr node; - struct index_state istate; - struct index_record record; - char tokenuriMAX_MAILBOX_PATH+1; - - /* XXX Handle Depth (cal-home-set at toplevel) */ - - istate.map = NULL; - - /* Open mailbox for reading */ - r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - fctx->mailbox = mailbox; - - highestmodseq = mailbox->i.highestmodseq; - if (mailbox_user_flag(mailbox, DFLAG_UNBIND, &userflag, 0)) userflag = -1; - - /* Parse children element of report */ - for (node = inroot->children; node; node = node->next) { - xmlNodePtr node2; - xmlChar *str = NULL; - if (node->type == XML_ELEMENT_NODE) { - if (!xmlStrcmp(node->name, BAD_CAST "sync-token") && - (str = xmlNodeListGetString(inroot->doc, node->children, 1))) { - if (sscanf((char *)str, XML_NS_CYRUS "sync/%u-" MODSEQ_FMT "-" MODSEQ_FMT, - &uidvalidity, &syncmodseq, &basemodseq) == 3) { - /* previous partial read token */ - if (basemodseq < mailbox->i.deletedmodseq) { - txn->error.precond = DAV_SYNC_TOKEN; - fctx->err->desc = "Token too old"; - ret = HTTP_FORBIDDEN; - } - } - else if (sscanf((char *)str, XML_NS_CYRUS "sync/%u-" MODSEQ_FMT, - &uidvalidity, &syncmodseq) == 2) { - /* regular match */ - if (syncmodseq < mailbox->i.deletedmodseq) { - txn->error.precond = DAV_SYNC_TOKEN; - fctx->err->desc = "Token too old"; - ret = HTTP_FORBIDDEN; - } - } - - /* this will fail unless we had a valid scan above */ - if (uidvalidity != mailbox->i.uidvalidity || syncmodseq == 0) { - /* DAV:valid-sync-token */ - txn->error.precond = DAV_SYNC_TOKEN; - ret = HTTP_FORBIDDEN; - } - } - else if (!xmlStrcmp(node->name, BAD_CAST "sync-level") && - (str = xmlNodeListGetString(inroot->doc, node->children, 1))) { - if (!strcmp((char *) str, "infinity")) { - fctx->err->desc = - "This server DOES NOT support infinite depth requests"; - ret = HTTP_SERVER_ERROR; - } - else if ((sscanf((char *) str, "%u", &fctx->depth) != 1) || - (fctx->depth != 1)) { - fctx->err->desc = "Illegal sync-level"; - ret = HTTP_BAD_REQUEST; - } - } - else if (!xmlStrcmp(node->name, BAD_CAST "limit")) { - for (node2 = node->children; node2; node2 = node2->next) { - if ((node2->type == XML_ELEMENT_NODE) && - !xmlStrcmp(node2->name, BAD_CAST "nresults") && - (!(str = xmlNodeListGetString(inroot->doc, - node2->children, 1)) || - (sscanf((char *) str, "%u", &limit) != 1))) { - txn->error.precond = DAV_OVER_LIMIT; - ret = HTTP_FORBIDDEN; - } - } - } - - if (str) xmlFree(str); - if (ret) goto done; - } - } - - /* Check Depth */ - if (!fctx->depth) { - fctx->err->desc = "Illegal sync-level"; - ret = HTTP_BAD_REQUEST; - goto done; - } - - if (!syncmodseq) { - basemodseq = highestmodseq; - } - - /* Construct array of records for sorting and/or fetching cached header */ - istate.mailbox = mailbox; - istate.map = xzmalloc(mailbox->i.num_records * - sizeof(struct index_map)); - - /* Find which resources we need to report */ - for (recno = 1; recno <= mailbox->i.num_records; recno++) { - /* XXX Corrupted record? Should we bail? */ - if (mailbox_read_index_record(mailbox, recno, &record)) - continue; - - /* Resource not added/removed since last sync */ - if (record.modseq <= syncmodseq) - continue; - - /* Resource replaced by a PUT, COPY, or MOVE - ignore it */ - if ((userflag >= 0) && - record.user_flagsuserflag / 32 & (1 << (userflag & 31))) - continue; - - /* Initial sync - ignore unmapped resources */ - if ((record.modseq <= basemodseq) && (record.system_flags & FLAG_EXPUNGED)) - continue; - - /* copy data into map (just like index.c - XXX helper fn? */ - istate.mapnresp.recno = recno; - istate.mapnresp.uid = record.uid; - istate.mapnresp.modseq = record.modseq; - istate.mapnresp.system_flags = record.system_flags; - for (i = 0; i < MAX_USER_FLAGS/32; i++) - istate.mapnresp.user_flagsi = record.user_flagsi; - istate.mapnresp.cache_offset = record.cache_offset; - - nresp++; - } - - if (limit < nresp) { - /* Need to truncate the responses */ - struct index_map *map = istate.map; - - /* Sort the response records by modseq */ - qsort(map, nresp, sizeof(struct index_map), - (int (*)(const void *, const void *)) &map_modseq_cmp); - - /* Our last response MUST be the last record with its modseq */ - for (nresp = limit; - nresp && mapnresp-1.modseq == mapnresp.modseq; - nresp--); - - if (!nresp) { - /* DAV:number-of-matches-within-limits */ - fctx->err->desc = "Unable to truncate results"; - ret = HTTP_NO_STORAGE; - goto done; - } - - /* highestmodseq will be modseq of last record we return */ - highestmodseq = mapnresp-1.modseq; - - /* Tell client we truncated the responses */ - xml_add_response(fctx, HTTP_NO_STORAGE); - } - - /* Report the resources within the client requested limit (if any) */ - for (msgno = 1; msgno <= nresp; msgno++) { - char *p, *resource = NULL; - struct dav_data ddata; - - if (mailbox_read_index_record(mailbox, istate.mapmsgno-1.recno, &record)) - continue; - - /* Get resource filename from Content-Disposition header */ - if ((p = index_getheader(&istate, msgno, "Content-Disposition"))) { - resource = strstr(p, "filename=") + 9; - } - if (!resource) continue; /* No filename */ - - if (*resource == '\"') { - resource++; - if ((p = strchr(resource, '\"'))) *p = '\0'; - } - else if ((p = strchr(resource, ';'))) *p = '\0'; - - memset(&ddata, 0, sizeof(struct dav_data)); - ddata.resource = resource; - - if (record.system_flags & FLAG_EXPUNGED) { - /* report as NOT FOUND - IMAP UID of 0 will cause index record to be ignored - propfind_by_resource() will append our resource name */ - propfind_by_resource(fctx, &ddata); - } - else { - fctx->record = &record; - ddata.imap_uid = record.uid; - propfind_by_resource(fctx, &ddata); - } - } - - /* Add sync-token element */ - if (highestmodseq < basemodseq) { - snprintf(tokenuri, MAX_MAILBOX_PATH, - XML_NS_CYRUS "sync/%u-" MODSEQ_FMT "-" MODSEQ_FMT, - mailbox->i.uidvalidity, highestmodseq, basemodseq); - } - else { - snprintf(tokenuri, MAX_MAILBOX_PATH, - XML_NS_CYRUS "sync/%u-" MODSEQ_FMT, - mailbox->i.uidvalidity, highestmodseq); - } - xmlNewChild(fctx->root, NULL, BAD_CAST "sync-token", BAD_CAST tokenuri); - - done: - if (istate.map) free(istate.map); - mailbox_close(&mailbox); - - return ret; -} - - -/* Perform a REPORT request */ -int meth_report(struct transaction_t *txn, void *params) -{ - struct meth_params *rparams = (struct meth_params *) params; - int ret = 0, r; - const char **hdr; - unsigned depth = 0; - xmlNodePtr inroot = NULL, outroot = NULL, cur, prop = NULL, props = NULL; - const struct report_type_t *report = NULL; - xmlNsPtr nsNUM_NAMESPACE; - struct hash_table ns_table = { 0, NULL, NULL }; - struct propfind_ctx fctx; - struct propfind_entry_list *elist = NULL; - - memset(&fctx, 0, sizeof(struct propfind_ctx)); - - /* Parse the path */ - if ((r = rparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) return r; - - /* Make sure method is allowed */ - if (!(txn->req_tgt.allow & ALLOW_DAV)) return HTTP_NOT_ALLOWED; - - /* Check Depth */ - if ((hdr = spool_getheader(txn->req_hdrs, "Depth"))) { - if (!strcmp(hdr0, "infinity")) { - depth = 2; - } - else if ((sscanf(hdr0, "%u", &depth) != 1) || (depth > 1)) { - txn->error.desc = "Illegal Depth value\r\n"; - return HTTP_BAD_REQUEST; - } - } - - /* Normalize depth so that: - * 0 = home-set collection, 1+ = calendar collection, 2+ = calendar resource - */ - if (txn->req_tgt.collection) depth++; - if (txn->req_tgt.resource) depth++; - - /* Parse the REPORT body */ - ret = parse_xml_body(txn, &inroot); - if (!ret && !inroot) { - txn->error.desc = "Missing request body\r\n"; - return HTTP_BAD_REQUEST; - } - if (ret) goto done; - - /* Add report type to our header cache */ - spool_cache_header(xstrdup(":type"), xstrdup((const char *) inroot->name), - txn->req_hdrs); - - /* Check the report type against our supported list */ - for (report = rparams->reports; report && report->name; report++) { - if (!xmlStrcmp(inroot->name, BAD_CAST report->name)) break; - } - if (!report || !report->name) { - syslog(LOG_WARNING, "REPORT %s", inroot->name); - /* DAV:supported-report */ - txn->error.precond = DAV_SUPP_REPORT; - ret = HTTP_FORBIDDEN; - goto done; - } - - if (report->flags & REPORT_NEED_MBOX) { - mbentry_t *mbentry = NULL; - int rights; - - /* Locate the mailbox */ - if ((r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL))) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: ret = HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: ret = HTTP_NOT_FOUND; - default: ret = HTTP_SERVER_ERROR; - } - goto done; - } - - /* Check ACL for current user */ - rights = httpd_myrights(httpd_authstate, mbentry->acl); - if ((rights & report->reqd_privs) != report->reqd_privs) { - if (report->reqd_privs == DACL_READFB) ret = HTTP_NOT_FOUND; - else { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = report->reqd_privs; - ret = HTTP_FORBIDDEN; - } - mboxlist_entry_free(&mbentry); - goto done; - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) ret = HTTP_UNAVAILABLE; - else ret = http_pipe_req_resp(be, txn); - goto done; - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - } - - /* Principal or Local Mailbox */ - - /* Parse children element of report */ - for (cur = inroot->children; cur; cur = cur->next) { - if (cur->type == XML_ELEMENT_NODE) { - if (!xmlStrcmp(cur->name, BAD_CAST "allprop")) { - fctx.mode = PROPFIND_ALL; - prop = cur; - break; - } - else if (!xmlStrcmp(cur->name, BAD_CAST "propname")) { - fctx.mode = PROPFIND_NAME; - fctx.prefer = PREFER_MIN; /* Don't want 404 (Not Found) */ - prop = cur; - break; - } - else if (!xmlStrcmp(cur->name, BAD_CAST "prop")) { - fctx.mode = PROPFIND_PROP; - prop = cur; - props = cur->children; - break; - } - } - } - - if (!prop && (report->flags & REPORT_NEED_PROPS)) { - txn->error.desc = "Missing <prop> element in REPORT\r\n"; - ret = HTTP_BAD_REQUEST; - goto done; - } - - /* Start construction of our multistatus response */ - if ((report->flags & REPORT_MULTISTATUS) && - !(outroot = init_xml_response("multistatus", NS_DAV, inroot, ns))) { - txn->error.desc = "Unable to create XML response\r\n"; - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Populate our propfind context */ - fctx.req_tgt = &txn->req_tgt; - fctx.depth = depth; - fctx.prefer |= get_preferences(txn); - fctx.userid = proxy_userid; - fctx.userisadmin = httpd_userisadmin; - fctx.authstate = httpd_authstate; - fctx.mailbox = NULL; - fctx.record = NULL; - fctx.reqd_privs = report->reqd_privs; - fctx.elist = NULL; - fctx.lprops = rparams->lprops; - fctx.root = outroot; - fctx.ns = ns; - fctx.ns_table = &ns_table; - fctx.err = &txn->error; - fctx.ret = &ret; - fctx.fetcheddata = 0; - - /* Parse the list of properties and build a list of callbacks */ - if (fctx.mode) ret = preload_proplist(props, &fctx); - - /* Process the requested report */ - if (!ret) ret = (*report->proc)(txn, inroot, &fctx); - - /* Output the XML response */ - if (!ret && outroot) { - /* iCalendar data in response should not be transformed */ - if (fctx.fetcheddata) txn->flags.cc |= CC_NOTRANSFORM; - - xml_response(HTTP_MULTI_STATUS, txn, outroot->doc); - } - - done: - /* Free the entry list */ - elist = fctx.elist; - while (elist) { - struct propfind_entry_list *freeme = elist; - elist = elist->next; - free(freeme); - } - - buf_free(&fctx.buf); - - free_hash_table(&ns_table, NULL); - - if (inroot) xmlFreeDoc(inroot->doc); - if (outroot) xmlFreeDoc(outroot->doc); - - return ret; -} - - -/* Perform a UNLOCK request - * - * preconditions: - * DAV:need-privileges - * DAV:lock-token-matches-request-uri - */ -int meth_unlock(struct transaction_t *txn, void *params) -{ - struct meth_params *lparams = (struct meth_params *) params; - int ret = HTTP_NO_CONTENT, r, precond, rights; - const char **hdr, *token; - struct mailbox *mailbox = NULL; - mbentry_t *mbentry = NULL; - struct dav_data *ddata; - struct index_record record; - const char *etag; - time_t lastmod; - size_t len; - void *davdb = NULL; - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Parse the path */ - if ((r = lparams->parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) return r; - - /* Make sure method is allowed (only allowed on resources) */ - if (!(txn->req_tgt.allow & ALLOW_WRITE)) return HTTP_NOT_ALLOWED; - - /* Check for mandatory Lock-Token header */ - if (!(hdr = spool_getheader(txn->req_hdrs, "Lock-Token"))) { - txn->error.desc = "Missing Lock-Token header"; - return HTTP_BAD_REQUEST; - } - token = hdr0; - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - rights = httpd_myrights(httpd_authstate, mbentry->acl); - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - - /* Open mailbox for reading */ - r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - /* Open the DAV DB corresponding to the mailbox */ - davdb = lparams->davdb.open_db(mailbox); - - /* Find message UID for the resource, if exists */ - lparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname, - txn->req_tgt.resource, 1, (void **) &ddata); - if (!ddata->rowid) { - ret = HTTP_NOT_FOUND; - goto done; - } - - /* Check if resource is locked */ - if (ddata->lock_expire <= time(NULL)) { - /* DAV:lock-token-matches-request-uri */ - txn->error.precond = DAV_BAD_LOCK_TOKEN; - ret = HTTP_CONFLICT; - goto done; - } - - /* Check if current user owns the lock */ - if (strcmp(ddata->lock_ownerid, httpd_userid)) { - /* Check ACL for current user */ - if (!(rights & DACL_ADMIN)) { - /* DAV:need-privileges */ - txn->error.precond = DAV_NEED_PRIVS; - txn->error.resource = txn->req_tgt.path; - txn->error.rights = DACL_ADMIN; - ret = HTTP_FORBIDDEN; - goto done; - } - } - - /* Check if lock token matches */ - len = strlen(ddata->lock_token); - if (token0 != '<' || strlen(token) != len+2 || tokenlen+1 != '>' || - strncmp(token+1, ddata->lock_token, len)) { - /* DAV:lock-token-matches-request-uri */ - txn->error.precond = DAV_BAD_LOCK_TOKEN; - ret = HTTP_CONFLICT; - goto done; - } - - if (ddata->imap_uid) { - /* Mapped URL - Fetch index record for the resource */ - r = mailbox_find_index_record(mailbox, ddata->imap_uid, &record, NULL); - if (r) { - txn->error.desc = error_message(r); - ret = HTTP_SERVER_ERROR; - goto done; - } - - etag = message_guid_encode(&record.guid); - lastmod = record.internaldate; - } - else { - /* Unmapped URL (empty resource) */ - etag = NULL_ETAG; - lastmod = ddata->creationdate; - } - - /* Check any preconditions */ - precond = lparams->check_precond(txn, ddata, etag, lastmod); - - if (precond != HTTP_OK) { - /* We failed a precondition - don't perform the request */ - ret = precond; - goto done; - } - - if (ddata->imap_uid) { - /* Mapped URL - Remove the lock */ - ddata->lock_token = NULL; - ddata->lock_owner = NULL; - ddata->lock_ownerid = NULL; - ddata->lock_expire = 0; - - lparams->davdb.write_resourceLOCKONLY(davdb, ddata, 1); - } - else { - /* Unmapped URL - Treat as lock-null and delete mapping entry */ - lparams->davdb.delete_resourceLOCKONLY(davdb, ddata->rowid, 1); - } - - done: - if (davdb) lparams->davdb.close_db(davdb); - mailbox_close(&mailbox); - - return ret; -}
View file
cyrus-imapd-2.5.tar.gz/imap/http_dav.h
Deleted
@@ -1,599 +0,0 @@ -/* http_dav.h -- Routines for dealing with DAV properties in httpd - * - * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef HTTP_DAV_H -#define HTTP_DAV_H - -#include <stdint.h> -#include <libical/ical.h> -#include <libxml/tree.h> - -#include "annotate.h" -#include "caldav_db.h" -#include "httpd.h" -#include "spool.h" -#include "quota.h" - -#define NULL_ETAG "da39a3ee5e6b4b0d3255bfef95601890afd80709" - /* SHA1("") */ - -#define DFLAG_UNBIND "DAV:unbind" - -#define ANNOT_NS "/vendor/cmu/cyrus-httpd/" - -#define SCHED_INBOX "Inbox/" -#define SCHED_OUTBOX "Outbox/" -#define SCHED_DEFAULT "Default/" - -/* XML namespace URIs */ -#define XML_NS_DAV "DAV:" -#define XML_NS_CALDAV "urn:ietf:params:xml:ns:caldav" -#define XML_NS_CARDDAV "urn:ietf:params:xml:ns:carddav" -#define XML_NS_ISCHED "urn:ietf:params:xml:ns:ischedule" -#define XML_NS_CS "http://calendarserver.org/ns/" -#define XML_NS_CYRUS "http://cyrusimap.org/ns/" -#define XML_NS_USERFLAG "http://cyrusimap.org/ns/userflag/" -#define XML_NS_SYSFLAG "http://cyrusimap.org/ns/sysflag/" - -/* Index into known namespace array */ -enum { - NS_DAV, - NS_CALDAV, - NS_CARDDAV, - NS_ISCHED, - NS_CS, - NS_CYRUS, -}; -#define NUM_NAMESPACE 6 - -/* Cyrus-specific privileges */ -#define DACL_MKCOL ACL_CREATE /* CY:make-collection */ -#define DACL_ADDRSRC ACL_POST /* CY:add-resource */ -#define DACL_RMCOL ACL_DELETEMBOX /* CY:remove-collection */ -#define DACL_RMRSRC ACL_DELETEMSG /* CY:remove-resource */ -#define DACL_ADMIN ACL_ADMIN /* CY:admin (aggregates - DAV:read-acl, write-acl, unlock) */ - -/* WebDAV (RFC 3744) privileges */ -#define DACL_READ (ACL_READ\ - |ACL_LOOKUP) /* DAV:read (aggregates - DAV:read-current-user-privilege-set - and CALDAV:read-free-busy) */ -#define DACL_WRITECONT ACL_INSERT /* DAV:write-content */ -#define DACL_WRITEPROPS ACL_WRITE /* DAV:write-properties */ -#define DACL_BIND (DACL_MKCOL\ - |DACL_ADDRSRC) /* DAV:bind */ -#define DACL_UNBIND (DACL_RMCOL\ - |DACL_RMRSRC) /* DAV:unbind */ -#define DACL_WRITE (DACL_WRITECONT\ - |DACL_WRITEPROPS\ - |DACL_BIND\ - |DACL_UNBIND) /* DAV:write */ -#define DACL_ALL (DACL_READ\ - |DACL_WRITE\ - |DACL_ADMIN) /* DAV:all */ - -/* CalDAV (RFC 4791) privileges */ -#define DACL_READFB ACL_USER9 /* CALDAV:read-free-busy - (implicit if user has DAV:read) */ - -/* CalDAV Scheduling (RFC 6638) privileges - - We use the same ACLs for both schedule-deliver* and schedule-send* because - functionality of Scheduling Inbox and Outbox are mutually exclusive. - We use ACL_USER9 for both read-free-busy and schedule-*-freebusy because - Scheduling Inbox and Outbox don't contribute to free-busy. -*/ -#define DACL_SCHEDFB ACL_USER9 /* For Scheduling Inbox: - CALDAV:schedule-query-freebusy - - For Scheduling Outbox: - CALDAV:schedule-send-freebusy */ -#define DACL_INVITE ACL_USER8 /* For Scheduling Inbox: - CALDAV:schedule-deliver-invite - - For Scheduling Outbox: - CALDAV:schedule-send-invite */ -#define DACL_REPLY ACL_USER7 /* For Scheduling Inbox: - CALDAV:schedule-deliver-reply - - For Scheduling Outbox: - CALDAV:schedule-send-reply */ -#define DACL_SCHED (DACL_SCHEDFB\ - |DACL_INVITE\ - |DACL_REPLY) /* For Scheduling Inbox: - CALDAV:schedule-deliver (aggregates - CALDAV:schedule-deliver-invite, - schedule-deliver-reply, - schedule-query-freebusy); - - For Scheduling Outbox: - CALDAV:schedule-send (aggregates - CALDAV:schedule-send-invite, - schedule-send-reply, - schedule-send-freebusy) */ - -/* Index into preconditions array */ -enum { - /* WebDAV (RFC 4918) preconditons */ - DAV_PROT_PROP = 1, - DAV_BAD_LOCK_TOKEN, - DAV_NEED_LOCK_TOKEN, - DAV_LOCKED, - - /* WebDAV Versioning (RFC 3253) preconditions */ - DAV_SUPP_REPORT, - DAV_RSRC_EXISTS, - - /* WebDAV ACL (RFC 3744) preconditions */ - DAV_NEED_PRIVS, - DAV_NO_INVERT, - DAV_NO_ABSTRACT, - DAV_SUPP_PRIV, - DAV_RECOG_PRINC, - - /* WebDAV Quota (RFC 4331) preconditions */ - DAV_OVER_QUOTA, - DAV_NO_DISK_SPACE, - - /* WebDAV Extended MKCOL (RFC 5689) preconditions */ - DAV_VALID_RESTYPE, - - /* WebDAV Sync (RFC 6578) preconditions */ - DAV_SYNC_TOKEN, - DAV_OVER_LIMIT, - - /* CalDAV (RFC 4791) preconditions */ - CALDAV_SUPP_DATA, - CALDAV_VALID_DATA, - CALDAV_VALID_OBJECT, - CALDAV_SUPP_COMP, - CALDAV_LOCATION_OK, - CALDAV_UID_CONFLICT, - CALDAV_SUPP_FILTER, - CALDAV_VALID_FILTER, - - /* CalDAV Scheduling (RFC 6638) preconditions */ - CALDAV_VALID_SCHED, - CALDAV_VALID_ORGANIZER, - CALDAV_UNIQUE_OBJECT, - CALDAV_SAME_ORGANIZER, - CALDAV_ALLOWED_ORG_CHANGE, - CALDAV_ALLOWED_ATT_CHANGE, - - /* iSchedule (draft-desruisseaux-ischedule) preconditions */ - ISCHED_UNSUPP_VERSION, - ISCHED_UNSUPP_DATA, - ISCHED_INVALID_DATA, - ISCHED_INVALID_SCHED, - ISCHED_ORIG_MISSING, - ISCHED_MULTIPLE_ORIG, - ISCHED_ORIG_INVALID, - ISCHED_ORIG_DENIED, - ISCHED_RECIP_MISSING, - ISCHED_RECIP_MISMATCH, - ISCHED_VERIFICATION_FAILED, - - /* CardDAV (RFC 6352) preconditions */ - CARDDAV_SUPP_DATA, - CARDDAV_VALID_DATA, - CARDDAV_UID_CONFLICT, - CARDDAV_LOCATION_OK, - CARDDAV_SUPP_FILTER -}; - -/* Preference bits */ -enum { - PREFER_MIN = (1<<0), - PREFER_REP = (1<<1), - PREFER_NOROOT = (1<<2) -}; - -#define NO_DUP_CHECK (1<<7) - - -typedef void *(*db_open_proc_t)(struct mailbox *mailbox); -typedef void (*db_close_proc_t)(void *davdb); - -/* Function to lookup DAV 'resource' in 'mailbox', with optional 'lock', - * placing the record in 'data' - */ -typedef int (*db_lookup_proc_t)(void *davdb, const char *mailbox, - const char *resource, int lock, void **data); - -/* Function to process each DAV resource in 'mailbox' with 'cb' */ -typedef int (*db_foreach_proc_t)(void *davdb, const char *mailbox, - int (*cb)(void *rock, void *data), void *rock); - -/* Context for fetching properties */ -struct propfind_entry_list; -struct prop_entry; -struct error_t; - -struct propfind_ctx { - struct request_target_t *req_tgt; /* parsed request target URL */ - unsigned mode; /* none, allprop, propname, prop */ - unsigned depth; /* 0 = root, 1 = calendar, 2 = resrc */ - unsigned prefer; /* bitmask of client preferences */ - const char *userid; /* userid client has logged in as */ - int userisadmin; /* is userid an admin */ - struct auth_state *authstate; /* authorization state for userid */ - void *davdb; /* DAV DB corresponding to collection */ - struct mailbox *mailbox; /* mailbox correspondng to collection */ - struct quota quota; /* quota info for collection */ - struct index_record *record; /* cyrus.index record for resource */ - void *data; /* DAV record for resource */ - struct buf msg_buf; /* mmap()'d resource file */ - unsigned long reqd_privs; /* privileges req'd on collections */ - int (*filter)(struct propfind_ctx *, - void *data); /* callback to filter resources */ - void *filter_crit; /* criteria to filter resources */ - db_open_proc_t open_db; /* open DAV DB for a given mailbox */ - db_close_proc_t close_db; /* close DAV DB for a given mailbox */ - db_lookup_proc_t lookup_resource; /* lookup a specific resource */ - db_foreach_proc_t foreach_resource; /* process all resources in a mailbox */ - int (*proc_by_resource)(void *rock, /* Callback to process a resource */ - void *data); - struct propfind_entry_list *elist; /* List of props to fetch w/callbacks */ - const struct prop_entry *lprops; /* Array of known "live" properties */ - xmlNodePtr root; /* root node to add to XML tree */ - xmlNsPtr *ns; /* Array of our known namespaces */ - struct hash_table *ns_table; /* Table of all ns attached to resp */ - unsigned prefix_count; /* Count of new ns added to resp */ - struct error_t *err; /* Error info to pass up to caller */ - int *ret; /* Return code to pass up to caller */ - int fetcheddata; /* Did we fetch iCalendar/vCard data? */ - struct buf buf; /* Working buffer */ -}; - - -/* Context for patching (writing) properties */ -struct proppatch_ctx { - struct request_target_t *req_tgt; /* parsed request target URL */ - unsigned meth; /* requested Method */ - struct mailbox *mailbox; /* mailbox related to the collection */ - struct index_record *record; /* record of the specific resource */ - const struct prop_entry *lprops; /* Array of known "live" properties */ - xmlNodePtr root; /* root node to add to XML tree */ - xmlNsPtr *ns; /* Array of our supported namespaces */ - struct txn *tid; /* Transaction ID for annot writes */ - struct error_t *err; /* Error info to pass up to caller */ - int *ret; /* Return code to pass up to caller */ - struct buf buf; /* Working buffer */ -}; - - -/* Structure for property status */ -struct propstat { - xmlNodePtr root; - long status; - unsigned precond; -}; - -/* Index into propstat array */ -enum { - PROPSTAT_OK = 0, - PROPSTAT_UNAUTH, - PROPSTAT_FORBID, - PROPSTAT_NOTFOUND, - PROPSTAT_CONFLICT, - PROPSTAT_FAILEDDEP, - PROPSTAT_ERROR, - PROPSTAT_OVERQUOTA -}; -#define NUM_PROPSTAT 8 - - -/* Context for "live" properties */ -struct prop_entry { - const char *name; /* Property name */ - unsigned ns; /* Property namespace */ - unsigned char flags; /* Flags for how/where props apply */ - int (*get)(const xmlChar *name, /* Callback to fetch property */ - xmlNsPtr ns, struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat *propstat, void *rock); - int (*put)(xmlNodePtr prop, /* Callback to write property */ - unsigned set, struct proppatch_ctx *pctx, - struct propstat *propstat, void *rock); - void *rock; /* Add'l data to pass to callback */ -}; - -/* Bitmask of property flags */ -enum { - PROP_ALLPROP = (1<<0), /* Returned in <allprop> request */ - PROP_COLLECTION = (1<<1), /* Returned for collection */ - PROP_RESOURCE = (1<<2), /* Returned for resource */ - PROP_PRESCREEN = (1<<3), /* Prescreen property using callback */ - PROP_NEEDPROP = (1<<4), /* Pass property node into callback */ -}; - - -/* Function to check headers for preconditions */ -typedef int (*check_precond_t)(struct transaction_t *txn, const void *data, - const char *etag, time_t lastmod); - -/* Function to insert/update DAV resource in 'data', optionally commiting txn */ -typedef int (*db_write_proc_t)(void *davdb, void *data, int commit); - -/* Function to delete resource in 'rowid', optionally commiting txn */ -typedef int (*db_delete_proc_t)(void *davdb, unsigned rowid, int commit); - -/* Function to delete all entries in 'mailbox', optionally commiting txn */ -typedef int (*db_delmbox_proc_t)(void *davdb, const char *mailbox, int commit); - -struct davdb_params { - db_open_proc_t open_db; /* open DAV DB for a given mailbox */ - db_close_proc_t close_db; /* close DAV DB for a given mailbox */ - db_lookup_proc_t lookup_resource; /* lookup a specific resource */ - db_foreach_proc_t foreach_resource; /* process all resources in a mailbox */ - /* XXX - convert these to lock management only. For everything else, - * we need to go via mailbox.c for replication support */ - db_write_proc_t write_resourceLOCKONLY; /* write a specific resource */ - db_delete_proc_t delete_resourceLOCKONLY; /* delete a specific resource */ - db_delmbox_proc_t delete_mboxDONTUSE; /* delete all resources in mailbox */ -}; - -/* - * Process 'priv', augmenting 'rights' as necessary. - * Returns 1 if processing is complete. - * Returns 0 if processing should continue in meth_acl() - */ -typedef int (*acl_proc_t)(struct transaction_t *txn, xmlNodePtr priv, - int *rights); - -/* Function to process and COPY a resource */ -typedef int (*copy_proc_t)(struct transaction_t *txn, - struct mailbox *src_mbox, struct index_record *src_rec, - struct mailbox *dest_mbox, const char *dest_rsrc, - void *dest_davdb, - unsigned overwrite, unsigned flags); - -/* Function to do special processing for DELETE method (optional) */ -typedef int (*delete_proc_t)(struct transaction_t *txn, struct mailbox *mailbox, - struct index_record *record, void *data); - -/* Function to convert to/from MIME type */ -struct mime_type_t { - const char *content_type; - const char *version; - const char *file_ext; - const char *file_ext2; - char* (*to_string)(void *); - void* (*from_string)(const char *); - void (*free)(void *); - const char* (*begin_stream)(struct buf *); - void (*end_stream)(struct buf *); -}; - -/* meth_mkcol() parameters */ -struct mkcol_params { - unsigned mbtype; /* mbtype to use for created mailbox */ - const char *xml_req; /* toplevel XML request element */ - const char *xml_resp; /* toplevel XML response element */ - unsigned xml_ns; /* namespace of response element */ -}; - -/* - * Function to do special processing for POST method (optional). - * Returns HTTP_CONTINUE if processing should continue in meth_post(), - * otherwise processing is complete. - */ -typedef int (*post_proc_t)(struct transaction_t *txn); - -/* meth_put() parameters */ -typedef int (*put_proc_t)(struct transaction_t *txn, struct mime_type_t *mime, - struct mailbox *mailbox, void *davdb, unsigned flags); - -struct put_params { - unsigned supp_data_precond; /* precond code for unsupported data */ - put_proc_t proc; /* function to process & PUT a rsrc */ -}; - -/* meth_report() parameters */ -typedef int (*report_proc_t)(struct transaction_t *txn, xmlNodePtr inroot, - struct propfind_ctx *fctx); - -struct report_type_t { - const char *name; /* report name */ - report_proc_t proc; /* function to generate the report */ - unsigned long reqd_privs; /* privileges required to run report */ - unsigned flags; /* report-specific flags */ -}; - -/* Report flags */ -enum { - REPORT_NEED_MBOX = (1<<0), - REPORT_NEED_PROPS = (1<<1), - REPORT_MULTISTATUS = (1<<2) -}; - -/* Overwrite flags */ -enum { - OVERWRITE_CHECK = -1, - OVERWRITE_NO, - OVERWRITE_YES -}; - -struct meth_params { - struct mime_type_t *mime_types; /* array of MIME types and conv funcs */ - parse_path_t parse_path; /* parse URI path & generate mboxname */ - check_precond_t check_precond; /* check headers for preconditions */ - struct davdb_params davdb; /* DAV DB access functions */ - acl_proc_t acl_ext; /* special ACL handling (extensions) */ - copy_proc_t copy; /* function to process & COPY a rsrc */ - delete_proc_t delete; /* special DELETE handling (optional) */ - struct mkcol_params mkcol; /* params for creating collection */ - post_proc_t post; /* special POST handling (optional) */ - struct put_params put; /* params for putting a resource */ - const struct prop_entry *lprops; /* array of "live" properties */ - struct report_type_t reports; /* array of reports & proc functions */ -}; - -int report_sync_col(struct transaction_t *txn, xmlNodePtr inroot, - struct propfind_ctx *fctx); - - -int parse_path(struct request_target_t *tgt, const char **errstr); -int target_to_mboxname(struct request_target_t *req_tgt, char *mboxname); -unsigned get_preferences(struct transaction_t *txn); -struct mime_type_t *get_accept_type(const char **hdr, struct mime_type_t *types); - -int parse_xml_body(struct transaction_t *txn, xmlNodePtr *root); - -/* Initialize an XML tree */ -xmlNodePtr init_xml_response(const char *resp, int ns, - xmlNodePtr req, xmlNsPtr *respNs); - -xmlNodePtr xml_add_href(xmlNodePtr parent, xmlNsPtr ns, const char *href); -xmlNodePtr xml_add_error(xmlNodePtr root, struct error_t *err, - xmlNsPtr *avail_ns); -xmlNodePtr xml_add_prop(long status, xmlNsPtr davns, - struct propstat *propstat, - const xmlChar *name, xmlNsPtr ns, - xmlChar *content, unsigned precond); -void xml_add_lockdisc(xmlNodePtr node, const char *path, struct dav_data *data); -int ensure_ns(xmlNsPtr *respNs, int ns, xmlNodePtr node, - const char *url, const char *prefix); - -int propfind_by_resource(void *rock, void *data); -int propfind_by_collection(char *mboxname, int matchlen, - int maycreate, void *rock); - -/* DAV method processing functions */ -int meth_acl(struct transaction_t *txn, void *params); -int meth_copy(struct transaction_t *txn, void *params); -int meth_delete(struct transaction_t *txn, void *params); -int meth_get_dav(struct transaction_t *txn, void *params); -int meth_lock(struct transaction_t *txn, void *params); -int meth_mkcol(struct transaction_t *txn, void *params); -int meth_propfind(struct transaction_t *txn, void *params); -int meth_proppatch(struct transaction_t *txn, void *params); -int meth_post(struct transaction_t *txn, void *params); -int meth_put(struct transaction_t *txn, void *params); -int meth_report(struct transaction_t *txn, void *params); -int meth_unlock(struct transaction_t *txn, void *params); - - -/* PROPFIND callbacks */ -int propfind_getdata(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, - struct propstat propstat, xmlNodePtr prop, - struct mime_type_t *mime_types, int precond, - const char *data, unsigned long datalen); -int propfind_fromdb(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_fromhdr(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_creationdate(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_getlength(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_getetag(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_getlastmod(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_lockdisc(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_suplock(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); - -int propfind_owner(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_supprivset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_curprivset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_acl(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_aclrestrict(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_princolset(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); - -int propfind_quota(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); - -int propfind_curprin(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); - -int propfind_addmember(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); - -int propfind_sync_token(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); - -int propfind_calurl(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_caluseraddr(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); -int propfind_abookurl(const xmlChar *name, xmlNsPtr ns, - struct propfind_ctx *fctx, xmlNodePtr resp, - struct propstat propstat, void *rock); - -/* PROPPATCH callbacks */ -int proppatch_todb(xmlNodePtr prop, unsigned set, struct proppatch_ctx *pctx, - struct propstat propstat, void *rock); -int proppatch_restype(xmlNodePtr prop, unsigned set, struct proppatch_ctx *pctx, - struct propstat propstat, void *rock); - -#endif /* HTTP_DAV_H */
View file
cyrus-imapd-2.5.tar.gz/imap/http_dblookup.c
Deleted
@@ -1,157 +0,0 @@ -/* http_dblookup.c -- Routines for dealing with HTTP based db lookups - * - * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - - -#include "carddav_db.h" -#include "http_err.h" -#include "http_dav.h" -#include <jansson.h> -#include "util.h" - -static int meth_get_db(struct transaction_t *txn, void *params); - -/* Namespace for DB lookups */ -struct namespace_t namespace_dblookup = { - URL_NS_DBLOOKUP, /*enabled*/1, "/dblookup", NULL, 0 /* auth */, - /*mbtype*/0, - ALLOW_READ, - NULL, NULL, NULL, NULL, - { - { NULL, NULL }, /* ACL */ - { NULL, NULL }, /* COPY */ - { NULL, NULL }, /* DELETE */ - { &meth_get_db, NULL }, /* GET */ - { NULL, NULL }, /* HEAD */ - { NULL, NULL }, /* LOCK */ - { NULL, NULL }, /* MKCALENDAR */ - { NULL, NULL }, /* MKCOL */ - { NULL, NULL }, /* MOVE */ - { NULL, NULL }, /* OPTIONS */ - { NULL, NULL }, /* POST */ - { NULL, NULL }, /* PROPFIND */ - { NULL, NULL }, /* PROPPATCH */ - { NULL, NULL }, /* PUT */ - { NULL, NULL }, /* REPORT */ - { NULL, NULL }, /* TRACE */ - { NULL, NULL } /* UNLOCK */ - } -}; - -static int get_email(struct transaction_t *txn __attribute__((unused)), - const char *userid, const char *key) -{ - struct carddav_db *db = NULL; - int ret = HTTP_NOT_FOUND; - - /* XXX init just incase carddav not enabled? */ - db = carddav_open_userid(userid, /*flags*/0); - if (!db) goto done; - - if (carddav_getemail(db, key)) - ret = HTTP_NO_CONTENT; - -done: - if (db) carddav_close(db); - return ret; -} - -static int get_group(struct transaction_t *txn, const char *userid, const char *key) -{ - struct carddav_db *db = NULL; - strarray_t *array = NULL; - char *result = NULL; - json_t *json; - int ret = HTTP_NOT_FOUND; - int i; - - /* XXX init just incase carddav not enabled? */ - db = carddav_open_userid(userid, /*flags*/0); - if (!db) goto done; - - array = carddav_getgroup(db, key); - if (!array) goto done; - - json = json_array(); - for (i = 0; i < strarray_size(array); i++) { - json_array_append_new(json, json_string(strarray_nth(array, i))); - } - - result = json_dumps(json, JSON_PRESERVE_ORDER|JSON_COMPACT); - json_decref(json); - - txn->resp_body.type = "application/json"; - txn->resp_body.len = strlen(result); - - write_body(HTTP_OK, txn, result, txn->resp_body.len); - ret = 0; - -done: - free(result); - if (array) strarray_free(array); - if (db) carddav_close(db); - return ret; -} - -static int meth_get_db(struct transaction_t *txn, - void *params __attribute__((unused))) -{ - const char **userhdrs; - const char **keyhdrs; - - userhdrs = spool_getheader(txn->req_hdrs, "User"); - keyhdrs = spool_getheader(txn->req_hdrs, "Key"); - - if (!userhdrs) return HTTP_BAD_REQUEST; - if (!keyhdrs) return HTTP_BAD_REQUEST; - - if (userhdrs1) return HTTP_NOT_ALLOWED; - if (keyhdrs1) return HTTP_NOT_ALLOWED; - - if (!strcmp(txn->req_uri->path, "/dblookup/email")) - return get_email(txn, userhdrs0, keyhdrs0); - - if (!strcmp(txn->req_uri->path, "/dblookup/group")) - return get_group(txn, userhdrs0, keyhdrs0); - - return HTTP_NOT_FOUND; -}
View file
cyrus-imapd-2.5.tar.gz/imap/http_err.et
Deleted
@@ -1,186 +0,0 @@ -# http_err.et -- Error codes for the Cyrus HTTP server -# -# Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# -# 3. The name "Carnegie Mellon University" must not be used to -# endorse or promote products derived from this software without -# prior written permission. For permission or any legal -# details, please contact -# Carnegie Mellon University -# Center for Technology Transfer and Enterprise Creation -# 4615 Forbes Avenue -# Suite 302 -# Pittsburgh, PA 15213 -# (412) 268-7393, fax: (412) 268-7395 -# innovation@andrew.cmu.edu -# -# 4. Redistributions of any form whatsoever must retain the following -# acknowledgment: -# "This product includes software developed by Computing Services -# at Carnegie Mellon University (http://www.cmu.edu/computing/)." -# -# CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO -# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE -# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN -# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING -# OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -error_table http - -# HTTP/1.1 Status Codes (draft-ietf-httpbis-*) -# -# 1xx: Informational - Request received, continuing process -# 2xx: Successful - Request successfully received, understood, and accepted -# 3xx: Redirection - Further action must be taken to complete the request -# 4xx: Client Error - The request contains bad syntax or cannot be fulfilled -# 5xx: Server Error - The server is incapable of performing the request - -ec HTTP_CONTINUE, - "100 Continue" - -ec HTTP_SWITCH_PROT, - "101 Switching Protocols" - -ec HTTP_OK, - "200 OK" - -ec HTTP_CREATED, - "201 Created" - -ec HTTP_NO_CONTENT, - "204 No Content" - -ec HTTP_PARTIAL, - "206 Partial Content" - -ec HTTP_MOVED, - "301 Moved Permanently" - -ec HTTP_NOT_MODIFIED, - "304 Not Modified" - -ec HTTP_TEMP_REDIRECT, - "307 Temporary Redirect" - -ec HTTP_BAD_REQUEST, - "400 Bad Request" - -ec HTTP_UNAUTHORIZED, - "401 Unauthorized" - -ec HTTP_FORBIDDEN, - "403 Forbidden" - -ec HTTP_NOT_FOUND, - "404 Not Found" - -ec HTTP_NOT_ALLOWED, - "405 Method Not Allowed" - -ec HTTP_NOT_ACCEPTABLE, - "406 Not Acceptable" - -ec HTTP_TIMEOUT, - "408 Request Timeout" - -ec HTTP_CONFLICT, - "409 Conflict" - -ec HTTP_GONE, - "410 Gone" - -ec HTTP_LENGTH_REQUIRED, - "411 Length Required" - -ec HTTP_PRECOND_FAILED, - "412 Precondition Failed" - -ec HTTP_TOO_LARGE, - "413 Payload Too Large" - -ec HTTP_TOO_LONG, - "414 URI Too Long" - -ec HTTP_BAD_MEDIATYPE, - "415 Unsupported Media Type" - -ec HTTP_UNSAT_RANGE, - "416 Range Not Satisfiable" - -ec HTTP_EXPECT_FAILED, - "417 Expectation Failed" - -ec HTTP_UPGRADE, - "426 Upgrade Required" - -ec HTTP_SERVER_ERROR, - "500 Internal Server Error" - -ec HTTP_NOT_IMPLEMENTED, - "501 Not Implemented" - -ec HTTP_BAD_GATEWAY, - "502 Bad Gateway" - -ec HTTP_UNAVAILABLE, - "503 Service Unavailable" - -ec HTTP_GATEWAY_TIMEOUT, - "504 Gateway Timeout" - -ec HTTP_BAD_VERSION, - "505 HTTP Version Not Supported" - - - -# WebDAV (RFC 2518/4918) - -ec HTTP_PROCESSING, - "102 Processing" - -ec HTTP_MULTI_STATUS, - "207 Multi-Status" - -ec HTTP_UNPROCESSABLE, - "422 Unprocessable Entity" - -ec HTTP_LOCKED, - "423 Locked" - -ec HTTP_FAILED_DEP, - "424 Failed Dependency" - -ec HTTP_NO_STORAGE, - "507 Insufficient Storage" - - - -# draft-reschke-http-status-308 - -ec HTTP_PERM_REDIRECT, - "308 Permanent Redirect" - - - -# Additional HTTP Status Codes (RFC 6585) - -ec HTTP_PRECOND_REQUIRED, - "428 Precondition Required" - - -end
View file
cyrus-imapd-2.5.tar.gz/imap/http_ischedule.c
Deleted
@@ -1,1018 +0,0 @@ -/* http_ischedule.c -- Routines for handling iSchedule in httpd - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <errno.h> -#include <syslog.h> - -#include <libical/ical.h> - -#include "global.h" -#include "httpd.h" -#include "http_caldav_sched.h" -#include "http_dav.h" -#include "http_err.h" -#include "http_proxy.h" -#include "jcal.h" -#include "map.h" -#include "proxy.h" -#include "tok.h" -#include "util.h" -#include "xmalloc.h" -#include "xcal.h" -#include "xstrlcpy.h" -#include <sasl/saslutil.h> - -#define ISCHED_WELLKNOWN_URI "/.well-known/ischedule" - -#ifdef WITH_DKIM -#include <dkim.h> - -//#define TEST - -#define BASE64_LEN(inlen) ((((inlen) + 2) / 3) * 4) - -static DKIM_LIB *dkim_lib = NULL; -static struct buf privkey = BUF_INITIALIZER; -static struct buf tmpbuf = BUF_INITIALIZER; -static struct buf b64req = BUF_INITIALIZER; -#endif /* WITH_DKIM */ - -static void isched_init(struct buf *serverinfo); -static void isched_shutdown(void); - -static int meth_get_isched(struct transaction_t *txn, void *params); -static int meth_post_isched(struct transaction_t *txn, void *params); -static int dkim_auth(struct transaction_t *txn); -static int meth_get_domainkey(struct transaction_t *txn, void *params); -static time_t compile_time; - -static struct mime_type_t isched_mime_types = { - /* First item MUST be the default type and storage format */ - { "text/calendar; charset=utf-8", "2.0", "ics", "ifb", - (char* (*)(void *)) &icalcomponent_as_ical_string_r, - (void * (*)(const char*)) &icalparser_parse_string, - (void (*)(void *)) &icalcomponent_free, NULL, NULL - }, - { "application/calendar+xml; charset=utf-8", NULL, "xcs", "xfb", - (char* (*)(void *)) &icalcomponent_as_xcal_string, - (void * (*)(const char*)) &xcal_string_as_icalcomponent, - NULL, NULL, NULL - }, -#ifdef WITH_JSON - { "application/calendar+json; charset=utf-8", NULL, "jcs", "jfb", - (char* (*)(void *)) &icalcomponent_as_jcal_string, - (void * (*)(const char*)) &jcal_string_as_icalcomponent, - NULL, NULL, NULL, - }, -#endif - { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } -}; - -struct namespace_t namespace_ischedule = { - URL_NS_ISCHEDULE, 0, "/ischedule", ISCHED_WELLKNOWN_URI, 0 /* auth */, - /*mbtype*/0, - (ALLOW_READ | ALLOW_POST | ALLOW_ISCHEDULE), - isched_init, NULL, NULL, isched_shutdown, - { - { NULL, NULL }, /* ACL */ - { NULL, NULL }, /* COPY */ - { NULL, NULL }, /* DELETE */ - { &meth_get_isched, NULL }, /* GET */ - { &meth_get_isched, NULL }, /* HEAD */ - { NULL, NULL }, /* LOCK */ - { NULL, NULL }, /* MKCALENDAR */ - { NULL, NULL }, /* MKCOL */ - { NULL, NULL }, /* MOVE */ - { &meth_options, NULL }, /* OPTIONS */ - { &meth_post_isched, NULL }, /* POST */ - { NULL, NULL }, /* PROPFIND */ - { NULL, NULL }, /* PROPPATCH */ - { NULL, NULL }, /* PUT */ - { NULL, NULL }, /* REPORT */ - { &meth_trace, NULL }, /* TRACE */ - { NULL, NULL } /* UNLOCK */ - } -}; - -struct namespace_t namespace_domainkey = { - URL_NS_DOMAINKEY, 0, "/domainkeys", "/.well-known/domainkey", 0 /* auth */, - /*mbtype*/0, - ALLOW_READ, NULL, NULL, NULL, NULL, - { - { NULL, NULL }, /* ACL */ - { NULL, NULL }, /* COPY */ - { NULL, NULL }, /* DELETE */ - { &meth_get_domainkey, NULL }, /* GET */ - { &meth_get_domainkey, NULL }, /* HEAD */ - { NULL, NULL }, /* LOCK */ - { NULL, NULL }, /* MKCALENDAR */ - { NULL, NULL }, /* MKCOL */ - { NULL, NULL }, /* MOVE */ - { &meth_options, NULL }, /* OPTIONS */ - { NULL, NULL }, /* POST */ - { NULL, NULL }, /* PROPFIND */ - { NULL, NULL }, /* PROPPATCH */ - { NULL, NULL }, /* PUT */ - { NULL, NULL }, /* REPORT */ - { &meth_trace, NULL }, /* TRACE */ - { NULL, NULL } /* UNLOCK */ - } -}; - - -/* iSchedule Receiver Capabilities */ -static int meth_get_isched(struct transaction_t *txn, - void *params __attribute__((unused))) -{ - int precond; - struct message_guid guid; - const char *etag; - static time_t lastmod = 0; - static xmlChar *buf = NULL; - static int bufsiz = 0; - - /* We don't handle GET on a anything other than ?action=capabilities */ - if (!URI_QUERY(txn->req_uri) || - strcmp(URI_QUERY(txn->req_uri), "action=capabilities")) { - txn->error.desc = "Invalid action"; - return HTTP_BAD_REQUEST; - } - - /* Generate ETag based on compile date/time of this source file. - * Extend this to include config file size/mtime if we add run-time options. - */ - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%ld", (long) compile_time); - message_guid_generate(&guid, buf_cstring(&txn->buf), buf_len(&txn->buf)); - etag = message_guid_encode(&guid); - - /* Check any preconditions, including range request */ - txn->flags.ranges = 1; - precond = check_precond(txn, NULL, etag, compile_time); - - switch (precond) { - case HTTP_OK: - case HTTP_PARTIAL: - case HTTP_NOT_MODIFIED: - /* Fill in Etag, Last-Modified, Expires, and iSchedule-Capabilities */ - txn->resp_body.etag = etag; - txn->resp_body.lastmod = compile_time; - txn->resp_body.maxage = 86400; /* 24 hrs */ - txn->flags.cc |= CC_MAXAGE; - txn->resp_body.iserial = compile_time; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - return precond; - } - - if (txn->resp_body.lastmod > lastmod) { - xmlNodePtr root, capa, node, comp, meth; - xmlNsPtr nsNUM_NAMESPACE; - struct mime_type_t *mime; - - /* Start construction of our query-result */ - if (!(root = init_xml_response("query-result", NS_ISCHED, NULL, ns))) { - txn->error.desc = "Unable to create XML response"; - return HTTP_SERVER_ERROR; - } - - capa = xmlNewChild(root, NULL, BAD_CAST "capabilities", NULL); - - node = xmlNewChild(capa, NULL, BAD_CAST "serial-number", - BAD_CAST buf_cstring(&txn->buf)); - - node = xmlNewChild(capa, NULL, BAD_CAST "versions", NULL); - node = xmlNewChild(node, NULL, BAD_CAST "version", BAD_CAST "1.0"); - - node = xmlNewChild(capa, NULL, - BAD_CAST "scheduling-messages", NULL); - comp = xmlNewChild(node, NULL, BAD_CAST "component", NULL); - xmlNewProp(comp, BAD_CAST "name", BAD_CAST "VEVENT"); - meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL); - xmlNewProp(meth, BAD_CAST "name", BAD_CAST "REQUEST"); - meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL); - xmlNewProp(meth, BAD_CAST "name", BAD_CAST "REPLY"); - meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL); - xmlNewProp(meth, BAD_CAST "name", BAD_CAST "CANCEL"); - comp = xmlNewChild(node, NULL, BAD_CAST "component", NULL); - xmlNewProp(comp, BAD_CAST "name", BAD_CAST "VTODO"); - meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL); - xmlNewProp(meth, BAD_CAST "name", BAD_CAST "REQUEST"); - meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL); - xmlNewProp(meth, BAD_CAST "name", BAD_CAST "REPLY"); - meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL); - xmlNewProp(meth, BAD_CAST "name", BAD_CAST "CANCEL"); - comp = xmlNewChild(node, NULL, BAD_CAST "component", NULL); - xmlNewProp(comp, BAD_CAST "name", BAD_CAST "VFREEBUSY"); - meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL); - xmlNewProp(meth, BAD_CAST "name", BAD_CAST "REQUEST"); - - node = xmlNewChild(capa, NULL, - BAD_CAST "calendar-data-types", NULL); - for (mime = isched_mime_types; mime->content_type; mime++) { - xmlNodePtr type = xmlNewChild(node, NULL, - BAD_CAST "calendar-data-type", NULL); - - /* Trim any charset from content-type */ - buf_reset(&txn->buf); - buf_printf(&txn->buf, "%.*s", - (int) strcspn(mime->content_type, ";"), - mime->content_type); - - xmlNewProp(type, BAD_CAST "content-type", - BAD_CAST buf_cstring(&txn->buf)); - - if (mime->version) - xmlNewProp(type, BAD_CAST "version", BAD_CAST mime->version); - } - - node = xmlNewChild(capa, NULL, BAD_CAST "attachments", NULL); - node = xmlNewChild(node, NULL, BAD_CAST "inline", NULL); - - /* Dump XML response tree into a text buffer */ - xmlDocDumpFormatMemoryEnc(root->doc, &buf, &bufsiz, "utf-8", 1); - xmlFree(root->doc); - - if (!buf) { - txn->error.desc = "Error dumping XML tree"; - return HTTP_SERVER_ERROR; - } - - lastmod = txn->resp_body.lastmod; - } - - /* Output the XML response */ - txn->resp_body.type = "application/xml; charset=utf-8"; - write_body(precond, txn, (char *) buf, bufsiz); - - xmlFree(buf); - - return 0; -} - - -/* iSchedule Receiver */ -static int meth_post_isched(struct transaction_t *txn, - void *params __attribute__((unused))) -{ - int ret = 0, r, authd = 0; - const char **hdr; - struct mime_type_t *mime = NULL; - icalcomponent *ical = NULL, *comp; - icalcomponent_kind kind = 0; - icalproperty_method meth = 0; - icalproperty *prop = NULL; - const char *uid = NULL; - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Check iSchedule-Version */ - if (!(hdr = spool_getheader(txn->req_hdrs, "iSchedule-Version")) || - strcmp(hdr0, "1.0")) { - txn->error.precond = ISCHED_UNSUPP_VERSION; - return HTTP_BAD_REQUEST; - } - - /* Check Content-Type */ - if ((hdr = spool_getheader(txn->req_hdrs, "Content-Type"))) { - for (mime = isched_mime_types; mime->content_type; mime++) { - if (is_mediatype(mime->content_type, hdr0)) break; - } - } - if (!mime || !mime->content_type) { - txn->error.precond = ISCHED_UNSUPP_DATA; - return HTTP_BAD_REQUEST; - } - - /* Check Originator */ - if (!(hdr = spool_getheader(txn->req_hdrs, "Originator"))) { - txn->error.precond = ISCHED_ORIG_MISSING; - return HTTP_BAD_REQUEST; - } - else if (hdr1) { - /* Multiple Originators */ - txn->error.precond = ISCHED_MULTIPLE_ORIG; - return HTTP_BAD_REQUEST; - } - - /* Check Recipient */ - if (!(hdr = spool_getheader(txn->req_hdrs, "Recipient"))) { - txn->error.precond = ISCHED_RECIP_MISSING; - return HTTP_BAD_REQUEST; - } - - /* Read body */ - txn->req_body.flags |= BODY_DECODE; - r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc); - if (r) { - txn->flags.conn = CONN_CLOSE; - return r; - } - - /* Make sure we have a body */ - if (!buf_len(&txn->req_body.payload)) { - txn->error.desc = "Missing request body\r\n"; - return HTTP_BAD_REQUEST; - } - - /* Check authorization */ - if (httpd_userid) { - /* Allow admins or proxyservers to auth and use iSchedule */ - authd = httpd_userisadmin || - global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS); - } - else if (!spool_getheader(txn->req_hdrs, "DKIM-Signature")) { - txn->error.desc = "No signature"; - } - else { - authd = dkim_auth(txn); - } - - if (!authd) { - ret = HTTP_FORBIDDEN; - txn->error.precond = ISCHED_VERIFICATION_FAILED; - goto done; - } - - /* Parse the iCal data for important properties */ - ical = mime->from_string(buf_cstring(&txn->req_body.payload)); - if (!ical || !icalrestriction_check(ical)) { - txn->error.precond = ISCHED_INVALID_DATA; - return HTTP_BAD_REQUEST; - } - - meth = icalcomponent_get_method(ical); - comp = icalcomponent_get_first_real_component(ical); - if (comp) { - uid = icalcomponent_get_uid(comp); - kind = icalcomponent_isa(comp); - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - } - - /* Check method preconditions */ - if (!meth || !uid || !prop) { - txn->error.precond = ISCHED_INVALID_SCHED; - ret = HTTP_BAD_REQUEST; - goto done; - } - - switch (kind) { - case ICAL_VFREEBUSY_COMPONENT: - if (meth == ICAL_METHOD_REQUEST) - ret = sched_busytime_query(txn, mime, ical); - else { - txn->error.precond = ISCHED_INVALID_SCHED; - ret = HTTP_BAD_REQUEST; - } - break; - - case ICAL_VEVENT_COMPONENT: - case ICAL_VTODO_COMPONENT: - switch (meth) { - case ICAL_METHOD_REQUEST: - case ICAL_METHOD_REPLY: - case ICAL_METHOD_CANCEL: { - struct sched_data sched_data = - { 1, meth == ICAL_METHOD_REPLY, ical, NULL, 0, NULL, NULL }; - xmlNodePtr root = NULL; - xmlNsPtr nsNUM_NAMESPACE; - struct auth_state *authstate; - icalcomponent *comp; - icalproperty *prop; - - /* Start construction of our schedule-response */ - if (!(root = init_xml_response("schedule-response", - NS_ISCHED, NULL, ns))) { - ret = HTTP_SERVER_ERROR; - txn->error.desc = "Unable to create XML response\r\n"; - goto done; - } - - authstate = auth_newstate("anonymous"); - comp = icalcomponent_get_first_real_component(ical); - - /* Process each attendee */ - for (prop = icalcomponent_get_first_property(comp, - ICAL_ATTENDEE_PROPERTY); - prop; - prop = - icalcomponent_get_next_property(comp, - ICAL_ATTENDEE_PROPERTY)) { - const char *attendee; - struct sched_param sparam; - int r; - - /* Is attendee remote or local? */ - attendee = icalproperty_get_attendee(prop); - r = caladdress_lookup(attendee, &sparam); - - /* Don't allow scheduling of remote users via an iSchedule request */ - if (sparam.flags & SCHEDTYPE_REMOTE) r = HTTP_FORBIDDEN; - - if (r) sched_data.status = REQSTAT_NOUSER; - else sched_deliver((char *) attendee, &sched_data, authstate); - - xml_add_schedresponse(root, NULL, BAD_CAST attendee, - BAD_CAST sched_data.status); - } - - /* Fill in iSchedule-Capabilities */ - txn->resp_body.iserial = compile_time; - - xml_response(HTTP_OK, txn, root->doc); - - auth_freestate(authstate); - } - break; - - default: - txn->error.precond = ISCHED_INVALID_SCHED; - ret = HTTP_BAD_REQUEST; - } - break; - - default: - txn->error.precond = ISCHED_INVALID_SCHED; - ret = HTTP_BAD_REQUEST; - } - - done: - if (ical) icalcomponent_free(ical); - - return ret; -} - - -int isched_send(struct sched_param *sparam, const char *recipient, - icalcomponent *ical, xmlNodePtr *xml) -{ - int r = 0; - struct backend *be; - static unsigned send_count = 0; - static struct buf hdrs = BUF_INITIALIZER; - const char *body, *uri; - size_t bodylen; - icalcomponent *comp; - icalcomponent_kind kind; - icalproperty *prop; - unsigned code; - struct transaction_t txn; - - *xml = NULL; - memset(&txn, 0, sizeof(struct transaction_t)); - - if (sparam->flags & SCHEDTYPE_REMOTE) uri = ISCHED_WELLKNOWN_URI; - else uri = namespace_ischedule.prefix; - - /* Open connection to iSchedule receiver. - Use header buffer to construct remote server:port/tls */ - buf_setcstr(&hdrs, sparam->server); - if (sparam->port) buf_printf(&hdrs, ":%u", sparam->port); - if (sparam->flags & SCHEDTYPE_SSL) buf_appendcstr(&hdrs, "/tls"); - if (sparam->flags & SCHEDTYPE_REMOTE) buf_appendcstr(&hdrs, "/noauth"); - be = proxy_findserver(buf_cstring(&hdrs), &http_protocol, NULL, - &backend_cached, NULL, NULL, httpd_in); - if (!be) return HTTP_UNAVAILABLE; - - /* Create iSchedule request body */ - body = icalcomponent_as_ical_string(ical); - bodylen = strlen(body); - - /* Create iSchedule request header. - * XXX Make sure that we don't use multiple headers of the same name - * or add WSP around commas in signed headers - * to obey ischedule-relaxed canonicalization. - */ - buf_reset(&hdrs); - buf_printf(&hdrs, "Host: %s", sparam->server); - if (sparam->port) buf_printf(&hdrs, ":%u", sparam->port); - buf_printf(&hdrs, "\r\n"); - buf_printf(&hdrs, "Cache-Control: no-cache, no-transform\r\n"); - if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) { - buf_printf(&hdrs, "User-Agent: %s\r\n", buf_cstring(&serverinfo)); - } - buf_printf(&hdrs, "iSchedule-Version: 1.0\r\n"); - buf_printf(&hdrs, "iSchedule-Message-ID: <cmu-ischedule-%u-%ld-%u@%s>\r\n", - getpid(), time(NULL), send_count++, config_servername); - buf_printf(&hdrs, "Content-Type: text/calendar; charset=utf-8"); - - comp = icalcomponent_get_first_real_component(ical); - kind = icalcomponent_isa(comp); - buf_printf(&hdrs, "; method=REQUEST; component=%s\r\n", - icalcomponent_kind_to_string(kind)); - - buf_printf(&hdrs, "Content-Length: %u\r\n", (unsigned) bodylen); - - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - buf_printf(&hdrs, "Originator: %s\r\n", icalproperty_get_organizer(prop)); - - if (recipient) { - /* Single recipient */ - buf_printf(&hdrs, "Recipient: %s\r\n", recipient); - } - else { - /* VFREEBUSY REQUEST - use ATTENDEES as Recipients */ - char sep = ' '; - - buf_printf(&hdrs, "Recipient:"); - for (prop = icalcomponent_get_first_property(comp, - ICAL_ATTENDEE_PROPERTY); - prop; - prop = icalcomponent_get_next_property(comp, - ICAL_ATTENDEE_PROPERTY)) { - buf_printf(&hdrs, "%c%s", sep, icalproperty_get_attendee(prop)); - sep = ','; - } - buf_printf(&hdrs, "\r\n"); - } - - buf_printf(&hdrs, "\r\n"); - - redirect: - /* Send request line */ - prot_printf(be->out, "POST %s %s\r\n", uri, HTTP_VERSION); - - if (sparam->flags & SCHEDTYPE_REMOTE) { -#ifdef WITH_DKIM - DKIM *dkim = NULL; - DKIM_STAT stat; - unsigned char *sig = NULL; - size_t siglen; - - /* Create iSchedule/DKIM signature */ - if (dkim_lib && - (dkim = dkim_sign(dkim_lib, NULL /* id */, NULL, - (dkim_sigkey_t) buf_cstring(&privkey), - (const u_char *) config_getstring(IMAPOPT_DKIM_SELECTOR), - (const u_char *) config_getstring(IMAPOPT_DKIM_DOMAIN), - /* Requires modified version of OpenDKIM - until we get OpenDOSETA */ - DKIM_CANON_ISCHEDULE, DKIM_CANON_SIMPLE, - DKIM_SIGN_RSASHA256, -1 /* entire body */, - &stat))) { - - /* Suppress folding of DKIM header */ -// stat = dkim_set_margin(dkim, 0); - - /* Add our query method list */ - stat = dkim_add_querymethod(dkim, "private-exchange", NULL); - stat = dkim_add_querymethod(dkim, "http", "well-known"); -// stat = dkim_add_querymethod(dkim, "dns", "txt"); - - /* Process the headers and body */ - stat = dkim_chunk(dkim, - (u_char *) buf_cstring(&hdrs), buf_len(&hdrs)); - stat = dkim_chunk(dkim, (u_char *) body, bodylen); - stat = dkim_chunk(dkim, NULL, 0); - stat = dkim_eom(dkim, NULL); - - /* Generate the signature */ - stat = dkim_getsighdr_d(dkim, strlen(DKIM_SIGNHEADER) + 2, - &sig, &siglen); - - /* Prepend a DKIM-Signature header */ - prot_printf(be->out, "%s: %s\r\n", DKIM_SIGNHEADER, sig); - - dkim_free(dkim); - } -#else - syslog(LOG_WARNING, "DKIM-Signature required, but DKIM isn't supported"); -#endif /* WITH_DKIM */ - } - - /* Send request headers and body */ - prot_putbuf(be->out, &hdrs); - prot_write(be->out, body, bodylen); - - /* Read response (req_hdr and req_body are actually the response) */ - txn.req_body.flags = BODY_DECODE; - r = http_read_response(be, METH_POST, &code, NULL, - &txn.req_hdrs, &txn.req_body, &txn.error.desc); - if (!r) { - switch (code) { - case 200: /* Successful */ - r = parse_xml_body(&txn, xml); - break; - - case 301: - case 302: - case 307: - case 308: /* Redirection */ - uri = spool_getheader(txn.req_hdrs, "Location")0; - goto redirect; - - default: - r = HTTP_UNAVAILABLE; - } - } - - if (txn.req_hdrs) spool_free_hdrcache(txn.req_hdrs); - buf_free(&txn.req_body.payload); - - return r; -} - - -#ifdef WITH_DKIM -static DKIM_CBSTAT isched_get_key(DKIM *dkim, DKIM_SIGINFO *sig, - u_char *buf, size_t buflen) -{ - DKIM_CBSTAT stat = DKIM_CBSTAT_NOTFOUND; - const char *domain, *selector, *query; - tok_t tok; - char *type, *opts; - - assert(dkim != NULL); - assert(sig != NULL); - - domain = (const char *) dkim_sig_getdomain(sig); - selector = (const char *) dkim_sig_getselector(sig); - if (!domain || !selector) return DKIM_CBSTAT_ERROR; - - query = (const char *) dkim_sig_gettagvalue(sig, 0, (u_char *) "q"); - if (!query) query = "dns/txt"; /* implicit default */ - - /* Parse the q= tag */ - tok_init(&tok, query, ":", 0); - while ((type = tok_next(&tok))) { - /* Split type/options */ - if ((opts = strchr(type, '/'))) *opts++ = '\0'; - - if (!strcmp(type, "private-exchange")) { - const char *prefix = config_getstring(IMAPOPT_HTTPDOCROOT); - struct buf path = BUF_INITIALIZER; - FILE *f; - - if (!prefix) continue; - - buf_setcstr(&path, prefix); - buf_printf(&path, "%s/%s/%s", - namespace_domainkey.prefix, domain, selector); - - if (!(f = fopen(buf_cstring(&path), "r"))) { - syslog(LOG_NOTICE, "%s: fopen(): %s", - buf_cstring(&path), strerror(errno)); - } - buf_free(&path); - if (!f) continue; - - memset(buf, '\0', buflen); - fgets((char *) buf, buflen, f); - fclose(f); - - if (buf0 != '\0') { - stat = DKIM_CBSTAT_CONTINUE; - break; - } - } - else if (!strcmp(type, "http") && !strcmp(opts, "well-known")) { - } - else if (!strcmp(type, "dns") && !strcmp(opts, "txt")) { - stat = DKIM_CBSTAT_DEFAULT; - break; - } - } - - tok_fini(&tok); - - return stat; -} - - -static void dkim_cachehdr(const char *name, const char *contents, void *rock) -{ - struct buf *hdrfield = &tmpbuf; - static const char *lastname = NULL; - int dup_hdr = name && lastname && !strcmp(name, lastname); - - /* Ignore private headers in our cache */ - if (name && name0 == ':') return; - - /* Combine header fields of the same name. - * Our hash table will always feed us duplicate headers consecutively. - */ - if (lastname && !dup_hdr) { - dkim_header((DKIM *) rock, - (u_char *) buf_cstring(hdrfield), buf_len(hdrfield)); - } - - lastname = name; - - if (name) { - tok_t tok; - char *token, sep = ':'; - - if (!dup_hdr) buf_setcstr(hdrfield, name); - else sep = ','; - - /* Trim leading/trailing WSP around comma-separated values */ - tok_init(&tok, contents, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT|TOK_EMPTY); - while ((token = tok_next(&tok))) { - buf_printf(hdrfield, "%c%s", sep, token); - sep = ','; - } - tok_fini(&tok); - } -} - -static int dkim_auth(struct transaction_t *txn) -{ - int authd = 0; - DKIM *dkim = NULL; - DKIM_STAT stat; - - if (!dkim_lib) return 0; - - dkim = dkim_verify(dkim_lib, NULL /* id */, NULL, &stat); - if (!dkim) return 0; - -#ifdef TEST - { - /* XXX Hack for local testing */ - dkim_query_t qtype = DKIM_QUERY_FILE; - struct buf keyfile = BUF_INITIALIZER; - - stat = dkim_options(dkim_lib, DKIM_OP_SETOPT, DKIM_OPTS_QUERYMETHOD, - &qtype, sizeof(qtype)); - - buf_printf(&keyfile, "%s/dkim.public", config_dir); - stat = dkim_options(dkim_lib, DKIM_OP_SETOPT, DKIM_OPTS_QUERYINFO, - (void *) buf_cstring(&keyfile), - buf_len(&keyfile)); - } -#endif - - /* Process the cached headers and body */ - spool_enum_hdrcache(txn->req_hdrs, &dkim_cachehdr, dkim); - dkim_cachehdr(NULL, NULL, dkim); /* Force canon of last header */ - stat = dkim_eoh(dkim); - if (stat == DKIM_STAT_OK) { - stat = dkim_body(dkim, (u_char *) buf_cstring(&txn->req_body.payload), - buf_len(&txn->req_body.payload)); - stat = dkim_eom(dkim, NULL); - } - - if (stat == DKIM_STAT_OK) authd = 1; - else if (stat == DKIM_STAT_CBREJECT) { - txn->error.desc = - "Unable to verify: HTTP request-line mismatch"; - } - else { - DKIM_SIGINFO *sig = dkim_getsignature(dkim); - - if (sig) { - const char *sigerr; - - if (dkim_sig_getbh(sig) == DKIM_SIGBH_MISMATCH) - sigerr = "body hash mismatch"; - else { - DKIM_SIGERROR err = dkim_sig_geterror(sig); - - sigerr = dkim_sig_geterrorstr(err); - } - - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%s: %s", - dkim_getresultstr(stat), sigerr); - txn->error.desc = buf_cstring(&txn->buf); - } - else txn->error.desc = dkim_getresultstr(stat); - } - - dkim_free(dkim); - - return authd; -} -#else -static int dkim_auth(struct transaction_t *txn __attribute__((unused))) -{ - syslog(LOG_WARNING, "DKIM-Signature provided, but DKIM isn't supported"); - - return 0; -} -#endif /* WITH_DKIM */ - - -/* Perform a GET/HEAD request for a domainkey */ -static int meth_get_domainkey(struct transaction_t *txn, - void *params __attribute__((unused))) -{ - int ret = 0, r, fd = -1, precond; - const char *path; - static struct buf pathbuf = BUF_INITIALIZER; - struct stat sbuf; - const char *msg_base = NULL; - unsigned long msg_size = 0; - struct resp_body_t *resp_body = &txn->resp_body; - - /* See if file exists and get Content-Length & Last-Modified time */ - buf_setcstr(&pathbuf, config_dir); - buf_appendcstr(&pathbuf, txn->req_uri->path); - path = buf_cstring(&pathbuf); - r = stat(path, &sbuf); - if (r || !S_ISREG(sbuf.st_mode)) return HTTP_NOT_FOUND; - - /* Generate Etag */ - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%ld-%ld", (long) sbuf.st_mtime, (long) sbuf.st_size); - - /* Check any preconditions, including range request */ - txn->flags.ranges = 1; - precond = check_precond(txn, NULL, buf_cstring(&txn->buf), sbuf.st_mtime); - - switch (precond) { - case HTTP_OK: - case HTTP_PARTIAL: - case HTTP_NOT_MODIFIED: - /* Fill in Content-Type, ETag, Last-Modified, and Expires */ - resp_body->type = "text/plain"; - resp_body->etag = buf_cstring(&txn->buf); - resp_body->lastmod = sbuf.st_mtime; - resp_body->maxage = 86400; /* 24 hrs */ - txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; - if (httpd_userid) txn->flags.cc |= CC_PUBLIC; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - resp_body->type = NULL; - return precond; - } - - if (txn->meth == METH_GET) { - /* Open and mmap the file */ - if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR; - map_refresh(fd, 1, &msg_base, &msg_size, sbuf.st_size, path, NULL); - } - - write_body(precond, txn, msg_base, sbuf.st_size); - - if (fd != -1) { - map_free(&msg_base, &msg_size); - close(fd); - } - - return ret; -} - - -static void isched_init(struct buf *serverinfo) -{ - if (!(config_httpmodules & IMAP_ENUM_HTTPMODULES_CALDAV) || - !config_getenum(IMAPOPT_CALDAV_ALLOWSCHEDULING)) { - /* Need CALDAV and CALDAV_SCHED in order to have ISCHEDULE */ - return; - } - - compile_time = calc_compile_time(__TIME__, __DATE__); - - if (config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS)) { - /* If backend server, we require ISCHEDULE (w/o DKIM) */ - namespace_ischedule.enabled = -1; - buf_len(serverinfo); // squash compiler warning when #undef WITH_DKIM - } -#ifdef WITH_DKIM - else { - namespace_ischedule.enabled = - config_httpmodules & IMAP_ENUM_HTTPMODULES_ISCHEDULE; - } - - if (namespace_ischedule.enabled) { - int fd; - struct buf keypath = BUF_INITIALIZER; - unsigned flags = ( DKIM_LIBFLAGS_BADSIGHANDLES | DKIM_LIBFLAGS_CACHE | -// DKIM_LIBFLAGS_KEEPFILES | DKIM_LIBFLAGS_TMPFILES | - DKIM_LIBFLAGS_VERIFYONE ); - uint64_t ttl = 3600; /* 1 hour */ - const char *requiredhdrs = { "Content-Type", "iSchedule-Version", - "Originator", "Recipient", NULL }; - const char *signhdrs = { "iSchedule-Message-ID", "User-Agent", NULL }; - const char *skiphdrs = { "Cache-Control", "Connection", - "Content-Length", "Host", "Keep-Alive", - "Proxy-Authenticate", "Proxy-Authorization", - "TE", "Trailer", "Transfer-Encoding", - "Upgrade", "Via", NULL }; - const char *senderhdrs = { "Originator", NULL }; - uint32_t ver = dkim_libversion(); - unsigned need_dkim = - namespace_ischedule.enabled == IMAP_ENUM_HTTPMODULES_ISCHEDULE; - - /* Add OpenDKIM version to serverinfo string */ - buf_printf(serverinfo, " OpenDKIM/%u.%u.%u", - (ver >> 24) & 0xff, (ver >> 16) & 0xff, (ver >> 8) & 0xff); - if (ver & 0xff) buf_printf(serverinfo, ".%u", ver & 0xff); - - /* Initialize DKIM library */ - if (!(dkim_lib = dkim_init(NULL, NULL))) { - syslog(LOG_ERR, "unable to initialize libopendkim"); - namespace_ischedule.enabled = !need_dkim; - return; - } - - /* Install our callback for doing key lookups */ - dkim_set_key_lookup(dkim_lib, isched_get_key); - - /* Setup iSchedule DKIM options */ -#ifdef TEST - flags |= ( DKIM_LIBFLAGS_SIGNLEN | DKIM_LIBFLAGS_ZTAGS ); -#endif - dkim_options(dkim_lib, DKIM_OP_SETOPT, DKIM_OPTS_FLAGS, - &flags, sizeof(flags)); - dkim_options(dkim_lib, DKIM_OP_SETOPT, DKIM_OPTS_SIGNATURETTL, - &ttl, sizeof(ttl)); - dkim_options(dkim_lib, DKIM_OP_SETOPT, DKIM_OPTS_REQUIREDHDRS, - requiredhdrs, sizeof(const char **)); - dkim_options(dkim_lib, DKIM_OP_SETOPT, DKIM_OPTS_MUSTBESIGNED, - requiredhdrs, sizeof(const char **)); - dkim_options(dkim_lib, DKIM_OP_SETOPT, DKIM_OPTS_SIGNHDRS, - signhdrs, sizeof(const char **)); - dkim_options(dkim_lib, DKIM_OP_SETOPT, DKIM_OPTS_SKIPHDRS, - skiphdrs, sizeof(const char **)); - dkim_options(dkim_lib, DKIM_OP_SETOPT, DKIM_OPTS_SENDERHDRS, - senderhdrs, sizeof(const char **)); - - /* Fetch DKIM private key for signing */ - buf_printf(&keypath, "%s/dkim.private", config_dir); - if ((fd = open(buf_cstring(&keypath), O_RDONLY)) != -1) { - const char *base = NULL; - unsigned long len = 0; - - map_refresh(fd, 1, &base, &len, - MAP_UNKNOWN_LEN, buf_cstring(&keypath), NULL); - buf_setmap(&privkey, base, len); - map_free(&base, &len); - close(fd); - } - else { - syslog(LOG_ERR, "unable to open private key file %s", - buf_cstring(&keypath)); - namespace_ischedule.enabled = !need_dkim; - } - buf_free(&keypath); - - namespace_domainkey.enabled = - config_httpmodules & IMAP_ENUM_HTTPMODULES_DOMAINKEY; - } -#endif /* WITH_DKIM */ -} - - -static void isched_shutdown(void) -{ -#ifdef WITH_DKIM - buf_free(&privkey); - buf_free(&tmpbuf); - buf_free(&b64req); - if (dkim_lib) dkim_close(dkim_lib); -#endif -}
View file
cyrus-imapd-2.5.tar.gz/imap/http_proxy.c
Deleted
@@ -1,1230 +0,0 @@ -/* http_proxy.c - HTTP proxy support functions - * - * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <assert.h> -#include <ctype.h> -#include <syslog.h> -#include <sasl/sasl.h> -#include <sasl/saslutil.h> - -#include "httpd.h" -#include "http_err.h" -#include "http_proxy.h" -#include "imap_err.h" -#include "iptostring.h" -#include "mupdate-client.h" -#include "prot.h" -#include "proxy.h" -#include "spool.h" -#include "tls.h" -#include "tok.h" -#include "version.h" -#include "xmalloc.h" -#include "xstrlcat.h" -#include "xstrlcpy.h" - -#include <libxml/uri.h> - -static int login(struct backend *s, const char *userid, - sasl_callback_t *cb, const char **status, - int noauth); -static int ping(struct backend *s, const char *userid); -static int logout(struct backend *s __attribute__((unused))); - - -HIDDEN struct protocol_t http_protocol = -{ "http", "HTTP", TYPE_SPEC, - { .spec = { &login, &ping, &logout } } -}; - - -EXPORTED const char *digest_recv_success(hdrcache_t hdrs) -{ - const char **hdr = spool_getheader(hdrs, "Authentication-Info"); - - return (hdr ? hdr0: NULL); -} - - -static const char *callback_getdata(sasl_conn_t *conn, - sasl_callback_t *callbacks, - unsigned long callbackid) -{ - sasl_callback_t *cb; - const char *result = NULL; - - for (cb = callbacks; cb->id != SASL_CB_LIST_END; cb++) { - if (cb->id == callbackid) { - switch (cb->id) { - case SASL_CB_USER: - case SASL_CB_AUTHNAME: { - sasl_getsimple_t *simple_cb = (sasl_getsimple_t *) cb->proc; - simple_cb(cb->context, cb->id, &result, NULL); - break; - } - - case SASL_CB_PASS: { - sasl_secret_t *pass; - sasl_getsecret_t *pass_cb = (sasl_getsecret_t *) cb->proc; - pass_cb(conn, cb->context, cb->id, &pass); - result = (const char *) pass->data; - break; - } - } - } - } - - return result; -} - - -#define BASE64_BUF_SIZE 21848 /* per RFC 2222bis: ((16K / 3) + 1) * 4 */ - -static int login(struct backend *s, const char *userid, - sasl_callback_t *cb, const char **status, - int noauth __attribute__((unused))) -{ - int r = 0; - socklen_t addrsize; - struct sockaddr_storage saddr_l, saddr_r; - char remoteip60, localip60; - static struct buf buf = BUF_INITIALIZER; - sasl_security_properties_t secprops = - { 0, 0xFF, PROT_BUFSIZE, 0, NULL, NULL }; /* default secprops */ - const char *mech_conf, *pass, *clientout = NULL; - struct auth_scheme_t *scheme = NULL; - unsigned need_tls = 0, tls_done = 0, clientoutlen; - hdrcache_t hdrs = NULL; - - if (status) *status = NULL; - - /* set the IP addresses */ - addrsize = sizeof(struct sockaddr_storage); - if (getpeername(s->sock, (struct sockaddr *) &saddr_r, &addrsize) || - iptostring((struct sockaddr *) &saddr_r, addrsize, remoteip, 60)) { - if (status) *status = "Failed to get remote IP address"; - return SASL_FAIL; - } - - addrsize = sizeof(struct sockaddr_storage); - if (getsockname(s->sock, (struct sockaddr *) &saddr_l, &addrsize) || - iptostring((struct sockaddr *) &saddr_l, addrsize, localip, 60)) { - if (status) *status = "Failed to get local IP address"; - return SASL_FAIL; - } - - /* Create callbacks, if necessary */ - if (!cb) { - buf_setmap(&buf, s->hostname, strcspn(s->hostname, ".")); - buf_appendcstr(&buf, "_password"); - pass = config_getoverflowstring(buf_cstring(&buf), NULL); - if (!pass) pass = config_getstring(IMAPOPT_PROXY_PASSWORD); - cb = mysasl_callbacks(NULL, /* userid */ - config_getstring(IMAPOPT_PROXY_AUTHNAME), - config_getstring(IMAPOPT_PROXY_REALM), - pass); - s->sasl_cb = cb; - } - - /* Create SASL context */ - r = sasl_client_new(s->prot->sasl_service, s->hostname, - localip, remoteip, cb, SASL_USAGE_FLAGS, &s->saslconn); - if (r != SASL_OK) goto done; - - r = sasl_setprop(s->saslconn, SASL_SEC_PROPS, &secprops); - if (r != SASL_OK) goto done; - - /* Get SASL mechanism list. We can force a particular - mechanism using a <shorthost>_mechs option */ - buf_setmap(&buf, s->hostname, strcspn(s->hostname, ".")); - buf_appendcstr(&buf, "_mechs"); - if (!(mech_conf = config_getoverflowstring(buf_cstring(&buf), NULL))) { - mech_conf = config_getstring(IMAPOPT_FORCE_SASL_CLIENT_MECH); - } - - do { - unsigned code; - const char **hdr, *errstr, *serverin; - char base64BASE64_BUF_SIZE+1; - unsigned int serverinlen; - struct body_t resp_body; -#ifdef SASL_HTTP_REQUEST - sasl_http_request_t httpreq = { "OPTIONS", /* Method */ - "*", /* URI */ - (u_char *) "", /* Empty body */ - 0, /* Zero-length body */ - 0 }; /* Persistent cxn? */ -#endif - - /* Base64 encode any client response, if necessary */ - if (clientout && scheme && (scheme->flags & AUTH_BASE64)) { - r = sasl_encode64(clientout, clientoutlen, - base64, BASE64_BUF_SIZE, &clientoutlen); - if (r != SASL_OK) break; - - clientout = base64; - } - - /* Send Authorization and/or Upgrade request to server */ - prot_puts(s->out, "OPTIONS * HTTP/1.1\r\n"); - prot_printf(s->out, "Host: %s\r\n", s->hostname); - prot_printf(s->out, "User-Agent: %s\r\n", buf_cstring(&serverinfo)); - if (scheme) { - prot_printf(s->out, "Authorization: %s %s\r\n", - scheme->name, clientout ? clientout : ""); - if (userid) prot_printf(s->out, "Authorize-As: %s\r\n", userid); - } - else { - prot_printf(s->out, "Upgrade: %s\r\n", TLS_VERSION); - if (need_tls) { - prot_puts(s->out, "Connection: Upgrade\r\n"); - need_tls = 0; - } - prot_puts(s->out, "Authorization: \r\n"); - } - prot_puts(s->out, "\r\n"); - prot_flush(s->out); - - serverin = clientout = NULL; - serverinlen = clientoutlen = 0; - - /* Read response(s) from backend until final response or error */ - do { - resp_body.flags = BODY_DISCARD; - r = http_read_response(s, METH_OPTIONS, &code, NULL, - &hdrs, &resp_body, &errstr); - if (r) { - if (status) *status = errstr; - break; - } - - if (code == 101) { /* Switching Protocols */ - if (tls_done) { - r = HTTP_BAD_GATEWAY; - if (status) *status = "TLS already active"; - break; - } - else if (backend_starttls(s, NULL)) { - r = HTTP_SERVER_ERROR; - if (status) *status = "Unable to start TLS"; - break; - } - else tls_done = 1; - } - } while (code < 200); - - switch (code) { - default: /* Failure */ - if (!r) { - r = HTTP_BAD_GATEWAY; - if (status) { - buf_reset(&buf); - buf_printf(&buf, - "Unexpected status code from backend: %u", code); - *status = buf_cstring(&buf); - } - } - break; - - case 426: /* Upgrade Required */ - if (tls_done) { - r = HTTP_BAD_GATEWAY; - if (status) *status = "TLS already active"; - } - else need_tls = 1; - break; - - case 200: /* OK */ - if (scheme->recv_success && - (serverin = scheme->recv_success(hdrs))) { - serverinlen = strlen(serverin); - } - /* Fall through and process any success data */ - - case 401: /* Unauthorized */ - if (!serverin) { - int i = 0; - - hdr = spool_getheader(hdrs, "WWW-Authenticate"); - - if (!scheme) { - unsigned avail_auth_schemes = 0; - const char *mech = NULL; - size_t len; - - /* Compare authentication schemes offered in - * WWW-Authenticate header(s) to what we support */ - buf_reset(&buf); - for (i = 0; hdr && hdri; i++) { - len = strcspn(hdri, " "); - - for (scheme = auth_schemes; scheme->name; scheme++) { - if (!strncmp(scheme->name, hdri, len) && - !((scheme->flags & AUTH_NEED_PERSIST) && - (resp_body.flags & BODY_CLOSE))) { - /* Tag the scheme as available */ - avail_auth_schemes |= (1 << scheme->idx); - - /* Add SASL-based schemes to SASL mech list */ - if (scheme->saslmech) { - if (buf_len(&buf)) buf_putc(&buf, ' '); - buf_appendcstr(&buf, scheme->saslmech); - } - break; - } - } - } - - /* If we have a mech_conf, use it */ - if (mech_conf && buf_len(&buf)) { - char *conf = xstrdup(mech_conf); - char *newmechlist = - intersect_mechlists(conf, - (char *) buf_cstring(&buf)); - - if (newmechlist) { - buf_setcstr(&buf, newmechlist); - free(newmechlist); - } - else { - syslog(LOG_DEBUG, "%s did not offer %s", - s->hostname, mech_conf); - buf_reset(&buf); - } - free(conf); - } - -#ifdef SASL_HTTP_REQUEST - /* Set HTTP request as specified above (REQUIRED) */ - httpreq.non_persist = (resp_body.flags & BODY_CLOSE); - sasl_setprop(s->saslconn, SASL_HTTP_REQUEST, &httpreq); -#endif - - /* Try to start SASL exchange using available mechs */ - r = sasl_client_start(s->saslconn, buf_cstring(&buf), - NULL, /* no prompts */ - NULL, NULL, /* no initial resp */ - &mech); - - if (mech) { - /* Find auth scheme associated with chosen SASL mech */ - for (scheme = auth_schemes; scheme->name; scheme++) { - if (scheme->saslmech && - !strcmp(scheme->saslmech, mech)) break; - } - } - else { - /* No matching SASL mechs - try Basic */ - scheme = &auth_schemesAUTH_BASIC; - if (!(avail_auth_schemes & (1 << scheme->idx))) { - need_tls = !tls_done; - break; /* case 401 */ - } - } - - /* Find the associated WWW-Authenticate header */ - for (i = 0; hdr && hdri; i++) { - len = strcspn(hdri, " "); - if (!strncmp(scheme->name, hdri, len)) break; - } - } - - /* Get server challenge, if any */ - if (hdr) { - const char *p = strchr(hdri, ' '); - serverin = p ? ++p : ""; - serverinlen = strlen(serverin); - } - } - - if (serverin) { - /* Perform the next step in the auth exchange */ - - if (scheme->idx == AUTH_BASIC) { - /* Don't care about "realm" in server challenge */ - const char *authid = - callback_getdata(s->saslconn, cb, SASL_CB_AUTHNAME); - pass = callback_getdata(s->saslconn, cb, SASL_CB_PASS); - - buf_reset(&buf); - buf_printf(&buf, "%s:%s", authid, pass); - clientout = buf_cstring(&buf); - clientoutlen = buf_len(&buf); - } - else { - /* Base64 decode any server challenge, if necessary */ - if (serverin && (scheme->flags & AUTH_BASE64)) { - r = sasl_decode64(serverin, serverinlen, - base64, BASE64_BUF_SIZE, &serverinlen); - if (r != SASL_OK) break; /* case 401 */ - - serverin = base64; - } - - /* SASL mech (Digest, Negotiate, NTLM) */ - r = sasl_client_step(s->saslconn, serverin, serverinlen, - NULL, /* no prompts */ - &clientout, &clientoutlen); - } - } - break; /* case 401 */ - } - - } while (need_tls || clientout); - - done: - if (hdrs) spool_free_hdrcache(hdrs); - - if (r && status && !*status) *status = sasl_errstring(r, NULL, NULL); - - return r; -} - - -static int ping(struct backend *s, const char *userid) -{ - unsigned code = 0; - const char *errstr; - hdrcache_t resp_hdrs = NULL; - struct body_t resp_body; - - /* Send Authorization request to server */ - prot_puts(s->out, "OPTIONS * HTTP/1.1\r\n"); - prot_printf(s->out, "Host: %s\r\n", s->hostname); - prot_printf(s->out, "User-Agent: %s\r\n", buf_cstring(&serverinfo)); - if (userid) prot_printf(s->out, "Authorize-As: %s\r\n", userid); - prot_puts(s->out, "\r\n"); - prot_flush(s->out); - - /* Read response(s) from backend until final response or error */ - do { - resp_body.flags = BODY_DISCARD; - if (http_read_response(s, METH_OPTIONS, &code, NULL, - &resp_hdrs, &resp_body, &errstr)) { - break; - } - } while (code < 200); - - if (resp_hdrs) spool_free_hdrcache(resp_hdrs); - - return (code != 200); -} - - -static int logout(struct backend *s __attribute__((unused))) -{ - /* Nothing to send, client just closes connection */ - return 0; -} - - -/* proxy mboxlist_lookup; on misses, it asks the listener for this - * machine to make a roundtrip to the master mailbox server to make - * sure it's up to date - */ -EXPORTED int http_mlookup(const char *name, mbentry_t **mbentryp, void *tid) -{ - mbentry_t *mbentry = NULL; - int r; - - r = mboxlist_lookup(name, &mbentry, tid); - if (r == IMAP_MAILBOX_NONEXISTENT && config_mupdate_server) { - kick_mupdate(); - r = mboxlist_lookup(name, &mbentry, tid); - } - if (r) return r; - if (mbentry->mbtype & MBTYPE_RESERVE) { - r = IMAP_MAILBOX_RESERVED; - goto done; - } - if (mbentry->mbtype & MBTYPE_MOVING) { - r = IMAP_MAILBOX_MOVED; - goto done; - } - if (mbentry->mbtype & MBTYPE_DELETED) { - r = IMAP_MAILBOX_NONEXISTENT; - goto done; - } - -done: - if (!r && mbentryp) *mbentryp = mbentry; - else mboxlist_entry_free(&mbentry); - return r; -} - - -/* Fetch protocol and host used for request from headers */ -EXPORTED void http_proto_host(hdrcache_t req_hdrs, const char **proto, const char **host) -{ - const char **fwd; - - if (config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS) && - (fwd = spool_getheader(req_hdrs, "Forwarded"))) { - /* Proxied request - parse last Forwarded header for proto and host */ - /* XXX This is destructive of the header but we don't care - * and more importantly, we need the tokens available after tok_fini() - */ - tok_t tok; - char *token; - - while (fwd1) ++fwd; /* Skip to last Forwarded header */ - - tok_initm(&tok, (char *) fwd0, ";", 0); - while ((token = tok_next(&tok))) { - if (proto && !strncmp(token, "proto=", 6)) *proto = token+6; - else if (host && !strncmp(token, "host=", 5)) *host = token+5; - } - tok_fini(&tok); - } - else { - /* Use our protocol and host */ - if (proto) *proto = https ? "https" : "http"; - if (host) *host = *spool_getheader(req_hdrs, "Host"); - } -} - -/* Construct and write Via header to protstream. */ -static void write_forwarding_hdrs(struct protstream *pout, hdrcache_t hdrs, - const char *version, const char *proto) -{ - const char **via = spool_getheader(hdrs, "Via"); - const char **fwd = spool_getheader(hdrs, "Forwarded"); - - /* Add any existing Via headers */ - for (; via && *via; via++) prot_printf(pout, "Via: %s\r\n", *via); - - /* Create our own Via header */ - prot_printf(pout, "Via: %s %s", version+5, config_servername); - if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) { - prot_printf(pout, " (Cyrus/%s)", cyrus_version()); - } - prot_puts(pout, "\r\n"); - - /* Add any existing Forwarded headers */ - for (; fwd && *fwd; fwd++) prot_printf(pout, "Forwarded: %s\r\n", *fwd); - - /* Create our own Forwarded header */ - if (proto) { - char localip60, remoteip60, *p; - socklen_t salen = sizeof(httpd_remoteaddr); - const char **host = spool_getheader(hdrs, "Host"); - - prot_printf(pout, "Forwarded: proto=%s", proto); - if (host) prot_printf(pout, ";host=%s", *host); - if (!iptostring((struct sockaddr *)&httpd_remoteaddr, salen, - remoteip, 60)) { - if ((p = strrchr(remoteip, ';'))) *p = '\0'; - prot_printf(pout, ";for=%s", remoteip); - } - if (!iptostring((struct sockaddr *)&httpd_localaddr, salen, - localip, 60)) { - if ((p = strrchr(localip, ';'))) *p = '\0'; - prot_printf(pout, ";by=%s", localip); - } - prot_puts(pout, "\r\n"); - } -} - - -/* Write end-to-end header (ignoring hop-by-hop) from cache to protstream. */ -static void write_cachehdr(const char *name, const char *contents, void *rock) -{ - struct protstream *pout = (struct protstream *) rock; - const char **hdr, *hop_by_hop = - { "authorization", "connection", "content-length", "expect", - "forwarded", "host", "keep-alive", "strict-transport-security", - "te", "trailer", "transfer-encoding", "upgrade", "via", NULL }; - - /* Ignore private headers in our cache */ - if (name0 == ':') return; - - for (hdr = hop_by_hop; *hdr && strcmp(name, *hdr); hdr++); - - if (!*hdr) { - if (!strcmp(name, "max-forwards")) { - /* Decrement Max-Forwards before forwarding */ - unsigned long max = strtoul(contents, NULL, 10); - - prot_printf(pout, "Max-Forwards: %lu\r\n", max-1); - } - else { - prot_printf(pout, "%c%s: %s\r\n", toupper(*name), name+1, contents); - } - } -} - - -/* Read a response from backend */ -EXPORTED int http_read_response(struct backend *be, unsigned meth, unsigned *code, - const char **statline, hdrcache_t *hdrs, - struct body_t *body, const char **errstr) -{ - static char statbuf2048; - const char **conn; - int c = EOF; - - if (statline) *statline = statbuf; - *errstr = NULL; - *code = HTTP_BAD_GATEWAY; - - if (*hdrs) spool_free_hdrcache(*hdrs); - if (!(*hdrs = spool_new_hdrcache())) { - *errstr = "Unable to create header cache for backend response"; - return HTTP_SERVER_ERROR; - } - if (!prot_fgets(statbuf, sizeof(statbuf), be->in) || - (sscanf(statbuf, HTTP_VERSION " %u ", code) != 1) || - spool_fill_hdrcache(be->in, NULL, *hdrs, NULL)) { - *errstr = "Unable to read status-line/headers from backend"; - return HTTP_BAD_GATEWAY; - } - eatline(be->in, c); /* CRLF separating headers & body */ - - /* 1xx (provisional) response - nothing else to do */ - if (*code < 200) return 0; - - /* Final response */ - if (!body) return 0; /* body will be piped */ - if (!(body->flags & BODY_DISCARD)) buf_reset(&body->payload); - - /* Check connection persistence */ - if (!strncmp(statbuf, "HTTP/1.0 ", 9)) body->flags |= BODY_CLOSE; - for (conn = spool_getheader(*hdrs, "Connection"); conn && *conn; conn++) { - tok_t tok = - TOK_INITIALIZER(*conn, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT); - char *token; - - while ((token = tok_next(&tok))) { - if (!strcasecmp(token, "keep-alive")) body->flags &= ~BODY_CLOSE; - else if (!strcasecmp(token, "close")) body->flags |= BODY_CLOSE; - } - tok_fini(&tok); - } - - /* Not expecting a body for 204/304 response or any HEAD response */ - switch (*code){ - case 204: /* No Content */ - case 304: /* Not Modified */ - break; - - default: - if (meth == METH_HEAD) break; - - else { - body->flags |= BODY_RESPONSE; - body->framing = FRAMING_UNKNOWN; - - if (read_body(be->in, *hdrs, body, errstr)) { - return HTTP_BAD_GATEWAY; - } - } - } - - return 0; -} - - -/* Send a cached response to the client */ -static void send_response(const char *statline, hdrcache_t hdrs, - struct buf *body, struct txn_flags_t *flags) -{ - unsigned long len; - - /* Stop method processing alarm */ - alarm(0); - - /* - * - Use cached Status Line - * - Add/append-to Via: header - * - Add our own hop-by-hop headers - * - Use all cached end-to-end headers - */ - prot_puts(httpd_out, statline); - write_forwarding_hdrs(httpd_out, hdrs, HTTP_VERSION, NULL); - if (flags->conn) { - /* Construct Connection header */ - const char *conn_tokens = - { "close", "Upgrade", "Keep-Alive", NULL }; - - if (flags->conn & CONN_KEEPALIVE) { - prot_printf(httpd_out, "Keep-Alive: timeout=%d\r\n", httpd_timeout); - } - - comma_list_hdr("Connection", conn_tokens, flags->conn); - } - if (httpd_tls_done) { - prot_puts(httpd_out, "Strict-Transport-Security: max-age=600\r\n"); - } - - spool_enum_hdrcache(hdrs, &write_cachehdr, httpd_out); - - if (!body || !(len = buf_len(body))) { - /* Empty body -- use payload headers from response, if any */ - const char **hdr; - - if (!flags->ver1_0 && - (hdr = spool_getheader(hdrs, "Transfer-Encoding"))) { - prot_printf(httpd_out, "Transfer-Encoding: %s\r\n", hdr0); - if ((hdr = spool_getheader(hdrs, "Trailer"))) { - prot_printf(httpd_out, "Trailer: %s\r\n", hdr0); - } - } - else if ((hdr = spool_getheader(hdrs, "Content-Length"))) { - prot_printf(httpd_out, "Content-Length: %s\r\n", hdr0); - } - - prot_puts(httpd_out, "\r\n"); - } - else { - /* Body is buffered, so send using "identity" TE */ - prot_printf(httpd_out, "Content-Length: %lu\r\n\r\n", len); - prot_putbuf(httpd_out, body); - } -} - - -/* Proxy (pipe) a chunk of body data to a client/server. */ -static unsigned pipe_chunk(struct protstream *pin, struct protstream *pout, - unsigned len) -{ - char bufPROT_BUFSIZE; - unsigned n = 0; - - /* Read 'len' octets */ - for (; len; len -= n) { - n = prot_read(pin, buf, MIN(len, PROT_BUFSIZE)); - if (!n) break; - - prot_write(pout, buf, n); - } - - return n; -} - - -/* Proxy (pipe) a response body to a client/server. */ -static int pipe_resp_body(struct protstream *pin, struct protstream *pout, - hdrcache_t resp_hdrs, struct body_t *resp_body, - int ver1_0, const char **errstr) -{ - char bufPROT_BUFSIZE; - - if (resp_body->framing == FRAMING_UNKNOWN) { - /* Get message framing */ - int r = parse_framing(resp_hdrs, resp_body, errstr); - if (r) return r; - } - - /* Read and pipe the body */ - switch (resp_body->framing) { - case FRAMING_LENGTH: - /* Read 'len' octets */ - if (resp_body->len && !pipe_chunk(pin, pout, resp_body->len)) { - syslog(LOG_ERR, "prot_read() error"); - *errstr = "Unable to read body data"; - return HTTP_BAD_GATEWAY; - } - break; - - case FRAMING_CHUNKED: { - unsigned chunk; - char *c; - - /* Read chunks until last-chunk (zero chunk-size) */ - do { - /* Read chunk-size */ - prot_NONBLOCK(pin); - c = prot_fgets(buf, PROT_BUFSIZE, pin); - prot_BLOCK(pin); - if (!c) { - prot_flush(pout); - c = prot_fgets(buf, PROT_BUFSIZE, pin); - } - if (!c || sscanf(buf, "%x", &chunk) != 1) { - *errstr = "Unable to read chunk size"; - return HTTP_BAD_GATEWAY; - - /* XXX Do we need to parse chunk-ext? */ - } - else if (chunk > resp_body->max - resp_body->len) - return HTTP_TOO_LARGE; - else if (!ver1_0) prot_puts(pout, buf); - - if (chunk) { - /* Read 'chunk' octets */ - if (!pipe_chunk(pin, pout, chunk)) { - syslog(LOG_ERR, "prot_read() error"); - *errstr = "Unable to read chunk data"; - return HTTP_BAD_GATEWAY; - } - } - else { - /* Read any trailing headers */ - for (*c = prot_ungetc(prot_getc(pin), pin); - *c != '\r' && *c != '\n'; - *c = prot_ungetc(prot_getc(pin), pin)) { - if (!prot_fgets(buf, sizeof(buf), pin)) { - *errstr = "Error reading trailer"; - return HTTP_BAD_GATEWAY; - } - else if (!ver1_0) prot_puts(pout, buf); - } - } - - - /* Read CRLF terminating the chunk/trailer */ - if (!prot_fgets(buf, sizeof(buf), pin)) { - *errstr = "Missing CRLF following chunk/trailer"; - return HTTP_BAD_GATEWAY; - } - else if (!ver1_0) prot_puts(pout, buf); - - } while (chunk); - - break; - } - - case FRAMING_CLOSE: - /* Read until EOF */ - if (pipe_chunk(pin, pout, UINT_MAX) || !pin->eof) - return HTTP_BAD_GATEWAY; - - break; - - default: - /* XXX Should never get here */ - *errstr = "Unknown length of body data"; - return HTTP_BAD_GATEWAY; - } - - return 0; -} - - - -/* Proxy (pipe) a client-request/server-response to/from a backend. */ -EXPORTED int http_pipe_req_resp(struct backend *be, struct transaction_t *txn) -{ - int r = 0, sent_body = 0; - xmlChar *uri; - unsigned code; - const char **hdr, *statline; - hdrcache_t resp_hdrs = NULL; - struct body_t resp_body; - - /* - * Send client request to backend: - * - * - Piece the Request Line back together - * - Add/append-to Via: header - * - Add Expect:100-continue header (for synchonicity) - * - Use all cached end-to-end headers from client - * - Body will be sent using "chunked" TE, since we might not have it yet - */ - uri = xmlURIEscapeStr(BAD_CAST txn->req_uri->path, BAD_CAST "/"); - prot_printf(be->out, "%s %s", txn->req_line.meth, uri); - free(uri); - if (URI_QUERY(txn->req_uri)) { - prot_printf(be->out, "?%s", URI_QUERY(txn->req_uri)); - } - prot_printf(be->out, " %s\r\n", HTTP_VERSION); - prot_printf(be->out, "Host: %s\r\n", be->hostname); - write_forwarding_hdrs(be->out, txn->req_hdrs, txn->req_line.ver, - https ? "https" : "http"); - spool_enum_hdrcache(txn->req_hdrs, &write_cachehdr, be->out); - if ((hdr = spool_getheader(txn->req_hdrs, "TE"))) { - for (; *hdr; hdr++) prot_printf(be->out, "TE: %s\r\n", *hdr); - } - if (http_methodstxn->meth.flags & METH_NOBODY) - prot_puts(be->out, "Content-Length: 0\r\n"); - else if (spool_getheader(txn->req_hdrs, "Transfer-Encoding") || - spool_getheader(txn->req_hdrs, "Content-Length")) { - prot_puts(be->out, "Expect: 100-continue\r\n"); - prot_puts(be->out, "Transfer-Encoding: chunked\r\n"); - } - prot_puts(be->out, "\r\n"); - prot_flush(be->out); - - /* Read response(s) from backend until final response or error */ - memset(&resp_body, 0, sizeof(struct body_t)); - - do { - r = http_read_response(be, txn->meth, &code, &statline, - &resp_hdrs, NULL, &txn->error.desc); - if (r) break; - - if (code == 100) { /* Continue */ - if (!sent_body++) { - unsigned len; - - /* Read body from client */ - r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, - &txn->error.desc); - if (r) { - /* Couldn't get the body and can't finish request */ - txn->flags.conn = CONN_CLOSE; - break; - } - - /* Send single-chunk body to backend to complete the request */ - if ((len = buf_len(&txn->req_body.payload))) { - prot_printf(be->out, "%x\r\n", len); - prot_putbuf(be->out, &txn->req_body.payload); - prot_puts(be->out, "\r\n"); - } - prot_puts(be->out, "0\r\n\r\n"); - prot_flush(be->out); - } - else { - prot_puts(httpd_out, statline); - spool_enum_hdrcache(resp_hdrs, &write_cachehdr, httpd_out); - prot_puts(httpd_out, "\r\n"); - prot_flush(httpd_out); - } - } - } while (code < 200); - - if (!r) { - /* Send response to client */ - send_response(statline, resp_hdrs, NULL, &txn->flags); - - /* Not expecting a body for 204/304 response or any HEAD response */ - switch (code) { - case 204: /* No Content */ - case 304: /* Not Modified */ - break; - - default: - if (txn->meth == METH_HEAD) break; - - if (pipe_resp_body(be->in, httpd_out, resp_hdrs, &resp_body, - txn->flags.ver1_0, &txn->error.desc)) { - /* Couldn't pipe the body and can't finish response */ - txn->flags.conn = CONN_CLOSE; - } - } - } - - if (r || (resp_body.flags & BODY_CLOSE)) proxy_downserver(be); - - if (resp_hdrs) spool_free_hdrcache(resp_hdrs); - - return r; -} - - -/* - * Proxy a COPY/MOVE client-request when the source and destination are - * on different backends. This is handled as a GET from the source and - * PUT on the destination, while obeying any Overwrite header. - * - * For a MOVE request, we also LOCK, DELETE, and possibly UNLOCK the source. - * - * XXX This function buffers the response bodies of the LOCK & GET requests. - * The response body of the PUT request is piped to the client. - */ -EXPORTED int http_proxy_copy(struct backend *src_be, struct backend *dest_be, - struct transaction_t *txn) -{ - int r = 0, sent_body; - unsigned code; - char *lock = NULL; - const char **hdr, *statline; - hdrcache_t resp_hdrs = NULL; - struct body_t resp_body; - -#define write_hdr(pout, name, hdrs) \ - if ((hdr = spool_getheader(hdrs, name))) \ - for (; *hdr; hdr++) prot_printf(pout, "%s: %s\r\n", name, *hdr) - - - resp_body.payload = txn->resp_body.payload; - - if (txn->meth == METH_MOVE) { - /* - * Send a LOCK request to source backend: - * - * - Use any relevant conditional headers specified by client - */ - prot_printf(src_be->out, "LOCK %s %s\r\n" - "Host: %s\r\n" - "User-Agent: %s\r\n", - txn->req_tgt.path, HTTP_VERSION, - src_be->hostname, buf_cstring(&serverinfo)); - write_hdr(src_be->out, "If", txn->req_hdrs); - write_hdr(src_be->out, "If-Match", txn->req_hdrs); - write_hdr(src_be->out, "If-Unmodified-Since", txn->req_hdrs); - write_hdr(src_be->out, "If-Schedule-Tag-Match", txn->req_hdrs); - - assert(!buf_len(&txn->buf)); - buf_printf_markup(&txn->buf, 0, - "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"); - buf_printf_markup(&txn->buf, 0, "<D:lockinfo xmlns:D='DAV:'>"); - buf_printf_markup(&txn->buf, 1, - "<D:lockscope><D:exclusive/></D:lockscope>"); - buf_printf_markup(&txn->buf, 1, - "<D:locktype><D:write/></D:locktype>"); - buf_printf_markup(&txn->buf, 1, "<D:owner>%s</D:owner>", httpd_userid); - buf_printf_markup(&txn->buf, 0, "</D:lockinfo>"); - - prot_printf(src_be->out, - "Content-Type: application/xml; charset=utf-8\r\n" - "Content-Length: %u\r\n\r\n%s", - (unsigned)buf_len(&txn->buf), buf_cstring(&txn->buf)); - buf_reset(&txn->buf); - - prot_flush(src_be->out); - - /* Read response(s) from source backend until final response or error */ - resp_body.flags = 0; - - do { - r = http_read_response(src_be, METH_LOCK, &code, &statline, - &resp_hdrs, &resp_body, &txn->error.desc); - if (r) { - proxy_downserver(src_be); - goto done; - } - } while (code < 200); - - /* Get lock token */ - if ((hdr = spool_getheader(resp_hdrs, "Lock-Token"))) - lock = xstrdup(*hdr); - - switch (code) { - case 200: - /* Success, continue */ - break; - - case 201: - /* Created empty resource, treat as 404 (Not Found) */ - r = HTTP_NOT_FOUND; - goto delete; - - case 409: - /* Failed to create resource, treat as 404 (Not Found) */ - r = HTTP_NOT_FOUND; - goto done; - - default: - /* Send failure response to client */ - send_response(statline, resp_hdrs, &resp_body.payload, &txn->flags); - goto done; - } - } - - - /* - * Send a GET request to source backend to fetch body: - * - * - Use any relevant conditional headers specified by client - * (if not already sent in LOCK request) - */ - prot_printf(src_be->out, "GET %s %s\r\n" - "Host: %s\r\n" - "User-Agent: %s\r\n", - txn->req_tgt.path, HTTP_VERSION, - src_be->hostname, buf_cstring(&serverinfo)); - if (txn->meth != METH_MOVE) { - write_hdr(src_be->out, "If", txn->req_hdrs); - write_hdr(src_be->out, "If-Match", txn->req_hdrs); - write_hdr(src_be->out, "If-Unmodified-Since", txn->req_hdrs); - write_hdr(src_be->out, "If-Schedule-Tag-Match", txn->req_hdrs); - } - prot_puts(src_be->out, "\r\n"); - prot_flush(src_be->out); - - /* Read response(s) from source backend until final response or error */ - resp_body.flags = 0; - - do { - r = http_read_response(src_be, METH_GET, &code, &statline, - &resp_hdrs, &resp_body, &txn->error.desc); - if (r || (resp_body.flags & BODY_CLOSE)) { - proxy_downserver(src_be); - goto done; - } - } while (code < 200); - - if (code != 200) { - /* Send failure response to client */ - send_response(statline, resp_hdrs, &resp_body.payload, &txn->flags); - goto done; - } - - - /* - * Send a synchonizing PUT request to dest backend: - * - * - Add Expect:100-continue header (for synchonicity) - * - Obey Overwrite by adding If-None-Match header - * - Use any TE, Prefer, Accept* headers specified by client - * - Use Content-Type, -Encoding, -Language headers from GET response - * - Body is buffered, so send using "identity" TE - */ - prot_printf(dest_be->out, "PUT %s %s\r\n" - "Host: %s\r\n" - "User-Agent: %s\r\n" - "Expect: 100-continue\r\n", - *spool_getheader(txn->req_hdrs, "Destination"), HTTP_VERSION, - dest_be->hostname, buf_cstring(&serverinfo)); - hdr = spool_getheader(txn->req_hdrs, "Overwrite"); - if (hdr && !strcmp(*hdr, "F")) - prot_puts(dest_be->out, "If-None-Match: *\r\n"); - write_hdr(dest_be->out, "TE", txn->req_hdrs); - write_hdr(dest_be->out, "Prefer", txn->req_hdrs); - write_hdr(dest_be->out, "Accept", txn->req_hdrs); - write_hdr(dest_be->out, "Accept-Charset", txn->req_hdrs); - write_hdr(dest_be->out, "Accept-Encoding", txn->req_hdrs); - write_hdr(dest_be->out, "Accept-Language", txn->req_hdrs); - write_hdr(dest_be->out, "Content-Type", resp_hdrs); - write_hdr(dest_be->out, "Content-Encoding", resp_hdrs); - write_hdr(dest_be->out, "Content-Language", resp_hdrs); - prot_printf(dest_be->out, "Content-Length: %u\r\n\r\n", - (unsigned)buf_len(&resp_body.payload)); - prot_flush(dest_be->out); - - /* Read response(s) from dest backend until final response or error */ - sent_body = 0; - - do { - r = http_read_response(dest_be, METH_PUT, &code, &statline, - &resp_hdrs, NULL, &txn->error.desc); - if (r) { - proxy_downserver(dest_be); - goto done; - } - - if ((code == 100) /* Continue */ && !sent_body++) { - /* Send body to dest backend to complete the PUT */ - prot_putbuf(dest_be->out, &resp_body.payload); - prot_flush(dest_be->out); - } - } while (code < 200); - - /* Send response to client */ - send_response(statline, resp_hdrs, NULL, &txn->flags); - if (code != 204) { - resp_body.framing = FRAMING_UNKNOWN; - if (pipe_resp_body(dest_be->in, httpd_out, resp_hdrs, &resp_body, - 0, &txn->error.desc)) { - /* Couldn't pipe the body and can't finish response */ - txn->flags.conn = CONN_CLOSE; - proxy_downserver(dest_be); - goto done; - } - } - - - delete: - if ((txn->meth == METH_MOVE) && (code < 300)) { - /* - * Send a DELETE request to source backend: - * - * - Add If header with lock token - */ - prot_printf(src_be->out, "DELETE %s %s\r\n" - "Host: %s\r\n" - "User-Agent: %s\r\n", - txn->req_tgt.path, HTTP_VERSION, - src_be->hostname, buf_cstring(&serverinfo)); - if (lock) prot_printf(src_be->out, "If: (%s)\r\n", lock); - prot_puts(src_be->out, "\r\n"); - prot_flush(src_be->out); - - /* Read response(s) from source backend until final resp or error */ - resp_body.flags = BODY_DISCARD; - - do { - if (http_read_response(src_be, METH_DELETE, &code, NULL, - &resp_hdrs, &resp_body, &txn->error.desc) - || (resp_body.flags & BODY_CLOSE)) { - proxy_downserver(src_be); - break; - } - } while (code < 200); - - if (code < 300 && lock) { - free(lock); - lock = NULL; - } - } - - - done: - if (lock) { - /* - * Something failed - Send an UNLOCK request to source backend: - */ - prot_printf(src_be->out, "UNLOCK %s %s\r\n" - "Host: %s\r\n" - "User-Agent: %s\r\n" - "Lock-Token: %s\r\n\r\n", - txn->req_tgt.path, HTTP_VERSION, - src_be->hostname, buf_cstring(&serverinfo), lock); - prot_flush(src_be->out); - - /* Read response(s) from source backend until final resp or error */ - resp_body.flags = BODY_DISCARD; - - do { - if (http_read_response(src_be, METH_UNLOCK, &code, NULL, - &resp_hdrs, &resp_body, &txn->error.desc)) { - proxy_downserver(src_be); - break; - } - } while (code < 200); - - free(lock); - } - - txn->resp_body.payload = resp_body.payload; - if (resp_hdrs) spool_free_hdrcache(resp_hdrs); - - return r; -}
View file
cyrus-imapd-2.5.tar.gz/imap/http_proxy.h
Deleted
@@ -1,62 +0,0 @@ -/* http_proxy.h - HTTP proxy support functions - * - * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef _HTTP_PROXY_H -#define _HTTP_PROXY_H - -#include "backend.h" - - -extern struct protocol_t http_protocol; - -extern int http_mlookup(const char *name, mbentry_t **mbentryp, void *tid); -extern void http_proto_host(hdrcache_t req_hdrs, - const char **proto, const char **host); -extern int http_pipe_req_resp(struct backend *be, struct transaction_t *txn); -extern int http_proxy_copy(struct backend *src_be, struct backend *dest_be, - struct transaction_t *txn); -extern int http_read_response(struct backend *be, unsigned meth, unsigned *code, - const char **statline, hdrcache_t *hdrs, - struct body_t *body, const char **errstr); - -#endif /* _HTTP_PROXY_H */
View file
cyrus-imapd-2.5.tar.gz/imap/http_rss.c
Deleted
@@ -1,1331 +0,0 @@ -/* http_rss.c -- Routines for handling RSS feeds of mailboxes in httpd - * - * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <ctype.h> -#include <string.h> -#include <syslog.h> -#include <assert.h> - -#include "acl.h" -#include "annotate.h" -#include "charset.h" -#include "global.h" -#include "httpd.h" -#include "http_err.h" -#include "http_proxy.h" -#include "imap_err.h" -#include "mailbox.h" -#include "map.h" -#include "mboxlist.h" -#include "message.h" -#include "parseaddr.h" -#include "proxy.h" -#include "times.h" -#include "seen.h" -#include "tok.h" -#include "util.h" -#include "version.h" -#include "wildmat.h" -#include "xmalloc.h" -#include "xstrlcpy.h" - -#define XML_NS_ATOM "http://www.w3.org/2005/Atom" -#define XML_NS_CYRUS "http://cyrusimap.org/ns/" -#define MAX_SECTION_LEN 128 -#define FEEDLIST_VAR "%RSS_FEEDLIST%" - -static const char def_template = - HTML_DOCTYPE - "<html>\n<head>\n<title>Cyrus RSS Feeds</title>\n</head>\n" - "<body>\n<h2>Cyrus RSS Feeds</h2>\n" - FEEDLIST_VAR - "</body>\n</html>\n"; - -static time_t compile_time; -static void rss_init(struct buf *serverinfo); -static int meth_get(struct transaction_t *txn, void *params); -static int rss_parse_path(const char *path, - struct request_target_t *tgt, const char **errstr); -static int is_feed(const char *mbox); -static int list_feeds(struct transaction_t *txn); -static int fetch_message(struct transaction_t *txn, struct mailbox *mailbox, - unsigned recno, uint32_t uid, - struct index_record *record, struct body **body, - struct buf *msg_buf); -static int list_messages(struct transaction_t *txn, struct mailbox *mailbox); -static void display_message(struct transaction_t *txn, - const char *mboxname, const struct index_record *record, - struct body *body, const struct buf *msg_buf); -static void fetch_part(struct transaction_t *txn, struct body *body, - const char *findsection, const char *cursection, - struct buf *msg_buf); - - -/* Namespace for RSS feeds of mailboxes */ -struct namespace_t namespace_rss = { - URL_NS_RSS, 0, "/rss", NULL, 1 /* auth */, ALLOW_READ, - /*mbtype*/0, - rss_init, NULL, NULL, NULL, - { - { NULL, NULL }, /* ACL */ - { NULL, NULL }, /* COPY */ - { NULL, NULL }, /* DELETE */ - { &meth_get, NULL }, /* GET */ - { &meth_get, NULL }, /* HEAD */ - { NULL, NULL }, /* LOCK */ - { NULL, NULL }, /* MKCALENDAR */ - { NULL, NULL }, /* MKCOL */ - { NULL, NULL }, /* MOVE */ - { &meth_options, &rss_parse_path }, /* OPTIONS */ - { NULL, NULL }, /* POST */ - { NULL, NULL }, /* PROPFIND */ - { NULL, NULL }, /* PROPPATCH */ - { NULL, NULL }, /* PUT */ - { NULL, NULL }, /* REPORT */ - { &meth_trace, &rss_parse_path }, /* TRACE */ - { NULL, NULL } /* UNLOCK */ - } -}; - - -static void rss_init(struct buf *serverinfo __attribute__((unused))) -{ - namespace_rss.enabled = config_httpmodules & IMAP_ENUM_HTTPMODULES_RSS; - - if (!namespace_rss.enabled) return; - - compile_time = calc_compile_time(__TIME__, __DATE__); -} - -/* Perform a GET/HEAD request */ -static int meth_get(struct transaction_t *txn, - void *params __attribute__((unused))) -{ - int ret = 0, r; - char sectionMAX_SECTION_LEN+1 = ""; - uint32_t uid = 0; - struct mailbox *mailbox = NULL; - mbentry_t *mbentry = NULL; - - /* Construct mailbox name corresponding to request target URI */ - if ((r = rss_parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) { - txn->error.desc = error_message(r); - return HTTP_NOT_FOUND; - } - - /* If no mailboxname, list all available feeds */ - if (!*txn->req_tgt.mboxname) return list_feeds(txn); - - /* Make sure its a mailbox that we are treating as an RSS feed */ - if (!is_feed(txn->req_tgt.mboxname)) return HTTP_NOT_FOUND; - - /* Locate the mailbox */ - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local Mailbox */ - - /* Open mailbox for reading */ - r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox); - if (r) { - syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - /* Parse query params, if any */ - if (URI_QUERY(txn->req_uri) && - !strncasecmp(URI_QUERY(txn->req_uri), "uid=", 4)) { - /* UID */ - char *end; - - uid = strtoul(URI_QUERY(txn->req_uri)+4, &end, 10); - if (!uid) uid = -1; - - if (!strncasecmp(end, ";section=", 9)) { - /* SECTION */ - strlcpy(section, end+9, MAX_SECTION_LEN); - } - } - - /* If no UID specified, list messages as an RSS feed */ - if (!uid) ret = list_messages(txn, mailbox); - else if (uid > mailbox->i.last_uid) { - txn->error.desc = "Message does not exist\r\n"; - ret = HTTP_NOT_FOUND; - } - else { - struct index_record record; - struct buf msg_buf = BUF_INITIALIZER; - struct body *body = NULL; - - /* Fetch the message */ - if (!(ret = fetch_message(txn, mailbox, 0, uid, - &record, &body, &msg_buf))) { - int precond; - const char *etag = NULL; - time_t lastmod = 0; - struct resp_body_t *resp_body = &txn->resp_body; - - /* Check any preconditions */ - if (!strcmp(section, "0")) { - /* Entire raw message */ - txn->flags.ranges = 1; - } - - etag = message_guid_encode(&record.guid); - lastmod = record.internaldate; - precond = check_precond(txn, NULL, etag, lastmod); - - switch (precond) { - case HTTP_OK: - case HTTP_PARTIAL: - case HTTP_NOT_MODIFIED: - /* Fill in ETag, Last-Modified, and Expires */ - resp_body->etag = etag; - resp_body->lastmod = lastmod; - resp_body->maxage = 31536000; /* 1 year */ - txn->flags.cc |= CC_MAXAGE; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - ret = precond; - goto done; - } - - if (!*section) { - /* Return entire message formatted as text/html */ - display_message(txn, mailbox->name, &record, body, &msg_buf); - } - else if (!strcmp(section, "0")) { - /* Return entire message as text/plain */ - resp_body->type = "text/plain"; - write_body(precond, txn, buf_base(&msg_buf), buf_len(&msg_buf)); - } - else { - /* Fetch, decode, and return the specified MIME message part */ - fetch_part(txn, body, section, "1", &msg_buf); - } - - done: - buf_free(&msg_buf); - - if (body) { - message_free_body(body); - free(body); - } - } - } - - mailbox_close(&mailbox); - - return ret; - -} - - -/* Create a mailbox name from the request URL */ -static int rss_parse_path(const char *path, - struct request_target_t *tgt, - const char **errstr __attribute__((unused))) -{ - const char *start, *end; - size_t len; - - /* Clip off RSS prefix */ - start = path + strlen("/rss"); - if (*start == '/') start++; - end = start + strlen(start); - - if ((end > start) && (end-1 == '/')) end--; - - len = end - start; - if (len > MAX_MAILBOX_BUFFER) return IMAP_MAILBOX_BADNAME; - - strncpy(tgt->mboxname, start, len); - tgt->mboxnamelen = '\0'; - - mboxname_hiersep_tointernal(&httpd_namespace, tgt->mboxname, len); - - return 0; -} - - -/* - * Checks to make sure that the given mailbox is actually something - * that we're treating as an RSS feed. Returns 1 if yes, 0 if no. - */ -static int is_feed(const char *mbox) -{ - static struct wildmat *feeds = NULL; - struct wildmat *wild; - - if (!feeds) { - feeds = split_wildmats((char *) config_getstring(IMAPOPT_RSS_FEEDS), - NULL); - } - - /* check mailbox against the 'rss_feeds' wildmat */ - wild = feeds; - while (wild->pat && wildmat(mbox, wild->pat) != 1) wild++; - - /* if we don't have a match, or its a negative match, don't use it */ - if (!wild->pat || wild->not) return 0; - - /* otherwise, its usable */ - return 1; -} - - -/* - * mboxlist_findall() callback function to list RSS feeds as a tree - */ -struct node { - char nameMAX_MAILBOX_BUFFER; - size_t len; - struct node *parent; - struct node *child; -}; - -struct list_rock { - struct transaction_t *txn; - struct node *last; -}; - -static int list_cb(char *name, int matchlen, int maycreate, void *rock) -{ - struct list_rock *lrock = (struct list_rock *) rock; - struct node *last = lrock->last; - struct buf *buf = &lrock->txn->resp_body.payload; - - if (name) { - int rights; - mbentry_t *mbentry = NULL; - int r; - - /* Don't list mailboxes that we don't treat as RSS feeds */ - if (!is_feed(name)) return 0; - - /* Don't list deleted mailboxes */ - if (mboxname_isdeletedmailbox(name, NULL)) return 0; - - /* Lookup the mailbox and make sure its readable */ - r = http_mlookup(name, &mbentry, NULL); - if (r) return 0; - - rights = httpd_myrights(httpd_authstate, mbentry->acl); - mboxlist_entry_free(&mbentry); - - if ((rights & ACL_READ) != ACL_READ) - return 0; - } - - if (name && - !strncmp(name, last->name, last->len) && - (!last->len || (namelast->len == '.'))) { - /* Found closest ancestor of 'name' */ - struct node *node; - size_t len = matchlen; - char shortnameMAX_MAILBOX_NAME+1, pathMAX_MAILBOX_PATH+1; - char *cp, *href = NULL; - - /* Send a body chunk once in a while */ - if (buf_len(buf) > PROT_BUFSIZE) { - write_body(0, lrock->txn, buf_cstring(buf), buf_len(buf)); - buf_reset(buf); - } - - if (last->child) { - /* Reuse our sibling */ - buf_printf(buf, "</li>\n"); - node = last->child; - } - else { - /* Create first child */ - buf_printf(buf, "\n<ul%s>\n", - last->parent ? "" : " id='feed'"); /* needed by CSS */ - node = xmalloc(sizeof(struct node)); - } - - /* See if we have a missing ancestor in the tree */ - if ((cp = strchr(&namelast->len+1, '.'))) len = cp - name; - else href = path; - - /* Populate new/updated node */ - strncpy(node->name, name, len); - node->namelen = '\0'; - node->len = len; - node->parent = last; - node->child = NULL; - lrock->last = last->child = node; - - /* Get last segment of mailbox name */ - if ((cp = strrchr(node->name, '.'))) cp++; - else cp = node->name; - - /* Translate short mailbox name to external form */ - strlcpy(shortname, cp, sizeof(shortname)); - mboxname_hiersep_toexternal(&httpd_namespace, shortname, 0); - - if (href) { - /* Add selectable feed with link */ - snprintf(path, sizeof(path), ".rss.%s", node->name); - mboxname_hiersep_toexternal(&httpd_namespace, href, 0); - buf_printf(buf, "<li><a href=\"%s\">%s</a>", - href, shortname); - } - else { - /* Add missing ancestor and recurse down the tree */ - buf_printf(buf, "<li>%s", shortname); - - list_cb(name, matchlen, maycreate, rock); - } - } - else { - /* Remove child */ - if (last->child) { - buf_printf(buf, "</li>\n</ul>\n"); - free(last->child); - last->child = NULL; - } - - if (last->parent) { - /* Recurse back up the tree */ - lrock->last = last->parent; - list_cb(name, matchlen, maycreate, rock); - } - } - - return 0; -} - - -/* Create a HTML document listing all RSS feeds available to the user */ -static int list_feeds(struct transaction_t *txn) -{ - const char *template_file = config_getstring(IMAPOPT_RSS_FEEDLIST_TEMPLATE); - const char *var = NULL, *template = NULL, *prefix, *suffix; - unsigned long template_len = 0, prefix_len, suffix_len; - size_t varlen = strlen(FEEDLIST_VAR); - int fd = -1; - struct message_guid guid; - time_t lastmod; - char mboxlistMAX_MAILBOX_PATH+1; - struct stat sbuf; - int ret = 0, precond; - struct buf *body = &txn->resp_body.payload; - struct list_rock lrock; - struct node root = { "", 0, NULL, NULL }; - - if (template_file) { - /* See if template exists and contains feedlist variable */ - if (!stat(template_file, &sbuf) && S_ISREG(sbuf.st_mode) && - (size_t) sbuf.st_size >= varlen && - (fd = open(template_file, O_RDONLY)) != -1) { - const char *p; - unsigned long len; - - map_refresh(fd, 1, &template, &template_len, sbuf.st_size, - template_file, NULL); - - for (p = template, len = template_len; - len >= varlen && strncmp(p, FEEDLIST_VAR, varlen); p++, len--); - if (len >= varlen) { - var = p; - lastmod = sbuf.st_mtime; - } - else { - map_free(&template, &template_len); - close(fd); - fd = -1; - } - } - } - - if (!var) { - /* No usable template specified, use our default */ - template = def_template; - template_len = strlen(def_template); - var = strstr(template, FEEDLIST_VAR); - lastmod = compile_time; - } - - prefix = template; - prefix_len = var - template; - suffix = template + prefix_len + varlen; - suffix_len = template_len - (prefix_len + varlen); - - /* Begin to generate ETag */ - message_guid_generate(&guid, template, template_len); - buf_setcstr(&txn->buf, message_guid_encode(&guid)); - - /* stat() mailboxes.db for Last-Modified and ETag */ - snprintf(mboxlist, MAX_MAILBOX_PATH, "%s%s", config_dir, FNAME_MBOXLIST); - stat(mboxlist, &sbuf); - lastmod = MAX(lastmod, sbuf.st_mtime); - buf_printf(&txn->buf, "-%ld-%ld", sbuf.st_mtime, sbuf.st_size); - - /* stat() imapd.conf for Last-Modified and ETag */ - stat(config_filename, &sbuf); - lastmod = MAX(lastmod, sbuf.st_mtime); - buf_printf(&txn->buf, "-%ld-%ld", sbuf.st_mtime, sbuf.st_size); - - /* Check any preconditions */ - precond = check_precond(txn, NULL, buf_cstring(&txn->buf), lastmod); - - switch (precond) { - case HTTP_OK: - case HTTP_NOT_MODIFIED: - /* Fill in ETag, Last-Modified, and Expires */ - txn->resp_body.etag = buf_cstring(&txn->buf); - txn->resp_body.lastmod = lastmod; - txn->resp_body.maxage = 86400; /* 24 hrs */ - txn->flags.cc |= CC_MAXAGE; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - ret = precond; - goto done; - } - - /* Setup for chunked response */ - txn->flags.te |= TE_CHUNKED; - txn->resp_body.type = "text/html; charset=utf-8"; - - /* Short-circuit for HEAD request */ - if (txn->meth == METH_HEAD) { - response_header(HTTP_OK, txn); - goto done; - } - - /* Send beginning of template */ - write_body(HTTP_OK, txn, prefix, prefix_len); - - /* Generate tree view of feeds */ - buf_reset(body); - lrock.txn = txn; - lrock.last = &root; - mboxlist_findall(NULL, "*", httpd_userisadmin, NULL, httpd_authstate, - list_cb, &lrock); - - /* Close out the tree */ - list_cb(NULL, 0, 0, &lrock); - if (buf_len(body)) write_body(0, txn, buf_cstring(body), buf_len(body)); - - /* Send rest of template */ - if (suffix_len) write_body(0, txn, suffix, suffix_len); - - /* End of output */ - write_body(0, txn, NULL, 0); - - done: - if (fd != -1) { - map_free(&template, &template_len); - close(fd); - } - - return ret; -} - - -/* Fetch the index record & bodystructure, and mmap the message */ -static int fetch_message(struct transaction_t *txn, struct mailbox *mailbox, - unsigned recno, uint32_t uid, - struct index_record *record, struct body **body, - struct buf *msg_buf) -{ - int r; - - buf_reset(msg_buf); - - /* Fetch index record for the message */ - if (uid) r = mailbox_find_index_record(mailbox, uid, record, NULL); - else r = mailbox_read_index_record(mailbox, recno, record); - if ((r == CYRUSDB_NOTFOUND) || - (record->system_flags & (FLAG_DELETED|FLAG_EXPUNGED))) { - txn->error.desc = "Message has been removed\r\n"; - - /* Fill in Expires */ - txn->resp_body.maxage = 31536000; /* 1 year */ - txn->flags.cc |= CC_MAXAGE; - return HTTP_GONE; - } - else if (r) { - syslog(LOG_ERR, "find index record failed"); - txn->error.desc = error_message(r); - return HTTP_SERVER_ERROR; - } - - /* Fetch cache record for the message */ - if ((r = mailbox_cacherecord(mailbox, record))) { - syslog(LOG_ERR, "read cache failed"); - txn->error.desc = error_message(r); - return HTTP_SERVER_ERROR; - } - - /* Read message bodystructure */ - message_read_bodystructure(record, body); - - /* Map the message into memory */ - mailbox_map_record(mailbox, record, msg_buf); - - return 0; -} - - -static void buf_escapestr(struct buf *buf, const char *str, unsigned max, - unsigned replace, unsigned level) -{ - const char *c; - unsigned buflen = buf_len(buf), len = 0; - - if (!replace && config_httpprettytelemetry) - buf_printf(buf, "%*s", level * MARKUP_INDENT, ""); - - for (c = str; c && *c && (!max || len < max); c++, len++) { - /* Translate CR to HTML <br> tag */ - if (*c == '\r') buf_appendcstr(buf, "<br>"); - else if (*c == '\n' && !config_httpprettytelemetry) continue; - - /* Translate XML/HTML specials */ - else if (*c == '"') buf_appendcstr(buf, """); -// else if (*c == '\'') buf_appendcstr(buf, "'"); - else if (*c == '&') buf_appendcstr(buf, "&"); - else if (*c == '<') buf_appendcstr(buf, "<"); - else if (*c == '>') buf_appendcstr(buf, ">"); - - /* Handle multi-byte UTF-8 sequences */ - else if ((*c & 0xc0) == 0xc0) { - /* Code points larger than 127 are represented by - * multi-byte sequences, composed of a leading byte and - * one or more continuation bytes. The leading byte has - * two or more high-order 1s followed by a 0, while - * continuation bytes all have '10' in the high-order - * position. The number of high-order 1s in the leading - * byte of a multi-byte sequence indicates the number of - * bytes in the sequence. - */ - unsigned char lead = *c; - - do buf_putc(buf, *c); - while (((lead <<= 1) & 0x80) && c++); - } - - /* Check for non-printable chars */ - else if (!(isspace(*c) || isprint(*c))) { - if (replace) { - /* Replace entire string with a warning */ - buf_truncate(buf, buflen); - buf_printf_markup(buf, level++, "<blockquote>"); - buf_printf_markup(buf, level, "<i><b>NOTE:</b> " - "This message contains characters " - "that can not be displayed in RSS</i>"); - buf_printf_markup(buf, --level, "</blockquote>"); - return; - } - else { - /* Translate non-printable chars to X */ - buf_putc(buf, 'X'); - } - } - - else buf_putc(buf, *c); - } - - if (!replace && config_httpprettytelemetry) buf_appendcstr(buf, "\n"); -} - - -/* List messages as an RSS feed */ -static int list_messages(struct transaction_t *txn, struct mailbox *mailbox) -{ - const char *proto = NULL, *host = NULL; - uint32_t url_len, recno, recentuid = 0; - int max_age, max_items, max_len, nitems, precond; - time_t age_mark = 0, lastmod; - char datestr80; - static char etag33; - struct buf *url = &txn->buf; - struct buf *buf = &txn->resp_body.payload; - unsigned level = 0; - char mboxnameMAX_MAILBOX_NAME+1; - struct buf attrib = BUF_INITIALIZER; - - /* Check any preconditions */ - lastmod = mailbox->i.last_appenddate; - sprintf(etag, "%u-%u-%u", - mailbox->i.uidvalidity, mailbox->i.last_uid, mailbox->i.exists); - precond = check_precond(txn, NULL, etag, lastmod); - - switch (precond) { - case HTTP_OK: - case HTTP_NOT_MODIFIED: - /* Fill in ETag, Last-Modified, and Expires */ - txn->resp_body.etag = etag; - txn->resp_body.lastmod = lastmod; - txn->resp_body.maxage = 3600; /* 1 hr */ - txn->flags.cc |= CC_MAXAGE; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - return precond; - } - - /* Setup for chunked response */ - txn->flags.te |= TE_CHUNKED; - txn->resp_body.type = "application/atom+xml; charset=utf-8"; - - /* Short-circuit for HEAD request */ - if (txn->meth == METH_HEAD) { - response_header(HTTP_OK, txn); - return 0; - } - - /* Get maximum age of items to display */ - max_age = config_getint(IMAPOPT_RSS_MAXAGE); - if (max_age > 0) age_mark = time(0) - (max_age * 60 * 60 * 24); - - /* Get number of items to display */ - max_items = config_getint(IMAPOPT_RSS_MAXITEMS); - if (max_items < 0) max_items = 0; - - /* Get length of description to display */ - max_len = config_getint(IMAPOPT_RSS_MAXSYNOPSIS); - if (max_len < 0) max_len = 0; - -#if 0 - /* Obtain recentuid */ - if (mailbox_internal_seen(mailbox, httpd_userid)) { - recentuid = mailbox->i.recentuid; - } - else if (httpd_userid) { - struct seen *seendb = NULL; - struct seendata sd; - - r = seen_open(httpd_userid, SEEN_CREATE, &seendb); - if (!r) r = seen_read(seendb, mailbox->uniqueid, &sd); - seen_close(&seendb); - - /* handle no seen DB gracefully */ - if (r) { - recentuid = mailbox->i.last_uid; - syslog(LOG_ERR, "Could not open seen state for %s (%s)", - httpd_userid, error_message(r)); - } - else { - recentuid = sd.lastuid; - free(sd.seenuids); - } - } - else { - recentuid = mailbox->i.last_uid; /* nothing is recent! */ - } -#endif - - /* Translate mailbox name to external form */ - strlcpy(mboxname, mailbox->name, sizeof(mboxname)); - mboxname_hiersep_toexternal(&httpd_namespace, mboxname, 0); - - /* Construct base URL */ - http_proto_host(txn->req_hdrs, &proto, &host); - assert(!buf_len(url)); - buf_printf(url, "%s://%s%s", proto, host, txn->req_uri->path); - url_len = buf_len(url); - - /* Start XML */ - buf_reset(buf); - buf_printf_markup(buf, level, "<?xml version=\"1.0\" encoding=\"utf-8\"?>"); - - /* Set up the Atom <feed> response for the mailbox */ - buf_printf_markup(buf, level++, - "<feed xmlns=\"" XML_NS_ATOM "\">"); - - /* <title> - required */ - buf_printf_markup(buf, level, "<title>%s</title>", mboxname); - - /* <id> - required */ - buf_printf_markup(buf, level, "<id>%sguid/%s</id>", - XML_NS_CYRUS, mailbox->uniqueid); - - /* <updated> - required */ - time_to_rfc3339(lastmod, datestr, sizeof(datestr)); - buf_printf_markup(buf, level, "<updated>%s</updated>", datestr); - - /* <author> - required (use 'Anonymous' as default <name>) */ - buf_printf_markup(buf, level++, "<author>"); - buf_printf_markup(buf, level, "<name>Anonymous</name>"); - buf_printf_markup(buf, --level, "</author>"); - - /* <subtitle> - optional */ - annotatemore_lookup(mailbox->name, "/comment", NULL, &attrib); - if (age_mark) { - time_to_rfc822(age_mark, datestr, sizeof(datestr)); - buf_printf_markup(buf, level, - "<subtitle>%s posts since %s</subtitle>", - buf_cstring(&attrib), datestr); - } - else { - buf_printf_markup(buf, level, - "<subtitle>%s %u most recent posts</subtitle>", - buf_cstring(&attrib), - max_items ? (unsigned) max_items : mailbox->i.exists); - } - - /* <link> - optional */ - buf_printf_markup(buf, level, - "<link rel=\"self\" type=\"application/atom+xml\"" - " href=\"%s\"/>", buf_cstring(url)); - - /* <generator> - optional */ - if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) { - buf_printf_markup(buf, level, "<generator>Cyrus HTTP %s</generator>", - cyrus_version()); - } - - write_body(HTTP_OK, txn, buf_cstring(buf), buf_len(buf)); - buf_reset(buf); - - /* Add an <entry> for each message */ - for (recno = mailbox->i.num_records, nitems = 0; - recno >= 1 && (!max_items || nitems < max_items); recno--) { - struct index_record record; - struct buf msg_buf = BUF_INITIALIZER; - struct body *body = NULL; - char *subj; - struct address *addr = NULL; - const char *content_types = { "text", NULL }; - struct message_content content; - struct bodypart **parts; - - /* Send a body chunk once in a while */ - if (buf_len(buf) > PROT_BUFSIZE) { - write_body(0, txn, buf_cstring(buf), buf_len(buf)); - buf_reset(buf); - } - - /* Fetch the message */ - if (fetch_message(txn, mailbox, recno, 0, - &record, &body, &msg_buf)) { - continue; - } - - /* XXX Are we going to do anything with \Recent? */ - if (record.uid <= recentuid) { - syslog(LOG_DEBUG, "recno %u not recent (%u/%u)", - recno, record.uid, recentuid); - continue; - } - - /* Make sure the message is new enough */ - if (record.gmtime < age_mark) continue; - - /* Feeding this message, increment counter */ - nitems++; - - buf_printf_markup(buf, level++, "<entry>"); - - /* <title> - required */ - subj = charset_parse_mimeheader(body->subject); - buf_printf_markup(buf, level++, "<title type=\"html\">"); - buf_escapestr(buf, subj && *subj ? subj : "Untitled", 0, 0, level); - buf_printf_markup(buf, --level, "</title>"); - free(subj); - - /* <id> - required */ - buf_printf_markup(buf, level, "<id>%sguid/%s</id>", - XML_NS_CYRUS, message_guid_encode(&record.guid)); - - /* <updated> - required */ - time_to_rfc3339(record.gmtime, datestr, sizeof(datestr)); - buf_printf_markup(buf, level, "<updated>%s</updated>", datestr); - - /* <published> - optional */ - buf_printf_markup(buf, level, "<published>%s</published>", datestr); - - /* <link> - optional */ - buf_truncate(url, url_len); - buf_printf(url, "?uid=%u", record.uid); - buf_printf_markup(buf, level, "<link rel=\"alternate\"" - " type=\"text/html\" href=\"%s\"/>", - buf_cstring(url)); - - /* <author> - optional */ - addr = body->from; - if (!addr) addr = body->sender; - if (addr && *addr->mailbox) { - buf_printf_markup(buf, level++, "<author>"); - - /* <name> - required */ - if (addr->name) { - char *name = charset_parse_mimeheader(addr->name); - buf_printf_markup(buf, level++, "<name>"); - buf_escapestr(buf, name, 0, 0, level); - buf_printf_markup(buf, --level, "</name>"); - free(name); - } - else { - buf_printf_markup(buf, level, "<name>%s@%s</name>", - addr->mailbox, addr->domain); - } - - /* <email> - optional */ - buf_printf_markup(buf, level, "<email>%s@%s</email>", - addr->mailbox, addr->domain); - - buf_printf_markup(buf, --level, "</author>"); - } - - /* <summary> - optional (find and use the first text/ part) */ - content.base = buf_base(&msg_buf); - content.len = buf_len(&msg_buf); - content.body = body; - message_fetch_part(&content, content_types, &parts); - - if (parts && *parts) { - buf_printf_markup(buf, level++, "<summary type=\"html\">"); - buf_printf_markup(buf, level++, "<!CDATA"); - buf_escapestr(buf, parts0->decoded_body, max_len, 1, level); - buf_printf_markup(buf, --level, ">"); - buf_printf_markup(buf, --level, "</summary>"); - } - - buf_printf_markup(buf, --level, "</entry>"); - - /* free the results */ - if (parts) { - struct bodypart **p; - - for (p = parts; *p; p++) free(*p); - free(parts); - } - - if (body) { - message_free_body(body); - free(body); - } - - buf_free(&msg_buf); - } - - /* End of Atom <feed> */ - buf_printf_markup(buf, --level, "</feed>"); - write_body(0, txn, buf_cstring(buf), buf_len(buf)); - - /* End of output */ - write_body(0, txn, NULL, 0); - - return 0; -} - - -static void display_address(struct buf *buf, struct address *addr, - const char *sep, unsigned level) -{ - if (config_httpprettytelemetry) - buf_printf(buf, "%*s", level * MARKUP_INDENT, ""); - - buf_printf(buf, "%s", sep); - if (addr->name) buf_printf(buf, "\"%s\" ", addr->name); - buf_printf(buf, "<a href=\"mailto:%s@%s\"><%s@%s></a>", - addr->mailbox, addr->domain, addr->mailbox, addr->domain); - - if (config_httpprettytelemetry) buf_appendcstr(buf, "\n"); -} - - -static void display_part(struct transaction_t *txn, - struct body *body, const struct index_record *record, - const char *cursection, const struct buf *msg_buf, - unsigned level) -{ - struct buf *buf = &txn->resp_body.payload; - char nextsectionMAX_SECTION_LEN+1; - - if (!strcmp(body->type, "MULTIPART")) { - int i; - - if (!strcmp(body->subtype, "ALTERNATIVE") && - !strcmp(body->subpart0.type, "TEXT")) { - /* Display a multpart/ or text/html subpart, - otherwise display first subpart */ - for (i = body->numparts; --i;) { - if (!strcmp(body->subparti.type, "MULTIPART") || - !strcmp(body->subparti.subtype, "HTML")) break; - } - snprintf(nextsection, sizeof(nextsection), "%s%s%d", - cursection, *cursection ? "." : "", i+1); - display_part(txn, &body->subparti, - record, nextsection, msg_buf, level); - } - else { - /* Display all subparts */ - for (i = 0; i < body->numparts; i++) { - snprintf(nextsection, sizeof(nextsection), "%s%s%d", - cursection, *cursection ? "." : "", i+1); - display_part(txn, &body->subparti, - record, nextsection, msg_buf, level); - } - } - } - else if (!strcmp(body->type, "MESSAGE") && - !strcmp(body->subtype, "RFC822")) { - struct body *subpart = body->subpart; - struct address *addr; - char *sep; - - /* Display enclosed message header as a shaded table */ - buf_printf_markup(buf, level++, - "<table width=\"100%%\" bgcolor=\"#CCCCCC\">"); - /* Subject header field */ - if (subpart->subject) { - char *subj; - - subj = charset_parse_mimeheader(subpart->subject); - buf_printf_markup(buf, level++, "<tr>"); - buf_printf_markup(buf, level, - "<td align=right valign=top><b>Subject: </b></td>"); - buf_printf_markup(buf, level, "<td>%s</td>", subj); - buf_printf_markup(buf, --level, "</tr>"); - free(subj); - } - /* From header field */ - if (subpart->from && *subpart->from->mailbox) { - buf_printf_markup(buf, level++, "<tr>"); - buf_printf_markup(buf, level, - "<td align=right><b>From: </b></td>"); - buf_printf_markup(buf, level++, "<td>"); - display_address(buf, subpart->from, "", level); - buf_printf_markup(buf, --level, "</td>"); - buf_printf_markup(buf, --level, "</tr>"); - } - /* Sender header field (if different than From */ - if (subpart->sender && *subpart->sender->mailbox && - (!subpart->from || - strcmp(subpart->sender->mailbox, subpart->from->mailbox) || - strcmp(subpart->sender->domain, subpart->from->domain))) { - buf_printf_markup(buf, level++, "<tr>"); - buf_printf_markup(buf, level, - "<td align=right><b>Sender: </b></td>"); - buf_printf_markup(buf, level++, "<td>"); - display_address(buf, subpart->sender, "", level); - buf_printf_markup(buf, --level, "</td>"); - buf_printf_markup(buf, --level, "</tr>"); - } - /* Reply-To header field (if different than From */ - if (subpart->reply_to && *subpart->reply_to->mailbox && - (!subpart->from || - strcmp(subpart->reply_to->mailbox, subpart->from->mailbox) || - strcmp(subpart->reply_to->domain, subpart->from->domain))) { - buf_printf_markup(buf, level++, "<tr>"); - buf_printf_markup(buf, level, - "<td align=right><b>Reply-To: </b></td>"); - buf_printf_markup(buf, level++, "<td>"); - display_address(buf, subpart->reply_to, "", level); - buf_printf_markup(buf, --level, "</td>"); - buf_printf_markup(buf, --level, "</tr>"); - } - /* Date header field */ - buf_printf_markup(buf, level++, "<tr>"); - buf_printf_markup(buf, level, - "<td align=right><b>Date: </b></td>"); - buf_printf_markup(buf, level, - "<td width=\"100%%\">%s</td>", subpart->date); - buf_printf_markup(buf, --level, "</tr>"); - /* To header field (possibly multiple addresses) */ - if (subpart->to) { - buf_printf_markup(buf, level++, "<tr>"); - buf_printf_markup(buf, level, - "<td align=right valign=top><b>To: </b></td>"); - buf_printf_markup(buf, level++, "<td>"); - for (sep = "", addr = subpart->to; addr; addr = addr->next) { - display_address(buf, addr, sep, level); - sep = ", "; - } - buf_printf_markup(buf, --level, "</td>"); - buf_printf_markup(buf, --level, "</tr>"); - } - /* Cc header field (possibly multiple addresses) */ - if (subpart->cc) { - buf_printf_markup(buf, level++, "<tr>"); - buf_printf_markup(buf, level, - "<td align=right valign=top><b>Cc: </b></td>"); - buf_printf_markup(buf, level++, "<td>"); - for (sep = "", addr = subpart->cc; addr; addr = addr->next) { - display_address(buf, addr, sep, level); - sep = ", "; - } - buf_printf_markup(buf, --level, "</td>"); - buf_printf_markup(buf, --level, "</tr>"); - } - buf_printf_markup(buf, --level, "</table>"); -// buf_printf_markup(buf, level, "<br>"); - - /* Display subpart */ - snprintf(nextsection, sizeof(nextsection), "%s%s%d", - cursection, *cursection ? "." : "", 1); - display_part(txn, subpart, record, nextsection, msg_buf, level); - } - else { - /* Leaf part - display something */ - - if (!strcmp(body->type, "TEXT")) { - /* Display text part */ - int ishtml = !strcmp(body->subtype, "HTML"); - int charset = body->charset_cte >> 16; - int encoding = body->charset_cte & 0xff; - - if (charset < 0) charset = 0; /* unknown, try ASCII */ - body->decoded_body = - charset_to_utf8(buf_base(msg_buf) + body->content_offset, - body->content_size, charset, encoding); - if (!ishtml) buf_printf_markup(buf, level, "<pre>"); - write_body(0, txn, buf_cstring(buf), buf_len(buf)); - buf_reset(buf); - - write_body(0, txn, body->decoded_body, strlen(body->decoded_body)); - if (!ishtml) buf_printf_markup(buf, level, "</pre>"); - } - else { - int is_image = !strcmp(body->type, "IMAGE"); - struct param *param = body->params; - const char *file_attr = "NAME"; - - /* Anything else is shown as an attachment. - * Show images inline, using name/description as alternative text. - */ - /* Look for a filename in parameters */ - if (body->disposition) { - if (!strcmp(body->disposition, "ATTACHMENT")) is_image = 0; - param = body->disposition_params; - file_attr = "FILENAME"; - } - for (; param && strcmp(param->attribute, file_attr); - param = param->next); - - buf_printf_markup(buf, level++, "<div align=center>"); - - /* Create link */ - buf_printf_markup(buf, level++, - "<a href=\"%s?uid=%u;section=%s\" type=\"%s/%s\">", - txn->req_tgt.path, record->uid, cursection, - body->type, body->subtype); - - if (config_httpprettytelemetry) - buf_printf(buf, "%*s", level * MARKUP_INDENT, ""); - - /* Add image */ - if (is_image) { - buf_printf(buf, "<img src=\"%s?uid=%u;section=%s\" alt=\"", - txn->req_tgt.path, record->uid, cursection); - } - - /* Create text for link or alternative text for image */ - if (param) buf_printf(buf, "%s", param->value); - else { - buf_printf(buf, "%s/%s %lu bytes", - body->type, body->subtype, body->content_size); - } - - if (is_image) buf_printf(buf, "\">"); - - if (config_httpprettytelemetry) buf_appendcstr(buf, "\n"); - - buf_printf_markup(buf, --level, "</a>"); - buf_printf_markup(buf, --level, "</div>"); - } - - buf_printf_markup(buf, level, "<hr>"); - } -} - - -/* Return entire message formatted as text/html */ -static void display_message(struct transaction_t *txn, - const char *mboxname, const struct index_record *record, - struct body *body, const struct buf *msg_buf) -{ - struct body toplevel; - struct buf *buf = &txn->resp_body.payload; - unsigned level = 0; - - /* Setup for chunked response */ - txn->flags.te |= TE_CHUNKED; - txn->resp_body.type = "text/html; charset=utf-8"; - - /* Short-circuit for HEAD request */ - if (txn->meth == METH_HEAD) { - response_header(HTTP_OK, txn); - return; - } - - /* Start HTML */ - buf_reset(buf); - buf_printf_markup(buf, level, HTML_DOCTYPE); - buf_printf_markup(buf, level++, "<html>"); - buf_printf_markup(buf, level++, "<head>"); - buf_printf_markup(buf, level, "<title>%s:%u</title>", - mboxname, record->uid); - buf_printf_markup(buf, --level, "</head>"); - buf_printf_markup(buf, level++, "<body>"); - - /* Create link to message source */ - buf_printf_markup(buf, level++, "<div align=center>"); - buf_printf_markup(buf, level, - "<a href=\"%s?uid=%u;section=0\" type=\"plain/text\">" - "View message source</a>", - txn->req_tgt.path, record->uid); - buf_printf_markup(buf, --level, "</div>"); - buf_printf_markup(buf, level, "<hr>"); - - write_body(HTTP_OK, txn, buf_cstring(buf), buf_len(buf)); - buf_reset(buf); - - /* Encapsulate our body in a message/rfc822 to display toplevel hdrs */ - memset(&toplevel, 0, sizeof(struct body)); - toplevel.type = "MESSAGE"; - toplevel.subtype = "RFC822"; - toplevel.subpart = body; - - display_part(txn, &toplevel, record, "", msg_buf, level); - - /* End of HTML */ - buf_printf_markup(buf, --level, "</body>"); - buf_printf_markup(buf, --level, "</html>"); - - write_body(0, txn, buf_cstring(buf), buf_len(buf)); - - /* End of output */ - write_body(0, txn, NULL, 0); -} - - -/* Fetch, decode, and return the specified MIME message part */ -static void fetch_part(struct transaction_t *txn, struct body *body, - const char *findsection, const char *cursection, - struct buf *msg_buf) -{ - char nextsectionMAX_SECTION_LEN+1; - - if (!strcmp(body->type, "MULTIPART")) { - int i; - - /* Recurse through all subparts */ - for (i = 0; i < body->numparts; i++) { - snprintf(nextsection, sizeof(nextsection), "%s%s%d", - cursection, *cursection ? "." : "", i+1); - fetch_part(txn, &body->subparti, - findsection, nextsection, msg_buf); - } - } - else if (!strcmp(body->type, "MESSAGE") && - !strcmp(body->subtype, "RFC822")) { - /* Recurse into supbart */ - snprintf(nextsection, sizeof(nextsection), "%s%s%d", - cursection, *cursection ? "." : "", 1); - fetch_part(txn, body->subpart, findsection, nextsection, msg_buf); - } - else if (!strcmp(findsection, cursection)) { - int encoding = body->charset_cte & 0xff; - const char *outbuf; - size_t outsize; - - outbuf = charset_decode_mimebody(buf_base(msg_buf) + body->content_offset, - body->content_size, encoding, - &body->decoded_body, &outsize); - - if (!outbuf) { - txn->error.desc = "Unknown MIME encoding\r\n"; - error_response(HTTP_SERVER_ERROR, txn); - return; - - } - - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%s/%s", body->type, body->subtype); - txn->resp_body.type = buf_cstring(&txn->buf); - - write_body(HTTP_OK, txn, outbuf, outsize); - } -}
View file
cyrus-imapd-2.5.tar.gz/imap/http_timezone.c
Deleted
@@ -1,1171 +0,0 @@ -/* http_timezone.c -- Routines for handling timezone service requests in httpd - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -/* - * TODO: - * - Implement localized names and "lang" parameter - * - Implement action=find with case-insensitive match - strncasecmp()? - */ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <ctype.h> -#include <string.h> -#include <syslog.h> -#include <assert.h> - -#include "global.h" -#include "hash.h" -#include "httpd.h" -#include "http_dav.h" -#include "http_err.h" -#include "http_proxy.h" -#include "jcal.h" -#include "map.h" -#include "tok.h" -#include "strhash.h" -#include "util.h" -#include "version.h" -#include "xcal.h" -#include "xstrlcpy.h" -#include "zoneinfo_db.h" - - -#define TIMEZONE_WELLKNOWN_URI "/.well-known/timezone" - -static time_t compile_time; -static void timezone_init(struct buf *serverinfo); -static void timezone_shutdown(void); -static int meth_get(struct transaction_t *txn, void *params); -static int action_capa(struct transaction_t *txn, struct hash_table *params); -static int action_list(struct transaction_t *txn, struct hash_table *params); -static int action_get(struct transaction_t *txn, struct hash_table *params); -static int action_expand(struct transaction_t *txn, struct hash_table *params); -static int json_response(int code, struct transaction_t *txn, json_t *root, - char **resp); -static int json_error_response(struct transaction_t *txn, const char *err); - -struct observance { - const char *name; - icaltimetype onset; - int offset_from; - int offset_to; - int is_daylight; -}; - -static const struct action_t { - const char *name; - int (*proc)(struct transaction_t *txn, struct hash_table *params); -} actions = { - { "capabilities", &action_capa }, - { "list", &action_list }, - { "get", &action_get }, - { "expand", &action_expand }, - { "find", &action_list }, - { NULL, NULL} -}; - - -static struct mime_type_t tz_mime_types = { - /* First item MUST be the default type and storage format */ - { "text/calendar; charset=utf-8", "2.0", "ics", "ifb", - (char* (*)(void *)) &icalcomponent_as_ical_string_r, - NULL, NULL, NULL, NULL - }, - { "application/calendar+xml; charset=utf-8", NULL, "xcs", "xfb", - (char* (*)(void *)) &icalcomponent_as_xcal_string, - NULL, NULL, NULL, NULL - }, - { "application/calendar+json; charset=utf-8", NULL, "jcs", "jfb", - (char* (*)(void *)) &icalcomponent_as_jcal_string, - NULL, NULL, NULL, NULL - }, - { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } -}; - - -/* Namespace for TIMEZONE feeds of mailboxes */ -struct namespace_t namespace_timezone = { - URL_NS_TIMEZONE, 0, "/timezone", TIMEZONE_WELLKNOWN_URI, 0 /* auth */, - /*mbtype*/0, - ALLOW_READ, - timezone_init, NULL, NULL, timezone_shutdown, - { - { NULL, NULL }, /* ACL */ - { NULL, NULL }, /* COPY */ - { NULL, NULL }, /* DELETE */ - { &meth_get, NULL }, /* GET */ - { &meth_get, NULL }, /* HEAD */ - { NULL, NULL }, /* LOCK */ - { NULL, NULL }, /* MKCALENDAR */ - { NULL, NULL }, /* MKCOL */ - { NULL, NULL }, /* MOVE */ - { &meth_options, NULL }, /* OPTIONS */ - { NULL, NULL }, /* POST */ - { NULL, NULL }, /* PROPFIND */ - { NULL, NULL }, /* PROPPATCH */ - { NULL, NULL }, /* PUT */ - { NULL, NULL }, /* REPORT */ - { &meth_trace, NULL }, /* TRACE */ - { NULL, NULL } /* UNLOCK */ - } -}; - - -static void timezone_init(struct buf *serverinfo) -{ - namespace_timezone.enabled = - config_httpmodules & IMAP_ENUM_HTTPMODULES_TIMEZONE; - - if (!namespace_timezone.enabled) return; - - /* Open zoneinfo db */ - if (zoneinfo_open(NULL)) { - namespace_timezone.enabled = 0; - return; - } - - if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON && - !strstr(buf_cstring(serverinfo), " Jansson/")) { - buf_printf(serverinfo, " Jansson/%s", JANSSON_VERSION); - } - - compile_time = calc_compile_time(__TIME__, __DATE__); -} - - -static void timezone_shutdown(void) -{ - zoneinfo_close(NULL); -} - - -/* Perform a GET/HEAD request */ -static int meth_get(struct transaction_t *txn, - void *params __attribute__((unused))) -{ - int ret; - tok_t tok; - char *param; - struct strlist *action; - struct hash_table query_params; - const struct action_t *ap = NULL; - - /* Parse the query string and add param/value pairs to hash table */ - construct_hash_table(&query_params, 10, 1); - tok_initm(&tok, URI_QUERY(txn->req_uri), "&=", TOK_TRIMLEFT|TOK_TRIMRIGHT); - while ((param = tok_next(&tok))) { - struct strlist *vals; - char *value = tok_next(&tok); - if (!value) break; - - vals = hash_lookup(param, &query_params); - appendstrlist(&vals, xmlURIUnescapeString(value, strlen(value), NULL)); - hash_insert(param, vals, &query_params); - } - tok_fini(&tok); - - action = hash_lookup("action", &query_params); - if (action && !action->next /* mandatory, once only */) { - for (ap = actions; ap->name && strcmp(action->s, ap->name); ap++); - } - - if (!ap || !ap->name) ret = json_error_response(txn, "invalid-action"); - else ret = ap->proc(txn, &query_params); - - free_hash_table(&query_params, (void (*)(void *)) &freestrlist); - - return ret; -} - - -/* Perform a capabilities action */ -static int action_capa(struct transaction_t *txn, - struct hash_table *params __attribute__((unused))) -{ - int precond; - struct message_guid guid; - const char *etag; - static time_t lastmod = 0; - static char *resp = NULL; - json_t *root = NULL; - - /* Generate ETag based on compile date/time of this source file. - * Extend this to include config file size/mtime if we add run-time options. - */ - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%ld", (long) compile_time); - message_guid_generate(&guid, buf_cstring(&txn->buf), buf_len(&txn->buf)); - etag = message_guid_encode(&guid); - - /* Check any preconditions, including range request */ - txn->flags.ranges = 1; - precond = check_precond(txn, NULL, etag, compile_time); - - switch (precond) { - case HTTP_OK: - case HTTP_PARTIAL: - case HTTP_NOT_MODIFIED: - /* Fill in Etag, Last-Modified, Expires */ - txn->resp_body.etag = etag; - txn->resp_body.lastmod = compile_time; - txn->resp_body.maxage = 86400; /* 24 hrs */ - txn->flags.cc |= CC_MAXAGE; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - return precond; - } - - if (txn->resp_body.lastmod > lastmod) { - struct zoneinfo info; - int r; - - /* Get info record from the database */ - if ((r = zoneinfo_lookup_info(&info))) return HTTP_SERVER_ERROR; - - /* Construct our response */ - root = json_pack("{s:i" /* version */ - " s:{s:s s:{s:b s:b} s:}" /* info */ - " s:" /* actions */ - " {s:s s:}" /* capabilities */ - " {s:s s:" /* list */ -// " {s:s s:b s:b}" /* lang */ - " {s:s s:b s:b}" /* tzid */ - " {s:s s:b s:b}" /* changedsince */ - " }" - " {s:s s:" /* get */ -// " {s:s s:b s:b}" /* lang */ - " {s:s s:b s:b}" /* tzid */ - " {s:s s:b s:b s:s s s}"/* format */ - " {s:s s:b s:b}" /* truncate */ - " }" - " {s:s s:" /* expand */ -// " {s:s s:b s:b}" /* lang */ - " {s:s s:b s:b}" /* tzid */ - " {s:s s:b s:b}" /* changedsince */ - " {s:s s:b s:b}" /* start */ - " {s:s s:b s:b}" /* end */ - " }" - " {s:s s:" /* find */ -// " {s:s s:b s:b}" /* lang */ - " {s:s s:b s:b}" /* name */ - " }" - " }", - "version", 1, - "info", "primary-source", info.data->s, - "truncated", "any", 1, "untruncated", 1, "contacts", - "actions", - "name", "capabilities", "parameters", - - "name", "list", "parameters", -// "name", "lang", "required", 0, "multi", 1, - "name", "tzid", "required", 0, "multi", 1, - "name", "changedsince", "required", 0, "multi", 0, - - "name", "get", "parameters", -// "name", "lang", "required", 0, "multi", 1, - "name", "tzid", "required", 1, "multi", 0, - "name", "format", "required", 0, "multi", 0, - "values", "text/calendar", "application/calendar+xml", - "application/calendar+json", - "name", "truncate", "required", 0, "multi", 0, - - "name", "expand", "parameters", -// "name", "lang", "required", 0, "multi", 1, - "name", "tzid", "required", 1, "multi", 0, - "name", "changedsince", "required", 0, "multi", 0, - "name", "start", "required", 0, "multi", 0, - "name", "end", "required", 0, "multi", 0, - - "name", "find", "parameters", -// "name", "lang", "required", 0, "multi", 1, - "name", "name", "required", 1, "multi", 0); - freestrlist(info.data); - - if (!root) { - txn->error.desc = "Unable to create JSON response"; - return HTTP_SERVER_ERROR; - } - - /* Update lastmod */ - lastmod = txn->resp_body.lastmod; - } - - /* Output the JSON object */ - return json_response(precond, txn, root, &resp); -} - - -static int list_cb(const char *tzid, int tzidlen, - struct zoneinfo *zi, void *rock) -{ - json_t *tzarray = (json_t *) rock, *tz; - char tzidbuf200, lastmod21; - - strlcpy(tzidbuf, tzid, tzidlen+1); - rfc3339date_gen(lastmod, sizeof(lastmod), zi->dtstamp); - - tz = json_pack("{s:s s:s}", "tzid", tzidbuf, "last-modified", lastmod); - json_array_append_new(tzarray, tz); - - if (zi->data) { - struct strlist *sl; - json_t *aliases = json_array(); - - json_object_set_new(tz, "aliases", aliases); - - for (sl = zi->data; sl; sl = sl->next) - json_array_append_new(aliases, json_string(sl->s)); - } - - return 0; -} - - -/* Perform a list action */ -static int action_list(struct transaction_t *txn, struct hash_table *params) -{ - int r, precond, tzid_only = 1; - struct strlist *param, *name = NULL; - struct resp_body_t *resp_body = &txn->resp_body; - struct zoneinfo info; - time_t lastmod, changedsince = 0; - json_t *root = NULL; - - /* Sanity check the parameters */ - param = hash_lookup("action", params); - if (!strcmp("find", param->s)) { - name = hash_lookup("name", params); - if (!name || name->next /* mandatory, once only */) { - return json_error_response(txn, "invalid-name"); - } - tzid_only = 0; - } - else { - param = hash_lookup("changedsince", params); - if (param) { - changedsince = icaltime_as_timet(icaltime_from_string(param->s)); - if (!changedsince || param->next /* once only */) - return json_error_response(txn, "invalid-changedsince"); - } - - name = hash_lookup("tzid", params); - if (name) { - if (changedsince) return json_error_response(txn, "invalid-tzid"); - else { - /* Check for tzid=*, and revert to empty list */ - struct strlist *sl; - - for (sl = name; sl && strcmp(sl->s, "*"); sl = sl->next); - if (sl) name = NULL; - } - } - } - - /* Get info record from the database */ - if ((r = zoneinfo_lookup_info(&info))) return HTTP_SERVER_ERROR; - - /* Generate ETag & Last-Modified from info record */ - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%u-%ld", strhash(info.data->s), info.dtstamp); - lastmod = info.dtstamp; - freestrlist(info.data); - - /* Check any preconditions, including range request */ - txn->flags.ranges = 1; - precond = check_precond(txn, NULL, buf_cstring(&txn->buf), lastmod); - - switch (precond) { - case HTTP_OK: - case HTTP_PARTIAL: - case HTTP_NOT_MODIFIED: - /* Fill in ETag, Last-Modified, and Expires */ - resp_body->etag = buf_cstring(&txn->buf); - resp_body->lastmod = lastmod; - resp_body->maxage = 86400; /* 24 hrs */ - txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; - if (httpd_userid) txn->flags.cc |= CC_PUBLIC; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - resp_body->type = NULL; - return precond; - } - - - if (txn->meth == METH_GET) { - char dtstamp21; - - /* Start constructing our response */ - rfc3339date_gen(dtstamp, sizeof(dtstamp), lastmod); - root = json_pack("{s:s s:}", "dtstamp", dtstamp, "timezones"); - if (!root) { - txn->error.desc = "Unable to create JSON response"; - return HTTP_SERVER_ERROR; - } - - /* Add timezones to array */ - do { - zoneinfo_find(name ? name->s : NULL, tzid_only, changedsince, - &list_cb, json_object_get(root, "timezones")); - } while (name && (name = name->next)); - } - - /* Output the JSON object */ - return json_response(precond, txn, root, NULL); -} - - -static void check_tombstone(struct observance *tombstone, - struct observance *obs, icaltimetype *recur) -{ - icaltimetype *onset = recur ? recur : &obs->onset; - - if (icaltime_compare(*onset, tombstone->onset) > 0) { - /* onset is closer to cutoff than existing tombstone */ - tombstone->name = icalmemory_tmp_copy(obs->name); - tombstone->offset_from = tombstone->offset_to = obs->offset_to; - tombstone->is_daylight = obs->is_daylight; - memcpy(&tombstone->onset, onset, sizeof(icaltimetype)); - } -} - -struct rdate { - icalproperty *prop; - struct icaldatetimeperiodtype date; -}; - -static int rdate_compare(const void *rdate1, const void *rdate2) -{ - return icaltime_compare(((struct rdate *) rdate1)->date.time, - ((struct rdate *) rdate2)->date.time); -} - -static void truncate_vtimezone(icalcomponent *vtz, icaltimetype *truncate) -{ - icalcomponent *comp, *nextc; - struct observance tombstone; - - memset(&tombstone, 0, sizeof(struct observance)); - - /* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */ - for (comp = icalcomponent_get_first_component(vtz, ICAL_ANY_COMPONENT); - comp; comp = nextc) { - icalproperty *prop, *dtstart_prop = NULL, *rrule_prop = NULL; - icalarray *rdate_array = icalarray_new(sizeof(struct rdate), 20); - struct observance obs; - unsigned n; - int r; - - nextc = icalcomponent_get_next_component(vtz, ICAL_ANY_COMPONENT); - - memset(&obs, 0, sizeof(struct observance)); - obs.is_daylight = (icalcomponent_isa(comp) == ICAL_XDAYLIGHT_COMPONENT); - - /* Grab the properties that we require to expand recurrences */ - for (prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); - prop; - prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY)) { - - switch (icalproperty_isa(prop)) { - case ICAL_TZNAME_PROPERTY: - obs.name = icalproperty_get_tzname(prop); - break; - - case ICAL_DTSTART_PROPERTY: - dtstart_prop = prop; - obs.onset = icalproperty_get_dtstart(prop); - break; - - case ICAL_TZOFFSETFROM_PROPERTY: - obs.offset_from = icalproperty_get_tzoffsetfrom(prop); - break; - - case ICAL_TZOFFSETTO_PROPERTY: - obs.offset_to = icalproperty_get_tzoffsetto(prop); - break; - - case ICAL_RRULE_PROPERTY: - rrule_prop = prop; - break; - - case ICAL_RDATE_PROPERTY: { - struct rdate rdate = { prop, icalproperty_get_rdate(prop) }; - - icalarray_append(rdate_array, &rdate); - break; - } - - default: - /* ignore all other properties */ - break; - } - } - - /* We MUST have DTSTART, TZNAME, TZOFFSETFROM, and TZOFFSETTO */ - if (!dtstart_prop || !obs.name || !obs.offset_from || !obs.offset_to) - continue; - - r = icaltime_compare(obs.onset, *truncate); - if (r <= 0) { - /* Check DTSTART vs tombstone */ - check_tombstone(&tombstone, &obs, NULL); - } - - if (r >= 0) { - /* All observances occur on or after our cutoff, nothing to do */ - icalarray_free(rdate_array); - continue; - } - - /* Check RRULE */ - if (rrule_prop) { - struct icalrecurrencetype rrule; - - rrule = icalproperty_get_rrule(rrule_prop); - - /* Check RRULE duration */ - if (!icaltime_is_null_time(rrule.until)) { - if (rrule.until.is_utc) { - /* Adjust UNTIL to local time */ - icaltime_adjust(&rrule.until, 0, 0, 0, obs.offset_from); - rrule.until.is_utc = 0; - } - - if (icaltime_compare(rrule.until, *truncate) < 0) { - /* RRULE ends prior to our cutoff - remove it */ - icalcomponent_remove_property(comp, rrule_prop); - icalproperty_free(rrule_prop); - rrule_prop = NULL; - - /* Check UNTIL vs tombstone */ - check_tombstone(&tombstone, &obs, &rrule.until); - } - } - - if (rrule_prop) { - icalrecur_iterator *ritr; - - /* Set iterator to start 1 year prior to our cutoff */ - obs.onset.year = truncate->year - 1; - obs.onset.month = truncate->month; - obs.onset.day = truncate->day; - - ritr = icalrecur_iterator_new(rrule, obs.onset); - - /* Check last recurrence prior to our cutoff vs tombstone */ - obs.onset = icalrecur_iterator_next(ritr); - check_tombstone(&tombstone, &obs, NULL); - - /* Use first recurrence after our cutoff as new DTSTART */ - obs.onset = icalrecur_iterator_next(ritr); - icalproperty_set_dtstart(dtstart_prop, obs.onset); - dtstart_prop = NULL; - - icalrecur_iterator_free(ritr); - } - } - - /* Sort the RDATEs by onset */ - icalarray_sort(rdate_array, &rdate_compare); - - /* Check RDATEs */ - for (n = 0; n < rdate_array->num_elements; n++) { - struct rdate *rdate = icalarray_element_at(rdate_array, n); - - r = icaltime_compare(rdate->date.time, *truncate); - if (r <= 0) { - /* Check RDATE vs tombstone */ - check_tombstone(&tombstone, &obs, &rdate->date.time); - } - - if (r < 0) { - /* RDATE occurs prior to our cutoff - remove it */ - icalcomponent_remove_property(comp, rdate->prop); - icalproperty_free(rdate->prop); - } - else { - if (dtstart_prop) { - /* Make this RDATE the new DTSTART */ - icalproperty_set_dtstart(dtstart_prop, rdate->date.time); - dtstart_prop = NULL; - - icalcomponent_remove_property(comp, rdate->prop); - icalproperty_free(rdate->prop); - } - break; - } - } - icalarray_free(rdate_array); - - /* Final check */ - if (dtstart_prop) { - /* All observances occur prior to our cutoff, remove comp */ - icalcomponent_remove_component(vtz, comp); - icalcomponent_free(comp); - } - } - - if (icaltime_compare(tombstone.onset, *truncate) < 0) { - /* Need to add a tombstone component starting at our cutoff */ - comp = icalcomponent_vanew( - tombstone.is_daylight ? - ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT, - icalproperty_new_tzoffsetfrom(tombstone.offset_from), - icalproperty_new_tzoffsetto(tombstone.offset_to), - icalproperty_new_tzname(tombstone.name), - icalproperty_new_dtstart(*truncate), - 0); - icalcomponent_add_component(vtz, comp); - } -} - -/* Perform a get action */ -static int action_get(struct transaction_t *txn, struct hash_table *params) -{ - int r, precond; - struct strlist *param; - const char *tzid; - struct zoneinfo zi; - time_t lastmod; - icaltimetype truncate = icaltime_null_time(); - char *data = NULL; - unsigned long datalen = 0; - struct resp_body_t *resp_body = &txn->resp_body; - struct mime_type_t *mime = NULL; - - /* Sanity check the parameters */ - param = hash_lookup("tzid", params); - if (!param || param->next /* mandatory, once only */ - || strchr(param->s, '.') /* paranoia */) { - return json_error_response(txn, "invalid-tzid"); - } - tzid = param->s; - - /* Check/find requested MIME type */ - param = hash_lookup("format", params); - if (param && !param->next /* optional, once only */) { - for (mime = tz_mime_types; mime->content_type; mime++) { - if (is_mediatype(param->s, mime->content_type)) break; - } - } - else mime = tz_mime_types; - - if (!mime || !mime->content_type) { - return json_error_response(txn, "invalid-format"); - } - - /* Check for any truncation */ - param = hash_lookup("truncate", params); - if (param) { - truncate = icaltime_from_day_of_year(1, atoi(param->s)); - truncate.is_date = truncate.hour = truncate.minute = truncate.second = 0; - if (icaltime_is_null_time(truncate) || param->next /* once only */) - return json_error_response(txn, "invalid-truncate"); - } - - /* Get info record from the database */ - if ((r = zoneinfo_lookup(tzid, &zi))) - return (r == CYRUSDB_NOTFOUND ? HTTP_NOT_FOUND : HTTP_SERVER_ERROR); - - /* Generate ETag & Last-Modified from info record */ - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%u-%ld", strhash(tzid), zi.dtstamp); - lastmod = zi.dtstamp; - freestrlist(zi.data); - - /* Check any preconditions, including range request */ - txn->flags.ranges = 1; - precond = check_precond(txn, NULL, buf_cstring(&txn->buf), lastmod); - - switch (precond) { - case HTTP_OK: - case HTTP_PARTIAL: - case HTTP_NOT_MODIFIED: - /* Fill in Content-Type, ETag, Last-Modified, and Expires */ - resp_body->type = mime->content_type; - resp_body->etag = buf_cstring(&txn->buf); - resp_body->lastmod = lastmod; - resp_body->maxage = 86400; /* 24 hrs */ - txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; - if (httpd_userid) txn->flags.cc |= CC_PUBLIC; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - resp_body->type = NULL; - return precond; - } - - - if (txn->meth == METH_GET) { - static struct buf pathbuf = BUF_INITIALIZER; - const char *path, *proto, *host, *msg_base = NULL; - unsigned long msg_size = 0; - icalcomponent *ical, *vtz; - icalproperty *prop; - int fd; - - /* Open, mmap, and parse the file */ - buf_reset(&pathbuf); - buf_printf(&pathbuf, "%s%s/%s.ics", - config_dir, FNAME_ZONEINFODIR, tzid); - path = buf_cstring(&pathbuf); - if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR; - - map_refresh(fd, 1, &msg_base, &msg_size, MAP_UNKNOWN_LEN, path, NULL); - if (!msg_base) return HTTP_SERVER_ERROR; - - ical = icalparser_parse_string(msg_base); - map_free(&msg_base, &msg_size); - close(fd); - - vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT); - prop = icalcomponent_get_first_property(vtz, ICAL_TZID_PROPERTY); - - if (zi.type == ZI_LINK) { - const char *equiv = icalproperty_get_tzid(prop); - - /* Substitute TZID alias */ - icalproperty_set_tzid(prop, tzid); - - /* Add EQUIVALENT-TZID */ - prop = icalproperty_new_x(equiv); - icalproperty_set_x_name(prop, "EQUIVALENT-TZID"); - icalcomponent_add_property(vtz, prop); - } - - /* Start constructing TZURL */ - buf_reset(&pathbuf); - http_proto_host(txn->req_hdrs, &proto, &host); - buf_printf(&pathbuf, "%s://%s%s?action=get&tzid=%s", - proto, host, namespace_timezone.prefix, tzid); - if (mime != tz_mime_types) { - buf_printf(&pathbuf, "&format=%.*s", - (int) strcspn(mime->content_type, ";"), - mime->content_type); - } - if (!icaltime_is_null_time(truncate)) { - buf_printf(&pathbuf, "&truncate=%d", truncate.year); - - /* Truncate the VTIMEZONE */ - truncate_vtimezone(vtz, &truncate); - } - - /* Set TZURL property */ - prop = icalproperty_new_tzurl(buf_cstring(&pathbuf)); - icalcomponent_add_property(vtz, prop); - - /* Convert to requested MIME type */ - data = mime->to_string(ical); - datalen = strlen(data); - - /* Set Content-Disposition filename */ - buf_reset(&pathbuf); - buf_printf(&pathbuf, "%s.%s", tzid, mime->file_ext); - resp_body->fname = buf_cstring(&pathbuf); - - icalcomponent_free(ical); - } - - write_body(precond, txn, data, datalen); - - if (data) free(data); - - return 0; -} - - -static int observance_compare(const void *obs1, const void *obs2) -{ - return icaltime_compare(((struct observance *) obs1)->onset, - ((struct observance *) obs2)->onset); -} - -/* Perform an expand action */ -static int action_expand(struct transaction_t *txn, struct hash_table *params) -{ - int r, precond; - struct strlist *param; - const char *tzid; - struct zoneinfo zi; - time_t lastmod, changedsince = 0; - icaltimetype start, end; - struct resp_body_t *resp_body = &txn->resp_body; - json_t *root = NULL; - - /* Sanity check the parameters */ - param = hash_lookup("tzid", params); - if (!param || param->next /* mandatory, once only */ - || strchr(param->s, '.') /* paranoia */) { - return json_error_response(txn, "invalid-tzid"); - } - tzid = param->s; - - param = hash_lookup("changedsince", params); - if (param) { - changedsince = icaltime_as_timet(icaltime_from_string(param->s)); - if (!changedsince || param->next /* once only */) - return json_error_response(txn, "invalid-changedsince"); - } - - param = hash_lookup("start", params); - if (param) { - start = icaltime_from_string(param->s); - if (icaltime_is_null_time(start) || param->next /* once only */) - return json_error_response(txn, "invalid-start"); - } - else { - /* Default to current year */ - time_t now = time(0); - struct tm *tm = gmtime(&now); - - start = icaltime_from_day_of_year(1, tm->tm_year + 1900); - } - - param = hash_lookup("end", params); - if (param) { - end = icaltime_from_string(param->s); - if (icaltime_compare(end, start) <= 0 /* end MUST be > start */ - || param->next /* once only */) - return json_error_response(txn, "invalid-end"); - } - else { - /* Default to start year + 10 */ - memcpy(&end, &start, sizeof(icaltimetype)); - end.year += 10; - } - - /* Get info record from the database */ - if ((r = zoneinfo_lookup(tzid, &zi))) - return (r == CYRUSDB_NOTFOUND ? HTTP_NOT_FOUND : HTTP_SERVER_ERROR); - - /* Generate ETag & Last-Modified from info record */ - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%u-%ld", strhash(tzid), zi.dtstamp); - lastmod = zi.dtstamp; - freestrlist(zi.data); - - /* Check any preconditions, including range request */ - txn->flags.ranges = 1; - if (lastmod <= changedsince) precond = HTTP_NOT_MODIFIED; - else precond = check_precond(txn, NULL, buf_cstring(&txn->buf), lastmod); - - switch (precond) { - case HTTP_OK: - case HTTP_PARTIAL: - case HTTP_NOT_MODIFIED: - /* Fill in ETag, Last-Modified, and Expires */ - resp_body->etag = buf_cstring(&txn->buf); - resp_body->lastmod = lastmod; - resp_body->maxage = 86400; /* 24 hrs */ - txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; - if (httpd_userid) txn->flags.cc |= CC_PUBLIC; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - resp_body->type = NULL; - return precond; - } - - - if (txn->meth == METH_GET) { - static struct buf pathbuf = BUF_INITIALIZER; - const char *path, *msg_base = NULL; - unsigned long msg_size = 0; - icalcomponent *ical, *vtz, *comp; - char dtstamp21; - icalarray *obsarray; - json_t *jobsarray; - unsigned n; - int fd; - - /* Open, mmap, and parse the file */ - buf_reset(&pathbuf); - buf_printf(&pathbuf, "%s%s/%s.ics", - config_dir, FNAME_ZONEINFODIR, tzid); - path = buf_cstring(&pathbuf); - if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR; - - map_refresh(fd, 1, &msg_base, &msg_size, MAP_UNKNOWN_LEN, path, NULL); - if (!msg_base) return HTTP_SERVER_ERROR; - - ical = icalparser_parse_string(msg_base); - map_free(&msg_base, &msg_size); - close(fd); - - /* Start constructing our response */ - rfc3339date_gen(dtstamp, sizeof(dtstamp), lastmod); - root = json_pack("{s:s s:}", "dtstamp", dtstamp, "observances"); - if (!root) { - txn->error.desc = "Unable to create JSON response"; - return HTTP_SERVER_ERROR; - } - - /* Create an array of observances */ - obsarray = icalarray_new(sizeof(struct observance), 20); - - /* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */ - vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT); - for (comp = icalcomponent_get_first_component(vtz, ICAL_ANY_COMPONENT); - comp; - comp = icalcomponent_get_next_component(vtz, ICAL_ANY_COMPONENT)) { - - icaltimetype dtstart = icaltime_null_time(); - struct observance obs; - icalproperty *prop, *rrule_prop = NULL; - - /* Grab the properties that we require to expand recurrences */ - memset(&obs, 0, sizeof(struct observance)); - for (prop = icalcomponent_get_first_property(comp, - ICAL_ANY_PROPERTY); - prop; - prop = icalcomponent_get_next_property(comp, - ICAL_ANY_PROPERTY)) { - - switch (icalproperty_isa(prop)) { - case ICAL_TZNAME_PROPERTY: - obs.name = icalproperty_get_tzname(prop); - break; - - case ICAL_DTSTART_PROPERTY: - dtstart = icalproperty_get_dtstart(prop); - break; - - case ICAL_TZOFFSETFROM_PROPERTY: - obs.offset_from = icalproperty_get_tzoffsetfrom(prop); - break; - - case ICAL_TZOFFSETTO_PROPERTY: - obs.offset_to = icalproperty_get_tzoffsetto(prop); - break; - - case ICAL_RRULE_PROPERTY: - rrule_prop = prop; - break; - - default: - /* ignore all other properties */ - break; - } - } - - /* We MUST have TZNAME, DTSTART, TZOFFSETFROM and TZOFFSETTO */ - if (!obs.name || !obs.offset_from || !obs.offset_to || - icaltime_is_null_time(dtstart)) continue; - - /* Adjust DTSTART to UTC */ - memcpy(&obs.onset, &dtstart, sizeof(icaltimetype)); - icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from); - obs.onset.is_utc = 1; - - if (icaltime_compare(obs.onset, end) > 0) { - /* Skip observance(s) after our window */ - } - else if (rrule_prop) { - /* Add any RRULE observances within our window */ - struct icalrecurrencetype rrule; - icalrecur_iterator *ritr; - icaltimetype recur; - - rrule = icalproperty_get_rrule(rrule_prop); - - if (!icaltime_is_null_time(rrule.until) && rrule.until.is_utc) { - /* Adjust UNTIL to local time */ - icaltime_adjust(&rrule.until, 0, 0, 0, obs.offset_from); - rrule.until.is_utc = 0; - } - - if (icaltime_compare(start, obs.onset) > 0) { - /* Set iterator dtstart to be 1 day prior to our window */ - obs.onset.year = start.year; - obs.onset.month = start.month; - obs.onset.day = start.day - 1; - } - - /* Adjust iterator dtstart to local time */ - icaltime_adjust(&obs.onset, 0, 0, 0, obs.offset_from); - obs.onset.is_utc = 0; - - ritr = icalrecur_iterator_new(rrule, obs.onset); - while (!icaltime_is_null_time(recur = - icalrecur_iterator_next(ritr))) { - /* Adjust observance to UTC */ - memcpy(&obs.onset, &recur, sizeof(icaltimetype)); - icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from); - obs.onset.is_utc = 1; - - if (icaltime_compare(obs.onset, end) > 0) { - /* Quit if we've gone past our window */ - break; - } - else if (icaltime_compare(obs.onset, start) < 0) { - /* Skip observances prior to our window */ - } - else { - /* Add the observance to our array */ - icalarray_append(obsarray, &obs); - } - } - icalrecur_iterator_free(ritr); - } - else if (icaltime_compare(obs.onset, start) < 0) { - /* Skip observances prior to our window */ - } - else { - /* Add the DTSTART observance to our array */ - icalarray_append(obsarray, &obs); - } - - /* Add any RDATE observances within our window */ - for (prop = icalcomponent_get_first_property(comp, - ICAL_RDATE_PROPERTY); - prop; - prop = icalcomponent_get_next_property(comp, - ICAL_RDATE_PROPERTY)) { - struct icaldatetimeperiodtype rdate = - icalproperty_get_rdate(prop); - - /* Adjust RDATE to UTC */ - memcpy(&obs.onset, &rdate.time, sizeof(icaltimetype)); - icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from); - obs.onset.is_utc = 1; - - if (icaltime_compare(obs.onset, start) < 0) { - /* Skip observances prior to our window */ - } - else if (icaltime_compare(obs.onset, end) > 0) { - /* Skip observances after our window */ - } - else if (icaltime_compare(obs.onset, dtstart) == 0) { - /* Skip duplicates of DTSTART observance */ - } - else { - /* Add the RDATE observance to our array */ - icalarray_append(obsarray, &obs); - } - } - } - - /* Sort the observances by onset */ - icalarray_sort(obsarray, &observance_compare); - - /* Add observances to JSON array */ - jobsarray = json_object_get(root, "observances"); - for (n = 0; n < obsarray->num_elements; n++) { - struct observance *obs = icalarray_element_at(obsarray, n); - - json_array_append_new(jobsarray, - json_pack("{s:s s:s s:i s:i}", - "name", obs->name, - "onset", - icaltime_as_ical_string(obs->onset), - "utc-offset-from", obs->offset_from, - "utc-offset-to", obs->offset_to)); - } - icalarray_free(obsarray); - - icalcomponent_free(ical); - } - - /* Output the JSON object */ - return json_response(precond, txn, root, NULL); -} - - -static int json_response(int code, struct transaction_t *txn, json_t *root, - char **resp) -{ - size_t flags = JSON_PRESERVE_ORDER; - char *buf = NULL; - - if (root) { - /* Dump JSON object into a text buffer */ - flags |= (config_httpprettytelemetry ? JSON_INDENT(2) : JSON_COMPACT); - buf = json_dumps(root, flags); - json_decref(root); - - if (!buf) { - txn->error.desc = "Error dumping JSON object"; - return HTTP_SERVER_ERROR; - } - else if (resp) { - if (*resp) free(*resp); - *resp = buf; - } - } - else if (resp) buf = *resp; - - /* Output the JSON object */ - txn->resp_body.type = "application/json; charset=utf-8"; - write_body(code, txn, buf, buf ? strlen(buf) : 0); - - if (!resp && buf) free(buf); - - return 0; -} - - -static int json_error_response(struct transaction_t *txn, const char *err) -{ - json_t *root; - - root = json_pack("{s:s}", "error", err); - if (!root) { - txn->error.desc = "Unable to create JSON response"; - return HTTP_SERVER_ERROR; - } - - return json_response(HTTP_BAD_REQUEST, txn, root, NULL); -}
View file
cyrus-imapd-2.5.tar.gz/imap/httpd.c
Deleted
@@ -1,4066 +0,0 @@ -/* httpd.c -- HTTP/WebDAV/CalDAV server protocol parsing - * - * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <stdio.h> -#include <errno.h> -#include <string.h> -#include <fcntl.h> -#include <signal.h> -#include <sys/types.h> -#include <sys/param.h> -#include <syslog.h> -#include <netdb.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <ctype.h> -#include "prot.h" - -#include <sasl/sasl.h> -#include <sasl/saslutil.h> - -#include "httpd.h" -#include "http_proxy.h" - -#include "acl.h" -#include "assert.h" -#include "util.h" -#include "iptostring.h" -#include "global.h" -#include "tls.h" -#include "map.h" - -#include "exitcodes.h" -#include "imapd.h" -#include "imap_err.h" -#include "http_err.h" -#include "proc.h" -#include "version.h" -#include "xstrlcpy.h" -#include "xstrlcat.h" -#include "sync_log.h" -#include "telemetry.h" -#include "backend.h" -#include "proxy.h" -#include "userdeny.h" -#include "message.h" -#include "idle.h" -#include "times.h" -#include "tok.h" -#include "wildmat.h" -#include "md5.h" - -#ifdef WITH_DAV -#include "http_dav.h" -#endif - -#include <libxml/tree.h> -#include <libxml/HTMLtree.h> -#include <libxml/uri.h> - -#ifdef HAVE_ZLIB -#include <zlib.h> -#endif /* HAVE_ZLIB */ - - -static const char tls_message = - HTML_DOCTYPE - "<html>\n<head>\n<title>TLS Required</title>\n</head>\n" \ - "<body>\n<h2>TLS is required to use Basic authentication</h2>\n" \ - "Use <a href=\"%s\">%s</a> instead.\n" \ - "</body>\n</html>\n"; - -extern int optind; -extern char *optarg; -extern int opterr; - - -#ifdef HAVE_SSL -static SSL *tls_conn; -#endif /* HAVE_SSL */ - -sasl_conn_t *httpd_saslconn; /* the sasl connection context */ - -static struct wildmat *allow_cors = NULL; -int httpd_timeout, httpd_keepalive; -char *httpd_userid = NULL, *proxy_userid = NULL; -char *httpd_extradomain = NULL; -struct auth_state *httpd_authstate = 0; -int httpd_userisadmin = 0; -int httpd_userisproxyadmin = 0; -struct sockaddr_storage httpd_localaddr, httpd_remoteaddr; -int httpd_haveaddr = 0; -char httpd_clienthostNI_MAXHOST*2+1 = "local"; -struct protstream *httpd_out = NULL; -struct protstream *httpd_in = NULL; -struct protgroup *protin = NULL; -static int httpd_logfd = -1; - -static sasl_ssf_t extprops_ssf = 0; -int https = 0; -int httpd_tls_done = 0; -int httpd_tls_required = 0; -unsigned avail_auth_schemes = 0; /* bitmask of available auth schemes */ -unsigned long config_httpmodules; -int config_httpprettytelemetry; - -static time_t compile_time; -struct buf serverinfo = BUF_INITIALIZER; - -static void digest_send_success(const char *name __attribute__((unused)), - const char *data) -{ - prot_printf(httpd_out, "Authentication-Info: %s\r\n", data); -} - -/* List of HTTP auth schemes that we support */ -struct auth_scheme_t auth_schemes = { - { AUTH_BASIC, "Basic", NULL, AUTH_SERVER_FIRST | AUTH_BASE64, NULL, NULL }, - { AUTH_DIGEST, "Digest", HTTP_DIGEST_MECH, AUTH_NEED_REQUEST|AUTH_SERVER_FIRST, - &digest_send_success, digest_recv_success }, - { AUTH_SPNEGO, "Negotiate", "GSS-SPNEGO", AUTH_BASE64, NULL, NULL }, - { AUTH_NTLM, "NTLM", "NTLM", AUTH_NEED_PERSIST | AUTH_BASE64, NULL, NULL }, - { -1, NULL, NULL, 0, NULL, NULL } -}; - - -/* the sasl proxy policy context */ -static struct proxy_context httpd_proxyctx = { - 0, 1, &httpd_authstate, &httpd_userisadmin, &httpd_userisproxyadmin -}; - -/* signal to config.c */ -const int config_need_data = CONFIG_NEED_PARTITION_DATA; - -/* current namespace */ -HIDDEN struct namespace httpd_namespace; - -/* PROXY STUFF */ -/* we want a list of our outgoing connections here and which one we're - currently piping */ - -/* the current server most commands go to */ -struct backend *backend_current = NULL; - -/* our cached connections */ -struct backend **backend_cached = NULL; - -/* end PROXY stuff */ - -static void starttls(int https); -void usage(void); -void shut_down(int code) __attribute__ ((noreturn)); - -/* Enable the resetting of a sasl_conn_t */ -static int reset_saslconn(sasl_conn_t **conn); - -static void cmdloop(void); -static int parse_expect(struct transaction_t *txn); -static void parse_connection(struct transaction_t *txn); -static int parse_ranges(const char *hdr, unsigned long len, - struct range **ranges); -static int proxy_authz(const char **authzid, struct transaction_t *txn); -static void auth_success(struct transaction_t *txn); -static int http_auth(const char *creds, struct transaction_t *txn); -static void keep_alive(int sig); - -static int meth_get(struct transaction_t *txn, void *params); -static int meth_propfind_root(struct transaction_t *txn, void *params); - - -static struct { - char *ipremoteport; - char *iplocalport; - sasl_ssf_t ssf; - char *authid; -} saslprops = {NULL,NULL,0,NULL}; - -static struct sasl_callback mysasl_cb = { - { SASL_CB_GETOPT, (mysasl_cb_ft *) &mysasl_config, NULL }, - { SASL_CB_PROXY_POLICY, (mysasl_cb_ft *) &mysasl_proxy_policy, (void*) &httpd_proxyctx }, - { SASL_CB_CANON_USER, (mysasl_cb_ft *) &mysasl_canon_user, NULL }, - { SASL_CB_LIST_END, NULL, NULL } -}; - -/* Array of HTTP methods known by our server. */ -const struct known_meth_t http_methods = { - { "ACL", 0 }, - { "COPY", METH_NOBODY }, - { "DELETE", METH_NOBODY }, - { "GET", METH_NOBODY }, - { "HEAD", METH_NOBODY }, - { "LOCK", 0 }, - { "MKCALENDAR", 0 }, - { "MKCOL", 0 }, - { "MOVE", METH_NOBODY }, - { "OPTIONS", METH_NOBODY }, - { "POST", 0 }, - { "PROPFIND", 0 }, - { "PROPPATCH", 0 }, - { "PUT", 0 }, - { "REPORT", 0 }, - { "TRACE", METH_NOBODY }, - { "UNLOCK", METH_NOBODY }, - { NULL, 0 } -}; - -/* Namespace to fetch static content from filesystem */ -struct namespace_t namespace_default = { - URL_NS_DEFAULT, 1, "", NULL, 0 /* no auth */, - /*mbtype*/0, - ALLOW_READ, - NULL, NULL, NULL, NULL, - { - { NULL, NULL }, /* ACL */ - { NULL, NULL }, /* COPY */ - { NULL, NULL }, /* DELETE */ - { &meth_get, NULL }, /* GET */ - { &meth_get, NULL }, /* HEAD */ - { NULL, NULL }, /* LOCK */ - { NULL, NULL }, /* MKCALENDAR */ - { NULL, NULL }, /* MKCOL */ - { NULL, NULL }, /* MOVE */ - { &meth_options, NULL }, /* OPTIONS */ - { NULL, NULL }, /* POST */ - { &meth_propfind_root, NULL }, /* PROPFIND */ - { NULL, NULL }, /* PROPPATCH */ - { NULL, NULL }, /* PUT */ - { NULL, NULL }, /* REPORT */ - { &meth_trace, NULL }, /* TRACE */ - { NULL, NULL }, /* UNLOCK */ - } -}; - -/* Array of different namespaces and features supported by the server */ -struct namespace_t *namespaces = { -#ifdef WITH_DAV - &namespace_principal, - &namespace_calendar, - &namespace_addressbook, - &namespace_ischedule, - &namespace_domainkey, -#ifdef WITH_JSON - &namespace_timezone, -#endif -#endif -#ifdef WITH_RSS - &namespace_rss, -#endif - &namespace_dblookup, - &namespace_default, /* MUST be present and be last!! */ - NULL, -}; - - -static void httpd_reset(void) -{ - int i; - int bytes_in = 0; - int bytes_out = 0; - - /* Do any namespace specific cleanup */ - for (i = 0; namespacesi; i++) { - if (namespacesi->enabled && namespacesi->reset) - namespacesi->reset(); - } - - proc_cleanup(); - - /* close backend connections */ - i = 0; - while (backend_cached && backend_cachedi) { - proxy_downserver(backend_cachedi); - free(backend_cachedi->context); - free(backend_cachedi); - i++; - } - if (backend_cached) free(backend_cached); - backend_cached = NULL; - backend_current = NULL; - - if (httpd_in) { - prot_NONBLOCK(httpd_in); - prot_fill(httpd_in); - bytes_in = prot_bytes_in(httpd_in); - prot_free(httpd_in); - } - - if (httpd_out) { - prot_flush(httpd_out); - bytes_out = prot_bytes_out(httpd_out); - prot_free(httpd_out); - } - - if (config_auditlog) { - syslog(LOG_NOTICE, - "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>", - session_id(), bytes_in, bytes_out); - } - - httpd_in = httpd_out = NULL; - - if (protin) protgroup_reset(protin); - -#ifdef HAVE_SSL - if (tls_conn) { - tls_reset_servertls(&tls_conn); - tls_conn = NULL; - } -#endif - - cyrus_reset_stdio(); - - strcpy(httpd_clienthost, "local"); - if (httpd_logfd != -1) { - close(httpd_logfd); - httpd_logfd = -1; - } - if (httpd_userid != NULL) { - free(httpd_userid); - httpd_userid = NULL; - } - if (httpd_extradomain != NULL) { - free(httpd_extradomain); - httpd_extradomain = NULL; - } - if (proxy_userid != NULL) { - free(proxy_userid); - proxy_userid = NULL; - } - if (httpd_authstate) { - auth_freestate(httpd_authstate); - httpd_authstate = NULL; - } - if (httpd_saslconn) { - sasl_dispose(&httpd_saslconn); - httpd_saslconn = NULL; - } - httpd_tls_done = 0; - - if(saslprops.iplocalport) { - free(saslprops.iplocalport); - saslprops.iplocalport = NULL; - } - if(saslprops.ipremoteport) { - free(saslprops.ipremoteport); - saslprops.ipremoteport = NULL; - } - if(saslprops.authid) { - free(saslprops.authid); - saslprops.authid = NULL; - } - saslprops.ssf = 0; -} - -/* - * run once when process is forked; - * MUST NOT exit directly; must return with non-zero error code - */ -int service_init(int argc __attribute__((unused)), - char **argv __attribute__((unused)), - char **envp __attribute__((unused))) -{ - int r, opt, i, allow_trace = config_getswitch(IMAPOPT_HTTPALLOWTRACE); - - LIBXML_TEST_VERSION - - initialize_http_error_table(); - - if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE); - setproctitle_init(argc, argv, envp); - - /* set signal handlers */ - signals_set_shutdown(&shut_down); - signal(SIGPIPE, SIG_IGN); - - /* load the SASL plugins */ - global_sasl_init(1, 1, mysasl_cb); - - /* open the mboxlist, we'll need it for real work */ - mboxlist_init(0); - mboxlist_open(NULL); - - /* open the quota db, we'll need it for expunge */ - quotadb_init(0); - quotadb_open(NULL); - - /* open the user deny db */ - denydb_init(0); - denydb_open(/*create*/0); - - /* open annotations.db, we'll need it for collection properties */ - annotatemore_open(); - - /* setup for sending IMAP IDLE notifications */ - idle_enabled(); - - /* Set namespace */ - if ((r = mboxname_init_namespace(&httpd_namespace, 1)) != 0) { - syslog(LOG_ERR, "%s", error_message(r)); - fatal(error_message(r), EC_CONFIG); - } - /* External names are in URIs (UNIX sep) */ - httpd_namespace.hier_sep = '/'; - - /* open the mboxevent system */ - mboxevent_init(); - - mboxevent_setnamespace(&httpd_namespace); - - while ((opt = getopt(argc, argv, "sp:")) != EOF) { - switch(opt) { - case 's': /* https (do TLS right away) */ - https = 1; - if (!tls_enabled()) { - syslog(LOG_ERR, "https: required OpenSSL options not present"); - fatal("https: required OpenSSL options not present", - EC_CONFIG); - } - break; - - case 'p': /* external protection */ - extprops_ssf = atoi(optarg); - break; - - default: - usage(); - } - } - - /* Create a protgroup for input from the client and selected backend */ - protin = protgroup_new(2); - - config_httpprettytelemetry = config_getswitch(IMAPOPT_HTTPPRETTYTELEMETRY); - - if (config_getstring(IMAPOPT_HTTPALLOWCORS)) { - allow_cors = - split_wildmats((char *) config_getstring(IMAPOPT_HTTPALLOWCORS), - NULL); - } - - /* Construct serverinfo string */ - if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) { - buf_printf(&serverinfo, "Cyrus/%s%s Cyrus-SASL/%u.%u.%u", - cyrus_version(), config_mupdate_server ? " (Murder)" : "", - SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP); -#ifdef HAVE_SSL - buf_printf(&serverinfo, " OpenSSL/%s", SHLIB_VERSION_NUMBER); -#endif -#ifdef HAVE_ZLIB - buf_printf(&serverinfo, " zlib/%s", ZLIB_VERSION); -#endif - buf_printf(&serverinfo, " libxml2/%s", LIBXML_DOTTED_VERSION); - } - - /* Do any namespace specific initialization */ - config_httpmodules = config_getbitfield(IMAPOPT_HTTPMODULES); - for (i = 0; namespacesi; i++) { - if (allow_trace) namespacesi->allow |= ALLOW_TRACE; - if (namespacesi->init) namespacesi->init(&serverinfo); - } - - compile_time = calc_compile_time(__TIME__, __DATE__); - - return 0; -} - - -/* - * run for each accepted connection - */ -int service_main(int argc __attribute__((unused)), - char **argv __attribute__((unused)), - char **envp __attribute__((unused))) -{ - socklen_t salen; - char hbufNI_MAXHOST; - char localip60, remoteip60; - int niflags; - sasl_security_properties_t *secprops=NULL; - const char *mechlist, *mech; - int mechcount = 0; - size_t mechlen; - struct auth_scheme_t *scheme; - - session_new_id(); - - signals_poll(); - - sync_log_init(); - - httpd_in = prot_new(0, 0); - httpd_out = prot_new(1, 1); - protgroup_insert(protin, httpd_in); - - /* Find out name of client host */ - salen = sizeof(httpd_remoteaddr); - if (getpeername(0, (struct sockaddr *)&httpd_remoteaddr, &salen) == 0 && - (httpd_remoteaddr.ss_family == AF_INET || - httpd_remoteaddr.ss_family == AF_INET6)) { - if (getnameinfo((struct sockaddr *)&httpd_remoteaddr, salen, - hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD) == 0) { - strncpy(httpd_clienthost, hbuf, sizeof(hbuf)); - strlcat(httpd_clienthost, " ", sizeof(httpd_clienthost)); - } else { - httpd_clienthost0 = '\0'; - } - niflags = NI_NUMERICHOST; -#ifdef NI_WITHSCOPEID - if (((struct sockaddr *)&httpd_remoteaddr)->sa_family == AF_INET6) - niflags |= NI_WITHSCOPEID; -#endif - if (getnameinfo((struct sockaddr *)&httpd_remoteaddr, salen, hbuf, - sizeof(hbuf), NULL, 0, niflags) != 0) - strlcpy(hbuf, "unknown", sizeof(hbuf)); - strlcat(httpd_clienthost, "", sizeof(httpd_clienthost)); - strlcat(httpd_clienthost, hbuf, sizeof(httpd_clienthost)); - strlcat(httpd_clienthost, "", sizeof(httpd_clienthost)); - salen = sizeof(httpd_localaddr); - if (getsockname(0, (struct sockaddr *)&httpd_localaddr, &salen) == 0) { - httpd_haveaddr = 1; - } - - /* Create pre-authentication telemetry log based on client IP */ - httpd_logfd = telemetry_log(hbuf, httpd_in, httpd_out, 0); - } - - /* other params should be filled in */ - if (sasl_server_new("HTTP", config_servername, NULL, NULL, NULL, NULL, - SASL_USAGE_FLAGS, &httpd_saslconn) != SASL_OK) - fatal("SASL failed initializing: sasl_server_new()",EC_TEMPFAIL); - - /* will always return something valid */ - secprops = mysasl_secprops(0); - - /* no HTTP clients seem to use "auth-int" */ - secprops->max_ssf = 0; /* "auth" only */ - secprops->maxbufsize = 0; /* don't need maxbuf */ - if (sasl_setprop(httpd_saslconn, SASL_SEC_PROPS, secprops) != SASL_OK) - fatal("Failed to set SASL property", EC_TEMPFAIL); - if (sasl_setprop(httpd_saslconn, SASL_SSF_EXTERNAL, &extprops_ssf) != SASL_OK) - fatal("Failed to set SASL property", EC_TEMPFAIL); - - if(iptostring((struct sockaddr *)&httpd_localaddr, - salen, localip, 60) == 0) { - sasl_setprop(httpd_saslconn, SASL_IPLOCALPORT, localip); - saslprops.iplocalport = xstrdup(localip); - } - - if(iptostring((struct sockaddr *)&httpd_remoteaddr, - salen, remoteip, 60) == 0) { - sasl_setprop(httpd_saslconn, SASL_IPREMOTEPORT, remoteip); - saslprops.ipremoteport = xstrdup(remoteip); - } - - /* See which auth schemes are available to us */ - if ((extprops_ssf >= 2) || config_getswitch(IMAPOPT_ALLOWPLAINTEXT)) { - avail_auth_schemes |= (1 << AUTH_BASIC); - } - sasl_listmech(httpd_saslconn, NULL, NULL, " ", NULL, - &mechlist, NULL, &mechcount); - for (mech = mechlist; mechcount--; mech += ++mechlen) { - mechlen = strcspn(mech, " \0"); - for (scheme = auth_schemes; scheme->name; scheme++) { - if (scheme->saslmech && !strncmp(mech, scheme->saslmech, mechlen)) { - avail_auth_schemes |= (1 << scheme->idx); - break; - } - } - } - httpd_tls_required = !avail_auth_schemes; - - proc_register("httpd", httpd_clienthost, NULL, NULL, NULL); - - /* Set inactivity timer */ - httpd_timeout = config_getint(IMAPOPT_HTTPTIMEOUT); - if (httpd_timeout < 0) httpd_timeout = 0; - httpd_timeout *= 60; - prot_settimeout(httpd_in, httpd_timeout); - prot_setflushonread(httpd_in, httpd_out); - - /* we were connected on https port so we should do - TLS negotiation immediatly */ - if (https == 1) starttls(1); - - /* Setup the signal handler for keepalive heartbeat */ - httpd_keepalive = config_getint(IMAPOPT_HTTPKEEPALIVE); - if (httpd_keepalive < 0) httpd_keepalive = 0; - if (httpd_keepalive) { - struct sigaction action; - - sigemptyset(&action.sa_mask); - action.sa_flags = 0; -#ifdef SA_RESTART - action.sa_flags |= SA_RESTART; -#endif - action.sa_handler = keep_alive; - if (sigaction(SIGALRM, &action, NULL) < 0) { - syslog(LOG_ERR, "unable to install signal handler for %d: %m", SIGALRM); - httpd_keepalive = 0; - } - } - - cmdloop(); - - /* Closing connection */ - - /* cleanup */ - signal(SIGALRM, SIG_IGN); - httpd_reset(); - - return 0; -} - - -/* Called by service API to shut down the service */ -void service_abort(int error) -{ - shut_down(error); -} - - -void usage(void) -{ - prot_printf(httpd_out, "%s: usage: httpd -C <alt_config> -s\r\n", - error_message(HTTP_SERVER_ERROR)); - prot_flush(httpd_out); - exit(EC_USAGE); -} - - -/* - * Cleanly shut down and exit - */ -void shut_down(int code) -{ - int i; - int bytes_in = 0; - int bytes_out = 0; - - in_shutdown = 1; - - if (allow_cors) free_wildmats(allow_cors); - - /* Do any namespace specific cleanup */ - for (i = 0; namespacesi; i++) { - if (namespacesi->enabled && namespacesi->shutdown) - namespacesi->shutdown(); - } - - xmlCleanupParser(); - - proc_cleanup(); - - /* close backend connections */ - i = 0; - while (backend_cached && backend_cachedi) { - proxy_downserver(backend_cachedi); - free(backend_cachedi->context); - free(backend_cachedi); - i++; - } - if (backend_cached) free(backend_cached); - - sync_log_done(); - - mboxlist_close(); - mboxlist_done(); - - quotadb_close(); - quotadb_done(); - - denydb_close(); - denydb_done(); - - annotatemore_close(); - - if (httpd_in) { - prot_NONBLOCK(httpd_in); - prot_fill(httpd_in); - bytes_in = prot_bytes_in(httpd_in); - prot_free(httpd_in); - } - - if (httpd_out) { - prot_flush(httpd_out); - bytes_out = prot_bytes_out(httpd_out); - prot_free(httpd_out); - } - - if (protin) protgroup_free(protin); - - if (config_auditlog) - syslog(LOG_NOTICE, - "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>", - session_id(), bytes_in, bytes_out); - -#ifdef HAVE_SSL - tls_shutdown_serverengine(); -#endif - - cyrus_done(); - - exit(code); -} - - -void fatal(const char* s, int code) -{ - static int recurse_code = 0; - - if (recurse_code) { - /* We were called recursively. Just give up */ - proc_cleanup(); - exit(recurse_code); - } - recurse_code = code; - if (httpd_out) { - prot_printf(httpd_out, - "HTTP/1.1 %s\r\n" - "Content-Type: text/plain\r\n" - "Connection: close\r\n\r\n" - "Fatal error: %s\r\n", - error_message(HTTP_SERVER_ERROR), s); - prot_flush(httpd_out); - } - syslog(LOG_ERR, "Fatal error: %s", s); - shut_down(code); -} - - - - -#ifdef HAVE_SSL -static void starttls(int https) -{ - int result; - int *layerp; - sasl_ssf_t ssf; - char *auth_id; - - /* SASL and openssl have different ideas about whether ssf is signed */ - layerp = (int *) &ssf; - - result=tls_init_serverengine("http", - 5, /* depth to verify */ - !https, /* can client auth? */ - !https); /* TLS only? */ - - if (result == -1) { - syslog(LOG_ERR, "httpd error initializing TLS"); - fatal("tls_init() failed",EC_TEMPFAIL); - } - - if (!https) { - /* tell client to start TLS upgrade (RFC 2817) */ - response_header(HTTP_SWITCH_PROT, NULL); - } - - result=tls_start_servertls(0, /* read */ - 1, /* write */ - https ? 180 : httpd_timeout, - layerp, - &auth_id, - &tls_conn); - - /* if error */ - if (result == -1) { - syslog(LOG_NOTICE, "https failed: %s", httpd_clienthost); - fatal("tls_start_servertls() failed", EC_TEMPFAIL); - } - - /* tell SASL about the negotiated layer */ - result = sasl_setprop(httpd_saslconn, SASL_SSF_EXTERNAL, &ssf); - if (result != SASL_OK) { - fatal("sasl_setprop() failed: starttls()", EC_TEMPFAIL); - } - saslprops.ssf = ssf; - - result = sasl_setprop(httpd_saslconn, SASL_AUTH_EXTERNAL, auth_id); - if (result != SASL_OK) { - fatal("sasl_setprop() failed: starttls()", EC_TEMPFAIL); - } - if (saslprops.authid) { - free(saslprops.authid); - saslprops.authid = NULL; - } - if (auth_id) saslprops.authid = xstrdup(auth_id); - - /* tell the prot layer about our new layers */ - prot_settls(httpd_in, tls_conn); - prot_settls(httpd_out, tls_conn); - - httpd_tls_done = 1; - httpd_tls_required = 0; - - avail_auth_schemes |= (1 << AUTH_BASIC); -} -#else -static void starttls(int https __attribute__((unused))) -{ - fatal("starttls() called, but no OpenSSL", EC_SOFTWARE); -} -#endif /* HAVE_SSL */ - - -/* Reset the given sasl_conn_t to a sane state */ -static int reset_saslconn(sasl_conn_t **conn) -{ - int ret; - sasl_security_properties_t *secprops = NULL; - - sasl_dispose(conn); - /* do initialization typical of service_main */ - ret = sasl_server_new("HTTP", config_servername, NULL, NULL, NULL, NULL, - SASL_USAGE_FLAGS, conn); - if(ret != SASL_OK) return ret; - - if(saslprops.ipremoteport) - ret = sasl_setprop(*conn, SASL_IPREMOTEPORT, - saslprops.ipremoteport); - if(ret != SASL_OK) return ret; - - if(saslprops.iplocalport) - ret = sasl_setprop(*conn, SASL_IPLOCALPORT, - saslprops.iplocalport); - if(ret != SASL_OK) return ret; - secprops = mysasl_secprops(0); - - /* no HTTP clients seem to use "auth-int" */ - secprops->max_ssf = 0; /* "auth" only */ - secprops->maxbufsize = 0; /* don't need maxbuf */ - ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops); - if(ret != SASL_OK) return ret; - /* end of service_main initialization excepting SSF */ - - /* If we have TLS/SSL info, set it */ - if(saslprops.ssf) { - ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &saslprops.ssf); - } else { - ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &extprops_ssf); - } - - if(ret != SASL_OK) return ret; - - if(saslprops.authid) { - ret = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, saslprops.authid); - if(ret != SASL_OK) return ret; - } - /* End TLS/SSL Info */ - - return SASL_OK; -} - - -/* - * Top-level command loop parsing - */ -static void cmdloop(void) -{ - int gzip_enabled = 0; - struct transaction_t txn; - - /* Start with an empty (clean) transaction */ - memset(&txn, 0, sizeof(struct transaction_t)); - - /* Pre-allocate our working buffer */ - buf_ensure(&txn.buf, 1024); - -#ifdef HAVE_ZLIB - /* Always use gzip format because IE incorrectly uses raw deflate */ - if (config_getswitch(IMAPOPT_HTTPALLOWCOMPRESS) && - deflateInit2(&txn.zstrm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, - 16+MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) == Z_OK) { - gzip_enabled = 1; - } -#endif - - for (;;) { - int ret, empty, r, i, c; - char *p; - tok_t tok; - const char **hdr; - const struct namespace_t *namespace; - const struct method_t *meth_t; - struct request_line_t *req_line = &txn.req_line; - - /* Reset txn state */ - txn.meth = METH_UNKNOWN; - memset(&txn.flags, 0, sizeof(struct txn_flags_t)); - txn.flags.conn = 0; - txn.flags.vary = VARY_AE; - memset(req_line, 0, sizeof(struct request_line_t)); - memset(&txn.req_tgt, 0, sizeof(struct request_target_t)); - txn.req_uri = NULL; - txn.auth_chal.param = NULL; - txn.req_hdrs = NULL; - txn.req_body.flags = 0; - buf_reset(&txn.req_body.payload); - txn.location = NULL; - memset(&txn.error, 0, sizeof(struct error_t)); - memset(&txn.resp_body, 0, /* Don't zero the response payload buffer */ - sizeof(struct resp_body_t) - sizeof(struct buf)); - buf_reset(&txn.resp_body.payload); - buf_reset(&txn.buf); - ret = empty = 0; - - /* Create header cache */ - if (!(txn.req_hdrs = spool_new_hdrcache())) { - txn.error.desc = "Unable to create header cache"; - ret = HTTP_SERVER_ERROR; - } - - req_line: - do { - /* Flush any buffered output */ - prot_flush(httpd_out); - if (backend_current) prot_flush(backend_current->out); - - /* Check for shutdown file */ - if (shutdown_file(txn.buf.s, txn.buf.alloc) || - (httpd_userid && - userdeny(httpd_userid, config_ident, txn.buf.s, txn.buf.alloc))) { - txn.error.desc = txn.buf.s; - ret = HTTP_UNAVAILABLE; - break; - } - - signals_poll(); - - } while (!proxy_check_input(protin, httpd_in, httpd_out, - backend_current ? backend_current->in : NULL, - NULL, 0)); - if (ret) { - txn.flags.conn = CONN_CLOSE; - error_response(ret, &txn); - protgroup_free(protin); - shut_down(0); - } - - - /* Read request-line */ - syslog(LOG_DEBUG, "read & parse request-line"); - if (!prot_fgets(req_line->buf, MAX_REQ_LINE+1, httpd_in)) { - txn.error.desc = prot_error(httpd_in); - if (txn.error.desc && strcmp(txn.error.desc, PROT_EOF_STRING)) { - /* client timed out */ - syslog(LOG_WARNING, "%s, closing connection", txn.error.desc); - ret = HTTP_TIMEOUT; - } - else { - /* client closed connection */ - } - - txn.flags.conn = CONN_CLOSE; - goto done; - } - - /* Trim CRLF from request-line */ - p = req_line->buf + strlen(req_line->buf); - if (p-1 == '\n') *--p = '\0'; - if (p-1 == '\r') *--p = '\0'; - - /* Ignore 1 empty line before request-line per HTTPbis Part 1 Sec 3.5 */ - if (!empty++ && !*req_line->buf) goto req_line; - - /* Parse request-line = method SP request-target SP HTTP-version CRLF */ - tok_initm(&tok, req_line->buf, " ", 0); - if (!(req_line->meth = tok_next(&tok))) { - ret = HTTP_BAD_REQUEST; - txn.error.desc = "Missing method in request-line"; - } - else if (!(req_line->uri = tok_next(&tok))) { - ret = HTTP_BAD_REQUEST; - txn.error.desc = "Missing request-target in request-line"; - } - else if ((size_t) (p - req_line->buf) > MAX_REQ_LINE - 2) { - /* request-line overran the size of our buffer */ - ret = HTTP_TOO_LONG; - buf_printf(&txn.buf, - "Length of request-line MUST be less than %u octets", - MAX_REQ_LINE); - txn.error.desc = buf_cstring(&txn.buf); - } - else if (!(req_line->ver = tok_next(&tok))) { - ret = HTTP_BAD_REQUEST; - txn.error.desc = "Missing HTTP-version in request-line"; - } - else if (tok_next(&tok)) { - ret = HTTP_BAD_REQUEST; - txn.error.desc = "Unexpected extra argument(s) in request-line"; - } - - /* Check HTTP-Version - MUST be HTTP/1.x */ - else if (strlen(req_line->ver) != HTTP_VERSION_LEN - || strncmp(req_line->ver, HTTP_VERSION, HTTP_VERSION_LEN-1) - || !isdigit(req_line->verHTTP_VERSION_LEN-1)) { - ret = HTTP_BAD_VERSION; - buf_printf(&txn.buf, - "This server only speaks %.*sx", - HTTP_VERSION_LEN-1, HTTP_VERSION); - txn.error.desc = buf_cstring(&txn.buf); - } - else if (req_line->verHTTP_VERSION_LEN-1 == '0') { - /* HTTP/1.0 connection */ - txn.flags.ver1_0 = 1; - } - tok_fini(&tok); - - if (ret) { - txn.flags.conn = CONN_CLOSE; - goto done; - } - - /* Read and parse headers */ - syslog(LOG_DEBUG, "read & parse headers"); - if ((r = spool_fill_hdrcache(httpd_in, NULL, txn.req_hdrs, NULL))) { - ret = HTTP_BAD_REQUEST; - txn.error.desc = error_message(r); - } - else if ((txn.error.desc = prot_error(httpd_in)) && - strcmp(txn.error.desc, PROT_EOF_STRING)) { - /* client timed out */ - syslog(LOG_WARNING, "%s, closing connection", txn.error.desc); - ret = HTTP_TIMEOUT; - } - - /* Read CRLF separating headers and body */ - else if ((c = prot_getc(httpd_in)) != '\r' || - (c = prot_getc(httpd_in)) != '\n') { - ret = HTTP_BAD_REQUEST; - txn.error.desc = error_message(IMAP_MESSAGE_NOBLANKLINE); - } - - if (ret) { - txn.flags.conn = CONN_CLOSE; - goto done; - } - - /* Check for Connection options */ - parse_connection(&txn); - if (txn.flags.conn & CONN_UPGRADE) { - starttls(0); - txn.flags.conn &= ~CONN_UPGRADE; - } - - /* Check for HTTP method override */ - if (!strcmp(req_line->meth, "POST") && - (hdr = spool_getheader(txn.req_hdrs, "X-HTTP-Method-Override"))) { - txn.flags.override = 1; - req_line->meth = (char *) hdr0; - } - - /* Check Method against our list of known methods */ - for (txn.meth = 0; (txn.meth < METH_UNKNOWN) && - strcmp(http_methodstxn.meth.name, req_line->meth); - txn.meth++); - - if (txn.meth == METH_UNKNOWN) ret = HTTP_NOT_IMPLEMENTED; - - /* Parse request-target URI */ - else if (!(txn.req_uri = parse_uri(txn.meth, req_line->uri, 1, - &txn.error.desc))) { - ret = HTTP_BAD_REQUEST; - } - - /* Check message framing */ - else if ((r = parse_framing(txn.req_hdrs, &txn.req_body, - &txn.error.desc))) { - ret = r; - } - - /* Check for Expectations */ - else if ((r = parse_expect(&txn))) { - ret = r; - } - - /* Check for mandatory Host header (HTTP/1.1+ only) */ - else if ((hdr = spool_getheader(txn.req_hdrs, "Host")) && hdr1) { - ret = HTTP_BAD_REQUEST; - txn.error.desc = "Too many Host headers"; - } - else if (!hdr) { - if (txn.flags.ver1_0) { - /* HTTP/1.0 - create a Host header from URI */ - if (txn.req_uri->server) { - buf_setcstr(&txn.buf, txn.req_uri->server); - if (txn.req_uri->port) - buf_printf(&txn.buf, ":%d", txn.req_uri->port); - } - else buf_setcstr(&txn.buf, config_servername); - - spool_cache_header(xstrdup("Host"), - xstrdup(buf_cstring(&txn.buf)), - txn.req_hdrs); - buf_reset(&txn.buf); - } - else { - ret = HTTP_BAD_REQUEST; - txn.error.desc = "Missing Host header"; - } - } - - if (ret) goto done; - - /* Find the namespace of the requested resource */ - for (i = 0; namespacesi; i++) { - const char *path = txn.req_uri->path; - const char *query = URI_QUERY(txn.req_uri); - size_t len; - - /* Skip disabled namespaces */ - if (!namespacesi->enabled) continue; - - /* Handle any /.well-known/ bootstrapping */ - if (namespacesi->well_known) { - len = strlen(namespacesi->well_known); - if (!strncmp(path, namespacesi->well_known, len) && - (!pathlen || pathlen == '/')) { - - buf_setcstr(&txn.buf, namespacesi->prefix); - buf_appendcstr(&txn.buf, path + len); - if (query) buf_printf(&txn.buf, "?%s", query); - txn.location = buf_cstring(&txn.buf); - - ret = HTTP_MOVED; - goto done; - } - } - - /* See if the prefix matches - terminated with NUL or '/' */ - len = strlen(namespacesi->prefix); - if (!strncmp(path, namespacesi->prefix, len) && - (!pathlen || (pathlen == '/') || !strcmp(path, "*"))) { - break; - } - } - if ((namespace = namespacesi)) { - txn.req_tgt.namespace = namespace->id; - txn.req_tgt.allow = namespace->allow; - txn.req_tgt.mboxtype = namespace->mboxtype; - - /* Check if method is supported in this namespace */ - meth_t = &namespace->methodstxn.meth; - if (!meth_t->proc) ret = HTTP_NOT_ALLOWED; - - /* Check if method expects a body */ - else if ((http_methodstxn.meth.flags & METH_NOBODY) && - (txn.req_body.framing != FRAMING_LENGTH || - /* XXX Will break if client sends just a last-chunk */ - txn.req_body.len)) { - ret = HTTP_BAD_MEDIATYPE; - } - } else { - /* XXX Should never get here */ - ret = HTTP_SERVER_ERROR; - } - - if (ret) goto done; - - /* Perform authentication, if necessary */ - if ((hdr = spool_getheader(txn.req_hdrs, "Authorization"))) { - if (httpd_userid) { - /* Reauth - reinitialize */ - syslog(LOG_DEBUG, "reauth - reinit"); - reset_saslconn(&httpd_saslconn); - txn.auth_chal.scheme = NULL; - } - - /* Check the auth credentials */ - r = http_auth(hdr0, &txn); - if ((r < 0) || !txn.auth_chal.scheme) { - /* Auth failed - reinitialize */ - syslog(LOG_DEBUG, "auth failed - reinit"); - reset_saslconn(&httpd_saslconn); - txn.auth_chal.scheme = NULL; - r = SASL_FAIL; - } - } - else if (!httpd_userid && txn.auth_chal.scheme) { - /* Started auth exchange, but client didn't engage - reinit */ - syslog(LOG_DEBUG, "client didn't complete auth - reinit"); - reset_saslconn(&httpd_saslconn); - txn.auth_chal.scheme = NULL; - } - - /* Perform proxy authorization, if necessary */ - else if (saslprops.authid && - (hdr = spool_getheader(txn.req_hdrs, "Authorize-As")) && - *hdr0) { - const char *authzid = hdr0; - - r = proxy_authz(&authzid, &txn); - if (r) { - /* Proxy authz failed - reinitialize */ - syslog(LOG_DEBUG, "proxy authz failed - reinit"); - reset_saslconn(&httpd_saslconn); - txn.auth_chal.scheme = NULL; - } - else { - httpd_userid = xstrdup(authzid); - auth_success(&txn); - } - } - - /* Request authentication, if necessary */ - if (!httpd_userid && - (r || (namespace->need_auth && txn.meth != METH_OPTIONS))) { - need_auth: - /* User must authenticate */ - - if (httpd_tls_required) { - /* We only support TLS+Basic, so tell client to use TLS */ - - /* Check which response is required */ - if ((hdr = spool_getheader(txn.req_hdrs, "Upgrade")) && - !strncmp(hdr0, TLS_VERSION, strcspn(hdr0, " ,"))) { - /* Client (Murder proxy) supports RFC 2817 (TLS upgrade) */ - - response_header(HTTP_UPGRADE, &txn); - } - else { - /* All other clients use RFC 2818 (HTTPS) */ - const char *path = txn.req_uri->path; - const char *query = URI_QUERY(txn.req_uri); - struct buf *html = &txn.resp_body.payload; - - /* Create https URL */ - hdr = spool_getheader(txn.req_hdrs, "Host"); - buf_printf(&txn.buf, "https://%s", hdr0); - if (strcmp(path, "*")) { - buf_appendcstr(&txn.buf, path); - if (query) buf_printf(&txn.buf, "?%s", query); - } - - txn.location = buf_cstring(&txn.buf); - - /* Create HTML body */ - buf_reset(html); - buf_printf(html, tls_message, - buf_cstring(&txn.buf), buf_cstring(&txn.buf)); - - /* Output our HTML response */ - txn.resp_body.type = "text/html; charset=utf-8"; - write_body(HTTP_MOVED, &txn, - buf_cstring(html), buf_len(html)); - } - } - else { - /* Tell client to authenticate */ - ret = HTTP_UNAUTHORIZED; - if (r == SASL_CONTINUE) - txn.error.desc = "Continue authentication exchange"; - else if (r) txn.error.desc = "Authentication failed"; - else txn.error.desc = - "Must authenticate to access the specified target"; - } - - goto done; - } - - /* Check if this is a Cross-Origin Resource Sharing request */ - if (allow_cors && (hdr = spool_getheader(txn.req_hdrs, "Origin"))) { - const char *err = NULL; - xmlURIPtr uri = parse_uri(METH_UNKNOWN, hdr0, 0, &err); - - if (uri && uri->scheme && uri->server) { - int o_https = !strcasecmp(uri->scheme, "https"); - - if ((https == o_https) && - !strcasecmp(uri->server, - *spool_getheader(txn.req_hdrs, "Host"))) { - txn.flags.cors = CORS_SIMPLE; - } - else { - struct wildmat *wild; - - /* Create URI w/o path or default port */ - assert(!buf_len(&txn.buf)); - buf_printf(&txn.buf, "%s://%s", - lcase(uri->scheme), lcase(uri->server)); - if (uri->port && - ((o_https && uri->port != 443) || - (!o_https && uri->port != 80))) { - buf_printf(&txn.buf, ":%d", uri->port); - } - - /* Check Origin against the 'httpallowcors' wildmat */ - for (wild = allow_cors; wild->pat; wild++) { - if (wildmat(buf_cstring(&txn.buf), wild->pat)) { - /* If we have a non-negative match, allow request */ - if (!wild->not) txn.flags.cors = CORS_SIMPLE; - break; - } - } - buf_reset(&txn.buf); - } - } - xmlFreeURI(uri); - } - - /* Check if we should compress response body */ - if (gzip_enabled) { - /* XXX Do we want to support deflate even though M$ - doesn't implement it correctly (raw deflate vs. zlib)? */ - - if (!txn.flags.ver1_0 && - (hdr = spool_getheader(txn.req_hdrs, "TE"))) { - struct accept *e, *enc = parse_accept(hdr); - - for (e = enc; e && e->token; e++) { - if (e->qual > 0.0 && - (!strcasecmp(e->token, "gzip") || - !strcasecmp(e->token, "x-gzip"))) { - txn.flags.te = TE_GZIP; - } - free(e->token); - } - if (enc) free(enc); - } - else if ((hdr = spool_getheader(txn.req_hdrs, "Accept-Encoding"))) { - struct accept *e, *enc = parse_accept(hdr); - - for (e = enc; e && e->token; e++) { - if (e->qual > 0.0 && - (!strcasecmp(e->token, "gzip") || - !strcasecmp(e->token, "x-gzip"))) { - txn.resp_body.enc = CE_GZIP; - } - free(e->token); - } - if (enc) free(enc); - } - } - - /* Start method processing alarm (HTTP/1.1+ only) */ - if (!txn.flags.ver1_0) alarm(httpd_keepalive); - - /* Process the requested method */ - ret = (*meth_t->proc)(&txn, meth_t->params); - if (ret == HTTP_UNAUTHORIZED) goto need_auth; - - done: - /* Handle errors (success responses handled by method functions) */ - if (ret) error_response(ret, &txn); - - /* Read and discard any unread request body */ - if (!(txn.flags.conn & CONN_CLOSE)) { - txn.req_body.flags |= BODY_DISCARD; - if (read_body(httpd_in, txn.req_hdrs, &txn.req_body, - &txn.error.desc)) { - txn.flags.conn = CONN_CLOSE; - } - } - - /* Memory cleanup */ - if (txn.req_uri) xmlFreeURI(txn.req_uri); - if (txn.req_hdrs) spool_free_hdrcache(txn.req_hdrs); - - if (txn.flags.conn & CONN_CLOSE) { - buf_free(&txn.buf); - buf_free(&txn.req_body.payload); - buf_free(&txn.resp_body.payload); -#ifdef HAVE_ZLIB - deflateEnd(&txn.zstrm); - buf_free(&txn.zbuf); -#endif - return; - } - - continue; - } -} - -/**************************** Parsing Routines ******************************/ - -/* Parse URI, returning the path */ -EXPORTED xmlURIPtr parse_uri(unsigned meth, const char *uri, unsigned path_reqd, - const char **errstr) -{ - xmlURIPtr p_uri; /* parsed URI */ - - /* Parse entire URI */ - if ((p_uri = xmlParseURI(uri)) == NULL) { - *errstr = "Illegal request target URI"; - goto bad_request; - } - - if (p_uri->scheme) { - /* Check sanity of scheme */ - - if (strcasecmp(p_uri->scheme, "http") && - strcasecmp(p_uri->scheme, "https")) { - *errstr = "Unsupported URI scheme"; - goto bad_request; - } - } - - /* Check sanity of path */ - if (path_reqd && (!p_uri->path || !*p_uri->path)) { - *errstr = "Empty path in target URI"; - goto bad_request; - } - else if (p_uri->path) { - if ((p_uri->path0 != '/') && - (strcmp(p_uri->path, "*") || (meth != METH_OPTIONS))) { - /* No special URLs except for "OPTIONS * HTTP/1.1" */ - *errstr = "Illegal request target URI"; - goto bad_request; - } - else if (strstr(p_uri->path, "/..")) { - /* Don't allow access up directory tree */ - *errstr = "Illegal request target URI"; - goto bad_request; - } - else if (strlen(p_uri->path) > MAX_MAILBOX_PATH) { - *errstr = "Request target URI too long"; - goto bad_request; - } - } - - return p_uri; - - bad_request: - if (p_uri) xmlFreeURI(p_uri); - return NULL; -} - - -/* Compare Content-Types */ -EXPORTED int is_mediatype(const char *pat, const char *type) -{ - const char *psep = strchr(pat, '/'); - const char *tsep = strchr(type, '/'); - size_t plen; - size_t tlen; - int alltypes; - - /* Check type */ - if (!psep || !tsep) return 0; - plen = psep - pat; - tlen = tsep - type; - - alltypes = !strncmp(pat, "*", plen); - - if (!alltypes && ((tlen != plen) || strncasecmp(pat, type, tlen))) return 0; - - /* Check subtype */ - pat = ++psep; - plen = strcspn(pat, "; \r\n\0"); - type = ++tsep; - tlen = strcspn(type, "; \r\n\0"); - - return (!strncmp(pat, "*", plen) || - (!alltypes && (tlen == plen) && !strncasecmp(pat, type, tlen))); -} - - -/* Calculate compile time of a file for use as Last-Modified and/or ETag */ -EXPORTED time_t calc_compile_time(const char *time, const char *date) -{ - struct tm tm; - char month4; - const char *monthname = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; - - memset(&tm, 0, sizeof(struct tm)); - tm.tm_isdst = -1; - sscanf(time, "%02d:%02d:%02d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec); - sscanf(date, "%s %2d %4d", month, &tm.tm_mday, &tm.tm_year); - tm.tm_year -= 1900; - for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) { - if (!strcmp(month, monthnametm.tm_mon)) break; - } - - return mktime(&tm); -} - - -/* - * Parse the framing of a request or response message. - * Handles chunked, gzip, deflate TE only. - * Handles close-delimited response bodies (no Content-Length specified) - */ -EXPORTED int parse_framing(hdrcache_t hdrs, struct body_t *body, const char **errstr) -{ - static unsigned max_msgsize = 0; - const char **hdr; - - if (!max_msgsize) { - max_msgsize = config_getint(IMAPOPT_MAXMESSAGESIZE); - - /* If max_msgsize is 0, allow any size */ - if (!max_msgsize) max_msgsize = INT_MAX; - } - - body->framing = FRAMING_LENGTH; - body->te = TE_NONE; - body->len = 0; - body->max = max_msgsize; - - /* Check for Transfer-Encoding */ - if ((hdr = spool_getheader(hdrs, "Transfer-Encoding"))) { - for (; *hdr; hdr++) { - tok_t tok = TOK_INITIALIZER(*hdr, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT); - char *token; - - while ((token = tok_next(&tok))) { - if (body->te & TE_CHUNKED) { - /* "chunked" MUST only appear once and MUST be last */ - break; - } - else if (!strcasecmp(token, "chunked")) { - body->te |= TE_CHUNKED; - body->framing = FRAMING_CHUNKED; - } - else if (body->te & ~TE_CHUNKED) { - /* can't combine compression codings */ - break; - } -#ifdef HAVE_ZLIB - else if (!strcasecmp(token, "deflate")) - body->te = TE_DEFLATE; - else if (!strcasecmp(token, "gzip") || - !strcasecmp(token, "x-gzip")) - body->te = TE_GZIP; -#endif - else if (!(body->flags & BODY_DISCARD)) { - /* unknown/unsupported TE */ - break; - } - } - tok_fini(&tok); - if (token) break; /* error */ - } - - if (*hdr) { - *errstr = "Specified Transfer-Encoding not implemented"; - return HTTP_NOT_IMPLEMENTED; - } - - /* Check if this is a non-chunked response */ - else if (!(body->te & TE_CHUNKED)) { - if ((body->flags & BODY_RESPONSE) && (body->flags & BODY_CLOSE)) { - body->framing = FRAMING_CLOSE; - } - else { - *errstr = "Final Transfer-Encoding MUST be \"chunked\""; - return HTTP_NOT_IMPLEMENTED; - } - } - } - - /* Check for Content-Length */ - else if ((hdr = spool_getheader(hdrs, "Content-Length"))) { - if (hdr1) { - *errstr = "Multiple Content-Length header fields"; - return HTTP_BAD_REQUEST; - } - - body->len = strtoul(hdr0, NULL, 10); - if (body->len > max_msgsize) return HTTP_TOO_LARGE; - - body->framing = FRAMING_LENGTH; - } - - /* Check if this is a close-delimited response */ - else if (body->flags & BODY_RESPONSE) { - if (body->flags & BODY_CLOSE) body->framing = FRAMING_CLOSE; - else return HTTP_LENGTH_REQUIRED; - } - - return 0; -} - - -/* - * Read the body of a request or response. - * Handles chunked, gzip, deflate TE only. - * Handles close-delimited response bodies (no Content-Length specified) - * Handles gzip and deflate CE only. - */ -EXPORTED int read_body(struct protstream *pin, hdrcache_t hdrs, struct body_t *body, - const char **errstr) -{ - char bufPROT_BUFSIZE; - unsigned n; - int r = 0; - - syslog(LOG_DEBUG, "read_body(%#x)", body->flags); - - if (body->flags & BODY_DONE) return 0; - body->flags |= BODY_DONE; - - if (!(body->flags & BODY_DISCARD)) buf_reset(&body->payload); - else if (body->flags & BODY_CONTINUE) { - /* Don't care about the body and client hasn't sent it, we're done */ - return 0; - } - - if (body->framing == FRAMING_UNKNOWN) { - /* Get message framing */ - r = parse_framing(hdrs, body, errstr); - if (r) return r; - } - - if (body->flags & BODY_CONTINUE) { - /* Tell client to send the body */ - response_header(HTTP_CONTINUE, NULL); - } - - /* Read and buffer the body */ - switch (body->framing) { - case FRAMING_LENGTH: - /* Read 'len' octets */ - for (; body->len; body->len -= n) { - if (body->flags & BODY_DISCARD) - n = prot_read(pin, buf, MIN(body->len, PROT_BUFSIZE)); - else - n = prot_readbuf(pin, &body->payload, body->len); - - if (!n) { - syslog(LOG_ERR, "prot_read() error"); - *errstr = "Unable to read body data"; - goto read_failure; - } - } - - break; - - case FRAMING_CHUNKED: - { - unsigned last = 0; - - /* Read chunks until last-chunk (zero chunk-size) */ - do { - unsigned chunk; - - /* Read chunk-size */ - if (!prot_fgets(buf, PROT_BUFSIZE, pin) || - sscanf(buf, "%x", &chunk) != 1) { - *errstr = "Unable to read chunk size"; - goto read_failure; - - /* XXX Do we need to parse chunk-ext? */ - } - else if (chunk > body->max - body->len) return HTTP_TOO_LARGE; - - if (!chunk) { - /* last-chunk */ - last = 1; - - /* Read/parse any trailing headers */ - spool_fill_hdrcache(pin, NULL, hdrs, NULL); - } - - /* Read 'chunk' octets */ - for (; chunk; chunk -= n) { - if (body->flags & BODY_DISCARD) - n = prot_read(pin, buf, MIN(chunk, PROT_BUFSIZE)); - else - n = prot_readbuf(pin, &body->payload, chunk); - - if (!n) { - syslog(LOG_ERR, "prot_read() error"); - *errstr = "Unable to read chunk data"; - goto read_failure; - } - body->len += n; - } - - /* Read CRLF terminating the chunk/trailer */ - if (!prot_fgets(buf, sizeof(buf), pin)) { - *errstr = "Missing CRLF following chunk/trailer"; - goto read_failure; - } - - } while (!last); - - body->te &= ~TE_CHUNKED; - - break; - } - - case FRAMING_CLOSE: - /* Read until EOF */ - do { - if (body->flags & BODY_DISCARD) - n = prot_read(pin, buf, PROT_BUFSIZE); - else - n = prot_readbuf(pin, &body->payload, PROT_BUFSIZE); - - if (n > body->max - body->len) return HTTP_TOO_LARGE; - body->len += n; - - } while (n); - - if (!pin->eof) goto read_failure; - - break; - - default: - /* XXX Should never get here */ - *errstr = "Unknown length of read body data"; - goto read_failure; - } - - - if (!(body->flags & BODY_DISCARD) && buf_len(&body->payload)) { -#ifdef HAVE_ZLIB - /* Decode the payload, if necessary */ - if (body->te == TE_DEFLATE) - r = buf_inflate(&body->payload, DEFLATE_ZLIB); - else if (body->te == TE_GZIP) - r = buf_inflate(&body->payload, DEFLATE_GZIP); - - if (r) { - *errstr = "Error decoding payload"; - return HTTP_BAD_REQUEST; - } -#endif - - /* Decode the representation, if necessary */ - if (body->flags & BODY_DECODE) { - const char **hdr; - - if (!(hdr = spool_getheader(hdrs, "Content-Encoding"))) { - /* nothing to see here */ - } - -#ifdef HAVE_ZLIB - else if (!strcasecmp(hdr0, "deflate")) { - const char **ua = spool_getheader(hdrs, "User-Agent"); - - /* Try to detect Microsoft's broken deflate */ - if (ua && strstr(ua0, "; MSIE ")) - r = buf_inflate(&body->payload, DEFLATE_RAW); - else - r = buf_inflate(&body->payload, DEFLATE_ZLIB); - } - else if (!strcasecmp(hdr0, "gzip") || - !strcasecmp(hdr0, "x-gzip")) - r = buf_inflate(&body->payload, DEFLATE_GZIP); -#endif - else { - *errstr = "Specified Content-Encoding not accepted"; - return HTTP_BAD_MEDIATYPE; - } - - if (r) { - *errstr = "Error decoding content"; - return HTTP_BAD_REQUEST; - } - } - } - - return 0; - - read_failure: - if (strcmpsafe(prot_error(httpd_in), PROT_EOF_STRING)) { - /* client timed out */ - *errstr = prot_error(httpd_in); - syslog(LOG_WARNING, "%s, closing connection", *errstr); - return HTTP_TIMEOUT; - } - else return HTTP_BAD_REQUEST; -} - - -/* Parse Expect header(s) for interesting expectations */ -static int parse_expect(struct transaction_t *txn) -{ - const char **exp = spool_getheader(txn->req_hdrs, "Expect"); - int i, ret = 0; - - /* Expect not supported by HTTP/1.0 clients */ - if (exp && txn->flags.ver1_0) return HTTP_EXPECT_FAILED; - - /* Look for interesting expectations. Unknown == error */ - for (i = 0; !ret && exp && expi; i++) { - tok_t tok = TOK_INITIALIZER(expi, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT); - char *token; - - while (!ret && (token = tok_next(&tok))) { - /* Check if this is a non-persistent connection */ - if (!strcasecmp(token, "100-continue")) { - syslog(LOG_DEBUG, "Expect: 100-continue"); - txn->req_body.flags |= BODY_CONTINUE; - } - else { - txn->error.desc = "Unsupported Expectation"; - ret = HTTP_EXPECT_FAILED; - } - } - - tok_fini(&tok); - } - - return ret; -} - - -/* Parse Connection header(s) for interesting options */ -static void parse_connection(struct transaction_t *txn) -{ - const char **conn = spool_getheader(txn->req_hdrs, "Connection"); - int i; - - /* Look for interesting connection tokens */ - for (i = 0; conn && conni; i++) { - tok_t tok = TOK_INITIALIZER(conni, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT); - char *token; - - while ((token = tok_next(&tok))) { - if (httpd_timeout) { - /* Check if this is a non-persistent connection */ - if (!strcasecmp(token, "close")) { - txn->flags.conn |= CONN_CLOSE; - continue; - } - - /* Check if this is a persistent connection */ - else if (!strcasecmp(token, "keep-alive")) { - txn->flags.conn |= CONN_KEEPALIVE; - continue; - } - } - - /* Check if we need to upgrade to TLS */ - if (!httpd_tls_done && tls_enabled() && - !strcasecmp(token, "Upgrade")) { - const char **upgrd; - - if ((upgrd = spool_getheader(txn->req_hdrs, "Upgrade")) && - !strncmp(upgrd0, TLS_VERSION, strcspn(upgrd0, " ,"))) { - syslog(LOG_DEBUG, "client requested TLS"); - txn->flags.conn |= CONN_UPGRADE; - } - } - } - - tok_fini(&tok); - } - - if (!httpd_timeout) txn->flags.conn |= CONN_CLOSE; - else if (txn->flags.conn & CONN_CLOSE) { - /* close overrides keep-alive */ - txn->flags.conn &= ~CONN_KEEPALIVE; - } - else if (txn->flags.ver1_0 && !(txn->flags.conn & CONN_KEEPALIVE)) { - /* HTTP/1.0 - non-persistent connection unless keep-alive */ - txn->flags.conn |= CONN_CLOSE; - } -} - - -/* Compare accept quality values so that they sort in descending order */ -static int compare_accept(const struct accept *a1, const struct accept *a2) -{ - if (a2->qual < a1->qual) return -1; - if (a2->qual > a1->qual) return 1; - return 0; -} - -struct accept *parse_accept(const char **hdr) -{ - int i, n = 0, alloc = 0; - struct accept *ret = NULL; -#define GROW_ACCEPT 10; - - for (i = 0; hdr && hdri; i++) { - tok_t tok = TOK_INITIALIZER(hdri, ";,", TOK_TRIMLEFT|TOK_TRIMRIGHT); - char *token; - - while ((token = tok_next(&tok))) { - if (!strncmp(token, "q=", 2)) { - if (!ret) break; - retn-1.qual = strtof(token+2, NULL); - } - else { - if (n + 1 >= alloc) { - alloc += GROW_ACCEPT; - ret = xrealloc(ret, alloc * sizeof(struct accept)); - } - retn.token = xstrdup(token); - retn.qual = 1.0; - ret++n.token = NULL; - } - } - tok_fini(&tok); - } - - qsort(ret, n, sizeof(struct accept), - (int (*)(const void *, const void *)) &compare_accept); - - return ret; -} - - -/**************************** Response Routines *****************************/ - - -/* Create RFC3339 date ('buf' must be at least 21 characters) */ -EXPORTED char *rfc3339date_gen(char *buf, size_t len, time_t t) -{ - struct tm *tm = gmtime(&t); - - snprintf(buf, len, "%4d-%02d-%02dT%02d:%02d:%02dZ", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec); - - return buf; -} - - -/* Create HTTP-date ('buf' must be at least 30 characters) */ -EXPORTED char *httpdate_gen(char *buf, size_t len, time_t t) -{ - static char *month = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - static char *wday = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - - struct tm *tm = gmtime(&t); - - snprintf(buf, len, "%3s, %02d %3s %4d %02d:%02d:%02d GMT", - wdaytm->tm_wday, - tm->tm_mday, monthtm->tm_mon, tm->tm_year + 1900, - tm->tm_hour, tm->tm_min, tm->tm_sec); - - return buf; -} - - -/* Create an HTTP Status-Line given response code */ -EXPORTED const char *http_statusline(long code) -{ - static struct buf statline = BUF_INITIALIZER; - static unsigned tail = 0; - - if (!tail) { - buf_setcstr(&statline, HTTP_VERSION); - buf_putc(&statline, ' '); - tail = buf_len(&statline); - } - - buf_truncate(&statline, tail); - buf_appendcstr(&statline, error_message(code)); - return buf_cstring(&statline); -} - - -/* Output an HTTP response header. - * 'code' specifies the HTTP Status-Code and Reason-Phrase. - * 'txn' contains the transaction context - */ - -#define WWW_Authenticate(name, param) \ - prot_printf(httpd_out, "WWW-Authenticate: %s", name); \ - if (param) prot_printf(httpd_out, " %s", param); \ - prot_puts(httpd_out, "\r\n") - -#define Access_Control_Expose(hdr) \ - prot_puts(httpd_out, "Access-Control-Expose-Headers: " hdr "\r\n") - -EXPORTED void comma_list_hdr(const char *hdr, const char *vals, unsigned flags, ...) -{ - const char *sep = " "; - va_list args; - int i; - - va_start(args, flags); - prot_printf(httpd_out, "%s:", hdr); - for (i = 0; valsi; i++) { - if (flags & (1 << i)) { - prot_puts(httpd_out, sep); - prot_vprintf(httpd_out, valsi, args); - sep = ", "; - } - else { - /* discard any unused args */ - vsnprintf(NULL, 0, valsi, args); - } - } - prot_puts(httpd_out, "\r\n"); - va_end(args); -} - -EXPORTED void allow_hdr(const char *hdr, unsigned allow) -{ - const char *meths = { - "OPTIONS, GET, HEAD", "POST", "PUT", "DELETE", "TRACE", NULL - }; - - comma_list_hdr(hdr, meths, allow); - - if (allow & ALLOW_DAV) { - prot_printf(httpd_out, "%s: PROPFIND, REPORT", hdr); - if (allow & ALLOW_WRITE) { - prot_puts(httpd_out, ", COPY, MOVE, LOCK, UNLOCK"); - } - if (allow & ALLOW_WRITECOL) { - prot_puts(httpd_out, ", PROPPATCH, MKCOL, ACL"); - if (allow & ALLOW_CAL) { - prot_printf(httpd_out, "\r\n%s: MKCALENDAR", hdr); - } - } - prot_puts(httpd_out, "\r\n"); - } -} - -#define MD5_BASE64_LEN 25 /* ((MD5_DIGEST_LENGTH / 3) + 1) * 4 */ - -EXPORTED void Content_MD5(const unsigned char *md5) -{ - char base64MD5_BASE64_LEN+1; - - sasl_encode64((char *) md5, MD5_DIGEST_LENGTH, - base64, MD5_BASE64_LEN, NULL); - prot_printf(httpd_out, "Content-MD5: %s\r\n", base64); -} - - -EXPORTED void response_header(long code, struct transaction_t *txn) -{ - time_t now; - char datestr30; - unsigned keepalive; - const char **hdr; - struct auth_challenge_t *auth_chal; - struct resp_body_t *resp_body; - static struct buf log = BUF_INITIALIZER; - - /* Stop method processing alarm */ - keepalive = alarm(0); - - - /* Status-Line */ - prot_printf(httpd_out, "%s\r\n", http_statusline(code)); - - - /* Connection Management */ - switch (code) { - case HTTP_SWITCH_PROT: - keepalive = 0; /* No alarm during TLS negotiation */ - - prot_printf(httpd_out, "Upgrade: %s\r\n", TLS_VERSION); - prot_puts(httpd_out, "Connection: Upgrade\r\n"); - - /* Fall through as provisional response */ - - case HTTP_CONTINUE: - case HTTP_PROCESSING: - /* Provisional response - nothing else needed */ - - /* CRLF terminating the header block */ - prot_puts(httpd_out, "\r\n"); - - /* Force the response to the client immediately */ - prot_flush(httpd_out); - - /* Reset method processing alarm */ - alarm(keepalive); - - return; - - case HTTP_UPGRADE: - txn->flags.conn |= CONN_UPGRADE; - prot_printf(httpd_out, "Upgrade: %s\r\n", TLS_VERSION); - - /* Fall through as final response */ - - default: - /* Final response */ - if (txn->flags.conn) { - /* Construct Connection header */ - const char *conn_tokens = - { "close", "Upgrade", "Keep-Alive", NULL }; - - if (txn->flags.conn & CONN_KEEPALIVE) { - prot_printf(httpd_out, "Keep-Alive: timeout=%d\r\n", - httpd_timeout); - } - - comma_list_hdr("Connection", conn_tokens, txn->flags.conn); - } - - auth_chal = &txn->auth_chal; - resp_body = &txn->resp_body; - } - - - /* Control Data */ - now = time(0); - httpdate_gen(datestr, sizeof(datestr), now); - prot_printf(httpd_out, "Date: %s\r\n", datestr); - - if (httpd_tls_done) { - prot_puts(httpd_out, "Strict-Transport-Security: max-age=600\r\n"); - } - if (txn->location) { - prot_printf(httpd_out, "Location: %s\r\n", txn->location); - } - if (txn->flags.cc) { - /* Construct Cache-Control header */ - const char *cc_dirs = - { "must-revalidate", "no-cache", "no-store", "no-transform", - "public", "private", "max-age=%d", NULL }; - - comma_list_hdr("Cache-Control", cc_dirs, txn->flags.cc, - resp_body->maxage); - - if (txn->flags.cc & CC_MAXAGE) { - httpdate_gen(datestr, sizeof(datestr), now + resp_body->maxage); - prot_printf(httpd_out, "Expires: %s\r\n", datestr); - } - } - if (txn->flags.cors) { - /* Construct Cross-Origin Resource Sharing headers */ - prot_printf(httpd_out, "Access-Control-Allow-Origin: %s\r\n", - *spool_getheader(txn->req_hdrs, "Origin")); - prot_puts(httpd_out, "Access-Control-Allow-Credentials: true\r\n"); - - if (txn->flags.cors == CORS_PREFLIGHT) { - allow_hdr("Access-Control-Allow-Methods", txn->req_tgt.allow); - - for (hdr = spool_getheader(txn->req_hdrs, - "Access-Control-Request-Headers"); - hdr && *hdr; hdr++) { - prot_printf(httpd_out, - "Access-Control-Allow-Headers: %s\r\n", *hdr); - } - prot_puts(httpd_out, "Access-Control-Max-Age: 3600\r\n"); - } - } - if (txn->flags.vary) { - /* Construct Vary header */ - const char *vary_hdrs = - { "Accept", "Accept-Encoding", "Brief", "Prefer", NULL }; - - comma_list_hdr("Vary", vary_hdrs, txn->flags.vary); - } - - - /* Response Context */ - if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) { - prot_printf(httpd_out, "Server: %s\r\n", buf_cstring(&serverinfo)); - } - if (txn->flags.mime) { - prot_puts(httpd_out, "MIME-Version: 1.0\r\n"); - } - if (txn->req_tgt.allow & ALLOW_ISCHEDULE) { - prot_puts(httpd_out, "iSchedule-Version: 1.0\r\n"); - if (resp_body->iserial) { - prot_printf(httpd_out, "iSchedule-Capabilities: %ld\r\n", - resp_body->iserial); - } - } - if (resp_body->prefs) { - /* Construct Preference-Applied header */ - const char *prefs = - { "return=minimal", "return=representation", "depth-noroot", NULL }; - - comma_list_hdr("Preference-Applied", prefs, resp_body->prefs); - if (txn->flags.cors) Access_Control_Expose("Preference-Applied"); - } - - switch (code) { - case HTTP_OK: - switch (txn->meth) { - case METH_GET: - case METH_HEAD: - /* Construct Accept-Ranges header for GET and HEAD responses */ - prot_printf(httpd_out, "Accept-Ranges: %s\r\n", - txn->flags.ranges ? "bytes" : "none"); - break; - - case METH_OPTIONS: - if (txn->req_tgt.allow & ALLOW_DAV) { - /* Construct DAV header(s) based on namespace of request URL */ - prot_printf(httpd_out, "DAV: 1,%s 3, access-control%s\r\n", - (txn->req_tgt.allow & ALLOW_WRITE) ? " 2," : "", - (txn->req_tgt.allow & ALLOW_WRITECOL) ? - ", extended-mkcol" : ""); - if (txn->req_tgt.allow & ALLOW_CAL) { - prot_printf(httpd_out, "DAV: calendar-access%s%s\r\n", - (txn->req_tgt.allow & ALLOW_CAL_AVAIL) ? - ", calendar-availability" : "", - (txn->req_tgt.allow & ALLOW_CAL_SCHED) ? - ", calendar-auto-schedule" : ""); - } - if (txn->req_tgt.allow & ALLOW_CARD) { - prot_puts(httpd_out, "DAV: addressbook\r\n"); - } - } - - if (txn->flags.cors == CORS_PREFLIGHT) { - /* Access-Control-Allow-Methods supersedes Allow */ - break; - } - else goto allow; - } - goto authorized; - - case HTTP_NOT_ALLOWED: - allow: - /* Construct Allow header(s) for OPTIONS and 405 response */ - allow_hdr("Allow", txn->req_tgt.allow); - goto authorized; - - case HTTP_UNAUTHORIZED: - /* Authentication Challenges */ - if (!auth_chal->scheme) { - /* Require authentication by advertising all possible schemes */ - struct auth_scheme_t *scheme; - - for (scheme = auth_schemes; scheme->name; scheme++) { - /* Only advertise what is available and - can work with the type of connection */ - if ((avail_auth_schemes & (1 << scheme->idx)) && - !((txn->flags.conn & CONN_CLOSE) && - (scheme->flags & AUTH_NEED_PERSIST))) { - auth_chal->param = NULL; - - if (scheme->flags & AUTH_SERVER_FIRST) { - /* Generate the initial challenge */ - http_auth(scheme->name, txn); - - if (!auth_chal->param) continue; /* If fail, skip it */ - } - WWW_Authenticate(scheme->name, auth_chal->param); - } - } - } - else { - /* Continue with current authentication exchange */ - WWW_Authenticate(auth_chal->scheme->name, auth_chal->param); - } - break; - - default: - authorized: - /* Authentication completed/unnecessary */ - if (auth_chal->param) { - /* Authentication completed with success data */ - if (auth_chal->scheme->send_success) { - /* Special handling of success data for this scheme */ - auth_chal->scheme->send_success(auth_chal->scheme->name, - auth_chal->param); - } - else { - /* Default handling of success data */ - WWW_Authenticate(auth_chal->scheme->name, auth_chal->param); - } - } - } - - - /* Validators */ - if (resp_body->lock) { - prot_printf(httpd_out, "Lock-Token: <%s>\r\n", resp_body->lock); - if (txn->flags.cors) Access_Control_Expose("Lock-Token"); - } - if (resp_body->stag) { - prot_printf(httpd_out, "Schedule-Tag: \"%s\"\r\n", resp_body->stag); - if (txn->flags.cors) Access_Control_Expose("Schedule-Tag"); - } - if (resp_body->etag) { - prot_printf(httpd_out, "ETag: %s\"%s\"\r\n", - resp_body->enc ? "W/" : "", resp_body->etag); - if (txn->flags.cors) Access_Control_Expose("ETag"); - } - if (resp_body->lastmod) { - /* Last-Modified MUST NOT be in the future */ - resp_body->lastmod = MIN(resp_body->lastmod, now); - httpdate_gen(datestr, sizeof(datestr), resp_body->lastmod); - prot_printf(httpd_out, "Last-Modified: %s\r\n", datestr); - } - - - /* Representation Metadata */ - if (resp_body->type) { - prot_printf(httpd_out, "Content-Type: %s\r\n", resp_body->type); - - if (resp_body->fname) { - prot_printf(httpd_out, - "Content-Disposition: inline; filename=\"%s\"\r\n", - resp_body->fname); - } - if (txn->resp_body.enc) { - /* Construct Content-Encoding header */ - const char *ce = - { "deflate", "gzip", NULL }; - - comma_list_hdr("Content-Encoding", ce, txn->resp_body.enc); - } - if (resp_body->lang) { - prot_printf(httpd_out, "Content-Language: %s\r\n", resp_body->lang); - } - if (resp_body->loc) { - prot_printf(httpd_out, "Content-Location: %s\r\n", resp_body->loc); - if (txn->flags.cors) Access_Control_Expose("Content-Location"); - } - if (resp_body->md5) { - Content_MD5(resp_body->md5); - } - } - - - /* Payload */ - switch (code) { - case HTTP_NO_CONTENT: - case HTTP_NOT_MODIFIED: - /* MUST NOT include a body */ - break; - - case HTTP_UNSAT_RANGE: - prot_printf(httpd_out, "Content-Range: bytes */%lu\r\n", - resp_body->len); - resp_body->len = 0; /* No content */ - - /* Fall through and specify framing */ - - case HTTP_PARTIAL: - if (resp_body->range) { - prot_printf(httpd_out, "Content-Range: bytes %lu-%lu/%lu\r\n", - resp_body->range->first, resp_body->range->last, - resp_body->len); - - /* Set actual content length of range */ - resp_body->len = resp_body->range->last - - resp_body->range->first + 1; - - free(resp_body->range); - } - - /* Fall through and specify framing */ - - default: - if (txn->flags.te) { - /* HTTP/1.1+ only - we use close-delimiting for HTTP/1.0 */ - if (!txn->flags.ver1_0) { - /* Construct Transfer-Encoding header */ - const char *te = - { "deflate", "gzip", "chunked", NULL }; - - comma_list_hdr("Transfer-Encoding", te, txn->flags.te); - - if (txn->flags.trailer) { - /* Construct Trailer header */ - const char *trailer_hdrs = - { "Content-MD5", NULL }; - - comma_list_hdr("Trailer", trailer_hdrs, txn->flags.trailer); - } - } - } - else if (resp_body->len || txn->meth != METH_HEAD) { - prot_printf(httpd_out, "Content-Length: %lu\r\n", resp_body->len); - } - } - - - /* CRLF terminating the header block */ - prot_puts(httpd_out, "\r\n"); - - - /* Log the client request and our response */ - buf_reset(&log); - /* Add client data */ - buf_printf(&log, "%s", httpd_clienthost); - if (proxy_userid) buf_printf(&log, " as \"%s\"", proxy_userid); - if (txn->req_hdrs && - (hdr = spool_getheader(txn->req_hdrs, "User-Agent"))) { - buf_printf(&log, " with \"%s\"", hdr0); - if ((hdr = spool_getheader(txn->req_hdrs, "X-Client"))) - buf_printf(&log, " by \"%s\"", hdr0); - else if ((hdr = spool_getheader(txn->req_hdrs, "X-Requested-With"))) - buf_printf(&log, " by \"%s\"", hdr0); - } - /* Add request-line */ - buf_appendcstr(&log, "; \""); - if (txn->req_line.meth) { - buf_printf(&log, "%s", - txn->flags.override ? "POST" : txn->req_line.meth); - if (txn->req_line.uri) { - buf_printf(&log, " %s", txn->req_line.uri); - if (txn->req_line.ver) { - buf_printf(&log, " %s", txn->req_line.ver); - if (code != HTTP_TOO_LONG) { - char *p = txn->req_line.ver + strlen(txn->req_line.ver) + 1; - if (*p) buf_printf(&log, " %s", p); - } - } - } - } - buf_appendcstr(&log, "\""); - if (txn->req_hdrs) { - /* Add any request modifying headers */ - const char *sep = " ("; - - if (txn->flags.override) { - buf_printf(&log, "%smethod-override=%s", sep, txn->req_line.meth); - sep = "; "; - } - if ((hdr = spool_getheader(txn->req_hdrs, "Origin"))) { - buf_printf(&log, "%sorigin=%s", sep, hdr0); - sep = "; "; - } - if ((hdr = spool_getheader(txn->req_hdrs, "Referer"))) { - buf_printf(&log, "%sreferer=%s", sep, hdr0); - sep = "; "; - } - if ((hdr = spool_getheader(txn->req_hdrs, "Destination"))) { - buf_printf(&log, "%sdestination=%s", sep, hdr0); - sep = "; "; - } - if ((hdr = spool_getheader(txn->req_hdrs, ":type"))) { - buf_printf(&log, "%stype=%s", sep, hdr0); - sep = "; "; - } - if ((hdr = spool_getheader(txn->req_hdrs, "Depth"))) { - buf_printf(&log, "%sdepth=%s", sep, hdr0); - sep = "; "; - } - if (*sep == ';') buf_appendcstr(&log, ")"); - } - buf_printf(&log, " => \"%s\"", error_message(code)); - /* Add any auxiliary response data */ - if (txn->location) { - buf_printf(&log, " (location=%s)", txn->location); - } - else if (txn->flags.cors) { - buf_appendcstr(&log, " (allow-origin)"); - } - else if (txn->error.desc) { - buf_printf(&log, " (error=%s)", txn->error.desc); - } - syslog(LOG_INFO, "%s", buf_cstring(&log)); -} - - -static void keep_alive(int sig) -{ - if (sig == SIGALRM) { - response_header(HTTP_CONTINUE, NULL); - alarm(httpd_keepalive); - } -} - - -/* - * Output an HTTP response with multipart body data. - * - * An initial call with 'code' != 0 will output a response header - * and the preamble. - * All subsequent calls should have 'code' = 0 to output just a body part. - * A final call with 'len' = 0 ends the multipart body. - */ -EXPORTED void write_multipart_body(long code, struct transaction_t *txn, - const char *buf, unsigned len) -{ - static char boundary100; - struct buf *body = &txn->resp_body.payload; - - if (code) { - const char *preamble = - "This is a message with multiple parts in MIME format.\r\n"; - - txn->flags.mime = 1; - - /* Create multipart boundary */ - snprintf(boundary, sizeof(boundary), "%s-%ld-%ld-%ld", - *spool_getheader(txn->req_hdrs, "Host"), - (long) getpid(), (long) time(0), (long) rand()); - - /* Create Content-Type w/ boundary */ - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%s; boundary=\"%s\"", - txn->resp_body.type, boundary); - txn->resp_body.type = buf_cstring(&txn->buf); - - /* Setup for chunked response and begin multipart */ - txn->flags.te |= TE_CHUNKED; - if (!buf) { - buf = preamble; - len = strlen(preamble); - } - write_body(code, txn, buf, len); - } - else if (len) { - /* Output delimiter and MIME part-headers */ - buf_reset(body); - buf_printf(body, "\r\n--%s\r\n", boundary); - buf_printf(body, "Content-Type: %s\r\n", txn->resp_body.type); - if (txn->resp_body.range) { - buf_printf(body, "Content-Range: bytes %lu-%lu/%lu\r\n", - txn->resp_body.range->first, - txn->resp_body.range->last, - txn->resp_body.len); - } - buf_printf(body, "Content-Length: %d\r\n\r\n", len); - write_body(0, txn, buf_cstring(body), buf_len(body)); - - /* Output body-part data */ - write_body(0, txn, buf, len); - } - else { - const char *epilogue = "\r\nEnd of MIME multipart body.\r\n"; - - /* Output close-delimiter and epilogue */ - buf_reset(body); - buf_printf(body, "\r\n--%s--\r\n%s", boundary, epilogue); - write_body(0, txn, buf_cstring(body), buf_len(body)); - - /* End of output */ - write_body(0, txn, NULL, 0); - } -} - - -/* Output multipart/byteranges */ -static void multipart_byteranges(struct transaction_t *txn, - const char *msg_base) -{ - /* Save Content-Range and Content-Type pointers */ - struct range *range = txn->resp_body.range; - const char *type = txn->resp_body.type; - - /* Start multipart response */ - txn->resp_body.range = NULL; - txn->resp_body.type = "multipart/byteranges"; - write_multipart_body(HTTP_PARTIAL, txn, NULL, 0); - - txn->resp_body.type = type; - while (range) { - unsigned long offset = range->first; - unsigned long datalen = range->last - range->first + 1; - struct range *next = range->next; - - /* Output range as body part */ - txn->resp_body.range = range; - write_multipart_body(0, txn, msg_base + offset, datalen); - - /* Cleanup */ - free(range); - range = next; - } - - /* End of multipart body */ - write_multipart_body(0, txn, NULL, 0); -} - - -/* - * Output an HTTP response with body data, compressed as necessary. - * - * For chunked body data, an initial call with 'code' != 0 will output - * a response header and the first body chunk. - * All subsequent calls should have 'code' = 0 to output just the body chunk. - * A final call with 'len' = 0 ends the chunked body. - * - * NOTE: HTTP/1.0 clients can't handle chunked encoding, - * so we use bare chunks and close the connection when done. - */ -EXPORTED void write_body(long code, struct transaction_t *txn, - const char *buf, unsigned len) -{ - unsigned is_dynamic = code ? (txn->flags.te & TE_CHUNKED) : 1; - unsigned outlen = len, offset = 0; - int do_md5 = config_getswitch(IMAPOPT_HTTPCONTENTMD5); - static MD5_CTX ctx; - static unsigned char md5MD5_DIGEST_LENGTH; - - if (!is_dynamic && len < GZIP_MIN_LEN) { - /* Don't compress small static content */ - txn->resp_body.enc = CE_IDENTITY; - txn->flags.te = TE_NONE; - } - - /* Compress data */ - if (txn->resp_body.enc || txn->flags.te & ~TE_CHUNKED) { -#ifdef HAVE_ZLIB - /* Only flush for static content or on last (zero-length) chunk */ - unsigned flush = (is_dynamic && len) ? Z_NO_FLUSH : Z_FINISH; - - if (code) deflateReset(&txn->zstrm); - - txn->zstrm.next_in = (Bytef *) buf; - txn->zstrm.avail_in = len; - buf_reset(&txn->zbuf); - - do { - buf_ensure(&txn->zbuf, - deflateBound(&txn->zstrm, txn->zstrm.avail_in)); - - txn->zstrm.next_out = (Bytef *) txn->zbuf.s + txn->zbuf.len; - txn->zstrm.avail_out = txn->zbuf.alloc - txn->zbuf.len; - - deflate(&txn->zstrm, flush); - txn->zbuf.len = txn->zbuf.alloc - txn->zstrm.avail_out; - - } while (!txn->zstrm.avail_out); - - buf = txn->zbuf.s; - outlen = txn->zbuf.len; -#else - /* XXX should never get here */ - fatal("Compression requested, but no zlib", EC_SOFTWARE); -#endif /* HAVE_ZLIB */ - } - - if (code) { - /* Initial call - prepare response header based on CE, TE and version */ - if (do_md5) MD5Init(&ctx); - - if (txn->flags.te & ~TE_CHUNKED) { - /* Transfer-Encoded content MUST be chunked */ - txn->flags.te |= TE_CHUNKED; - - if (!is_dynamic) { - /* Handle static content as last chunk */ - len = 0; - } - } - - if (!(txn->flags.te & TE_CHUNKED)) { - /* Full/partial body (no encoding). - * - * In all cases, 'resp_body.len' is used to specify complete-length - * In the case of a 206 or 416 response, Content-Length will be - * set accordingly in response_header(). - */ - txn->resp_body.len = outlen; - - if (code == HTTP_PARTIAL) { - /* check_precond() tells us that this is a range request */ - code = parse_ranges(*spool_getheader(txn->req_hdrs, "Range"), - outlen, &txn->resp_body.range); - - switch (code) { - case HTTP_OK: - /* Full body (unknown range-unit) */ - break; - - case HTTP_PARTIAL: - /* One or more range request(s) */ - txn->resp_body.len = outlen; - - if (txn->resp_body.range->next) { - /* Multiple ranges */ - multipart_byteranges(txn, buf); - return; - } - else { - /* Single range - set data parameters accordingly */ - offset += txn->resp_body.range->first; - outlen = txn->resp_body.range->last - - txn->resp_body.range->first + 1; - } - break; - - case HTTP_UNSAT_RANGE: - /* No valid ranges */ - outlen = 0; - break; - } - } - - if (outlen && do_md5) { - MD5Update(&ctx, buf+offset, outlen); - MD5Final(md5, &ctx); - txn->resp_body.md5 = md5; - } - } - else if (txn->flags.ver1_0) { - /* HTTP/1.0 doesn't support chunked - close-delimit the body */ - txn->flags.conn = CONN_CLOSE; - } - else if (do_md5) txn->flags.trailer = TRAILER_CMD5; - - response_header(code, txn); - - /* MUST NOT send a body for 1xx/204/304 response or any HEAD response */ - switch (code) { - case HTTP_CONTINUE: - case HTTP_SWITCH_PROT: - case HTTP_PROCESSING: - case HTTP_NO_CONTENT: - case HTTP_NOT_MODIFIED: - return; - - default: - if (txn->meth == METH_HEAD) return; - } - } - - /* Output data */ - if ((txn->flags.te & TE_CHUNKED) && !txn->flags.ver1_0) { - /* HTTP/1.1 chunk */ - if (outlen) { - prot_printf(httpd_out, "%x\r\n", outlen); - prot_write(httpd_out, buf, outlen); - prot_puts(httpd_out, "\r\n"); - - if (do_md5) MD5Update(&ctx, buf, outlen); - } - if (!len) { - /* Terminate the HTTP/1.1 body with a zero-length chunk */ - prot_puts(httpd_out, "0\r\n"); - - /* Trailer */ - if (do_md5) { - MD5Final(md5, &ctx); - Content_MD5(md5); - } - - prot_puts(httpd_out, "\r\n"); - } - } - else { - /* Full body or HTTP/1.0 close-delimited body */ - prot_write(httpd_out, buf + offset, outlen); - } -} - - -/* Output an HTTP response with application/xml body */ -EXPORTED void xml_response(long code, struct transaction_t *txn, xmlDocPtr xml) -{ - xmlChar *buf; - int bufsiz; - - switch (code) { - case HTTP_OK: - case HTTP_CREATED: - case HTTP_NO_CONTENT: - case HTTP_MULTI_STATUS: - break; - - default: - /* Neither Brief nor Prefer affect error response bodies */ - txn->flags.vary &= ~(VARY_BRIEF | VARY_PREFER); - txn->resp_body.prefs = 0; - } - - /* Dump XML response tree into a text buffer */ - xmlDocDumpFormatMemoryEnc(xml, &buf, &bufsiz, "utf-8", - config_httpprettytelemetry); - - if (buf) { - /* Output the XML response */ - txn->resp_body.type = "application/xml; charset=utf-8"; - - write_body(code, txn, (char *) buf, bufsiz); - - /* Cleanup */ - xmlFree(buf); - } - else { - txn->error.precond = 0; - txn->error.desc = "Error dumping XML tree\r\n"; - error_response(HTTP_SERVER_ERROR, txn); - } -} - -EXPORTED void buf_printf_markup(struct buf *buf, unsigned level, const char *fmt, ...) -{ - va_list args; - const char *eol = "\n"; - - if (!config_httpprettytelemetry) { - level = 0; - eol = ""; - } - - va_start(args, fmt); - - buf_printf(buf, "%*s", level * MARKUP_INDENT, ""); - buf_vprintf(buf, fmt, args); - buf_appendcstr(buf, eol); - - va_end(args); -} - - -/* Output an HTTP error response with optional XML or HTML body */ -EXPORTED void error_response(long code, struct transaction_t *txn) -{ - struct buf *html = &txn->resp_body.payload; - - /* Neither Brief nor Prefer affect error response bodies */ - txn->flags.vary &= ~(VARY_BRIEF | VARY_PREFER); - txn->resp_body.prefs = 0; - -#ifdef WITH_DAV - if (txn->error.precond) { - xmlNodePtr root = xml_add_error(NULL, &txn->error, NULL); - - if (root) { - xml_response(code, txn, root->doc); - xmlFreeDoc(root->doc); - return; - } - } -#endif - - if (!txn->error.desc) { - switch (code) { - /* 4xx codes */ - case HTTP_BAD_REQUEST: - txn->error.desc = - "The request was not understood by this server."; - break; - - case HTTP_NOT_FOUND: - txn->error.desc = - "The requested URL was not found on this server."; - break; - - case HTTP_NOT_ALLOWED: - txn->error.desc = - "The requested method is not allowed for the URL."; - break; - - case HTTP_GONE: - txn->error.desc = - "The requested URL has been removed from this server."; - break; - - /* 5xx codes */ - case HTTP_SERVER_ERROR: - txn->error.desc = - "The server encountered an internal error."; - break; - - case HTTP_NOT_IMPLEMENTED: - txn->error.desc = - "The requested method is not implemented by this server."; - break; - - case HTTP_UNAVAILABLE: - txn->error.desc = - "The server is unable to process the request at this time."; - break; - } - } - - buf_reset(html); - if (txn->error.desc) { - const char **hdr, *host = ""; - char *port = NULL; - unsigned level = 0; - - if (txn->req_hdrs && - (hdr = spool_getheader(txn->req_hdrs, "Host")) && - hdr0 && *hdr0) { - host = (char *) hdr0; - if ((port = strchr(host, ':'))) *port++ = '\0'; - } - else if (config_serverinfo != IMAP_ENUM_SERVERINFO_OFF) { - host = config_servername; - } - if (!port) port = strchr(saslprops.iplocalport, ';')+1; - - buf_printf_markup(html, level, HTML_DOCTYPE); - buf_printf_markup(html, level++, "<html>"); - buf_printf_markup(html, level++, "<head>"); - buf_printf_markup(html, level, "<title>%s</title>", - error_message(code)); - buf_printf_markup(html, --level, "</head>"); - buf_printf_markup(html, level++, "<body>"); - buf_printf_markup(html, level, "<h1>%s</h1>", error_message(code)+4); - buf_printf_markup(html, level, "<p>%s</p>", txn->error.desc); - buf_printf_markup(html, level, "<hr>"); - buf_printf_markup(html, level, - "<address>%s Server at %s Port %s</address>", - buf_cstring(&serverinfo), host, port); - buf_printf_markup(html, --level, "</body>"); - buf_printf_markup(html, --level, "</html>"); - - txn->resp_body.type = "text/html; charset=utf-8"; - } - - write_body(code, txn, buf_cstring(html), buf_len(html)); -} - - -static int proxy_authz(const char **authzid, struct transaction_t *txn) -{ - static char authzbufMAX_MAILBOX_BUFFER; - unsigned authzlen; - int status; - - syslog(LOG_DEBUG, "proxy_auth: authzid='%s'", *authzid); - - /* Free userid & authstate previously allocated for auth'd user */ - if (httpd_userid) { - free(httpd_userid); - httpd_userid = NULL; - } - if (httpd_extradomain) { - free(httpd_extradomain); - httpd_extradomain = NULL; - } - if (httpd_authstate) { - auth_freestate(httpd_authstate); - httpd_authstate = NULL; - } - - if (!(config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS))) { - /* Not a backend in a Murder - proxy authz is not allowed */ - syslog(LOG_NOTICE, "badlogin: %s %s %s %s", - httpd_clienthost, txn->auth_chal.scheme->name, saslprops.authid, - "proxy authz attempted on non-Murder backend"); - return SASL_NOAUTHZ; - } - - /* Canonify the authzid */ - status = mysasl_canon_user(httpd_saslconn, NULL, - *authzid, strlen(*authzid), - SASL_CU_AUTHZID, NULL, - authzbuf, sizeof(authzbuf), &authzlen); - if (status) { - syslog(LOG_NOTICE, "badlogin: %s %s %s invalid user", - httpd_clienthost, txn->auth_chal.scheme->name, - beautify_string(*authzid)); - return status; - } - - /* See if auth'd user is allowed to proxy */ - status = mysasl_proxy_policy(httpd_saslconn, &httpd_proxyctx, - authzbuf, authzlen, - saslprops.authid, strlen(saslprops.authid), - NULL, 0, NULL); - - if (status) { - syslog(LOG_NOTICE, "badlogin: %s %s %s %s", - httpd_clienthost, txn->auth_chal.scheme->name, saslprops.authid, - sasl_errdetail(httpd_saslconn)); - return status; - } - - *authzid = authzbuf; - - return status; -} - - -/* Write cached header (redacting authorization credentials) to buffer. */ -static void log_cachehdr(const char *name, const char *contents, void *rock) -{ - struct buf *buf = (struct buf *) rock; - - /* Ignore private headers in our cache */ - if (name0 == ':') return; - - buf_printf(buf, "%c%s: ", toupper(name0), name+1); - if (!strcmp(name, "authorization")) { - /* Replace authorization credentials with an ellipsis */ - const char *creds = strchr(contents, ' ') + 1; - buf_printf(buf, "%.*s%-*s\r\n", (int) (creds - contents), contents, - (int) strlen(creds), "..."); - } - else buf_printf(buf, "%s\r\n", contents); -} - - -static void auth_success(struct transaction_t *txn) -{ - struct auth_scheme_t *scheme = txn->auth_chal.scheme; - int i; - - proc_register("httpd", httpd_clienthost, httpd_userid, NULL, NULL); - - syslog(LOG_NOTICE, "login: %s %s %s%s %s SESSIONID=<%s>", - httpd_clienthost, httpd_userid, scheme->name, - httpd_tls_done ? "+TLS" : "", "User logged in", - session_id()); - - - /* Recreate telemetry log entry for request (w/ credentials redacted) */ - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "<%ld<", time(NULL)); /* timestamp */ - buf_printf(&txn->buf, "%s %s %s\r\n", /* request-line*/ - txn->req_line.meth, txn->req_line.uri, txn->req_line.ver); - spool_enum_hdrcache(txn->req_hdrs, /* header fields */ - &log_cachehdr, &txn->buf); - buf_appendcstr(&txn->buf, "\r\n"); /* CRLF */ - buf_append(&txn->buf, &txn->req_body.payload); /* message body */ - buf_appendmap(&txn->buf, /* buffered input */ - (const char *) httpd_in->ptr, httpd_in->cnt); - - if (httpd_logfd != -1) { - /* Rewind log to current request and truncate it */ - off_t end = lseek(httpd_logfd, 0, SEEK_END); - - ftruncate(httpd_logfd, end - buf_len(&txn->buf)); - } - - if (!proxy_userid || strcmp(proxy_userid, httpd_userid)) { - /* Close existing telemetry log */ - close(httpd_logfd); - - prot_setlog(httpd_in, PROT_NO_FD); - prot_setlog(httpd_out, PROT_NO_FD); - - /* Create telemetry log based on new userid */ - httpd_logfd = telemetry_log(httpd_userid, httpd_in, httpd_out, 0); - } - - if (httpd_logfd != -1) { - /* Log credential-redacted request */ - write(httpd_logfd, buf_cstring(&txn->buf), buf_len(&txn->buf)); - } - - buf_reset(&txn->buf); - - /* Make a copy of the external userid for use in proxying */ - if (proxy_userid) free(proxy_userid); - proxy_userid = xstrdup(httpd_userid); - - /* Translate any separators in userid */ - mboxname_hiersep_tointernal(&httpd_namespace, httpd_userid, - config_virtdomains ? - strcspn(httpd_userid, "@") : 0); - - /* Do any namespace specific post-auth processing */ - for (i = 0; namespacesi; i++) { - if (namespacesi->enabled && namespacesi->auth) - namespacesi->auth(httpd_userid); - } -} - - -/* Perform HTTP Authentication based on the given credentials ('creds'). - * Returns the selected auth scheme and any server challenge in 'chal'. - * May be called multiple times if auth scheme requires multiple steps. - * SASL status between steps is maintained in 'status'. - */ -#define BASE64_BUF_SIZE 21848 /* per RFC 4422: ((16K / 3) + 1) * 4 */ - -static int http_auth(const char *creds, struct transaction_t *txn) -{ - struct auth_challenge_t *chal = &txn->auth_chal; - static int status = SASL_OK; - int slen; - const char *clientin = NULL, *realm = NULL, *user, **authzid; - unsigned int clientinlen = 0; - struct auth_scheme_t *scheme; - static char base64BASE64_BUF_SIZE+1; - const void *canon_user; - - /* Split credentials into auth scheme and response */ - slen = strcspn(creds, " \0"); - if ((clientin = strchr(creds, ' '))) clientinlen = strlen(++clientin); - - syslog(LOG_DEBUG, - "http_auth: status=%d scheme='%s' creds='%.*s%s'", - status, chal->scheme ? chal->scheme->name : "", - slen, creds, clientin ? " <response>" : ""); - - /* Free userid & authstate previously allocated for auth'd user */ - if (httpd_userid) { - free(httpd_userid); - httpd_userid = NULL; - } - if (httpd_extradomain) { - free(httpd_extradomain); - httpd_extradomain = NULL; - } - if (httpd_authstate) { - auth_freestate(httpd_authstate); - httpd_authstate = NULL; - } - chal->param = NULL; - - if (chal->scheme) { - /* Use current scheme, if possible */ - scheme = chal->scheme; - - if (strncasecmp(scheme->name, creds, slen)) { - /* Changing auth scheme -> reset state */ - syslog(LOG_DEBUG, "http_auth: changing scheme"); - reset_saslconn(&httpd_saslconn); - chal->scheme = NULL; - status = SASL_OK; - } - } - - if (!chal->scheme) { - /* Find the client-specified auth scheme */ - syslog(LOG_DEBUG, "http_auth: find client scheme"); - for (scheme = auth_schemes; scheme->name; scheme++) { - if (slen && !strncasecmp(scheme->name, creds, slen)) { - /* Found a supported scheme, see if its available */ - if (!(avail_auth_schemes & (1 << scheme->idx))) scheme = NULL; - break; - } - } - if (!scheme || !scheme->name) { - /* Didn't find a matching scheme that is available */ - syslog(LOG_DEBUG, "Unknown auth scheme '%.*s'", slen, creds); - return SASL_NOMECH; - } - /* We found it! */ - syslog(LOG_DEBUG, "http_auth: found matching scheme: %s", scheme->name); - chal->scheme = scheme; - status = SASL_OK; - } - - /* Base64 decode any client response, if necesary */ - if (clientin && (scheme->flags & AUTH_BASE64)) { - int r = sasl_decode64(clientin, clientinlen, - base64, BASE64_BUF_SIZE, &clientinlen); - if (r != SASL_OK) { - syslog(LOG_ERR, "Base64 decode failed: %s", - sasl_errstring(r, NULL, NULL)); - return r; - } - clientin = base64; - } - - /* Get realm - based on namespace of URL */ - switch (txn->req_tgt.namespace) { - case URL_NS_DEFAULT: - case URL_NS_PRINCIPAL: - realm = config_getstring(IMAPOPT_DAV_REALM); - break; - - case URL_NS_CALENDAR: - realm = config_getstring(IMAPOPT_CALDAV_REALM); - break; - - case URL_NS_ADDRESSBOOK: - realm = config_getstring(IMAPOPT_CARDDAV_REALM); - break; - - case URL_NS_RSS: - realm = config_getstring(IMAPOPT_RSS_REALM); - break; - } - if (!realm) realm = config_servername; - -#ifdef SASL_HTTP_REQUEST - /* Setup SASL HTTP request, if necessary */ - if (scheme->flags & AUTH_NEED_REQUEST) { - sasl_http_request_t sasl_http_req; - - sasl_http_req.method = txn->req_line.meth; - sasl_http_req.uri = txn->req_line.uri; - sasl_http_req.entity = NULL; - sasl_http_req.elen = 0; - sasl_http_req.non_persist = txn->flags.conn & CONN_CLOSE; - sasl_setprop(httpd_saslconn, SASL_HTTP_REQUEST, &sasl_http_req); - } -#endif /* SASL_HTTP_REQUEST */ - - if (scheme->idx == AUTH_BASIC) { - /* Basic (plaintext) authentication */ - char *pass; - char *extra; - - if (!clientin) { - /* Create initial challenge (base64 buffer is static) */ - snprintf(base64, BASE64_BUF_SIZE, "realm=\"%s\"", realm); - chal->param = base64; - chal->scheme = NULL; /* make sure we don't reset the SASL ctx */ - return status; - } - - /* Split credentials into <user> ':' <pass>. - * We are working with base64 buffer, so we can modify it. - */ - user = base64; - pass = strchr(base64, ':'); - if (!pass) { - syslog(LOG_ERR, "Basic auth: Missing password"); - return SASL_BADPARAM; - } - *pass++ = '\0'; - extra = strchr(user, '%'); - if (extra) *extra++ = '\0'; - /* Verify the password */ - status = sasl_checkpass(httpd_saslconn, user, strlen(user), - pass, strlen(pass)); - memset(pass, 0, strlen(pass)); /* erase plaintext password */ - - if (status) { - syslog(LOG_NOTICE, "badlogin: %s Basic %s %s", - httpd_clienthost, user, sasl_errdetail(httpd_saslconn)); - - /* Don't allow user probing */ - if (status == SASL_NOUSER) status = SASL_BADAUTH; - return status; - } - - /* Successful authentication - fall through */ - httpd_extradomain = xstrdupnull(extra); - } - else { - /* SASL-based authentication (Digest, Negotiate, NTLM) */ - const char *serverout = NULL; - unsigned int serveroutlen = 0; - - if (status == SASL_CONTINUE) { - /* Continue current authentication exchange */ - syslog(LOG_DEBUG, "http_auth: continue %s", scheme->saslmech); - status = sasl_server_step(httpd_saslconn, clientin, clientinlen, - &serverout, &serveroutlen); - } - else { - /* Start new authentication exchange */ - syslog(LOG_DEBUG, "http_auth: start %s", scheme->saslmech); - status = sasl_server_start(httpd_saslconn, scheme->saslmech, - clientin, clientinlen, - &serverout, &serveroutlen); - } - - /* Failure - probably bad client response */ - if ((status != SASL_OK) && (status != SASL_CONTINUE)) { - syslog(LOG_ERR, "SASL failed: %s", - sasl_errstring(status, NULL, NULL)); - return status; - } - - /* Base64 encode any server challenge, if necesary */ - if (serverout && (scheme->flags & AUTH_BASE64)) { - int r = sasl_encode64(serverout, serveroutlen, - base64, BASE64_BUF_SIZE, NULL); - if (r != SASL_OK) { - syslog(LOG_ERR, "Base64 encode failed: %s", - sasl_errstring(r, NULL, NULL)); - return r; - } - serverout = base64; - } - - chal->param = serverout; - - if (status == SASL_CONTINUE) { - /* Need another step to complete authentication */ - return status; - } - - /* Successful authentication - * - * HTTP doesn't support security layers, - * so don't attach SASL context to prot layer. - */ - } - - /* Get the userid from SASL - already canonicalized */ - status = sasl_getprop(httpd_saslconn, SASL_USERNAME, &canon_user); - if (status != SASL_OK) { - syslog(LOG_ERR, "weird SASL error %d getting SASL_USERNAME", status); - return status; - } - user = (const char *) canon_user; - - if (saslprops.authid) free(saslprops.authid); - saslprops.authid = xstrdup(user); - - authzid = spool_getheader(txn->req_hdrs, "Authorize-As"); - if (authzid && *authzid0) { - /* Trying to proxy as another user */ - user = authzid0; - - status = proxy_authz(&user, txn); - if (status) return status; - } - - httpd_userid = xstrdup(user); - - auth_success(txn); - - return status; -} - - -/************************* Method Execution Routines ************************/ - - -/* Compare an etag in a header to a resource etag. - * Returns 0 if a match, non-zero otherwise. - */ -EXPORTED int etagcmp(const char *hdr, const char *etag) -{ - size_t len; - - if (!etag) return -1; /* no representation */ - if (!strcmp(hdr, "*")) return 0; /* any representation */ - - len = strlen(etag); - if (!strncmp(hdr, "W/", 2)) hdr+=2; /* skip weak prefix */ - if (*hdr++ != '\"') return 1; /* match/skip open DQUOTE */ - if (strlen(hdr) != len+1) return 1; /* make sure lengths match */ - if (hdrlen != '\"') return 1; /* match close DQUOTE */ - - return strncmp(hdr, etag, len); -} - - -/* Compare a resource etag to a comma-separated list and/or multiple headers - * looking for a match. Returns 1 if a match is found, 0 otherwise. - */ -static unsigned etag_match(const char *hdr, const char *etag) -{ - unsigned i, match = 0; - tok_t tok; - char *token; - - for (i = 0; !match && hdri; i++) { - tok_init(&tok, hdri, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT); - while (!match && (token = tok_next(&tok))) { - if (!etagcmp(token, etag)) match = 1; - } - tok_fini(&tok); - } - - return match; -} - - -/* Evaluate If header. Note that we can't short-circuit any of the tests - because we need to check for a lock-token anywhere in the header */ -static int eval_if(const char *hdr, const char *etag, const char *lock_token, - unsigned *locked) -{ - unsigned ret = 0; - tok_t tok_l; - char *list; - - /* Process each list, ORing the results */ - tok_init(&tok_l, hdr, ")", TOK_TRIMLEFT|TOK_TRIMRIGHT); - while ((list = tok_next(&tok_l))) { - unsigned ret_l = 1; - tok_t tok_c; - char *cond; - - /* XXX Need to handle Resource-Tag for Tagged-list (COPY/MOVE dest) */ - - /* Process each condition, ANDing the results */ - tok_initm(&tok_c, list+1, ">", TOK_TRIMLEFT|TOK_TRIMRIGHT); - while ((cond = tok_next(&tok_c))) { - unsigned r, not = 0; - - if (!strncmp(cond, "Not", 3)) { - not = 1; - cond += 3; - while (*cond == ' ') cond++; - } - if (*cond == '') { - /* ETag */ - r = !etagcmp(cond+1, etag); - } - else { - /* State Token */ - if (!lock_token) r = 0; - else { - r = !strcmp(cond+1, lock_token); - if (r) { - /* Correct lock-token has been provided */ - *locked = 0; - } - } - } - - ret_l &= (not ? !r : r); - } - - tok_fini(&tok_c); - - ret |= ret_l; - } - - tok_fini(&tok_l); - - return (ret || locked); -} - - -static int parse_ranges(const char *hdr, unsigned long len, - struct range **ranges) -{ - int ret = HTTP_UNSAT_RANGE; - struct range *new, *tail = *ranges = NULL; - tok_t tok; - char *token; - - if (!len) return HTTP_OK; /* need to know length of representation */ - - /* we only handle byte-unit */ - if (!hdr || strncmp(hdr, "bytes=", 6)) return HTTP_OK; - - tok_init(&tok, hdr+6, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT); - while ((token = tok_next(&tok))) { - /* default to entire representation */ - unsigned long first = 0; - unsigned long last = len - 1; - char *p, *endp; - - if (!(p = strchr(token, '-'))) continue; /* bad byte-range-set */ - - if (p == token) { - /* suffix-byte-range-spec */ - unsigned long suffix = strtoul(++p, &endp, 10); - - if (endp == p || *endp) continue; /* bad suffix-length */ - if (!suffix) continue; /* unsatisfiable suffix-length */ - - /* don't start before byte zero */ - if (suffix < len) first = len - suffix; - } - else { - /* byte-range-spec */ - first = strtoul(token, &endp, 10); - if (endp != p) continue; /* bad first-byte-pos */ - if (first >= len) continue; /* unsatisfiable first-byte-pos */ - - if (*++p) { - /* last-byte-pos */ - last = strtoul(p, &endp, 10); - if (*endp || last < first) continue; /* bad last-byte-pos */ - - /* don't go past end of representation */ - if (last >= len) last = len - 1; - } - } - - ret = HTTP_PARTIAL; - - /* Coalesce overlapping ranges, or those with a gap < 80 bytes */ - if (tail && - first >= tail->first && (long) (first - tail->last) < 80) { - tail->last = MAX(last, tail->last); - continue; - } - - /* Create a new range and append it to linked list */ - new = xzmalloc(sizeof(struct range)); - new->first = first; - new->last = last; - - if (tail) tail->next = new; - else *ranges = new; - tail = new; - } - - tok_fini(&tok); - - return ret; -} - - -/* Check headers for any preconditions. - * - * Interaction is complex and is documented in RFC 4918 and - * Section 5 of HTTPbis, Part 4. - */ -EXPORTED int check_precond(struct transaction_t *txn, const void *data, - const char *etag, time_t lastmod) -{ - const char *lock_token = NULL; - unsigned locked = 0; - hdrcache_t hdrcache = txn->req_hdrs; - const char **hdr; - time_t since; - -#ifdef WITH_DAV - struct dav_data *ddata = (struct dav_data *) data; - - /* Check for a write-lock on the source */ - if (ddata && ddata->lock_expire > time(NULL)) { - lock_token = ddata->lock_token; - - switch (txn->meth) { - case METH_DELETE: - case METH_LOCK: - case METH_MOVE: - case METH_POST: - case METH_PUT: - /* State-changing method: Only the lock owner can execute - and MUST provide the correct lock-token in an If header */ - if (strcmp(ddata->lock_ownerid, httpd_userid)) return HTTP_LOCKED; - - locked = 1; - break; - - case METH_UNLOCK: - /* State-changing method: Authorized in meth_unlock() */ - break; - - case METH_ACL: - case METH_MKCALENDAR: - case METH_MKCOL: - case METH_PROPPATCH: - /* State-changing method: Locks on collections unsupported */ - break; - - default: - /* Non-state-changing method: Always allowed */ - break; - } - } -#else - assert(!data); -#endif /* WITH_DAV */ - - /* Per RFC 4918, If is similar to If-Match, but with lock-token submission. - Per Section 5 of HTTPbis, Part 4, LOCK errors supercede preconditions */ - if ((hdr = spool_getheader(hdrcache, "If"))) { - /* State tokens (sync-token, lock-token) and Etags */ - if (!eval_if(hdr0, etag, lock_token, &locked)) - return HTTP_PRECOND_FAILED; - } - - if (locked) { - /* Correct lock-token was not provided in If header */ - return HTTP_LOCKED; - } - - /* Evaluate other precondition headers per Section 5 of HTTPbis, Part 4 */ - - /* Step 1 */ - if ((hdr = spool_getheader(hdrcache, "If-Match"))) { - if (!etag_match(hdr, etag)) return HTTP_PRECOND_FAILED; - - /* Continue to step 3 */ - } - - /* Step 2 */ - else if ((hdr = spool_getheader(hdrcache, "If-Unmodified-Since"))) { - if (time_from_rfc822(hdr0, &since)) - return HTTP_BAD_REQUEST; - - if (since && (lastmod > since)) return HTTP_PRECOND_FAILED; - - /* Continue to step 3 */ - } - - /* Step 3 */ - if ((hdr = spool_getheader(hdrcache, "If-None-Match"))) { - if (etag_match(hdr, etag)) { - if (txn->meth == METH_GET || txn->meth == METH_HEAD) - return HTTP_NOT_MODIFIED; - else - return HTTP_PRECOND_FAILED; - } - - /* Continue to step 5 */ - } - - /* Step 4 */ - else if ((txn->meth == METH_GET || txn->meth == METH_HEAD) && - (hdr = spool_getheader(hdrcache, "If-Modified-Since"))) { - if (time_from_rfc822(hdr0, &since)) - return HTTP_BAD_REQUEST; - - if (lastmod <= since) return HTTP_NOT_MODIFIED; - - /* Continue to step 5 */ - } - - /* Step 5 */ - if (txn->flags.ranges && /* Only if we support Range requests */ - txn->meth == METH_GET && (hdr = spool_getheader(hdrcache, "Range"))) { - - if ((hdr = spool_getheader(hdrcache, "If-Range"))) { - time_from_rfc822(hdr0, &since); /* error OK here, could be an etag */ - } - - /* Only process Range if If-Range isn't present or validator matches */ - if (!hdr || (since && (lastmod <= since)) || !etagcmp(hdr0, etag)) - return HTTP_PARTIAL; - } - - /* Step 6 */ - return HTTP_OK; -} - - -const struct mimetype { - const char *ext; - const char *type; - unsigned int compressible; -} mimetypes = { - { ".css", "text/css", 1 }, - { ".htm", "text/html", 1 }, - { ".html", "text/html", 1 }, - { ".text", "text/plain", 1 }, - { ".txt", "text/plain", 1 }, - - { ".gif", "image/gif", 0 }, - { ".jpg", "image/jpeg", 0 }, - { ".jpeg", "image/jpeg", 0 }, - { ".png", "image/png", 0 }, - - { ".svg", "image/svg+xml", 1 }, - { ".tif", "image/tiff", 1 }, - { ".tiff", "image/tiff", 1 }, - - { ".bz", "application/x-bzip", 0 }, - { ".bz2", "application/x-bzip2", 0 }, - { ".gz", "application/gzip", 0 }, - { ".gzip", "application/gzip", 0 }, - { ".tgz", "application/gzip", 0 }, - { ".zip", "application/zip", 0 }, - - { ".doc", "application/msword", 1 }, - { ".js", "application/javascript", 1 }, - { ".pdf", "application/pdf", 1 }, - { ".ppt", "application/vnd.ms-powerpoint", 1 }, - { ".sh", "application/x-sh", 1 }, - { ".tar", "application/x-tar", 1 }, - { ".xls", "application/vnd.ms-excel", 1 }, - { ".xml", "application/xml", 1 }, - - { NULL, NULL, 0 } -}; - - -static int list_well_known(struct transaction_t *txn) -{ - static struct buf body = BUF_INITIALIZER; - static time_t lastmod = 0; - struct stat sbuf; - int precond; - - /* stat() imapd.conf for Last-Modified and ETag */ - stat(config_filename, &sbuf); - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%ld-%ld-%ld", - compile_time, sbuf.st_mtime, sbuf.st_size); - sbuf.st_mtime = MAX(compile_time, sbuf.st_mtime); - - /* Check any preconditions, including range request */ - txn->flags.ranges = 1; - precond = check_precond(txn, NULL, buf_cstring(&txn->buf), sbuf.st_mtime); - - switch (precond) { - case HTTP_OK: - case HTTP_NOT_MODIFIED: - /* Fill in ETag, Last-Modified, and Expires */ - txn->resp_body.etag = buf_cstring(&txn->buf); - txn->resp_body.lastmod = sbuf.st_mtime; - txn->resp_body.maxage = 86400; /* 24 hrs */ - txn->flags.cc |= CC_MAXAGE; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - return precond; - } - - if (txn->resp_body.lastmod > lastmod) { - const char *proto = NULL, *host = NULL; - unsigned i, level = 0; - - /* Start HTML */ - buf_reset(&body); - buf_printf_markup(&body, level, HTML_DOCTYPE); - buf_printf_markup(&body, level++, "<html>"); - buf_printf_markup(&body, level++, "<head>"); - buf_printf_markup(&body, level, - "<title>%s</title>", "Well-Known Locations"); - buf_printf_markup(&body, --level, "</head>"); - buf_printf_markup(&body, level++, "<body>"); - buf_printf_markup(&body, level, - "<h2>%s</h2>", "Well-Known Locations"); - buf_printf_markup(&body, level++, "<ul>"); - - /* Add the list of enabled /.well-known/ URLs */ - http_proto_host(txn->req_hdrs, &proto, &host); - for (i = 0; namespacesi; i++) { - - if (namespacesi->enabled && namespacesi->well_known) { - buf_printf_markup(&body, level, - "<li><a href=\"%s://%s%s\">%s</a></li>", - proto, host, namespacesi->prefix, - namespacesi->well_known); - } - } - - /* Finish HTML */ - buf_printf_markup(&body, --level, "</ul>"); - buf_printf_markup(&body, --level, "</body>"); - buf_printf_markup(&body, --level, "</html>"); - - lastmod = txn->resp_body.lastmod; - } - - /* Output the HTML response */ - txn->resp_body.type = "text/html; charset=utf-8"; - write_body(precond, txn, buf_cstring(&body), buf_len(&body)); - - return 0; -} - - -#define WELL_KNOWN_PREFIX "/.well-known" - -/* Perform a GET/HEAD request */ -static int meth_get(struct transaction_t *txn, - void *params __attribute__((unused))) -{ - int ret = 0, r, fd = -1, precond, len; - const char *prefix, *urls, *path, *ext; - static struct buf pathbuf = BUF_INITIALIZER; - struct stat sbuf; - const char *msg_base = NULL; - unsigned long msg_size = 0; - struct resp_body_t *resp_body = &txn->resp_body; - - /* Check if this is a request for /.well-known/ listing */ - len = strlen(WELL_KNOWN_PREFIX); - if (!strncmp(txn->req_uri->path, WELL_KNOWN_PREFIX, len)) { - if (txn->req_uri->pathlen == '/') len++; - if (txn->req_uri->pathlen == '\0') return list_well_known(txn); - else return HTTP_NOT_FOUND; - } - - /* Serve up static pages */ - prefix = config_getstring(IMAPOPT_HTTPDOCROOT); - if (!prefix) return HTTP_NOT_FOUND; - - if ((urls = config_getstring(IMAPOPT_HTTPALLOWEDURLS))) { - tok_t tok = TOK_INITIALIZER(urls, " \t", TOK_TRIMLEFT|TOK_TRIMRIGHT); - char *token; - - while ((token = tok_next(&tok)) && strcmp(token, txn->req_uri->path)) - tok_fini(&tok); - - if (!token) return HTTP_NOT_FOUND; - } - - buf_setcstr(&pathbuf, prefix); - buf_appendcstr(&pathbuf, txn->req_uri->path); - path = buf_cstring(&pathbuf); - - /* See if path is a directory and look for index.html */ - if (!(r = stat(path, &sbuf)) && S_ISDIR(sbuf.st_mode)) { - buf_appendcstr(&pathbuf, "/index.html"); - path = buf_cstring(&pathbuf); - r = stat(path, &sbuf); - } - - /* See if file exists and get Content-Length & Last-Modified time */ - if (r || !S_ISREG(sbuf.st_mode)) return HTTP_NOT_FOUND; - - if (!resp_body->type) { - /* Caller hasn't specified the Content-Type */ - resp_body->type = "application/octet-stream"; - - if ((ext = strrchr(path, '.'))) { - /* Try to use filename extension to identity Content-Type */ - const struct mimetype *mtype; - - for (mtype = mimetypes; mtype->ext; mtype++) { - if (!strcasecmp(ext, mtype->ext)) { - resp_body->type = mtype->type; - if (!mtype->compressible) { - /* Never compress non-compressible resources */ - txn->resp_body.enc = CE_IDENTITY; - txn->flags.te = TE_NONE; - txn->flags.vary &= ~VARY_AE; - } - break; - } - } - } - } - - /* Generate Etag */ - assert(!buf_len(&txn->buf)); - buf_printf(&txn->buf, "%ld-%ld", (long) sbuf.st_mtime, (long) sbuf.st_size); - - /* Check any preconditions, including range request */ - txn->flags.ranges = 1; - precond = check_precond(txn, NULL, buf_cstring(&txn->buf), sbuf.st_mtime); - - switch (precond) { - case HTTP_OK: - case HTTP_PARTIAL: - case HTTP_NOT_MODIFIED: - /* Fill in ETag, Last-Modified, and Expires */ - resp_body->etag = buf_cstring(&txn->buf); - resp_body->lastmod = sbuf.st_mtime; - resp_body->maxage = 86400; /* 24 hrs */ - txn->flags.cc |= CC_MAXAGE; - if (httpd_userid) txn->flags.cc |= CC_PUBLIC; - - if (precond != HTTP_NOT_MODIFIED) break; - - default: - /* We failed a precondition - don't perform the request */ - resp_body->type = NULL; - return precond; - } - - if (txn->meth == METH_GET) { - /* Open and mmap the file */ - if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR; - map_refresh(fd, 1, &msg_base, &msg_size, sbuf.st_size, path, NULL); - } - - write_body(precond, txn, msg_base, sbuf.st_size); - - if (fd != -1) { - map_free(&msg_base, &msg_size); - close(fd); - } - - return ret; -} - - -/* Perform an OPTIONS request */ -EXPORTED int meth_options(struct transaction_t *txn, void *params) -{ - parse_path_t parse_path = (parse_path_t) params; - int r, i; - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Response doesn't have a body, so no Vary */ - txn->flags.vary = 0; - - /* Special case "*" - show all features/methods available on server */ - if (!strcmp(txn->req_uri->path, "*")) { - for (i = 0; namespacesi; i++) { - if (namespacesi->enabled) - txn->req_tgt.allow |= namespacesi->allow; - } - } - else { - if (parse_path) { - /* Parse the path */ - r = parse_path(txn->req_uri->path, &txn->req_tgt, &txn->error.desc); - if (r) return r; - } - - if (txn->flags.cors) { - const char **hdr = - spool_getheader(txn->req_hdrs, "Access-Control-Request-Method"); - - if (hdr) { - /* CORS preflight request */ - unsigned meth; - - txn->flags.cors = CORS_PREFLIGHT; - - /* Check Method against our list of known methods */ - for (meth = 0; (meth < METH_UNKNOWN) && - strcmp(http_methodsmeth.name, hdr0); meth++); - - if (meth == METH_UNKNOWN) txn->flags.cors = 0; - else { - /* Check Method against those supported by the resource */ - for (i = 0; namespacesi && - namespacesi->id != txn->req_tgt.namespace; i++); - - if (!namespacesi->methodsmeth.proc) txn->flags.cors = 0; - } - } - } - } - - response_header(HTTP_OK, txn); - return 0; -} - - -/* Perform an PROPFIND request on "/" iff we support CalDAV */ -static int meth_propfind_root(struct transaction_t *txn, - void *params __attribute__((unused))) -{ - assert(txn); - -#ifdef WITH_DAV - /* Apple iCal and Evolution both check "/" */ - if (!strcmp(txn->req_uri->path, "/") || - !strcmp(txn->req_uri->path, "/dav/")) { - /* Array of known "live" properties */ - const struct prop_entry root_props = { - - /* WebDAV ACL (RFC 3744) properties */ - { "principal-collection-set", NS_DAV, PROP_COLLECTION, - propfind_princolset, NULL, NULL }, - - /* WebDAV Current Principal (RFC 5397) properties */ - { "current-user-principal", NS_DAV, PROP_COLLECTION, - propfind_curprin, NULL, NULL }, - - { NULL, 0, 0, NULL, NULL, NULL } - }; - - struct meth_params root_params = { - .lprops = root_props - }; - - if (!httpd_userid) return HTTP_UNAUTHORIZED; - - /* Make a working copy of target path */ - strlcpy(txn->req_tgt.path, txn->req_uri->path, - sizeof(txn->req_tgt.path)); - txn->req_tgt.tail = txn->req_tgt.path + strlen(txn->req_tgt.path); - - txn->req_tgt.allow |= ALLOW_DAV; - return meth_propfind(txn, &root_params); - } -#endif - - return HTTP_NOT_ALLOWED; -} - - -/* Write cached header to buf, excluding any that might have sensitive data. */ -static void trace_cachehdr(const char *name, const char *contents, void *rock) -{ - struct buf *buf = (struct buf *) rock; - const char **hdr, *sensitive = - { "authorization", "cookie", "proxy-authorization", NULL }; - - /* Ignore private headers in our cache */ - if (name0 == ':') return; - - for (hdr = sensitive; *hdr && strcmp(name, *hdr); hdr++); - - if (!*hdr) buf_printf(buf, "%c%s: %s\r\n", - toupper(name0), name+1, contents); -} - -/* Perform an TRACE request */ -EXPORTED int meth_trace(struct transaction_t *txn, void *params) -{ - parse_path_t parse_path = (parse_path_t) params; - const char **hdr; - unsigned long max_fwd = -1; - struct buf *msg = &txn->resp_body.payload; - - /* Response should not be cached */ - txn->flags.cc |= CC_NOCACHE; - - /* Make sure method is allowed */ - if (!(txn->req_tgt.allow & ALLOW_TRACE)) return HTTP_NOT_ALLOWED; - - if ((hdr = spool_getheader(txn->req_hdrs, "Max-Forwards"))) { - max_fwd = strtoul(hdr0, NULL, 10); - } - - if (max_fwd && parse_path) { - /* Parse the path */ - int r; - - if ((r = parse_path(txn->req_uri->path, - &txn->req_tgt, &txn->error.desc))) return r; - - if (*txn->req_tgt.mboxname) { - /* Locate the mailbox */ - mbentry_t *mbentry = NULL; - - r = http_mlookup(txn->req_tgt.mboxname, &mbentry, NULL); - if (r) { - syslog(LOG_ERR, "mlookup(%s) failed: %s", - txn->req_tgt.mboxname, error_message(r)); - txn->error.desc = error_message(r); - - switch (r) { - case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN; - case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND; - default: return HTTP_SERVER_ERROR; - } - } - - if (mbentry->server) { - /* Remote mailbox */ - struct backend *be; - - be = proxy_findserver(mbentry->server, &http_protocol, proxy_userid, - &backend_cached, NULL, NULL, httpd_in); - mboxlist_entry_free(&mbentry); - if (!be) return HTTP_UNAVAILABLE; - - return http_pipe_req_resp(be, txn); - } - - mboxlist_entry_free(&mbentry); - - /* Local mailbox */ - } - } - - /* Echo the request back to the client as a message/http: - * - * - Piece the Request-line back together - * - Use all non-sensitive cached headers from client - */ - buf_reset(msg); - buf_printf(msg, "TRACE %s %s\r\n", txn->req_line.uri, txn->req_line.ver); - spool_enum_hdrcache(txn->req_hdrs, &trace_cachehdr, msg); - buf_appendcstr(msg, "\r\n"); - - txn->resp_body.type = "message/http"; - txn->resp_body.len = buf_len(msg); - - write_body(HTTP_OK, txn, buf_cstring(msg), buf_len(msg)); - - return 0; -} - -/* simple wrapper to implicity add READFB if we have the READ ACL */ -EXPORTED int httpd_myrights(struct auth_state *authstate, const char *acl) -{ - int rights = acl ? cyrus_acl_myrights(authstate, acl) : 0; - if ((rights & DACL_READ) == DACL_READ) rights |= DACL_READFB; - return rights; -}
View file
cyrus-imapd-2.5.tar.gz/imap/httpd.h
Deleted
@@ -1,492 +0,0 @@ -/* httpd.h -- Common state for HTTP/RSS/WebDAV/CalDAV/iSchedule daemon - * - * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef HTTPD_H -#define HTTPD_H - -#include <sasl/sasl.h> -#include <libxml/tree.h> -#include <libxml/uri.h> - -#ifdef HAVE_ZLIB -#include <zlib.h> -#endif /* HAVE_ZLIB */ - -#include "mailbox.h" -#include "spool.h" - -#define MAX_REQ_LINE 8000 /* minimum size per HTTPbis */ -#define MARKUP_INDENT 2 /* # spaces to indent each line of markup */ -#define GZIP_MIN_LEN 300 /* minimum length of data to gzip */ - -/* Supported HTTP version */ -#define HTTP_VERSION "HTTP/1.1" -#define HTTP_VERSION_LEN 8 - -/* Supported TLS version for Upgrade */ -#define TLS_VERSION "TLS/1.0" - -/* Supported HTML DOCTYPE */ -#define HTML_DOCTYPE \ - "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" " \ - "\"http://www.w3.org/TR/html4/loose.dtd\">" - -/* Macro to access query part of URI */ -#if LIBXML_VERSION >= 20700 -#define URI_QUERY(uri) uri->query_raw -#else -#define URI_QUERY(uri) uri->query -#endif - -/* SASL usage based on availability */ -#if defined(SASL_NEED_HTTP) && defined(SASL_HTTP_REQUEST) - #define HTTP_DIGEST_MECH "DIGEST-MD5" - #define SASL_USAGE_FLAGS (SASL_NEED_HTTP | SASL_SUCCESS_DATA) -#else - #define HTTP_DIGEST_MECH NULL /* not supported by our SASL version */ - #define SASL_USAGE_FLAGS SASL_SUCCESS_DATA -#endif /* SASL_NEED_HTTP */ - - -/* Array of HTTP methods known by our server. */ -struct known_meth_t { - const char *name; - unsigned flags; -}; -extern const struct known_meth_t http_methods; - -/* Flags for known methods*/ -enum { - METH_NOBODY = (1<<0), /* Method does not expect a body */ -}; - -/* Index into known HTTP methods - needs to stay in sync with array */ -enum { - METH_ACL = 0, - METH_COPY, - METH_DELETE, - METH_GET, - METH_HEAD, - METH_LOCK, - METH_MKCALENDAR, - METH_MKCOL, - METH_MOVE, - METH_OPTIONS, - METH_POST, - METH_PROPFIND, - METH_PROPPATCH, - METH_PUT, - METH_REPORT, - METH_TRACE, - METH_UNLOCK, - - METH_UNKNOWN, /* MUST be last */ -}; - - -/* Path namespaces */ -enum { - URL_NS_DEFAULT = 0, - URL_NS_PRINCIPAL, - URL_NS_CALENDAR, - URL_NS_ADDRESSBOOK, - URL_NS_ISCHEDULE, - URL_NS_DOMAINKEY, - URL_NS_TIMEZONE, - URL_NS_RSS, - URL_NS_DBLOOKUP -}; - -/* Bitmask of features/methods to allow, based on URL */ -enum { - ALLOW_READ = (1<<0), /* Read resources/properties */ - ALLOW_POST = (1<<1), /* Post to a URL */ - ALLOW_WRITE = (1<<2), /* Create/modify/lock resources */ - ALLOW_DELETE = (1<<3), /* Delete resources/collections */ - ALLOW_TRACE = (1<<4), /* TRACE a request */ - ALLOW_DAV = (1<<5), /* WebDAV specific methods/features */ - ALLOW_WRITECOL = (1<<6), /* Create/modify collections */ - ALLOW_CAL = (1<<7), /* CalDAV specific methods/features */ - ALLOW_CAL_AVAIL = (1<<8), /* CalDAV Availability specific features */ - ALLOW_CAL_SCHED = (1<<9), /* CalDAV Scheduling specific features */ - ALLOW_CARD = (1<<10),/* CardDAV specific methods/features */ - ALLOW_ISCHEDULE = (1<<11) /* iSchedule specific methods/features */ -}; - -struct auth_scheme_t { - unsigned idx; /* Index value of the scheme */ - const char *name; /* HTTP auth scheme name */ - const char *saslmech; /* Corresponding SASL mech name */ - unsigned flags; /* Bitmask of requirements/features */ - /* Optional function to send success data */ - void (*send_success)(const char *name, const char *data); - /* Optional function to recv success data */ - const char *(*recv_success)(hdrcache_t hdrs); -}; - -/* Index into available schemes */ -enum { - AUTH_BASIC = 0, - AUTH_DIGEST, - AUTH_SPNEGO, - AUTH_NTLM -}; - -/* Auth scheme flags */ -enum { - AUTH_NEED_PERSIST = (1<<0), /* Persistent connection required */ - AUTH_NEED_REQUEST = (1<<1), /* Request-line required */ - AUTH_SERVER_FIRST = (1<<2), /* SASL mech is server-first */ - AUTH_BASE64 = (1<<3) /* Base64 encode/decode auth data */ -}; - -/* List of HTTP auth schemes that we support */ -extern struct auth_scheme_t auth_schemes; - -extern const char *digest_recv_success(hdrcache_t hdrs); - - -/* Request-line context */ -struct request_line_t { - char bufMAX_REQ_LINE+1; /* working copy of request-line */ - char *meth; /* method */ - char *uri; /* request-target */ - char *ver; /* HTTP-version */ -}; - - -/* Request target context */ -struct request_target_t { - char pathMAX_MAILBOX_PATH+1; /* working copy of URL path */ - char *tail; /* tail of original request path */ - unsigned namespace; /* namespace of path */ - char *user; /* ptr to owner of collection (NULL = shared) */ - size_t userlen; - char *collection; /* ptr to collection name */ - size_t collen; - char *resource; /* ptr to resource name */ - size_t reslen; - unsigned flags; /* target-specific flags/meta-data */ - unsigned long allow; /* bitmask of allowed features/methods */ - int mboxtype; /* mailbox types to match on findall */ - char mboxnameMAX_MAILBOX_BUFFER+1; - const char *prefix; /* namespace prefix */ -}; - -/* Request target flags */ -enum { - TGT_SCHED_INBOX = 1, - TGT_SCHED_OUTBOX -}; - -/* Function to parse URI path and generate a mailbox name */ -typedef int (*parse_path_t)(const char *path, - struct request_target_t *tgt, const char **errstr); - -/* Auth challenge context */ -struct auth_challenge_t { - struct auth_scheme_t *scheme; /* Selected AUTH scheme */ - const char *param; /* Server challenge */ -}; - -/* Meta-data for error response */ -struct error_t { - const char *desc; /* Error description */ - unsigned precond; /* CalDAV precondition */ - const char *resource; /* Resource which lacks privileges */ - int rights; /* Privileges needed by resource */ -}; - -struct range { - unsigned long first; - unsigned long last; - struct range *next; -}; - -/* Context for reading request/response body */ -struct body_t { - unsigned char flags; /* Disposition flags */ - unsigned char framing; /* Message framing */ - unsigned char te; /* Transfer-Encoding */ - unsigned max; /* Max allowed len */ - ulong len; /* Content-Length */ - struct buf payload; /* Payload */ -}; - -/* Message Framing flags */ -enum { - FRAMING_UNKNOWN = 0, - FRAMING_LENGTH, - FRAMING_CHUNKED, - FRAMING_CLOSE -}; - - -/* Meta-data for response body (payload & representation headers) */ -struct resp_body_t { - ulong len; /* Content-Length */ - struct range *range;/* Content-Range */ - const char *fname; /* Content-Dispo */ - unsigned char enc; /* Content-Encoding */ - const char *lang; /* Content-Language */ - const char *loc; /* Content-Location */ - const u_char *md5; /* Content-MD5 */ - const char *type; /* Content-Type */ - unsigned prefs; /* Prefer */ - const char *lock; /* Lock-Token */ - const char *etag; /* ETag */ - time_t lastmod; /* Last-Modified */ - time_t maxage; /* Expires */ - const char *stag; /* Schedule-Tag */ - time_t iserial; /* iSched serial# */ - struct buf payload; /* Payload */ -}; - -/* Transaction flags */ -struct txn_flags_t { - unsigned char ver1_0; /* Request from HTTP/1.0 client */ - unsigned char conn; /* Connection opts on req/resp */ - unsigned char override; /* HTTP method override */ - unsigned char cors; /* Cross-Origin Resource Sharing */ - unsigned char mime; /* MIME-conformant response */ - unsigned char te; /* Transfer-Encoding for resp */ - unsigned char cc; /* Cache-Control directives for resp */ - unsigned char ranges; /* Accept range requests for resource */ - unsigned char vary; /* Headers on which response varied */ - unsigned char trailer; /* Headers which will be in trailer */ -}; - -/* Transaction context */ -struct transaction_t { - unsigned meth; /* Index of Method to be performed */ - struct txn_flags_t flags; /* Flags for this txn */ - struct request_line_t req_line; /* Parsed request-line */ - xmlURIPtr req_uri; /* Parsed request-target URI */ - struct request_target_t req_tgt; /* Parsed request-target path */ - hdrcache_t req_hdrs; /* Cached HTTP headers */ - struct body_t req_body; /* Buffered request body */ - struct auth_challenge_t auth_chal; /* Authentication challenge */ - const char *location; /* Location of resource */ - struct error_t error; /* Error response meta-data */ - struct resp_body_t resp_body; /* Response body meta-data */ -#ifdef HAVE_ZLIB - z_stream zstrm; /* Compression context */ - struct buf zbuf; /* Compression buffer */ -#endif - struct buf buf; /* Working buffer - currently used for: - httpd: - - telemetry of auth'd request - - error desc string - - Location hdr on redirects - - Etag for static docs - http_rss: - - Content-Type for MIME parts - - URL for feed & items - http_caldav: - - precond error resource URL - http_ischedule: - - error desc string - */ -}; - -/* Connection token flags */ -enum { - CONN_CLOSE = (1<<0), - CONN_UPGRADE = (1<<1), - CONN_KEEPALIVE = (1<<2) -}; - -/* Cross-Origin Resource Sharing flags */ -enum { - CORS_NONE = 0, - CORS_SIMPLE = 1, - CORS_PREFLIGHT = 2 -}; - -/* read_body() flags */ -enum { - BODY_RESPONSE = (1<<0), /* Response body, otherwise request */ - BODY_CONTINUE = (1<<1), /* Expect:100-continue request */ - BODY_CLOSE = (1<<1), /* Close-delimited response body */ - BODY_DECODE = (1<<2), /* Decode any Content-Encoding */ - BODY_DISCARD = (1<<3), /* Discard body (don't buffer or decode) */ - BODY_DONE = (1<<4) /* Body has been read */ -}; - -/* Transfer-Encoding flags (coding of response payload) */ -enum { - TE_NONE = 0, - TE_DEFLATE = (1<<0), /* Implies TE_CHUNKED as final coding */ - TE_GZIP = (1<<1), /* Implies TE_CHUNKED as final coding */ - TE_CHUNKED = (1<<2) /* MUST be last */ -}; - -/* Content-Encoding flags (coding of representation) */ -enum { - CE_IDENTITY = 0, - CE_DEFLATE = (1<<0), - CE_GZIP = (1<<1) -}; - -/* Cache-Control directive flags */ -enum { - CC_REVALIDATE = (1<<0), - CC_NOCACHE = (1<<1), - CC_NOSTORE = (1<<2), - CC_NOTRANSFORM = (1<<3), - CC_PUBLIC = (1<<4), - CC_PRIVATE = (1<<5), - CC_MAXAGE = (1<<6) -}; - -/* Vary header flags (headers used in selecting/producing representation) */ -enum { - VARY_ACCEPT = (1<<0), - VARY_AE = (1<<1), /* Accept-Encoding */ - VARY_BRIEF = (1<<2), - VARY_PREFER = (1<<3) -}; - -/* Trailer header flags */ -enum { - TRAILER_CMD5 = (1<<0) /* Content-MD5 */ -}; - -typedef int (*method_proc_t)(struct transaction_t *txn, void *params); -typedef int (*filter_proc_t)(struct transaction_t *txn, - const char *base, unsigned long len); - -struct method_t { - method_proc_t proc; /* Function to perform the method */ - void *params; /* Parameters to pass to the method */ -}; - -struct namespace_t { - unsigned id; /* Namespace identifier */ - unsigned enabled; /* Is this namespace enabled? */ - const char *prefix; /* Prefix of URL path denoting namespace */ - const char *well_known; /* Any /.well-known/ URI */ - unsigned need_auth; /* Do we need to auth for this namespace? */ - int mboxtype; /* what type of mailbox can be seen in this namespace? */ - unsigned long allow; /* Bitmask of allowed features/methods */ - void (*init)(struct buf *serverinfo); - void (*auth)(const char *userid); - void (*reset)(void); - void (*shutdown)(void); - struct method_t methods; /* Array of functions to perform HTTP methods. - * MUST be an entry for EACH method listed, - * and in the SAME ORDER in which they appear, - * in the http_methods array. - * If the method is not supported, - * the function pointer MUST be NULL. - */ -}; - -struct accept { - char *token; - float qual; - struct accept *next; -}; - -extern struct namespace_t namespace_default; -extern struct namespace_t namespace_principal; -extern struct namespace_t namespace_calendar; -extern struct namespace_t namespace_addressbook; -extern struct namespace_t namespace_ischedule; -extern struct namespace_t namespace_domainkey; -extern struct namespace_t namespace_timezone; -extern struct namespace_t namespace_rss; -extern struct namespace_t namespace_dblookup; - - -/* XXX These should be included in struct transaction_t */ -extern struct buf serverinfo; -extern struct backend **backend_cached; -extern struct protstream *httpd_in; -extern struct protstream *httpd_out; -extern int https; -extern int httpd_tls_done; -extern int httpd_timeout; -extern int httpd_userisadmin; -extern int httpd_userisproxyadmin; -extern char *httpd_userid, *proxy_userid; -extern char *httpd_extradomain; -extern struct auth_state *httpd_authstate; -extern struct namespace httpd_namespace; -extern struct sockaddr_storage httpd_localaddr, httpd_remoteaddr; -extern unsigned long config_httpmodules; -extern int config_httpprettytelemetry; - -extern xmlURIPtr parse_uri(unsigned meth, const char *uri, unsigned path_reqd, - const char **errstr); -extern struct accept *parse_accept(const char **hdr); -extern int is_mediatype(const char *pat, const char *type); -extern time_t calc_compile_time(const char *time, const char *date); -extern const char *http_statusline(long code); -extern char *rfc3339date_gen(char *buf, size_t len, time_t t); -extern char *httpdate_gen(char *buf, size_t len, time_t t); -extern void comma_list_hdr(const char *hdr, const char *vals, - unsigned flags, ...); -extern void response_header(long code, struct transaction_t *txn); -extern void buf_printf_markup(struct buf *buf, unsigned level, - const char *fmt, ...); -extern void error_response(long code, struct transaction_t *txn); -extern void html_response(long code, struct transaction_t *txn, xmlDocPtr html); -extern void xml_response(long code, struct transaction_t *txn, xmlDocPtr xml); -extern void write_body(long code, struct transaction_t *txn, - const char *buf, unsigned len); -extern void write_multipart_body(long code, struct transaction_t *txn, - const char *buf, unsigned len); -extern int meth_options(struct transaction_t *txn, void *params); -extern int meth_trace(struct transaction_t *txn, void *params); -extern int etagcmp(const char *hdr, const char *etag); -extern int check_precond(struct transaction_t *txn, const void *data, - const char *etag, time_t lastmod); -extern int parse_framing(hdrcache_t hdrs, struct body_t *body, - const char **errstr); -extern int read_body(struct protstream *pin, hdrcache_t hdrs, - struct body_t *body, const char **errstr); - -extern int httpd_myrights(struct auth_state *authstate, const char *acl); - -#endif /* HTTPD_H */
View file
cyrus-imapd-2.5.tar.gz/imap/idlemsg.c
Changed
@@ -164,18 +164,12 @@ EXPORTED int idle_send(const struct sockaddr_un *remote, const idle_message_t *msg) { - int flags = 0; - -#ifdef MSG_DONTWAIT - flags |= MSG_DONTWAIT; -#endif - if (idle_sock < 0) return IMAP_SERVER_UNAVAILABLE; if (sendto(idle_sock, (void *) msg, IDLE_MESSAGE_BASE_SIZE+strlen(msg->mboxname)+1, /* 1 for NULL */ - flags, (struct sockaddr *) remote, sizeof(*remote)) == -1) { + 0, (struct sockaddr *) remote, sizeof(*remote)) == -1) { return errno; }
View file
cyrus-imapd-2.5.tar.gz/imap/imap_err.et
Changed
@@ -222,22 +222,6 @@ ec IMAP_NO_UNKNOWN_CTE, "UNKNOWN-CTE Can not process the binary data" -# For conversations - -ec IMAP_CONVERSATIONS_ALREADY_OPEN, - "Conversation DB is already opened" - -ec IMAP_CONVERSATIONS_NOT_OPEN, - "Conversation DB is not opened" - -ec IMAP_ANCHOR_NOT_FOUND, - "Anchor not found" - -ec IMAP_CANCELLED, - "Command cancelled by user" - -# Used for search engine integration - # Following used for internationalization of untagged BYE response ec IMAP_BYE_LOGOUT,
View file
cyrus-imapd-2.5.tar.gz/imap/imapd.c
Changed
@@ -110,7 +110,6 @@ #include "xstrlcat.h" #include "xstrlcpy.h" #include "ptrarray.h" -#include "xstats.h" #include "imap/pushstats.h" /* SNMP interface */ @@ -137,10 +136,6 @@ referrals that are likely to change the mailbox list */ -/* global conversations database holder to avoid re-opening during - * status command or list responses */ -struct conversations_state *global_conversations = NULL; - /* all subscription commands go to the backend server containing the user's inbox */ struct backend *backend_inbox = NULL; @@ -177,13 +172,10 @@ static void *imapd_tls_comp = NULL; /* TLS compression method, if any */ static int imapd_compress_done = 0; /* have we done a successful compress? */ static const char *plaintextloginalert = NULL; -static int ignorequota = 0; -#define QUIRK_SEARCHFUZZY (1<<0) static struct id_data { struct attvaluelist *params; int did_id; - int quirks; } imapd_id; #ifdef HAVE_SSL @@ -213,6 +205,16 @@ /* current namespace */ struct namespace imapd_namespace; +static const char *monthname = { + "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" +}; + +static const int max_monthdays = { + 31, 29, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 +}; + /* track if we're idling */ static int idling = 0; @@ -301,7 +303,6 @@ { "CATENATE", 2 }, { "CONDSTORE", 2 }, { "ESEARCH", 2 }, - { "SEARCH=FUZZY", 2 }, /* RFC 6203 */ { "SORT", 2 }, { "SORT=MODSEQ", 2 }, { "SORT=DISPLAY", 2 }, @@ -313,7 +314,6 @@ { "METADATA", 2 }, { "LIST-EXTENDED", 2 }, { "LIST-STATUS", 2 }, - { "LIST-MYRIGHTS", 2 }, /* not standard */ { "WITHIN", 2 }, { "QRESYNC", 2 }, { "SCAN", 2 }, @@ -322,7 +322,6 @@ { "MOVE", 2 }, /* draft */ { "SPECIAL-USE", 2 }, { "CREATE-SPECIAL-USE", 2 }, - { "DIGEST=SHA1", 2 }, #ifdef HAVE_SSL { "URLAUTH", 2 }, @@ -336,6 +335,11 @@ { 0, 0 } }; +enum { + GETSEARCH_CHARSET = 0x01, + GETSEARCH_RETURN = 0x02, +}; + static void motd_file(void); void shut_down(int code); @@ -365,7 +369,7 @@ static void cmd_dump(char *tag, char *name, int uid_start); static void cmd_undump(char *tag, char *name); static void cmd_xfer(const char *tag, const char *name, - const char *toserver, const char *topart); + const char *toserver, const char *topart); static void cmd_rename(char *tag, char *oldname, char *newname, char *partition); static void cmd_reconstruct(const char *tag, const char *name, int recursive); static void getlistargs(char *tag, struct listargs *listargs); @@ -388,16 +392,6 @@ static void cmd_starttls(char *tag, int imaps); -static void cmd_xconvsort(char *tag, int updates); -static void cmd_xconvmultisort(char *tag); -static void cmd_xconvmeta(const char *tag); -static void cmd_xconvfetch(const char *tag); -static int do_xconvfetch(struct dlist *cidlist, - modseq_t ifchangedsince, - struct fetchargs *fetchargs); -static void cmd_xsnippets(char *tag); -static void cmd_xstats(char *tag, int c); - #ifdef HAVE_SSL static void cmd_urlfetch(char *tag); static void cmd_genurlauth(char *tag); @@ -418,14 +412,9 @@ static void cmd_setmetadata(const char* tag, char *mboxpat); static void cmd_xrunannotator(const char *tag, const char *sequence, int usinguid); -static void cmd_xwarmup(const char *tag); static void cmd_enable(char* tag); -static void cmd_xkillmy(const char *tag, const char *cmdname); -static void cmd_xforever(const char *tag); -static void cmd_xmeid(const char *tag, const char *id); - static int parsecreateargs(struct dlist **extargs); static int parse_annotate_fetch_data(const char *tag, @@ -444,18 +433,25 @@ static int getlistselopts(char *tag, struct listargs *args); static int getlistretopts(char *tag, struct listargs *args); -static int get_snippetargs(struct snippetargs **sap); -static void free_snippetargs(struct snippetargs **sap); +static int getsearchreturnopts(const char *tag, struct searchargs *searchargs); +static int getsearchprogram(const char *tag, struct searchargs *searchargs, + int *charsetp, int is_search_cmd); +static int getsearchcriteria(const char *tag, struct searchargs *searchargs, + int *charsetp, int *searchstatep); +static int getsearchdate(time_t *start, time_t *end); static int getsortcriteria(char *tag, struct sortcrit **sortcrit); +static char *sortcrit_as_string(const struct sortcrit *sortcrit); static int getdatetime(time_t *date); -static int parse_windowargs(const char *tag, struct windowargs **, int); -static void free_windowargs(struct windowargs *wa); static void appendfieldlist(struct fieldlist **l, char *section, strarray_t *fields, char *trail, void *d, size_t size); static void freefieldlist(struct fieldlist *l); void freestrlist(struct strlist *l); +static void appendsearchargs(struct searchargs *s, struct searchargs *s1, + struct searchargs *s2); +static void freesearchargs(struct searchargs *s); +static void freesortcrit(struct sortcrit *s); static int set_haschildren(char *name, int matchlen, int maycreate, int *attributes); @@ -844,13 +840,11 @@ /* setup for mailbox event notifications */ mboxevent_init(); - search_attr_init(); - /* create connection to the SNMP listener, if available. */ snmp_connect(); /* ignore return code */ snmp_set_str(SERVER_NAME_VERSION,cyrus_version()); - while ((opt = getopt(argc, argv, "Np:sq")) != EOF) { + while ((opt = getopt(argc, argv, "sp:N")) != EOF) { switch (opt) { case 's': /* imaps (do starttls right away) */ imaps = 1; @@ -867,9 +861,6 @@ * you know what you're doing! */ nosaslpasswdcheck = 1; break; - case 'q': /* don't enforce quotas */ - ignorequota = 1; - break; default: break; } @@ -1153,7 +1144,6 @@ } syslog(LOG_ERR, "Fatal error: %s", s); - abort(); shut_down(code); } @@ -1202,9 +1192,6 @@ } prot_printf(imapd_out, " server ready\r\n"); - /* clear cancelled flag if present before the next command */ - cmd_cancelled(); - motd_file(); /* Get command timer logging paramater. This string @@ -2148,33 +2135,7 @@ break; case 'X': - if (!strcmp(cmd.s, "Xconvfetch")) { - cmd_xconvfetch(tag.s); - -// snmp_increment(XCONVFETCH_COUNT, 1); - } - else if (!strcmp(cmd.s, "Xconvmultisort")) { - if (c != ' ') goto missingargs; - if (!imapd_index && !backend_current) goto nomailbox; - cmd_xconvmultisort(tag.s); - -// snmp_increment(XCONVMULTISORT_COUNT, 1); - } - else if (!strcmp(cmd.s, "Xconvsort")) { - if (c != ' ') goto missingargs; - if (!imapd_index && !backend_current) goto nomailbox; - cmd_xconvsort(tag.s, 0); - -// snmp_increment(XCONVSORT_COUNT, 1); - } - else if (!strcmp(cmd.s, "Xconvupdates")) { - if (c != ' ') goto missingargs; - if (!imapd_index && !backend_current) goto nomailbox; - cmd_xconvsort(tag.s, 1); - -// snmp_increment(XCONVUPDATES_COUNT, 1); - } - else if (!strcmp(cmd.s, "Xfer")) { + if (!strcmp(cmd.s, "Xfer")) { int havepartition = 0; /* Mailbox */ @@ -2199,9 +2160,6 @@ (havepartition ? arg3.s : NULL)); /* snmp_increment(XFER_COUNT, 1);*/ } - else if (!strcmp(cmd.s, "Xconvmeta")) { - cmd_xconvmeta(tag.s); - } else if (!strcmp(cmd.s, "Xlist")) { struct listargs listargs; @@ -2231,44 +2189,6 @@ cmd_xrunannotator(tag.s, arg1.s, usinguid); // snmp_increment(XRUNANNOTATOR_COUNT, 1); } - else if (!strcmp(cmd.s, "Xsnippets")) { - if (c != ' ') goto missingargs; - if (!imapd_index && !backend_current) goto nomailbox; - cmd_xsnippets(tag.s); - -// snmp_increment(XSNIPPETS_COUNT, 1); - } - else if (!strcmp(cmd.s, "Xstats")) { - cmd_xstats(tag.s, c); - } - else if (!strcmp(cmd.s, "Xwarmup")) { - /* XWARMUP doesn't need a mailbox to be selected */ - if (c != ' ') goto missingargs; - cmd_xwarmup(tag.s); -// snmp_increment(XWARMUP_COUNT, 1); - } - else if (!strcmp(cmd.s, "Xkillmy")) { - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); - if (c == EOF) goto missingargs; - if (c == '\r') c = prot_getc(imapd_in); - if (c != '\n') goto extraargs; - cmd_xkillmy(tag.s, arg1.s); - } - else if (!strcmp(cmd.s, "Xforever")) { - if (c == '\r') c = prot_getc(imapd_in); - if (c != '\n') goto extraargs; - cmd_xforever(tag.s); - } - else if (!strcmp(cmd.s, "Xmeid")) { - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); - if (c == EOF) goto missingargs; - if (c == '\r') c = prot_getc(imapd_in); - if (c != '\n') goto extraargs; - cmd_xmeid(tag.s, arg1.s); - } - else goto badcmd; break; @@ -2774,7 +2694,6 @@ static void cmd_id(char *tag) { int c = EOF, npair = 0; - int is_ios = 0; static struct buf arg, field; /* check if we've already had an ID in non-authenticated state */ @@ -2848,13 +2767,6 @@ return; } - if (!strcmp(field.s, "os") && !strcmp(arg.s, "iOS")) { - is_ios = 1; - } - if (is_ios && !strcmp(field.s, "os-version") && arg.s0 == '7') { - imapd_id.quirks |= QUIRK_SEARCHFUZZY; - } - /* ok, we're happy enough */ appendattvalue(&imapd_id.params, field.s, &arg); } @@ -2884,7 +2796,6 @@ for (pptr = imapd_id.params; pptr; pptr = pptr->next) { const char *val = buf_cstring(&pptr->value); - /* should we check for and format literals here ??? */ buf_printf(&logbuf, " \"%s\" ", pptr->attrib); if (!val || !strcmp(val, "NIL")) @@ -3112,9 +3023,6 @@ if (!(flags & CAPA_POSTAUTH)) return; - if (config_getswitch(IMAPOPT_CONVERSATIONS)) - prot_printf(imapd_out, " XCONVERSATIONS"); - #ifdef HAVE_ZLIB if (!imapd_compress_done && !imapd_tls_comp) { prot_printf(imapd_out, " COMPRESS=DEFLATE"); @@ -3537,7 +3445,7 @@ /* local mailbox */ if (!r) { qdiffsQUOTA_MESSAGE = 1; - r = append_check(mailboxname, imapd_authstate, ACL_INSERT, ignorequota ? NULL : qdiffs); + r = append_check(mailboxname, imapd_authstate, ACL_INSERT, qdiffs); } if (r) { eatline(imapd_in, ' '); @@ -3691,7 +3599,7 @@ qdiffsQUOTA_MESSAGE = stages.count; r = append_setup(&appendstate, mailboxname, imapd_userid, imapd_authstate, ACL_INSERT, - ignorequota ? NULL : qdiffs, &imapd_namespace, + qdiffs, &imapd_namespace, (imapd_userisadmin || imapd_userisproxyadmin), EVENT_MESSAGE_APPEND); } @@ -4450,21 +4358,6 @@ else goto badatt; break; - case 'C': - if (!strcmp(fetchatt.s, "CID") && - config_getswitch(IMAPOPT_CONVERSATIONS)) { - fa->fetchitems |= FETCH_CID; - } - else goto badatt; - break; - - case 'D': - if (!strcmp(fetchatt.s, "DIGEST.SHA1")) { - fa->fetchitems |= FETCH_GUID; - } - else goto badatt; - break; - case 'E': if (!strcmp(fetchatt.s, "ENVELOPE")) { fa->fetchitems |= FETCH_ENVELOPE; @@ -4482,9 +4375,6 @@ else if (!strcmp(fetchatt.s, "FLAGS")) { fa->fetchitems |= FETCH_FLAGS; } - else if (!strcmp(fetchatt.s, "FOLDER")) { - fa->fetchitems |= FETCH_FOLDER; - } else goto badatt; break; @@ -4501,7 +4391,6 @@ } else goto badatt; break; - case 'R': if (!strcmp(fetchatt.s, "RFC822")) { fa->fetchitems |= FETCH_RFC822|FETCH_SETSEEN; @@ -4518,12 +4407,6 @@ else if (!strcmp(fetchatt.s, "RFC822.TEXT")) { fa->fetchitems |= FETCH_TEXT|FETCH_SETSEEN; } - else if (!strcmp(fetchatt.s, "RFC822.SHA1")) { - fa->fetchitems |= FETCH_SHA1; - } - else if (!strcmp(fetchatt.s, "RFC822.FILESIZE")) { - fa->fetchitems |= FETCH_FILESIZE; - } else if (!strcmp(fetchatt.s, "RFC822.TEXT.PEEK")) { fa->fetchitems |= FETCH_TEXT; } @@ -4584,9 +4467,6 @@ if (!strcmp(fetchatt.s, "UID")) { fa->fetchitems |= FETCH_UID; } - else if (!strcmp(fetchatt.s, "UIDVALIDITY")) { - fa->fetchitems |= FETCH_UIDVALIDITY; - } else goto badatt; break; @@ -4685,17 +4565,14 @@ if (fa->fetchitems & FETCH_MODSEQ) { if (!(imapd_client_capa & CAPA_CONDSTORE)) { imapd_client_capa |= CAPA_CONDSTORE; - if (imapd_index) - prot_printf(imapd_out, "* OK HIGHESTMODSEQ " MODSEQ_FMT " \r\n", + prot_printf(imapd_out, "* OK HIGHESTMODSEQ " MODSEQ_FMT " \r\n", index_highestmodseq(imapd_index)); } } - if (fa->fetchitems & (FETCH_ANNOTATION|FETCH_FOLDER)) { + if (fa->fetchitems & FETCH_ANNOTATION) { fa->namespace = &imapd_namespace; fa->userid = imapd_userid; - } - if (fa->fetchitems & FETCH_ANNOTATION) { fa->isadmin = imapd_userisadmin || imapd_userisproxyadmin; fa->authstate = imapd_authstate; } @@ -4778,396 +4655,6 @@ fetchargs_fini(&fetchargs); } -static void do_one_xconvmeta(struct conversations_state *state, - conversation_id_t cid, - conversation_t *conv, - struct dlist *itemlist) -{ - struct dlist *item = dlist_newpklist(NULL, ""); - struct dlist *fl; - - assert(conv); - assert(itemlist); - - for (fl = itemlist->head; fl; fl = fl->next) { - const char *key = dlist_cstring(fl); - - /* xxx - parse to a fetchitems? */ - if (!strcasecmp(key, "MODSEQ")) - dlist_setnum64(item, "MODSEQ", conv->modseq); - else if (!strcasecmp(key, "EXISTS")) - dlist_setnum32(item, "EXISTS", conv->exists); - else if (!strcasecmp(key, "UNSEEN")) - dlist_setnum32(item, "UNSEEN", conv->unseen); - else if (!strcasecmp(key, "SIZE")) - dlist_setnum32(item, "SIZE", conv->size); - else if (!strcasecmp(key, "COUNT")) { - struct dlist *flist = dlist_newlist(item, "COUNT"); - fl = fl->next; - if (dlist_isatomlist(fl)) { - struct dlist *tmp; - for (tmp = fl->head; tmp; tmp = tmp->next) { - const char *lookup = dlist_cstring(tmp); - int i = strarray_find_case(state->counted_flags, lookup, 0); - if (i >= 0) { - dlist_setflag(flist, "FLAG", lookup); - dlist_setnum32(flist, "COUNT", conv->countsi); - } - } - } - } - else if (!strcasecmp(key, "SENDERS")) { - conv_sender_t *sender; - struct dlist *slist = dlist_newlist(item, "SENDERS"); - for (sender = conv->senders; sender; sender = sender->next) { - struct dlist *sli = dlist_newlist(slist, ""); - dlist_setatom(sli, "NAME", sender->name); - dlist_setatom(sli, "ROUTE", sender->route); - dlist_setatom(sli, "MAILBOX", sender->mailbox); - dlist_setatom(sli, "DOMAIN", sender->domain); - } - } - /* XXX - maybe rename FOLDERCOUNTS or something? */ - else if (!strcasecmp(key, "FOLDEREXISTS")) { - struct dlist *flist = dlist_newlist(item, "FOLDEREXISTS"); - conv_folder_t *folder; - fl = fl->next; - if (dlist_isatomlist(fl)) { - struct dlist *tmp; - for (tmp = fl->head; tmp; tmp = tmp->next) { - const char *fname = dlist_cstring(tmp); - char intnameMAX_MAILBOX_NAME; - /* ugly city */ - if ((*imapd_namespace.mboxname_tointernal)(&imapd_namespace, fname, - imapd_userid, intname)) - continue; - folder = conversation_find_folder(state, conv, intname); - dlist_setatom(flist, "MBOXNAME", fname); - /* ok if it's not there */ - dlist_setnum32(flist, "EXISTS", folder ? folder->exists : 0); - } - } - } - else if (!strcasecmp(key, "FOLDERUNSEEN")) { - struct dlist *flist = dlist_newlist(item, "FOLDERUNSEEN"); - conv_folder_t *folder; - fl = fl->next; - if (dlist_isatomlist(fl)) { - struct dlist *tmp; - for (tmp = fl->head; tmp; tmp = tmp->next) { - const char *fname = dlist_cstring(tmp); - char intnameMAX_MAILBOX_NAME; - /* ugly city */ - if ((*imapd_namespace.mboxname_tointernal)(&imapd_namespace, fname, - imapd_userid, intname)) - continue; - folder = conversation_find_folder(state, conv, intname); - dlist_setatom(flist, "MBOXNAME", fname); - /* ok if it's not there */ - dlist_setnum32(flist, "UNSEEN", folder ? folder->unseen : 0); - } - } - } - else { - dlist_setatom(item, key, NULL); /* add a NIL response */ - } - } - - prot_printf(imapd_out, "* XCONVMETA %s ", conversation_id_encode(cid)); - dlist_print(item, 0, imapd_out); - prot_printf(imapd_out, "\r\n"); - - dlist_free(&item); -} - -static void do_xconvmeta(const char *tag, - struct conversations_state *state, - struct dlist *cidlist, - struct dlist *itemlist) -{ - conversation_id_t cid; - struct dlist *dl; - int r; - - for (dl = cidlist->head; dl; dl = dl->next) { - const char *cidstr = dlist_cstring(dl); - conversation_t *conv = NULL; - - if (!conversation_id_decode(&cid, cidstr) || !cid) { - prot_printf(imapd_out, "%s BAD Invalid CID %s\r\n", tag, cidstr); - return; - } - - r = conversation_load(state, cid, &conv); - if (r) { - prot_printf(imapd_out, "%s BAD Failed to read %s\r\n", tag, cidstr); - conversation_free(conv); - return; - } - - if (conv && conv->exists) - do_one_xconvmeta(state, cid, conv, itemlist); - - conversation_free(conv); - } - - prot_printf(imapd_out, "%s OK Completed\r\n", tag); -} - -/* - * Parse and perform a XCONVMETA command. - */ -void cmd_xconvmeta(const char *tag) -{ - int r; - char c = ' '; - struct conversations_state *state = NULL; - struct dlist *cidlist = NULL; - struct dlist *itemlist = NULL; - - if (backend_current) { - /* remote mailbox */ - prot_printf(backend_current->out, "%s XCONVMETA ", tag); - if (!pipe_command(backend_current, 65536)) { - pipe_including_tag(backend_current, tag, 0); - } - return; - } - - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) { - prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag); - eatline(imapd_in, c); - goto done; - } - - c = dlist_parse_asatomlist(&cidlist, 0, imapd_in); - if (c != ' ') { - prot_printf(imapd_out, "%s BAD Failed to parse CID list\r\n", tag); - eatline(imapd_in, c); - goto done; - } - - c = dlist_parse_asatomlist(&itemlist, 0, imapd_in); - if (c == '\r') c = prot_getc(imapd_in); - if (c != '\n') { - prot_printf(imapd_out, "%s BAD Failed to parse item list\r\n", tag); - eatline(imapd_in, c); - goto done; - } - - r = conversations_open_user(imapd_userid, &state); - if (r) { - prot_printf(imapd_out, "%s BAD failed to open db: %s\r\n", - tag, error_message(r)); - goto done; - } - - do_xconvmeta(tag, state, cidlist, itemlist); - - done: - - dlist_free(&itemlist); - dlist_free(&cidlist); - conversations_commit(&state); -} - -/* - * Parse and perform a XCONVFETCH command. - */ -void cmd_xconvfetch(const char *tag) -{ - int c = ' '; - struct fetchargs fetchargs; - int r; - clock_t start = clock(); - modseq_t ifchangedsince = 0; - char mytime100; - struct dlist *cidlist = NULL; - struct dlist *item; - - if (backend_current) { - /* remote mailbox */ - prot_printf(backend_current->out, "%s XCONVFETCH ", tag); - if (!pipe_command(backend_current, 65536)) { - pipe_including_tag(backend_current, tag, 0); - } - return; - } - - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) { - prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag); - eatline(imapd_in, c); - return; - } - - /* local mailbox */ - memset(&fetchargs, 0, sizeof(struct fetchargs)); - - c = dlist_parse_asatomlist(&cidlist, 0, imapd_in); - if (c != ' ') - goto syntax_error; - - /* check CIDs */ - for (item = cidlist->head; item; item = item->next) { - if (!dlist_ishex64(item)) { - prot_printf(imapd_out, "%s BAD Invalid CID\r\n", tag); - eatline(imapd_in, c); - goto freeargs; - } - } - - c = getmodseq(imapd_in, &ifchangedsince); - if (c != ' ') - goto syntax_error; - - r = parse_fetch_args(tag, "Xconvfetch", 0, &fetchargs); - if (r) - goto freeargs; - fetchargs.fetchitems |= (FETCH_UIDVALIDITY|FETCH_FOLDER); - fetchargs.namespace = &imapd_namespace; - fetchargs.userid = imapd_userid; - - r = do_xconvfetch(cidlist, ifchangedsince, &fetchargs); - - snprintf(mytime, sizeof(mytime), "%2.3f", - (clock() - start) / (double) CLOCKS_PER_SEC); - - if (r) { - prot_printf(imapd_out, "%s NO %s (%s sec)\r\n", tag, - error_message(r), mytime); - } else { - prot_printf(imapd_out, "%s OK Completed (%s sec)\r\n", - tag, mytime); - } - -freeargs: - dlist_free(&cidlist); - fetchargs_fini(&fetchargs); - return; - -syntax_error: - prot_printf(imapd_out, "%s BAD Syntax error\r\n", tag); - eatline(imapd_in, c); - dlist_free(&cidlist); - fetchargs_fini(&fetchargs); -} - -static int xconvfetch_lookup(struct conversations_state *statep, - conversation_id_t cid, - modseq_t ifchangedsince, - hash_table *wanted_cids, - strarray_t *folder_list) -{ - const char *key = conversation_id_encode(cid); - conversation_t *conv = NULL; - conv_folder_t *folder; - int r; - - r = conversation_load(statep, cid, &conv); - if (r) return r; - - if (!conv) - goto out; - - if (!conv->exists) - goto out; - - /* output the metadata for this conversation */ - { - struct dlist *dl = dlist_newlist(NULL, ""); - dlist_setatom(dl, "", "MODSEQ"); - do_one_xconvmeta(statep, cid, conv, dl); - dlist_free(&dl); - } - - if (ifchangedsince >= conv->modseq) - goto out; - - hash_insert(key, (void *)1, wanted_cids); - - for (folder = conv->folders; folder; folder = folder->next) { - /* no contents */ - if (!folder->exists) - continue; - - /* finally, something worth looking at */ - strarray_add(folder_list, strarray_nth(statep->folder_names, folder->number)); - } - -out: - conversation_free(conv); - return 0; -} - -static int do_xconvfetch(struct dlist *cidlist, - modseq_t ifchangedsince, - struct fetchargs *fetchargs) -{ - struct conversations_state *state = NULL; - int r = 0; - struct index_state *index_state = NULL; - struct dlist *dl; - hash_table wanted_cids = HASH_TABLE_INITIALIZER; - strarray_t folder_list = STRARRAY_INITIALIZER; - struct index_init init; - int i; - - r = conversations_open_user(imapd_userid, &state); - if (r) goto out; - - construct_hash_table(&wanted_cids, 1024, 0); - - for (dl = cidlist->head; dl; dl = dl->next) { - r = xconvfetch_lookup(state, dlist_num(dl), ifchangedsince, - &wanted_cids, &folder_list); - if (r) goto out; - } - - /* unchanged, woot */ - if (!folder_list.count) - goto out; - - fetchargs->cidhash = &wanted_cids; - - memset(&init, 0, sizeof(struct index_init)); - init.userid = imapd_userid; - init.authstate = imapd_authstate; - init.out = imapd_out; - - for (i = 0; i < folder_list.count; i++) { - const char *mboxname = folder_list.datai; - - r = index_open(mboxname, &init, &index_state); - if (r == IMAP_MAILBOX_NONEXISTENT) - continue; - if (r) - goto out; - - index_checkflags(index_state, 0, 0); - - /* make sure \Deleted messages are expunged. Will also lock the - * mailbox state and read any new information */ - r = index_expunge(index_state, NULL, 1); - - if (!r) - index_fetchresponses(index_state, NULL, /*usinguid*/1, - fetchargs, NULL); - - index_close(&index_state); - - if (r) goto out; - } - - r = 0; - -out: - index_close(&index_state); - conversations_commit(&state); - free_hash_table(&wanted_cids, NULL); - strarray_fini(&folder_list); - return r; -} - #undef PARSE_PARTIAL /* cleanup */ /* @@ -5406,6 +4893,7 @@ static void cmd_search(char *tag, int usinguid) { int c; + int charset = 0; struct searchargs *searchargs; clock_t start = clock(); char mytime100; @@ -5423,15 +4911,9 @@ } /* local mailbox */ - searchargs = new_searchargs(tag, GETSEARCH_CHARSET_KEYWORD|GETSEARCH_RETURN, - &imapd_namespace, imapd_userid, imapd_authstate, - imapd_userisadmin || imapd_userisproxyadmin); - - /* special case quirk for iPhones */ - if (imapd_id.quirks & QUIRK_SEARCHFUZZY) - searchargs->fuzzy_depth++; - - c = get_search_program(imapd_in, imapd_out, searchargs); + searchargs = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); + searchargs->tag = tag; + c = getsearchprogram(tag, searchargs, &charset, 1); if (c == EOF) { eatline(imapd_in, ' '); freesearchargs(searchargs); @@ -5446,7 +4928,7 @@ return; } - if (searchargs->charset == -1) { + if (charset == -1) { prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(IMAP_UNRECOGNIZED_CHARSET)); } @@ -5468,6 +4950,8 @@ { int c; struct sortcrit *sortcrit = NULL; + static struct buf arg; + int charset = 0; struct searchargs *searchargs = NULL; clock_t start = clock(); char mytime100; @@ -5488,389 +4972,60 @@ c = getsortcriteria(tag, &sortcrit); if (c == EOF) goto error; - searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST, - &imapd_namespace, imapd_userid, imapd_authstate, - imapd_userisadmin || imapd_userisproxyadmin); - if (imapd_id.quirks & QUIRK_SEARCHFUZZY) - searchargs->fuzzy_depth++; - c = get_search_program(imapd_in, imapd_out, searchargs); - if (c == EOF) goto error; - - if (c == '\r') c = prot_getc(imapd_in); - if (c != '\n') { - prot_printf(imapd_out, - "%s BAD Unexpected extra arguments to Sort\r\n", tag); - goto error; - } - - n = index_sort(imapd_index, sortcrit, searchargs, usinguid); - snprintf(mytime, sizeof(mytime), "%2.3f", - (clock() - start) / (double) CLOCKS_PER_SEC); - if (CONFIG_TIMING_VERBOSE) { - char *s = sortcrit_as_string(sortcrit); - syslog(LOG_DEBUG, "SORT (%s) processing time: %d msg in %s sec", - s, n, mytime); - free(s); - } - prot_printf(imapd_out, "%s OK %s (%d msgs in %s secs)\r\n", tag, - error_message(IMAP_OK_COMPLETED), n, mytime); - - freesortcrit(sortcrit); - freesearchargs(searchargs); - return; - -error: - eatline(imapd_in, (c == EOF ? ' ' : c)); - freesortcrit(sortcrit); - freesearchargs(searchargs); -} - -/* - * Perform a XCONVSORT or XCONVUPDATES command - */ -void cmd_xconvsort(char *tag, int updates) -{ - int c; - struct sortcrit *sortcrit = NULL; - struct searchargs *searchargs = NULL; - struct windowargs *windowargs = NULL; - struct index_init init; - struct index_state *oldstate = NULL; - struct conversations_state *cstate = NULL; - clock_t start = clock(); - char mytime100; - int r; - - if (backend_current) { - /* remote mailbox */ - const char *cmd = "Xconvsort"; - - prot_printf(backend_current->out, "%s %s ", tag, cmd); - if (!pipe_command(backend_current, 65536)) { - pipe_including_tag(backend_current, tag, 0); - } - return; - } - assert(imapd_index); - - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) { - prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag); - eatline(imapd_in, ' '); - return; - } - - c = getsortcriteria(tag, &sortcrit); - if (c == EOF) goto error; - + /* get charset */ if (c != ' ') { - prot_printf(imapd_out, "%s BAD Missing window args in XConvSort\r\n", + prot_printf(imapd_out, "%s BAD Missing charset in Sort\r\n", tag); goto error; } - c = parse_windowargs(tag, &windowargs, updates); - if (c != ' ') - goto error; - - /* open the conversations state first - we don't care if it fails, - * because that probably just means it's already open */ - conversations_open_mbox(index_mboxname(imapd_index), &cstate); - - if (updates) { - /* in XCONVUPDATES, need to force a re-read from scratch into - * a new index, because we ask for deleted messages */ - - oldstate = imapd_index; - imapd_index = NULL; - - memset(&init, 0, sizeof(struct index_init)); - init.userid = imapd_userid; - init.authstate = imapd_authstate; - init.out = imapd_out; - init.want_expunged = 1; - - r = index_open(index_mboxname(oldstate), &init, &imapd_index); - if (r) { - prot_printf(imapd_out, "%s NO %s\r\n", tag, - error_message(r)); - goto error; - } - - index_checkflags(imapd_index, 0, 0); - } - - /* need index loaded to even parse searchargs! */ - searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST, - &imapd_namespace, imapd_userid, imapd_authstate, - imapd_userisadmin || imapd_userisproxyadmin); - c = get_search_program(imapd_in, imapd_out, searchargs); - if (c == EOF) goto error; - - if (c == '\r') c = prot_getc(imapd_in); - if (c != '\n') { - prot_printf(imapd_out, - "%s BAD Unexpected extra arguments to Xconvsort\r\n", tag); + c = getword(imapd_in, &arg); + if (c != ' ') { + prot_printf(imapd_out, "%s BAD Missing search criteria in Sort\r\n", + tag); goto error; } + lcase(arg.s); + charset = charset_lookupname(arg.s); - if (updates) - r = index_convupdates(imapd_index, sortcrit, searchargs, windowargs); - else - r = index_convsort(imapd_index, sortcrit, searchargs, windowargs); - - if (oldstate) { - index_close(&imapd_index); - imapd_index = oldstate; - } - - if (r < 0) { + if (charset == -1) { prot_printf(imapd_out, "%s NO %s\r\n", tag, - error_message(r)); - goto error; - } - - snprintf(mytime, sizeof(mytime), "%2.3f", - (clock() - start) / (double) CLOCKS_PER_SEC); - if (CONFIG_TIMING_VERBOSE) { - char *s = sortcrit_as_string(sortcrit); - syslog(LOG_DEBUG, "XCONVSORT (%s) processing time %s sec", - s, mytime); - free(s); - } - prot_printf(imapd_out, "%s OK %s (in %s secs)\r\n", tag, - error_message(IMAP_OK_COMPLETED), mytime); - -out: - if (cstate) conversations_commit(&cstate); - freesortcrit(sortcrit); - freesearchargs(searchargs); - free_windowargs(windowargs); - return; - -error: - if (cstate) conversations_commit(&cstate); - if (oldstate) { - if (imapd_index) index_close(&imapd_index); - imapd_index = oldstate; - } - eatline(imapd_in, (c == EOF ? ' ' : c)); - goto out; -} - -/* - * Perform a XCONVMULTISORT command. This is like XCONVSORT but returns - * search results from multiple folders. It still requires a selected - * mailbox, for two reasons: - * - * a) it's a useful shorthand for choosing what the current - * conversations scope is, and - * - * b) the code to parse a search program currently relies on a selected - * mailbox. - * - * Unlike ESEARCH it doesn't take folder names for scope, instead the - * search scope is implicitly the current conversation scope. This is - * implemented more or less by accident because both the Sphinx index - * and the conversations database are hardcoded to be per-user. - */ -static void cmd_xconvmultisort(char *tag) -{ - int c; - struct sortcrit *sortcrit = NULL; - struct searchargs *searchargs = NULL; - struct windowargs *windowargs = NULL; - struct conversations_state *cstate = NULL; - clock_t start = clock(); - char mytime100; - int r; - - if (backend_current) { - /* remote mailbox */ - const char *cmd = "Xconvmultisort"; - - prot_printf(backend_current->out, "%s %s ", tag, cmd); - if (!pipe_command(backend_current, 65536)) { - pipe_including_tag(backend_current, tag, 0); - } - return; - } - assert(imapd_index); - - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) { - prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag); - eatline(imapd_in, ' '); - return; - } - - c = getsortcriteria(tag, &sortcrit); - if (c == EOF) goto error; - - if (c != ' ') { - prot_printf(imapd_out, "%s BAD Missing window args in XConvMultiSort\r\n", - tag); + error_message(IMAP_UNRECOGNIZED_CHARSET)); goto error; } - c = parse_windowargs(tag, &windowargs, /*updates*/0); - if (c != ' ') - goto error; + searchargs = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); - /* open the conversations state first - we don't care if it fails, - * because that probably just means it's already open */ - conversations_open_mbox(index_mboxname(imapd_index), &cstate); - - /* need index loaded to even parse searchargs! */ - searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST, - &imapd_namespace, imapd_userid, imapd_authstate, - imapd_userisadmin || imapd_userisproxyadmin); - c = get_search_program(imapd_in, imapd_out, searchargs); + c = getsearchprogram(tag, searchargs, &charset, 0); if (c == EOF) goto error; if (c == '\r') c = prot_getc(imapd_in); if (c != '\n') { prot_printf(imapd_out, - "%s BAD Unexpected extra arguments to XconvMultiSort\r\n", tag); - goto error; - } - - r = index_convmultisort(imapd_index, sortcrit, searchargs, windowargs); - - if (r < 0) { - prot_printf(imapd_out, "%s NO %s\r\n", tag, - error_message(r)); + "%s BAD Unexpected extra arguments to Sort\r\n", tag); goto error; } + n = index_sort(imapd_index, sortcrit, searchargs, usinguid); snprintf(mytime, sizeof(mytime), "%2.3f", (clock() - start) / (double) CLOCKS_PER_SEC); if (CONFIG_TIMING_VERBOSE) { char *s = sortcrit_as_string(sortcrit); - syslog(LOG_DEBUG, "XCONVMULTISORT (%s) processing time %s sec", - s, mytime); + syslog(LOG_DEBUG, "SORT (%s) processing time: %d msg in %s sec", + s, n, mytime); free(s); } - prot_printf(imapd_out, "%s OK %s (in %s secs)\r\n", tag, - error_message(IMAP_OK_COMPLETED), mytime); + prot_printf(imapd_out, "%s OK %s (%d msgs in %s secs)\r\n", tag, + error_message(IMAP_OK_COMPLETED), n, mytime); -out: - if (cstate) conversations_commit(&cstate); freesortcrit(sortcrit); freesearchargs(searchargs); - free_windowargs(windowargs); return; error: - if (cstate) conversations_commit(&cstate); eatline(imapd_in, (c == EOF ? ' ' : c)); - goto out; -} - -static void cmd_xsnippets(char *tag) -{ - int c; - struct searchargs *searchargs = NULL; - struct snippetargs *snippetargs = NULL; - clock_t start = clock(); - char mytime100; - int r; - - if (backend_current) { - /* remote mailbox */ - const char *cmd = "Xsnippets"; - - prot_printf(backend_current->out, "%s %s ", tag, cmd); - if (!pipe_command(backend_current, 65536)) { - pipe_including_tag(backend_current, tag, 0); - } - return; - } - assert(imapd_index); - - c = get_snippetargs(&snippetargs); - if (c == EOF) { - prot_printf(imapd_out, "%s BAD Syntax error in snippet arguments\r\n", tag); - goto error; - } - if (c != ' ') { - prot_printf(imapd_out, - "%s BAD Unexpected arguments in Xsnippets\r\n", tag); - goto error; - } - - /* need index loaded to even parse searchargs! */ - searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST, - &imapd_namespace, imapd_userid, imapd_authstate, - imapd_userisadmin || imapd_userisproxyadmin); - c = get_search_program(imapd_in, imapd_out, searchargs); - if (c == EOF) goto error; - - if (c == '\r') c = prot_getc(imapd_in); - if (c != '\n') { - prot_printf(imapd_out, - "%s BAD Unexpected extra arguments to Xsnippets\r\n", tag); - goto error; - } - - r = index_snippets(imapd_index, snippetargs, searchargs); - - if (r < 0) { - prot_printf(imapd_out, "%s NO %s\r\n", tag, - error_message(r)); - goto error; - } - - snprintf(mytime, sizeof(mytime), "%2.3f", - (clock() - start) / (double) CLOCKS_PER_SEC); - prot_printf(imapd_out, "%s OK %s (in %s secs)\r\n", tag, - error_message(IMAP_OK_COMPLETED), mytime); - -out: + freesortcrit(sortcrit); freesearchargs(searchargs); - free_snippetargs(&snippetargs); - return; - -error: - eatline(imapd_in, (c == EOF ? ' ' : c)); - goto out; -} - -static void cmd_xstats(char *tag, int c) -{ - int metric; - - if (backend_current) { - /* remote mailbox */ - const char *cmd = "Xstats"; - - prot_printf(backend_current->out, "%s %s ", tag, cmd); - if (!pipe_command(backend_current, 65536)) { - pipe_including_tag(backend_current, tag, 0); - } - return; - } - - if (c == EOF) { - prot_printf(imapd_out, "%s BAD Syntax error in Xstats arguments\r\n", tag); - goto error; - } - if (c == '\r') c = prot_getc(imapd_in); - if (c != '\n') { - prot_printf(imapd_out, - "%s BAD Unexpected extra arguments to Xstats\r\n", tag); - goto error; - } - - prot_printf(imapd_out, "* XSTATS"); - for (metric = 0 ; metric < XSTATS_NUM_METRICS ; metric++) - prot_printf(imapd_out, " %s %u", xstats_namesmetric, xstatsmetric); - prot_printf(imapd_out, "\r\n"); - - prot_printf(imapd_out, "%s OK %s\r\n", tag, - error_message(IMAP_OK_COMPLETED)); - return; - -error: - eatline(imapd_in, (c == EOF ? ' ' : c)); } /* @@ -5880,6 +5035,7 @@ { static struct buf arg; int c; + int charset = 0; int alg; struct searchargs *searchargs; clock_t start = clock(); @@ -5913,10 +5069,27 @@ return; } - searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST, - &imapd_namespace, imapd_userid, imapd_authstate, - imapd_userisadmin || imapd_userisproxyadmin); - c = get_search_program(imapd_in, imapd_out, searchargs); + /* get charset */ + c = getastring(imapd_in, imapd_out, &arg); + if (c != ' ') { + prot_printf(imapd_out, "%s BAD Missing charset in Thread\r\n", + tag); + eatline(imapd_in, c); + return; + } + lcase(arg.s); + charset = charset_lookupname(arg.s); + + if (charset == -1) { + prot_printf(imapd_out, "%s NO %s\r\n", tag, + error_message(IMAP_UNRECOGNIZED_CHARSET)); + eatline(imapd_in, c); + return; + } + + searchargs = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); + + c = getsearchprogram(tag, searchargs, &charset, 0); if (c == EOF) { eatline(imapd_in, ' '); freesearchargs(searchargs); @@ -6077,8 +5250,7 @@ r = index_copy(imapd_index, sequence, usinguid, mailboxname, ©uid, !config_getswitch(IMAPOPT_SINGLEINSTANCESTORE), &imapd_namespace, - (imapd_userisadmin || imapd_userisproxyadmin), ismove, - ignorequota); + (imapd_userisadmin || imapd_userisproxyadmin), ismove); } imapd_check(NULL, ismove || usinguid); @@ -6169,16 +5341,6 @@ dlist_getatom(extargs, "PARTITION", &partition); dlist_getatom(extargs, "SERVER", &server); - - const char *type = NULL; - - dlist_getatom(extargs, "PARTITION", &partition); - dlist_getatom(extargs, "SERVER", &server); - if (dlist_getatom(extargs, "TYPE", &type)) { - if (!strcasecmp(type, "CALENDAR")) mbtype |= MBTYPE_CALENDAR; - else if (!strcasecmp(type, "ADDRESSBOOK")) mbtype |= MBTYPE_ADDRESSBOOK; - else r = IMAP_MAILBOX_BADTYPE; - } use = dlist_getchild(extargs, "USE"); if (use) { struct dlist *item; @@ -6201,7 +5363,7 @@ } } - if (!r && partition && !imapd_userisadmin) { + if (partition && !imapd_userisadmin) { r = IMAP_PERMISSION_DENIED; } @@ -6219,7 +5381,7 @@ if (!r) { char *copy = xstrdup(mailboxname); lcase(copy); - if (strstr(copy, "inbox.inbox.")) + if (strstr(copy, "inbox.inbox")) r = IMAP_MAILBOX_BADNAME; free(copy); } @@ -6361,7 +5523,7 @@ int autocreatequotamessage = config_getint(IMAPOPT_AUTOCREATEQUOTAMSG); if (!r && ((autocreatequotastorage > 0) || (autocreatequotamessage > 0))) { int res; - quota_t newquotasQUOTA_NUMRESOURCES; + int newquotasQUOTA_NUMRESOURCES; for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) newquotasres = QUOTA_UNLIMITED; @@ -6373,9 +5535,7 @@ } if (!r && specialuse.len) { - const char *userid = mboxname_to_userid(mailboxname); - if (!userid) userid = imapd_userid; - r = annotatemore_write(mailboxname, "/specialuse", userid, &specialuse); + r = annotatemore_write(mailboxname, "/specialuse", imapd_userid, &specialuse); if (r) { /* XXX - failure here SHOULD cause a cleanup of the created mailbox */ syslog(LOG_ERR, "IOERROR: failed to write specialuse for %s on %s (%s)", @@ -6606,19 +5766,10 @@ char oldextnameMAX_MAILBOX_BUFFER; char newextnameMAX_MAILBOX_BUFFER; struct renrock *text = (struct renrock *)rock; - struct mboxlist_entry *mbentry = NULL; - int r = 0; - - r = mboxlist_lookup(name, &mbentry, NULL); - if (r == IMAP_MAILBOX_NONEXISTENT) { - /* skip these mailboxes */ - r = 0; - goto done; - } - if (r) goto done; + int r; if((text->nl + strlen(name + text->ol)) >= MAX_MAILBOX_BUFFER) - goto done; + return 0; strcpy(text->newmailboxname + text->nl, name + text->ol); @@ -6638,21 +5789,13 @@ if(r) { prot_printf(imapd_out, "* NO rename %s %s: %s\r\n", oldextname, newextname, error_message(r)); - if (!RENAME_STOP_ON_ERROR) r = 0; + if (RENAME_STOP_ON_ERROR) return r; } else { /* If we're renaming a user, change quotaroot and ACL */ if (text->rename_user) { user_copyquotaroot(name, text->newmailboxname); user_renameacl(text->namespace, text->newmailboxname, text->acl_olduser, text->acl_newuser); -#ifdef WITH_DAV - if (mbentry->mbtype & (MBTYPE_CALENDAR|MBTYPE_ADDRESSBOOK)) { - struct mailbox *mailbox = NULL; - r = mailbox_open_irl(text->newmailboxname, &mailbox); - if (!r) r = mailbox_add_dav(mailbox); - mailbox_close(&mailbox); - } -#endif //WITH_DAV } @@ -6660,13 +5803,19 @@ oldextname, newextname); sync_log_mailbox_double(name, text->newmailboxname); + + if (text->rename_user) { + /* allow the replica to get the correct new quotaroot + * and acls copied across */ + sync_log_user(text->newuser); + /* allow the replica to clean up the old meta files */ + sync_log_unuser(text->olduser); + } } -done: - mboxlist_entry_free(&mbentry); prot_flush(imapd_out); - return r; + return 0; } /* @@ -7017,14 +6166,8 @@ } /* take care of deleting old ACLs, subscriptions, seen state and quotas */ - if (!r && rename_user) { + if (!r && rename_user) user_deletedata(olduser, 1); - /* allow the replica to get the correct new quotaroot - * and acls copied across */ - sync_log_user(newuser); - /* allow the replica to clean up the old meta files */ - sync_log_unuser(olduser); - } imapd_check(NULL, 0); @@ -7345,11 +6488,6 @@ if (list_callback_calls) prot_printf(imapd_out, " %u calls", list_callback_calls); prot_printf(imapd_out, ")\r\n"); - - if (global_conversations) { - conversations_abort(&global_conversations); - global_conversations = NULL; - } } /* @@ -7597,50 +6735,39 @@ error_message(IMAP_OK_COMPLETED)); } -static int printmyrights(const char *extname, mbentry_t *mbentry) -{ - int rights = 0; - char strACL_MAXSTR; - - rights = cyrus_acl_myrights(imapd_authstate, mbentry->acl); - - /* Add in implicit rights */ - if (imapd_userisadmin) { - rights |= ACL_LOOKUP|ACL_ADMIN; - } - else if (mboxname_userownsmailbox(imapd_userid, mbentry->name)) { - rights |= config_implicitrights; - } - - if (!(rights & (ACL_LOOKUP|ACL_READ|ACL_INSERT|ACL_CREATE|ACL_DELETEMBOX|ACL_ADMIN))) { - return IMAP_MAILBOX_NONEXISTENT; - } - - prot_printf(imapd_out, "* MYRIGHTS "); - prot_printastring(imapd_out, extname); - prot_printf(imapd_out, " "); - prot_printastring(imapd_out, cyrus_acl_masktostr(rights, str)); - prot_printf(imapd_out, "\r\n"); - - return 0; -} - /* * Perform a MYRIGHTS command */ static void cmd_myrights(const char *tag, const char *name) { char mailboxnameMAX_MAILBOX_BUFFER; + int r, rights = 0; + char strACL_MAXSTR; mbentry_t *mbentry = NULL; - int r; r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, imapd_userid, mailboxname); - if (!r) r = mlookup(tag, name, mailboxname, &mbentry); + if (!r) { + r = mlookup(tag, name, mailboxname, &mbentry); + } if (r == IMAP_MAILBOX_MOVED) return; - if (!r) r = printmyrights(name, mbentry); + if (!r) { + rights = cyrus_acl_myrights(imapd_authstate, mbentry->acl); + + /* Add in implicit rights */ + if (imapd_userisadmin) { + rights |= ACL_LOOKUP|ACL_ADMIN; + } + else if (mboxname_userownsmailbox(imapd_userid, mailboxname)) { + rights |= config_implicitrights; + } + + if (!(rights & (ACL_LOOKUP|ACL_READ|ACL_INSERT|ACL_CREATE|ACL_DELETEMBOX|ACL_ADMIN))) { + r = IMAP_MAILBOX_NONEXISTENT; + } + } mboxlist_entry_free(&mbentry); @@ -7650,8 +6777,12 @@ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); return; } - - prot_printf(imapd_out, "%s OK %s\r\n", tag, + + prot_printf(imapd_out, "* MYRIGHTS "); + prot_printastring(imapd_out, name); + prot_printf(imapd_out, " "); + prot_printastring(imapd_out, cyrus_acl_masktostr(rights, str)); + prot_printf(imapd_out, "\r\n%s OK %s\r\n", tag, error_message(IMAP_OK_COMPLETED)); } @@ -7761,7 +6892,7 @@ prot_putc('(', o); for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) { if (q->limitsres >= 0) { - prot_printf(o, "%s%s " QUOTA_T_FMT " " QUOTA_T_FMT, + prot_printf(o, "%s%s " QUOTA_T_FMT " %d", sep, quota_namesres, q->usedsres/quota_unitsres, q->limitsres); @@ -7779,7 +6910,7 @@ prot_putc('(', o); for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) { if (q->limitsres >= 0) { - prot_printf(o, "%s%s " QUOTA_T_FMT, + prot_printf(o, "%s%s %d", sep, quota_namesres, q->limitsres); sep = " "; @@ -7999,7 +7130,7 @@ */ void cmd_setquota(const char *tag, const char *quotaroot) { - quota_t newquotasQUOTA_NUMRESOURCES; + int newquotasQUOTA_NUMRESOURCES; int res; int c; int force = 0; @@ -8114,7 +7245,7 @@ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); return; } - + prot_printf(imapd_out, "%s OK %s\r\n", tag, error_message(IMAP_OK_COMPLETED)); return; @@ -8239,7 +7370,6 @@ static struct buf arg; unsigned statusitems = 0; int c; - int hasconv = config_getswitch(IMAPOPT_CONVERSATIONS); c = prot_getc(imapd_in); if (c != '(') return EOF; @@ -8266,15 +7396,6 @@ else if (!strcmp(arg.s, "highestmodseq")) { statusitems |= STATUS_HIGHESTMODSEQ; } - else if (hasconv && !strcmp(arg.s, "xconvexists")) { - statusitems |= STATUS_XCONVEXISTS; - } - else if (hasconv && !strcmp(arg.s, "xconvunseen")) { - statusitems |= STATUS_XCONVUNSEEN; - } - else if (hasconv && !strcmp(arg.s, "xconvmodseq")) { - statusitems |= STATUS_XCONVMODSEQ; - } else { static char buf200; snprintf(buf, 200, "Invalid Status attributes %s", arg.s); @@ -8332,19 +7453,6 @@ sepchar, sd->highestmodseq); sepchar = ' '; } - if (statusitems & STATUS_XCONVEXISTS) { - prot_printf(imapd_out, "%cXCONVEXISTS %u", sepchar, sd->xconv.exists); - sepchar = ' '; - } - if (statusitems & STATUS_XCONVUNSEEN) { - prot_printf(imapd_out, "%cXCONVUNSEEN %u", sepchar, sd->xconv.unseen); - sepchar = ' '; - } - if (statusitems & STATUS_XCONVMODSEQ) { - prot_printf(imapd_out, "%cXCONVMODSEQ " MODSEQ_FMT, sepchar, sd->xconv.modseq); - sepchar = ' '; - } - prot_printf(imapd_out, ")\r\n"); return 0; @@ -8353,41 +7461,12 @@ static int imapd_statusdata(const char *mailboxname, unsigned statusitems, struct statusdata *sd) { - int r; - struct conversations_state *state = NULL; - - if (!(statusitems & STATUS_CONVITEMS)) goto nonconv; - statusitems &= ~STATUS_CONVITEMS; /* strip them for the regular lookup */ - - /* use the existing state if possible */ - state = conversations_get_mbox(mailboxname); - - /* otherwise fetch a new one! */ - if (!state) { - if (global_conversations) { - conversations_abort(&global_conversations); - global_conversations = NULL; - } - r = conversations_open_mbox(mailboxname, &state); - if (r) { - /* maybe the mailbox doesn't even have conversations - just ignore */ - goto nonconv; - } - global_conversations = state; - } - - r = conversation_getstatus(state, mailboxname, &sd->xconv); - if (r) return r; - -nonconv: /* use the index status if we can so we get the 'alive' Recent count */ if (!strcmpsafe(mailboxname, index_mboxname(imapd_index))) - r = index_status(imapd_index, sd); - /* fall back to generic lookup */ - else - r = status_lookup(mailboxname, imapd_userid, statusitems, sd); + return index_status(imapd_index, sd); - return r; + /* fall back to generic lookup */ + return status_lookup(mailboxname, imapd_userid, statusitems, sd); } /* @@ -8401,7 +7480,7 @@ char mailboxnameMAX_MAILBOX_BUFFER; const char *errstr = "Bad status string"; mbentry_t *mbentry = NULL; - struct statusdata sdata = STATUSDATA_INIT; + struct statusdata sdata; int r = 0; r = (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, name, @@ -8491,10 +7570,6 @@ } done: - if (global_conversations) { - conversations_abort(&global_conversations); - global_conversations = NULL; - } mboxlist_entry_free(&mbentry); return; } @@ -9800,214 +8875,619 @@ error_message(IMAP_OK_COMPLETED), mytime); } +/* + * Parse a ANNOTATION item for SEARCH (RFC5257) into a struct + * searchannot and append it to the chain of such structures at *lp. + * Returns the next character. + */ +static int parse_search_annotation(int c, struct searchannot **lp) +{ + struct searchannot *sa; + struct buf entry = BUF_INITIALIZER; + struct buf attrib = BUF_INITIALIZER; + struct buf value = BUF_INITIALIZER; -static void cmd_xwarmup(const char *tag) -{ - const char *cmd = "Xwarmup"; - clock_t start = clock(); - char mytime100; - struct buf arg = BUF_INITIALIZER; - int warmup_flags = 0; - struct seqset *uids = NULL; - /* We deal with the mboxlist API instead of the index_state API or - * mailbox API to avoid the overhead of index_open(), which will - * block while reading all the cyrus.index...we want to be - * non-blocking */ - struct mboxlist_entry *mbentry = NULL; - int myrights; - int c, r = 0; - char mboxnameMAX_MAILBOX_BUFFER; - - /* parse arguments: expect <mboxname> '('<warmup-items>')' */ + if (c != ' ') + return EOF; - c = getastring(imapd_in, imapd_out, &arg); - if (c != ' ') { -syntax_error: - prot_printf(imapd_out, "%s BAD syntax error in %s\r\n", tag, cmd); - eatline(imapd_in, c); - goto out_noprint; + /* parse the entry */ + c = getastring(imapd_in, imapd_out, &entry); + if (!entry.len || c != ' ') { + c = EOF; + goto out; } - r = imapd_namespace.mboxname_tointernal(&imapd_namespace, arg.s, - imapd_userid, mboxname); - if (r) { - prot_printf(imapd_out, "%s BAD Invalid mboxname in %s\r\n", tag, cmd); - eatline(imapd_in, c); - goto out_noprint; + /* parse the attrib */ + c = getastring(imapd_in, imapd_out, &attrib); + if (!attrib.len || c != ' ') { + c = EOF; + goto out; + } + if (strcmp(attrib.s, "value") && + strcmp(attrib.s, "value.shared") && + strcmp(attrib.s, "value.priv")) { + c = EOF; + goto out; } - r = mboxlist_lookup(mboxname, &mbentry, NULL); - if (r) goto out; + /* parse the value */ + c = getbnstring(imapd_in, imapd_out, &value); + if (c == EOF) + goto out; - /* Do a permissions check to avoid server DoS opportunity. But we - * only need read permission to warmup a mailbox. Also, be careful - * to avoid telling the client about the existance of mailboxes to - * which he doesn't have LOOKUP rights. */ - r = IMAP_PERMISSION_DENIED; - myrights = (mbentry->acl ? cyrus_acl_myrights(imapd_authstate, mbentry->acl) : 0); - if (imapd_userisadmin) - r = 0; - else if (!(myrights & ACL_LOOKUP)) - r = IMAP_MAILBOX_NONEXISTENT; - else if (myrights & ACL_READ) - r = 0; - if (r) goto out; + sa = xzmalloc(sizeof(*sa)); + sa->entry = buf_release(&entry); + sa->attrib = buf_release(&attrib); + sa->namespace = &imapd_namespace; + sa->isadmin = imapd_userisadmin || imapd_userisproxyadmin; + sa->userid = imapd_userid; + sa->auth_state = imapd_authstate; + buf_move(&sa->value, &value); + + /* append to *lp: move lp along the chain until + * it points to the last ->next pointer */ + while (*lp && (*lp)->next) + lp = &(*lp)->next; + *lp = sa; - if (mbentry->mbtype & MBTYPE_REMOTE) { - /* remote mailbox */ - struct backend *be; +out: + buf_free(&entry); + buf_free(&attrib); + buf_free(&value); + return c; +} - be = proxy_findserver(mbentry->server, &imap_protocol, - proxy_userid, &backend_cached, - &backend_current, &backend_inbox, imapd_in); - if (!be) { - r = IMAP_SERVER_UNAVAILABLE; - goto out; - } +/* + * Parse search return options + */ +static int getsearchreturnopts(const char *tag, struct searchargs *searchargs) +{ + int c; + static struct buf opt; - prot_printf(be->out, "%s %s %s ", tag, cmd, arg.s); - if (!pipe_command(backend_current, 65536)) { - pipe_including_tag(backend_current, tag, 0); - } - goto out; + c = prot_getc(imapd_in); + if (c != '(') { + prot_printf(imapd_out, + "%s BAD Missing return options in Search\r\n", tag); + return EOF; } - /* local mailbox */ - /* parse the arguments after the mailbox */ + do { + c = getword(imapd_in, &opt); + if (!opt.s0) break; - c = prot_getc(imapd_in); - if (c != '(') goto syntax_error; + lcase(opt.s); + if (!strcmp(opt.s, "min")) { + searchargs->returnopts |= SEARCH_RETURN_MIN; + } + else if (!strcmp(opt.s, "max")) { + searchargs->returnopts |= SEARCH_RETURN_MAX; + } + else if (!strcmp(opt.s, "all")) { + searchargs->returnopts |= SEARCH_RETURN_ALL; + } + else if (!strcmp(opt.s, "count")) { + searchargs->returnopts |= SEARCH_RETURN_COUNT; + } + else { + prot_printf(imapd_out, + "%s BAD Invalid Search return option %s\r\n", + tag, opt.s); + return EOF; + } - for (;;) { - c = getword(imapd_in, &arg); - if (arg.len) { - if (!strcasecmp(arg.s, "index")) - warmup_flags |= WARMUP_INDEX; - else if (!strcasecmp(arg.s, "conversations")) - warmup_flags |= WARMUP_CONVERSATIONS; - else if (!strcasecmp(arg.s, "annotations")) - warmup_flags |= WARMUP_ANNOTATIONS; - else if (!strcasecmp(arg.s, "folderstatus")) - warmup_flags |= WARMUP_FOLDERSTATUS; - else if (!strcasecmp(arg.s, "search")) - warmup_flags |= WARMUP_SEARCH; - else if (!strcasecmp(arg.s, "uids")) { - if (c != ' ') goto syntax_error; - c = getword(imapd_in, &arg); - if (c == EOF) goto syntax_error; - if (!imparse_issequence(arg.s)) goto syntax_error; - uids = seqset_parse(arg.s, NULL, /*maxval*/0); - if (!uids) goto syntax_error; - } - else if (!strcasecmp(arg.s, "all")) - warmup_flags |= WARMUP_ALL; - else - goto syntax_error; - } - if (c == ')') - break; - if (c != ' ') goto syntax_error; + } while (c == ' '); + + if (c != ')') { + prot_printf(imapd_out, + "%s BAD Missing close parenthesis in Search\r\n", tag); + return EOF; } - /* we're expecting no more arguments */ + if (!searchargs->returnopts) searchargs->returnopts = SEARCH_RETURN_ALL; + c = prot_getc(imapd_in); - if (c == '\r') c = prot_getc(imapd_in); - if (c != '\n') goto syntax_error; - r = index_warmup(mbentry, warmup_flags, uids); + return c; +} -out: - snprintf(mytime, sizeof(mytime), "%2.3f", - (clock() - start) / (double) CLOCKS_PER_SEC); +/* + * Parse a search program + */ +static int getsearchprogram(const char *tag, struct searchargs *searchargs, + int *charsetp, int is_search_cmd) +{ + int c; + int searchstate = 0; - if (r) - prot_printf(imapd_out, "%s NO %s (%s sec)\r\n", tag, - error_message(r), mytime); - else - prot_printf(imapd_out, "%s OK %s (%s sec)\r\n", tag, - error_message(IMAP_OK_COMPLETED), mytime); + if (is_search_cmd) + searchstate |= GETSEARCH_CHARSET|GETSEARCH_RETURN; -out_noprint: - mboxlist_entry_free(&mbentry); - buf_free(&arg); - if (uids) seqset_free(uids); + do { + c = getsearchcriteria(tag, searchargs, charsetp, &searchstate); + } while (c == ' '); + return c; } -static void free_snippetargs(struct snippetargs **sap) +/* + * Parse a search criteria + */ +static int getsearchcriteria(const char *tag, struct searchargs *searchargs, + int *charsetp, int *searchstatep) { - while (*sap) { - struct snippetargs *sa = *sap; - *sap = sa->next; - free(sa->mboxname); - free(sa->uids.data); - free(sa); - } -} + static struct buf criteria, arg; + struct searchargs *sub1, *sub2; + char *p, *str; + int c, flag; + unsigned size; + time_t start, end, now = time(0); + int keep_charset = 0; -static int get_snippetargs(struct snippetargs **sap) -{ - int r; - int c; - struct snippetargs **prevp = sap; - struct snippetargs *sa = NULL; - struct buf arg = BUF_INITIALIZER; - uint32_t uid; - char mboxnameMAX_MAILBOX_NAME; + c = getword(imapd_in, &criteria); + lcase(criteria.s); + switch (criteria.s0) { + case '\0': + if (c != '(') goto badcri; + c = getsearchprogram(tag, searchargs, charsetp, 0); + if (c == EOF) return EOF; + if (c != ')') { + prot_printf(imapd_out, "%s BAD Missing required close paren in Search command\r\n", + tag); + if (c != EOF) prot_ungetc(c, imapd_in); + return EOF; + } + c = prot_getc(imapd_in); + break; - c = prot_getc(imapd_in); - if (c != '(') goto syntax_error; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '*': + if (imparse_issequence(criteria.s)) { + appendsequencelist(imapd_index, &searchargs->sequence, criteria.s, 0); + } + else goto badcri; + break; - for (;;) { - c = prot_getc(imapd_in); - if (c == ')') break; - if (c != '(') goto syntax_error; + case 'a': + if (!strcmp(criteria.s, "answered")) { + searchargs->system_flags_set |= FLAG_ANSWERED; + } + else if (!strcmp(criteria.s, "all")) { + break; + } + else if (!strcmp(criteria.s, "annotation")) { + c = parse_search_annotation(c, &searchargs->annotations); + if (c == EOF) + goto badcri; + } + else goto badcri; + break; - c = getastring(imapd_in, imapd_out, &arg); - if (c != ' ') goto syntax_error; + case 'b': + if (!strcmp(criteria.s, "before")) { + if (c != ' ') goto missingarg; + c = getsearchdate(&start, &end); + if (c == EOF) goto baddate; + if (!searchargs->before || searchargs->before > start) { + searchargs->before = start; + } + } + else if (!strcmp(criteria.s, "bcc")) { + if (c != ' ') goto missingarg; + c = getastring(imapd_in, imapd_out, &arg); + if (c == EOF) goto missingarg; + str = charset_convert(arg.s, *charsetp, charset_flags); + if (str) appendstrlistpat(&searchargs->bcc, str); + else searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); + } + else if (!strcmp(criteria.s, "body")) { + if (c != ' ') goto missingarg; + c = getastring(imapd_in, imapd_out, &arg); + if (c == EOF) goto missingarg; + str = charset_convert(arg.s, *charsetp, charset_flags); + if (str) appendstrlistpat(&searchargs->body, str); + else searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); + } + else goto badcri; + break; + + case 'c': + if (!strcmp(criteria.s, "cc")) { + if (c != ' ') goto missingarg; + c = getastring(imapd_in, imapd_out, &arg); + if (c == EOF) goto missingarg; + str = charset_convert(arg.s, *charsetp, charset_flags); + if (str) appendstrlistpat(&searchargs->cc, str); + else searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); + } + else if ((*searchstatep & GETSEARCH_CHARSET) + && !strcmp(criteria.s, "charset")) { + if (c != ' ') goto missingarg; + c = getastring(imapd_in, imapd_out, &arg); + if (c != ' ') goto missingarg; + lcase(arg.s); + *charsetp = charset_lookupname(arg.s); + } + else goto badcri; + break; + + case 'd': + if (!strcmp(criteria.s, "deleted")) { + searchargs->system_flags_set |= FLAG_DELETED; + } + else if (!strcmp(criteria.s, "draft")) { + searchargs->system_flags_set |= FLAG_DRAFT; + } + else goto badcri; + break; + + case 'f': + if (!strcmp(criteria.s, "flagged")) { + searchargs->system_flags_set |= FLAG_FLAGGED; + } + else if (!strcmp(criteria.s, "from")) { + if (c != ' ') goto missingarg; + c = getastring(imapd_in, imapd_out, &arg); + if (c == EOF) goto missingarg; + str = charset_convert(arg.s, *charsetp, charset_flags); + if (str) appendstrlistpat(&searchargs->from, str); + else searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); + } + else goto badcri; + break; - r = imapd_namespace.mboxname_tointernal(&imapd_namespace, buf_cstring(&arg), - imapd_userid, mboxname); - if (r) goto out; - - /* allocate a new snippetargs */ - sa = xzmalloc(sizeof(struct snippetargs)); - sa->mboxname = xstrdup(mboxname); - /* append to the list */ - *prevp = sa; - prevp = &sa->next; + case 'h': + if (!strcmp(criteria.s, "header")) { + struct strlist **patlist; - c = getuint32(imapd_in, &sa->uidvalidity); - if (c != ' ') goto syntax_error; + if (c != ' ') goto missingarg; + c = getastring(imapd_in, imapd_out, &arg); + if (c != ' ') goto missingarg; + lcase(arg.s); - c = prot_getc(imapd_in); - if (c != '(') break; - for (;;) { - c = getuint32(imapd_in, &uid); - if (c != ' ' && c != ')') goto syntax_error; - if (sa->uids.count + 1 > sa->uids.alloc) { - sa->uids.alloc += 64; - sa->uids.data = xrealloc(sa->uids.data, - sizeof(uint32_t) * sa->uids.alloc); + /* some headers can be reduced to search terms */ + if (!strcmp(arg.s, "bcc")) { + patlist = &searchargs->bcc; + } + else if (!strcmp(arg.s, "cc")) { + patlist = &searchargs->cc; + } + else if (!strcmp(arg.s, "to")) { + patlist = &searchargs->to; + } + else if (!strcmp(arg.s, "from")) { + patlist = &searchargs->from; + } + else if (!strcmp(arg.s, "subject")) { + patlist = &searchargs->subject; + } + + /* we look message-id up in the envelope */ + else if (!strcmp(arg.s, "message-id")) { + patlist = &searchargs->messageid; + } + + /* all other headers we handle normally */ + else { + if (searchargs->cache_atleast < BIT32_MAX) { + bit32 this_ver = + mailbox_cached_header(arg.s); + if(this_ver > searchargs->cache_atleast) + searchargs->cache_atleast = this_ver; + } + appendstrlist(&searchargs->header_name, arg.s); + patlist = &searchargs->header; } - sa->uids.datasa->uids.count++ = uid; - if (c == ')') break; + + c = getastring(imapd_in, imapd_out, &arg); + if (c == EOF) goto missingarg; + str = charset_convert(arg.s, *charsetp, charset_flags); + if (str) appendstrlistpat(patlist, str); + else searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); } + else goto badcri; + break; - c = prot_getc(imapd_in); - if (c != ')') goto syntax_error; + case 'k': + if (!strcmp(criteria.s, "keyword")) { + if (c != ' ') goto missingarg; + c = getword(imapd_in, &arg); + if (!imparse_isatom(arg.s)) goto badflag; + lcase(arg.s); + for (flag=0; flag < MAX_USER_FLAGS; flag++) { + if (imapd_index->flagnameflag && + !strcasecmp(imapd_index->flagnameflag, arg.s)) break; + } + if (flag == MAX_USER_FLAGS) { + /* Force failure */ + searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); + break; + } + searchargs->user_flags_setflag/32 |= 1<<(flag&31); + } + else goto badcri; + break; + + case 'l': + if (!strcmp(criteria.s, "larger")) { + if (c != ' ') goto missingarg; + c = getword(imapd_in, &arg); + size = 0; + for (p = arg.s; *p && Uisdigit(*p); p++) { + size = size * 10 + *p - '0'; + /* if (size < 0) goto badnumber; */ + } + if (!arg.s || *p) goto badnumber; + if (size > searchargs->larger) searchargs->larger = size; + } + else goto badcri; + break; + + case 'm': + if (!strcmp(criteria.s, "modseq")) { + if (c != ' ') goto missingarg; + /* Check for optional search-modseq-ext */ + c = getqstring(imapd_in, imapd_out, &arg); + if (c != EOF) { + if (c != ' ') goto missingarg; + c = getword(imapd_in, &arg); + if (c != ' ') goto missingarg; + } + c = getmodseq(imapd_in, &searchargs->modseq); + if (c == EOF) goto badnumber; + } + else goto badcri; + break; + + case 'n': + if (!strcmp(criteria.s, "not")) { + if (c != ' ') goto missingarg; + sub1 = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); + c = getsearchcriteria(tag, sub1, charsetp, searchstatep); + if (c == EOF) { + freesearchargs(sub1); + return EOF; + } + + appendsearchargs(searchargs, sub1, (struct searchargs *)0); + } + else if (!strcmp(criteria.s, "new")) { + searchargs->flags |= (SEARCH_SEEN_UNSET|SEARCH_RECENT_SET); + } + else goto badcri; + break; + + case 'o': + if (!strcmp(criteria.s, "or")) { + if (c != ' ') goto missingarg; + sub1 = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); + c = getsearchcriteria(tag, sub1, charsetp, searchstatep); + if (c == EOF) { + freesearchargs(sub1); + return EOF; + } + if (c != ' ') goto missingarg; + sub2 = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); + c = getsearchcriteria(tag, sub2, charsetp, searchstatep); + if (c == EOF) { + freesearchargs(sub1); + freesearchargs(sub2); + return EOF; + } + appendsearchargs(searchargs, sub1, sub2); + } + else if (!strcmp(criteria.s, "old")) { + searchargs->flags |= SEARCH_RECENT_UNSET; + } + else if (!strcmp(criteria.s, "older")) { + if (c != ' ') goto missingarg; + c = getword(imapd_in, &arg); + if (c == EOF || !imparse_isnumber(arg.s)) goto badinterval; + start = now - atoi(arg.s); + if (!searchargs->before || searchargs->before > start) { + searchargs->before = start; + } + } + else if (!strcmp(criteria.s, "on")) { + if (c != ' ') goto missingarg; + c = getsearchdate(&start, &end); + if (c == EOF) goto baddate; + if (!searchargs->before || searchargs->before > end) { + searchargs->before = end; + } + if (!searchargs->after || searchargs->after < start) { + searchargs->after = start; + } + } + else goto badcri; + break; + + case 'r': + if (!strcmp(criteria.s, "recent")) { + searchargs->flags |= SEARCH_RECENT_SET; + } + else if ((*searchstatep & GETSEARCH_RETURN) && + !strcmp(criteria.s, "return")) { + c = getsearchreturnopts(tag, searchargs); + if (c == EOF) return EOF; + keep_charset = 1; + } + else goto badcri; + break; + + case 's': + if (!strcmp(criteria.s, "seen")) { + searchargs->flags |= SEARCH_SEEN_SET; + } + else if (!strcmp(criteria.s, "sentbefore")) { + if (c != ' ') goto missingarg; + c = getsearchdate(&start, &end); + if (c == EOF) goto baddate; + if (!searchargs->sentbefore || searchargs->sentbefore > start) { + searchargs->sentbefore = start; + } + } + else if (!strcmp(criteria.s, "senton")) { + if (c != ' ') goto missingarg; + c = getsearchdate(&start, &end); + if (c == EOF) goto baddate; + if (!searchargs->sentbefore || searchargs->sentbefore > end) { + searchargs->sentbefore = end; + } + if (!searchargs->sentafter || searchargs->sentafter < start) { + searchargs->sentafter = start; + } + } + else if (!strcmp(criteria.s, "sentsince")) { + if (c != ' ') goto missingarg; + c = getsearchdate(&start, &end); + if (c == EOF) goto baddate; + if (!searchargs->sentafter || searchargs->sentafter < start) { + searchargs->sentafter = start; + } + } + else if (!strcmp(criteria.s, "since")) { + if (c != ' ') goto missingarg; + c = getsearchdate(&start, &end); + if (c == EOF) goto baddate; + if (!searchargs->after || searchargs->after < start) { + searchargs->after = start; + } + } + else if (!strcmp(criteria.s, "smaller")) { + if (c != ' ') goto missingarg; + c = getword(imapd_in, &arg); + size = 0; + for (p = arg.s; *p && Uisdigit(*p); p++) { + size = size * 10 + *p - '0'; + /* if (size < 0) goto badnumber; */ + } + if (!arg.s || *p) goto badnumber; + if (size == 0) size = 1; + if (!searchargs->smaller || size < searchargs->smaller) + searchargs->smaller = size; + } + else if (!strcmp(criteria.s, "subject")) { + if (c != ' ') goto missingarg; + c = getastring(imapd_in, imapd_out, &arg); + if (c == EOF) goto missingarg; + str = charset_convert(arg.s, *charsetp, charset_flags); + if (str) appendstrlistpat(&searchargs->subject, str); + else searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); + } + else goto badcri; + break; + + case 't': + if (!strcmp(criteria.s, "to")) { + if (c != ' ') goto missingarg; + c = getastring(imapd_in, imapd_out, &arg); + if (c == EOF) goto missingarg; + str = charset_convert(arg.s, *charsetp, charset_flags); + if (str) appendstrlistpat(&searchargs->to, str); + else searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); + } + else if (!strcmp(criteria.s, "text")) { + if (c != ' ') goto missingarg; + c = getastring(imapd_in, imapd_out, &arg); + if (c == EOF) goto missingarg; + str = charset_convert(arg.s, *charsetp, charset_flags); + if (str) appendstrlistpat(&searchargs->text, str); + else searchargs->flags = (SEARCH_RECENT_SET|SEARCH_RECENT_UNSET); + } + else goto badcri; + break; + + case 'u': + if (!strcmp(criteria.s, "uid")) { + if (c != ' ') goto missingarg; + c = getword(imapd_in, &arg); + if (!imparse_issequence(arg.s)) goto badcri; + appendsequencelist(imapd_index, &searchargs->uidsequence, arg.s, 1); + } + else if (!strcmp(criteria.s, "unseen")) { + searchargs->flags |= SEARCH_SEEN_UNSET; + } + else if (!strcmp(criteria.s, "unanswered")) { + searchargs->system_flags_unset |= FLAG_ANSWERED; + } + else if (!strcmp(criteria.s, "undeleted")) { + searchargs->system_flags_unset |= FLAG_DELETED; + } + else if (!strcmp(criteria.s, "undraft")) { + searchargs->system_flags_unset |= FLAG_DRAFT; + } + else if (!strcmp(criteria.s, "unflagged")) { + searchargs->system_flags_unset |= FLAG_FLAGGED; + } + else if (!strcmp(criteria.s, "unkeyword")) { + if (c != ' ') goto missingarg; + c = getword(imapd_in, &arg); + if (!imparse_isatom(arg.s)) goto badflag; + lcase(arg.s); + for (flag=0; flag < MAX_USER_FLAGS; flag++) { + if (imapd_index->flagnameflag && + !strcasecmp(imapd_index->flagnameflag, arg.s)) break; + } + if (flag != MAX_USER_FLAGS) { + searchargs->user_flags_unsetflag/32 |= 1<<(flag&31); + } + } + else goto badcri; + break; + + case 'y': + if (!strcmp(criteria.s, "younger")) { + if (c != ' ') goto missingarg; + c = getword(imapd_in, &arg); + if (c == EOF || !imparse_isnumber(arg.s)) goto badinterval; + start = now - atoi(arg.s); + if (!searchargs->after || searchargs->after < start) { + searchargs->after = start; + } + } + else goto badcri; + break; + + default: + badcri: + prot_printf(imapd_out, "%s BAD Invalid Search criteria\r\n", tag); + if (c != EOF) prot_ungetc(c, imapd_in); + return EOF; } - c = prot_getc(imapd_in); - if (c != ' ') goto syntax_error; + if (!keep_charset) + *searchstatep &= ~GETSEARCH_CHARSET; + *searchstatep &= ~GETSEARCH_RETURN; -out: - buf_free(&arg); return c; -syntax_error: - free_snippetargs(sap); - c = EOF; - goto out; + missingarg: + prot_printf(imapd_out, "%s BAD Missing required argument to Search %s\r\n", + tag, criteria.s); + if (c != EOF) prot_ungetc(c, imapd_in); + return EOF; + + badflag: + prot_printf(imapd_out, "%s BAD Invalid flag name %s in Search command\r\n", + tag, arg.s); + if (c != EOF) prot_ungetc(c, imapd_in); + return EOF; + + baddate: + prot_printf(imapd_out, "%s BAD Invalid date in Search command\r\n", tag); + if (c != EOF) prot_ungetc(c, imapd_in); + return EOF; + + badnumber: + prot_printf(imapd_out, "%s BAD Invalid number in Search command\r\n", tag); + if (c != EOF) prot_ungetc(c, imapd_in); + return EOF; + + badinterval: + prot_printf(imapd_out, "%s BAD Invalid interval in Search command\r\n", tag); + if (c != EOF) prot_ungetc(c, imapd_in); + return EOF; } static void cmd_dump(char *tag, char *name, int uid_start) @@ -10943,6 +10423,95 @@ return; } +/* + * Parse a "date", for SEARCH criteria + * The time_t's pointed to by 'start' and 'end' are set to the + * times of the start and end of the parsed date. + */ +static int getsearchdate(time_t *start, time_t *end) +{ + int c; + struct tm tm; + int quoted = 0; + char month4; + + memset(&tm, 0, sizeof tm); + + c = prot_getc(imapd_in); + if (c == '\"') { + quoted++; + c = prot_getc(imapd_in); + } + + /* Day of month */ + if (!isdigit(c)) goto baddate; + tm.tm_mday = c - '0'; + c = prot_getc(imapd_in); + if (isdigit(c)) { + tm.tm_mday = tm.tm_mday * 10 + c - '0'; + c = prot_getc(imapd_in); + } + + if (c != '-') goto baddate; + c = prot_getc(imapd_in); + + /* Month name */ + if (!isalpha(c)) goto baddate; + month0 = c; + c = prot_getc(imapd_in); + if (!isalpha(c)) goto baddate; + month1 = c; + c = prot_getc(imapd_in); + if (!isalpha(c)) goto baddate; + month2 = c; + c = prot_getc(imapd_in); + month3 = '\0'; + lcase(month); + + for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) { + if (!strcmp(month, monthnametm.tm_mon)) break; + } + if (tm.tm_mon == 12) goto baddate; + + if (c != '-') goto baddate; + c = prot_getc(imapd_in); + + /* Year */ + if (!isdigit(c)) goto baddate; + tm.tm_year = c - '0'; + c = prot_getc(imapd_in); + if (!isdigit(c)) goto baddate; + tm.tm_year = tm.tm_year * 10 + c - '0'; + c = prot_getc(imapd_in); + if (isdigit(c)) { + if (tm.tm_year < 19) goto baddate; + tm.tm_year -= 19; + tm.tm_year = tm.tm_year * 10 + c - '0'; + c = prot_getc(imapd_in); + if (!isdigit(c)) goto baddate; + tm.tm_year = tm.tm_year * 10 + c - '0'; + c = prot_getc(imapd_in); + } + + if (quoted) { + if (c != '\"') goto baddate; + c = prot_getc(imapd_in); + } + + tm.tm_isdst = -1; + *start = mktime(&tm); + + tm.tm_hour = 24; + tm.tm_isdst = -1; + *end = mktime(&tm); + + return c; + + baddate: + prot_ungetc(c, imapd_in); + return EOF; +} + #define SORTGROWSIZE 10 /* @@ -10953,7 +10522,6 @@ int c; static struct buf criteria; int nsort, n; - int hasconv = config_getswitch(IMAPOPT_CONVERSATIONS); *sortcrit = NULL; @@ -11021,30 +10589,6 @@ (*sortcrit)n.key = SORT_MODSEQ; else if (!strcmp(criteria.s, "uid")) (*sortcrit)n.key = SORT_UID; - else if (!strcmp(criteria.s, "hasflag")) { - (*sortcrit)n.key = SORT_HASFLAG; - if (c != ' ') goto missingarg; - c = getastring(imapd_in, imapd_out, &criteria); - if (c == EOF) goto missingarg; - (*sortcrit)n.args.flag.name = xstrdup(criteria.s); - } - else if (hasconv && !strcmp(criteria.s, "convmodseq")) - (*sortcrit)n.key = SORT_CONVMODSEQ; - else if (hasconv && !strcmp(criteria.s, "convexists")) - (*sortcrit)n.key = SORT_CONVEXISTS; - else if (hasconv && !strcmp(criteria.s, "convsize")) - (*sortcrit)n.key = SORT_CONVSIZE; - else if (hasconv && !strcmp(criteria.s, "hasconvflag")) { - (*sortcrit)n.key = SORT_HASCONVFLAG; - if (c != ' ') goto missingarg; - c = getastring(imapd_in, imapd_out, &criteria); - if (c == EOF) goto missingarg; - (*sortcrit)n.args.flag.name = xstrdup(criteria.s); - } - else if (!strcmp(criteria.s, "folder")) - (*sortcrit)n.key = SORT_FOLDER; - else if (!strcmp(criteria.s, "relevancy")) - (*sortcrit)n.key = SORT_RELEVANCY; else { prot_printf(imapd_out, "%s BAD Invalid Sort criterion %s\r\n", tag, criteria.s); @@ -11091,187 +10635,37 @@ return EOF; } -static int parse_windowargs(const char *tag, - struct windowargs **wa, - int updates) -{ - struct windowargs windowargs; - struct buf arg = BUF_INITIALIZER; - struct buf ext_folder = BUF_INITIALIZER; - int c; - - memset(&windowargs, 0, sizeof(windowargs)); - - c = prot_getc(imapd_in); - if (c == EOF) - goto out; - if (c != '(') { - /* no window args at all */ - prot_ungetc(c, imapd_in); - goto out; - } - - for (;;) - { - c = prot_getc(imapd_in); - if (c == EOF) - goto out; - if (c == ')') - break; /* end of window args */ - - prot_ungetc(c, imapd_in); - c = getword(imapd_in, &arg); - if (!arg.len) - goto syntax_error; - - if (!strcasecmp(arg.s, "CONVERSATIONS")) - windowargs.conversations = 1; - else if (!strcasecmp(arg.s, "POSITION")) { - if (updates) - goto syntax_error; - if (c != ' ') - goto syntax_error; - c = prot_getc(imapd_in); - if (c != '(') - goto syntax_error; - c = getuint32(imapd_in, &windowargs.position); - if (c != ' ') - goto syntax_error; - c = getuint32(imapd_in, &windowargs.limit); - if (c != ')') - goto syntax_error; - c = prot_getc(imapd_in); - if (windowargs.position == 0) - goto syntax_error; - } - else if (!strcasecmp(arg.s, "ANCHOR")) { - if (updates) - goto syntax_error; - if (c != ' ') - goto syntax_error; - c = prot_getc(imapd_in); - if (c != '(') - goto syntax_error; - c = getuint32(imapd_in, &windowargs.anchor); - if (c != ' ') - goto syntax_error; - c = getuint32(imapd_in, &windowargs.offset); - if (c != ' ') - goto syntax_error; - c = getuint32(imapd_in, &windowargs.limit); - if (c != ')') - goto syntax_error; - c = prot_getc(imapd_in); - if (windowargs.anchor == 0) - goto syntax_error; - } - else if (!strcasecmp(arg.s, "MULTIANCHOR")) { - if (updates) - goto syntax_error; - if (c != ' ') - goto syntax_error; - c = prot_getc(imapd_in); - if (c != '(') - goto syntax_error; - c = getuint32(imapd_in, &windowargs.anchor); - if (c != ' ') - goto syntax_error; - c = getastring(imapd_in, imapd_out, &ext_folder); - if (c != ' ') - goto syntax_error; - c = getuint32(imapd_in, &windowargs.offset); - if (c != ' ') - goto syntax_error; - c = getuint32(imapd_in, &windowargs.limit); - if (c != ')') - goto syntax_error; - c = prot_getc(imapd_in); - if (windowargs.anchor == 0) - goto syntax_error; - } - else if (!strcasecmp(arg.s, "CHANGEDSINCE")) { - if (!updates) - goto syntax_error; - windowargs.changedsince = 1; - if (c != ' ') - goto syntax_error; - c = prot_getc(imapd_in); - if (c != '(') - goto syntax_error; - c = getmodseq(imapd_in, &windowargs.modseq); - if (c != ' ') - goto syntax_error; - c = getuint32(imapd_in, &windowargs.uidnext); - if (c != ')') - goto syntax_error; - c = prot_getc(imapd_in); - } else if (!strcasecmp(arg.s, "UPTO")) { - if (!updates) - goto syntax_error; - if (c != ' ') - goto syntax_error; - c = prot_getc(imapd_in); - if (c != '(') - goto syntax_error; - c = getuint32(imapd_in, &windowargs.upto); - if (c != ')') - goto syntax_error; - c = prot_getc(imapd_in); +static char *sortcrit_as_string(const struct sortcrit *sortcrit) +{ + struct buf b = BUF_INITIALIZER; + static const char * const key_names = { + "SEQUENCE", "ARRIVAL", "CC", "DATE", + "DISPLAYFROM", "DISPLAYTO", "FROM", + "SIZE", "SUBJECT", "TO", "ANNOTATION", + "MODSEQ", "UID" + }; + + for ( ; sortcrit->key ; sortcrit++) { + if (b.len) + buf_putc(&b, ' '); + if (sortcrit->flags & SORT_REVERSE) + buf_appendcstr(&b, "REVERSE "); - if (windowargs.upto == 0) - goto syntax_error; - } + if (sortcrit->key < VECTOR_SIZE(key_names)) + buf_appendcstr(&b, key_namessortcrit->key); else - goto syntax_error; + buf_printf(&b, "UNKNOWN%u", sortcrit->key); - if (c == ')') + switch (sortcrit->key) { + case SORT_ANNOTATION: + buf_printf(&b, " \"%s\" \"%s\"", + sortcrit->args.annot.entry, + *sortcrit->args.annot.userid ? + "value.priv" : "value.shared"); break; - if (c != ' ') - goto syntax_error; - } - - c = prot_getc(imapd_in); - if (c != ' ') - goto syntax_error; - -out: - /* these two are mutually exclusive */ - if (windowargs.anchor && windowargs.position) - goto syntax_error; - /* changedsince is mandatory for XCONVUPDATES - * and illegal for XCONVSORT */ - if (!!updates != windowargs.changedsince) - goto syntax_error; - - if (ext_folder.len) { - int r; - char int_mboxnameMAX_MAILBOX_PATH+1; - - r = imapd_namespace.mboxname_tointernal(&imapd_namespace, - buf_cstring(&ext_folder), - imapd_userid, int_mboxname); - if (!r) - windowargs.anchorfolder = xstrdup(int_mboxname); + } } - - *wa = xmemdup(&windowargs, sizeof(windowargs)); - buf_free(&ext_folder); - buf_free(&arg); - return c; - -syntax_error: - free(windowargs.anchorfolder); - buf_free(&ext_folder); - prot_printf(imapd_out, "%s BAD Syntax error in window arguments\r\n", tag); - return EOF; -} - -static void free_windowargs(struct windowargs *wa) -{ - if (!wa) - return; - free(wa->anchorfolder); - free(wa); + return buf_release(&b); } /* @@ -11384,8 +10778,6 @@ args->ret |= LIST_RET_SUBSCRIBED; else if (!strcmp(buf.s, "children")) args->ret |= LIST_RET_CHILDREN; - else if (!strcmp(buf.s, "myrights")) - args->ret |= LIST_RET_MYRIGHTS; else if (!strcmp(buf.s, "special-use")) args->ret |= LIST_RET_SPECIALUSE; else if (!strcmp(buf.s, "status")) { @@ -11496,6 +10888,83 @@ } } +/* + * Append the searchargs 's1' and 's2' to the sublist of 's' + */ +static void appendsearchargs(struct searchargs *s, + struct searchargs *s1, + struct searchargs *s2) +{ + struct searchsub **tail = &s->sublist; + + while (*tail) tail = &(*tail)->next; + + *tail = (struct searchsub *)xmalloc(sizeof(struct searchsub)); + (*tail)->sub1 = s1; + (*tail)->sub2 = s2; + (*tail)->next = 0; +} + + +/* + * Free the searchargs 's' + */ +static void freesearchargs(struct searchargs *s) +{ + struct searchsub *sub, *n; + struct searchannot *sa; + + if (!s) return; + + freesequencelist(s->sequence); + freesequencelist(s->uidsequence); + freestrlist(s->from); + freestrlist(s->to); + freestrlist(s->cc); + freestrlist(s->bcc); + freestrlist(s->subject); + freestrlist(s->body); + freestrlist(s->text); + freestrlist(s->header_name); + freestrlist(s->header); + + while ((sa = s->annotations)) { + s->annotations = sa->next; + free(sa->entry); + free(sa->attrib); + buf_free(&sa->value); + free(sa); + } + + for (sub = s->sublist; sub; sub = n) { + n = sub->next; + freesearchargs(sub->sub1); + freesearchargs(sub->sub2); + free(sub); + } + free(s); +} + +/* + * Free an array of sortcrit + */ +static void freesortcrit(struct sortcrit *s) +{ + int i = 0; + + if (!s) return; + do { + switch (si.key) { + case SORT_ANNOTATION: + free(si.args.annot.entry); + free(si.args.annot.userid); + break; + } + i++; + } while (si.key != SORT_SEQUENCE); + free(s); +} + static int set_haschildren(char *name, int matchlen, int maycreate __attribute__((unused)), int *attributes) @@ -11553,7 +11022,7 @@ const char *sep; const char *cmd; mbentry_t *mbentry = NULL; - struct statusdata sdata = STATUSDATA_INIT; + struct statusdata sdata; if (!name) return; @@ -11779,11 +11248,6 @@ print_statusline(mboxname, listargs->statusitems, &sdata); } - if ((listargs->ret & LIST_RET_MYRIGHTS) && - !(attributes & MBOX_ATTRIBUTE_NOSELECT)) { - /*ignore result*/printmyrights(mboxname, mbentry); - } - done: mboxlist_entry_free(&mbentry); } @@ -12799,47 +12263,4 @@ prot_printf(imapd_out, "%s OK %s\r\n", tag, error_message(IMAP_OK_COMPLETED)); -} - -static void cmd_xkillmy(const char *tag, const char *cmdname) -{ - char *cmd = xstrdup(cmdname); - char *p; - - /* normalise to imapd conventions */ - if (Uislower(cmd0)) - cmd0 = toupper((unsigned char) cmd0); - for (p = cmd+1; *p; p++) { - if (Uisupper(*p)) *p = tolower((unsigned char) *p); - } - - proc_killusercmd(imapd_userid, cmd, SIGUSR2); - - free(cmd); - - prot_printf(imapd_out, "%s OK %s\r\n", tag, - error_message(IMAP_OK_COMPLETED)); -} - -static void cmd_xforever(const char *tag) -{ - unsigned n = 1; - int r = 0; - - while (!r) { - sleep(1); - prot_printf(imapd_out, "* FOREVER %u\r\n", n++); - prot_flush(imapd_out); - r = cmd_cancelled(); - } - - prot_printf(imapd_out, "%s OK %s\r\n", tag, error_message(r)); -} - -static void cmd_xmeid(const char *tag, const char *id) -{ - mboxevent_set_client_id(id); - - prot_printf(imapd_out, "%s OK %s\r\n", tag, - error_message(IMAP_OK_COMPLETED)); }
View file
cyrus-imapd-2.5.tar.gz/imap/imapd.h
Changed
@@ -46,11 +46,8 @@ #include "annotate.h" #include "hash.h" #include "mailbox.h" -#include "message.h" #include "prot.h" #include "strarray.h" -#include "search_expr.h" -#include "conversations.h" /* Userid client has logged in as */ extern char *imapd_userid; @@ -106,7 +103,6 @@ strarray_t attribs; int isadmin; struct auth_state *authstate; - hash_table *cidhash; /* for XCONVFETCH */ }; /* Bitmasks for fetchitems */ @@ -125,13 +121,7 @@ /* FETCH_UNCACHEDHEADER = (1<<11) -- obsolete */ FETCH_IS_PARTIAL = (1<<12), /* this is the PARTIAL command */ FETCH_MODSEQ = (1<<13), - FETCH_ANNOTATION = (1<<14), - FETCH_GUID = (1<<15), - FETCH_SHA1 = (1<<16), - FETCH_FILESIZE = (1<<17), - FETCH_CID = (1<<18), - FETCH_FOLDER = (1<<19), - FETCH_UIDVALIDITY = (1<<20) + FETCH_ANNOTATION = (1<<14) }; enum { @@ -148,7 +138,7 @@ int silent; int seen; /* for STORE_*_FLAGS */ - uint32_t system_flags; + bit32 system_flags; /* Note that we must pass the user flags as names because the * lookup of user flag names must proceed under the index lock */ strarray_t flags; @@ -177,48 +167,73 @@ }; struct searchannot { - struct searchannot *next; /* gnb:TODO remove */ + struct searchannot *next; char *entry; char *attrib; - struct namespace *namespace; /* gnb:TODO get this from searchargs */ - int isadmin; /* gnb:TODO get this from searchargs */ - const char *userid; /* gnb:TODO get this from searchargs */ - struct auth_state *auth_state; /* gnb:TODO get this from searchargs */ + struct namespace *namespace; + int isadmin; + const char *userid; + struct auth_state *auth_state; struct buf value; }; -/* Flags for searchargs.state */ -enum { - GETSEARCH_CHARSET_KEYWORD = 0x01, - GETSEARCH_RETURN = 0x02, - GETSEARCH_CHARSET_FIRST = 0x04, +struct searchsub { + struct searchsub *next; + struct searchargs *sub1; + /* + * If sub2 is null, then sub1 is NOT'ed. + * Otherwise sub1 and sub2 are OR'ed. + */ + struct searchargs *sub2; }; +/* Bitmasks for search flags */ +enum { + SEARCH_RECENT_SET = (1<<0), + SEARCH_RECENT_UNSET = (1<<1), + SEARCH_SEEN_SET = (1<<2), + SEARCH_SEEN_UNSET = (1<<3) +/* SEARCH_UNCACHEDHEADER = (1<<4) -- obsolete */ +}; /* Bitmasks for search return options */ enum { SEARCH_RETURN_MIN = (1<<0), SEARCH_RETURN_MAX = (1<<1), SEARCH_RETURN_ALL = (1<<2), - SEARCH_RETURN_COUNT = (1<<3), - SEARCH_RETURN_RELEVANCY = (1<<4) + SEARCH_RETURN_COUNT = (1<<3) }; /* Things that may be searched for */ struct searchargs { - struct search_expr *root; - int charset; - int state; - /* used only during parsing */ - int fuzzy_depth; + int flags; + unsigned smaller, larger; + time_t before, after; + time_t sentbefore, sentafter; + bit32 system_flags_set; + bit32 system_flags_unset; + bit32 user_flags_setMAX_USER_FLAGS/32; + bit32 user_flags_unsetMAX_USER_FLAGS/32; + struct seqset *sequence; + struct seqset *uidsequence; + struct strlist *from; + struct strlist *to; + struct strlist *cc; + struct strlist *bcc; + struct strlist *subject; + struct strlist *messageid; + struct strlist *body; + struct strlist *text; + struct strlist *header_name, *header; + struct searchsub *sublist; + modseq_t modseq; + struct searchannot *annotations; + + bit32 cache_atleast; - /* For ESEARCH & XCONVMULTISORT */ + /* For ESEARCH */ const char *tag; int returnopts; - struct namespace *namespace; - const char *userid; - struct auth_state *authstate; - int isadmin; }; /* Sort criterion */ @@ -230,99 +245,40 @@ char *entry; char *userid; } annot; - struct { - char *name; - } flag; } args; }; /* Values for sort keys */ enum { SORT_SEQUENCE = 0, - SORT_ARRIVAL, /* RFC 5256 */ - SORT_CC, /* RFC 5256 */ - SORT_DATE, /* RFC 5256 */ - SORT_DISPLAYFROM, /* RFC 5957 */ - SORT_DISPLAYTO, /* RFC 5957 */ - SORT_FROM, /* RFC 5256 */ - SORT_SIZE, /* RFC 5256 */ - SORT_SUBJECT, /* RFC 5256 */ - SORT_TO, /* RFC 5256 */ - SORT_ANNOTATION, /* RFC 5257 */ - SORT_MODSEQ, /* nonstandard */ - SORT_UID, /* nonstandard */ - SORT_HASFLAG, /* nonstandard */ - SORT_CONVMODSEQ, /* nonstandard */ - SORT_CONVEXISTS, /* nonstandard */ - SORT_CONVSIZE, /* nonstandard */ - SORT_HASCONVFLAG, /* nonstandard */ - SORT_FOLDER, /* nonstandard */ - SORT_RELEVANCY /* RFC 6203 */ + SORT_ARRIVAL, + SORT_CC, + SORT_DATE, + SORT_DISPLAYFROM, + SORT_DISPLAYTO, + SORT_FROM, + SORT_SIZE, + SORT_SUBJECT, + SORT_TO, + SORT_ANNOTATION, + SORT_MODSEQ, + SORT_UID /* values > 255 are reserved for internal use */ }; /* Sort key modifier flag bits */ -#define SORT_REVERSE (1<<0) /* RFC 5256 */ - -/* Windowing arguments for the XCONVSORT command */ -struct windowargs { - int conversations; /* whether to limit the results by - conversation id */ - uint32_t limit; /* limit on how many messages to return, - * 0 means unlimited. */ - uint32_t position; /* 1-based index into results of first - * message to return. 0 means not - * specified which is the same as 1. - * Mutually exclusive with @anchor */ - uint32_t anchor; /* UID of a message used to locate the - * start of the window; 0 means not - * specified. If the anchor is found, - * the first message reported will be - * the largest of 1 and the anchor minus - * @offset. If specified but not found, - * an error will be returned. Mutually - * exclusive with @position.*/ - char *anchorfolder; /* internal mboxname of a folder to - * which the anchor applies; only used - * for XCONVMULTISORT. */ - uint32_t offset; - int changedsince; /* if 1, show messages a) added since @uidnext, - * b) removed since @modseq, or c) modified - * since @modseq */ - modseq_t modseq; - uint32_t uidnext; - uint32_t upto; /* UID of a message used to terminate an - * XCONVUPDATES early, 0 means not - * specified. */ -}; - -struct snippetargs -{ - struct snippetargs *next; - char *mboxname; /* internal */ - uint32_t uidvalidity; - struct { - uint32_t *data; - int count; - int alloc; - } uids; -}; +#define SORT_REVERSE (1<<0) /* Bitmask for status queries */ enum { - STATUS_MESSAGES = (1<<0), + STATUS_MESSAGES = (1<<0), STATUS_RECENT = (1<<1), STATUS_UIDNEXT = (1<<2), STATUS_UIDVALIDITY = (1<<3), STATUS_UNSEEN = (1<<4), - STATUS_HIGHESTMODSEQ = (1<<5), - STATUS_XCONVEXISTS = (1<<6), - STATUS_XCONVUNSEEN = (1<<7), - STATUS_XCONVMODSEQ = (1<<8) + STATUS_HIGHESTMODSEQ = (1<<5) }; -#define STATUS_CONVITEMS (STATUS_XCONVEXISTS|STATUS_XCONVUNSEEN|STATUS_XCONVMODSEQ) - /* Arguments to List functions */ struct listargs { unsigned cmd; /* Command variant */ @@ -332,7 +288,7 @@ strarray_t pat; /* Mailbox pattern(s) */ const char *scan; /* SCAN content */ hash_table server_table; /* for proxying SCAN */ - unsigned statusitems; /* for RETURN STATUS */ + unsigned statusitems; /* for RETURN STATUS */ }; /* Value for List command variant */ @@ -356,8 +312,7 @@ LIST_RET_SUBSCRIBED = (1<<0), LIST_RET_CHILDREN = (1<<1), LIST_RET_SPECIALUSE = (1<<2), - LIST_RET_STATUS = (1<<3), - LIST_RET_MYRIGHTS = (1<<4) + LIST_RET_STATUS = (1<<3) }; /* Bitmask for List name attributes */ @@ -380,7 +335,7 @@ /* Bitmask for client capabilities */ enum { CAPA_CONDSTORE = (1<<0), - CAPA_QRESYNC = (1<<1) + CAPA_QRESYNC = (1<<1) }; /* Bitmask for urlfetch params */
View file
cyrus-imapd-2.5.tar.gz/imap/imapparse.c
Changed
@@ -47,10 +47,6 @@ #include "exitcodes.h" #include "global.h" -#include "imparse.h" -#include "imap_err.h" -#include "search_expr.h" -#include "imapd.h" /* for struct searchargs */ #include "prot.h" #include "util.h" #include "xmalloc.h" @@ -188,8 +184,8 @@ * than whitespace, parens, or double quotes */ for (;;) { - if (c == EOF || isspace(c) || c == '(' || - c == ')' || c == '\"') { + if (c == EOF || isspace(c) || c == '(' || + c == ')' || c == '\"') { /* gotta handle NIL here too */ if ((flags & GXS_NIL) && buf->len == 3 && !memcmp(buf->s, "NIL", 3)) buf_free(buf); @@ -361,826 +357,3 @@ if (c == EOF) return; } } - -/* - * Parse a "date", for SEARCH criteria - * The time_t's pointed to by 'start' and 'end' are set to the - * times of the start and end of the parsed date. - */ -static int get_search_date(struct protstream *pin, time_t *start, time_t *end) -{ - int c; - struct tm tm; - int quoted = 0; - char month4; - static const char *monthname = { - "jan", "feb", "mar", "apr", "may", "jun", - "jul", "aug", "sep", "oct", "nov", "dec" - }; - - memset(&tm, 0, sizeof tm); - - c = prot_getc(pin); - if (c == '\"') { - quoted++; - c = prot_getc(pin); - } - - /* Day of month */ - if (!isdigit(c)) goto baddate; - tm.tm_mday = c - '0'; - c = prot_getc(pin); - if (isdigit(c)) { - tm.tm_mday = tm.tm_mday * 10 + c - '0'; - c = prot_getc(pin); - } - - if (c != '-') goto baddate; - c = prot_getc(pin); - - /* Month name */ - if (!isalpha(c)) goto baddate; - month0 = c; - c = prot_getc(pin); - if (!isalpha(c)) goto baddate; - month1 = c; - c = prot_getc(pin); - if (!isalpha(c)) goto baddate; - month2 = c; - c = prot_getc(pin); - month3 = '\0'; - lcase(month); - - for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) { - if (!strcmp(month, monthnametm.tm_mon)) break; - } - if (tm.tm_mon == 12) goto baddate; - - if (c != '-') goto baddate; - c = prot_getc(pin); - - /* Year */ - if (!isdigit(c)) goto baddate; - tm.tm_year = c - '0'; - c = prot_getc(pin); - if (!isdigit(c)) goto baddate; - tm.tm_year = tm.tm_year * 10 + c - '0'; - c = prot_getc(pin); - if (isdigit(c)) { - if (tm.tm_year < 19) goto baddate; - tm.tm_year -= 19; - tm.tm_year = tm.tm_year * 10 + c - '0'; - c = prot_getc(pin); - if (!isdigit(c)) goto baddate; - tm.tm_year = tm.tm_year * 10 + c - '0'; - c = prot_getc(pin); - } - - if (quoted) { - if (c != '\"') goto baddate; - c = prot_getc(pin); - } - - tm.tm_isdst = -1; - *start = mktime(&tm); - - tm.tm_hour = 24; - tm.tm_isdst = -1; - *end = mktime(&tm); - - return c; - - baddate: - prot_ungetc(c, pin); - return EOF; -} - -/* - * Parse search return options - */ -static int get_search_return_opts(struct protstream *pin, - struct protstream *pout, - struct searchargs *searchargs) -{ - int c; - static struct buf opt; - - c = prot_getc(pin); - if (c != '(') { - prot_printf(pout, - "%s BAD Missing return options in Search\r\n", searchargs->tag); - return EOF; - } - - do { - c = getword(pin, &opt); - if (!opt.s0) break; - - lcase(opt.s); - if (!strcmp(opt.s, "min")) { - searchargs->returnopts |= SEARCH_RETURN_MIN; - } - else if (!strcmp(opt.s, "max")) { - searchargs->returnopts |= SEARCH_RETURN_MAX; - } - else if (!strcmp(opt.s, "all")) { - searchargs->returnopts |= SEARCH_RETURN_ALL; - } - else if (!strcmp(opt.s, "count")) { - searchargs->returnopts |= SEARCH_RETURN_COUNT; - } - else if (!strcmp(opt.s, "relevancy")) { /* RFC 6203 */ - searchargs->returnopts |= SEARCH_RETURN_RELEVANCY; - } - else { - prot_printf(pout, - "%s BAD Invalid Search return option %s\r\n", - searchargs->tag, opt.s); - return EOF; - } - - } while (c == ' '); - - /* rfc4731: - * If the list of result options is empty, that requests the server to - * return an ESEARCH response instead of the SEARCH response. This is - * equivalent to "(ALL)". - */ - if (!searchargs->returnopts) - searchargs->returnopts = SEARCH_RETURN_ALL; - - if (c != ')') { - prot_printf(pout, - "%s BAD Missing close parenthesis in Search\r\n", - searchargs->tag); - return EOF; - } - - c = prot_getc(pin); - - return c; -} - -/* - * Parse a ANNOTATION item for SEARCH (RFC5257) into a struct - * searchannot and append it to the chain of such structures at *lp. - * Returns the next character. - */ -static int get_search_annotation(struct protstream *pin, - struct protstream *pout, - struct searchargs *base, - int c, struct searchannot **lp) -{ - struct searchannot *sa; - struct buf entry = BUF_INITIALIZER; - struct buf attrib = BUF_INITIALIZER; - struct buf value = BUF_INITIALIZER; - - if (c != ' ') - return EOF; - - /* parse the entry */ - c = getastring(pin, pout, &entry); - if (!entry.len || c != ' ') { - c = EOF; - goto out; - } - - /* parse the attrib */ - c = getastring(pin, pout, &attrib); - if (!attrib.len || c != ' ') { - c = EOF; - goto out; - } - if (strcmp(attrib.s, "value") && - strcmp(attrib.s, "value.shared") && - strcmp(attrib.s, "value.priv")) { - c = EOF; - goto out; - } - - /* parse the value */ - c = getbnstring(pin, pout, &value); - if (c == EOF) - goto out; - - sa = xzmalloc(sizeof(*sa)); - sa->entry = buf_release(&entry); - sa->attrib = buf_release(&attrib); - sa->namespace = base->namespace; - sa->isadmin = base->isadmin; - sa->userid = base->userid; - sa->auth_state = base->authstate; - buf_move(&sa->value, &value); - - *lp = sa; - -out: - buf_free(&entry); - buf_free(&attrib); - buf_free(&value); - return c; -} - - -static void string_match(search_expr_t *parent, const char *val, - const char *aname, struct searchargs *base) -{ - search_expr_t *e; - const search_attr_t *attr = search_attr_find(aname); - enum search_op op = SEOP_MATCH; - - if (base->fuzzy_depth > 0 && - search_attr_is_fuzzable(attr)) - op = SEOP_FUZZYMATCH; - - e = search_expr_new(parent, op); - e->attr = attr; - e->value.s = charset_convert(val, base->charset, charset_flags); - if (!e->value.s) { - e->op = SEOP_FALSE; - e->attr = NULL; - } -} - -static void systemflag_match(search_expr_t *parent, unsigned int flag, int not) -{ - search_expr_t *e; - - if (not) - parent = search_expr_new(parent, SEOP_NOT); - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find("systemflags"); - e->value.u = flag; -} - -static void indexflag_match(search_expr_t *parent, unsigned int flag, int not) -{ - search_expr_t *e; - - if (not) - parent = search_expr_new(parent, SEOP_NOT); - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find("indexflags"); - e->value.u = flag; -} - -static void convflag_match(search_expr_t *parent, const char *flagname, int not) -{ - search_expr_t *e; - - if (not) - parent = search_expr_new(parent, SEOP_NOT); - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find("convflags"); - e->value.s = xstrdup(flagname); -} - -static void date_range(search_expr_t *parent, const char *aname, - time_t start, time_t end) -{ - search_expr_t *e; - const search_attr_t *attr = search_attr_find(aname); - - parent = search_expr_new(parent, SEOP_AND); - - e = search_expr_new(parent, SEOP_LT); - e->attr = attr; - e->value.u = end; - - e = search_expr_new(parent, SEOP_GE); - e->attr = attr; - e->value.u = start; -} - -/* - * Parse a single search criterion - */ -static int get_search_criterion(struct protstream *pin, - struct protstream *pout, - search_expr_t *parent, - struct searchargs *base) -{ - static struct buf criteria, arg, arg2; - search_expr_t *e; - int c; - int keep_charset = 0; - time_t start, end, now = time(0); - uint32_t u; - int hasconv = config_getswitch(IMAPOPT_CONVERSATIONS); - char mboxnameMAX_MAILBOX_NAME; - - if (base->state & GETSEARCH_CHARSET_FIRST) { - c = getcharset(pin, pout, &arg); - if (c != ' ') goto missingcharset; - lcase(arg.s); - base->charset = charset_lookupname(arg.s); - if (base->charset == -1) goto badcharset; - base->state &= ~GETSEARCH_CHARSET_FIRST; - } - - c = getword(pin, &criteria); - lcase(criteria.s); - switch (criteria.s0) { - case '\0': - if (c != '(') goto badcri; - e = search_expr_new(parent, SEOP_AND); - do { - c = get_search_criterion(pin, pout, e, base); - } while (c == ' '); - if (c == EOF) return EOF; - if (c != ')') { - prot_printf(pout, "%s BAD Missing required close paren in Search command\r\n", - base->tag); - if (c != EOF) prot_ungetc(c, pin); - return EOF; - } - c = prot_getc(pin); - break; - - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '*': /* RFC 3501 */ - if (imparse_issequence(criteria.s)) { - struct seqset *seq; - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find("msgno"); - seq = seqset_parse(criteria.s, NULL, /*maxval*/0); - if (!seq) goto badcri; - seqset_free(seq); - e->value.s = xstrdup(criteria.s); - } - else goto badcri; - break; - - case 'a': - if (!strcmp(criteria.s, "answered")) { /* RFC 3501 */ - systemflag_match(parent, FLAG_ANSWERED, /*not*/0); - } - else if (!strcmp(criteria.s, "all")) { /* RFC 3501 */ - search_expr_new(parent, SEOP_TRUE); - break; - } - else if (!strcmp(criteria.s, "annotation")) { /* RFC 5259 */ - struct searchannot *annot = NULL; - c = get_search_annotation(pin, pout, base, c, &annot); - if (c == EOF) - goto badcri; - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find("annotation"); - e->value.annot = annot; - } - else goto badcri; - break; - - case 'b': - if (!strcmp(criteria.s, "before")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = get_search_date(pin, &start, &end); - if (c == EOF) goto baddate; - e = search_expr_new(parent, SEOP_LT); - e->attr = search_attr_find("internaldate"); - e->value.u = start; - } - else if (!strcmp(criteria.s, "bcc")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c == EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else if (!strcmp(criteria.s, "body")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c == EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else goto badcri; - break; - - case 'c': - if (!strcmp(criteria.s, "cc")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c == EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else if (hasconv && !strcmp(criteria.s, "convflag")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getword(pin, &arg); - lcase(arg.s); - convflag_match(parent, arg.s, /*not*/0); - } - else if (hasconv && !strcmp(criteria.s, "convread")) { /* nonstandard */ - convflag_match(parent, "\\Seen", /*not*/0); - } - else if (hasconv && !strcmp(criteria.s, "convunread")) { /* nonstandard */ - convflag_match(parent, "\\Seen", /*not*/1); - } - else if (hasconv && !strcmp(criteria.s, "convseen")) { /* nonstandard */ - convflag_match(parent, "\\Seen", /*not*/0); - } - else if (hasconv && !strcmp(criteria.s, "convunseen")) { /* nonstandard */ - convflag_match(parent, "\\Seen", /*not*/1); - } - else if (hasconv && !strcmp(criteria.s, "convmodseq")) { /* nonstandard */ - modseq_t ms; - if (c != ' ') goto missingarg; - c = getmodseq(pin, &ms); - if (c == EOF) goto badnumber; - e = search_expr_new(parent, SEOP_GE); - e->attr = search_attr_find("convmodseq"); - e->value.u = ms; - } - else if ((base->state & GETSEARCH_CHARSET_KEYWORD) - && !strcmp(criteria.s, "charset")) { /* RFC 3501 */ - if (c != ' ') goto missingcharset; - c = getcharset(pin, pout, &arg); - if (c != ' ') goto missingcharset; - lcase(arg.s); - base->charset = charset_lookupname(arg.s); - if (base->charset == -1) goto badcharset; - } - else if (!strcmp(criteria.s, "cid")) { /* nonstandard */ - conversation_id_t cid; - if (c != ' ') goto missingarg; - c = getword(pin, &arg); - if (c == EOF) goto badnumber; - if (!conversation_id_decode(&cid, arg.s)) goto badnumber; - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find("cid"); - e->value.u = cid; - } - else goto badcri; - break; - - case 'd': - if (!strcmp(criteria.s, "deleted")) { /* RFC 3501 */ - systemflag_match(parent, FLAG_DELETED, /*not*/0); - } - else if (!strcmp(criteria.s, "draft")) { /* RFC 3501 */ - systemflag_match(parent, FLAG_DRAFT, /*not*/0); - } - else goto badcri; - break; - - case 'f': - if (!strcmp(criteria.s, "flagged")) { /* RFC 3501 */ - systemflag_match(parent, FLAG_FLAGGED, /*not*/0); - } - else if (!strcmp(criteria.s, "folder")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c == EOF) goto missingarg; - base->namespace->mboxname_tointernal(base->namespace, arg.s, - base->userid, mboxname); - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find("folder"); - e->value.s = xstrdup(mboxname); - } - else if (!strcmp(criteria.s, "from")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c == EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else if (!strcmp(criteria.s, "fuzzy")) { /* RFC 6203 */ - if (c != ' ') goto missingarg; - base->fuzzy_depth++; - c = get_search_criterion(pin, pout, parent, base); - base->fuzzy_depth--; - if (c == EOF) return EOF; - break; - } - else goto badcri; - break; - - case 'h': - if (!strcmp(criteria.s, "header")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg2); - if (c == EOF) goto missingarg; - - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find_field(arg.s); - e->value.s = charset_convert(arg2.s, base->charset, charset_flags); - if (!e->value.s) { - e->op = SEOP_FALSE; - e->attr = NULL; - } - } - else goto badcri; - break; - - case 'k': - if (!strcmp(criteria.s, "keyword")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getword(pin, &arg); - if (!imparse_isatom(arg.s)) goto badflag; - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find("keyword"); - e->value.s = xstrdup(arg.s); - } - else goto badcri; - break; - - case 'l': - if (!strcmp(criteria.s, "larger")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getint32(pin, (int32_t *)&u); - if (c == EOF) goto badnumber; - e = search_expr_new(parent, SEOP_GT); - e->attr = search_attr_find("size"); - e->value.u = u; - } - else goto badcri; - break; - - case 'm': - if (!strcmp(criteria.s, "modseq")) { /* RFC 4551 */ - modseq_t modseq; - if (c != ' ') goto missingarg; - /* Check for optional search-modseq-ext */ - c = getqstring(pin, pout, &arg); - if (c != EOF) { - if (c != ' ') goto missingarg; - c = getword(pin, &arg); - if (c != ' ') goto missingarg; - } - c = getmodseq(pin, &modseq); - if (c == EOF) goto badnumber; - e = search_expr_new(parent, SEOP_GE); - e->attr = search_attr_find("modseq"); - e->value.u = modseq; - } - else goto badcri; - break; - - case 'n': - if (!strcmp(criteria.s, "not")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - e = search_expr_new(parent, SEOP_NOT); - c = get_search_criterion(pin, pout, e, base); - if (c == EOF) return EOF; - } - else if (!strcmp(criteria.s, "new")) { /* RFC 3501 */ - e = search_expr_new(parent, SEOP_AND); - indexflag_match(e, MESSAGE_SEEN, /*not*/1); - indexflag_match(e, MESSAGE_RECENT, /*not*/0); - } - else goto badcri; - break; - - case 'o': - if (!strcmp(criteria.s, "or")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - e = search_expr_new(parent, SEOP_OR); - c = get_search_criterion(pin, pout, e, base); - if (c == EOF) return EOF; - if (c != ' ') goto missingarg; - c = get_search_criterion(pin, pout, e, base); - if (c == EOF) return EOF; - } - else if (!strcmp(criteria.s, "old")) { /* RFC 3501 */ - indexflag_match(parent, MESSAGE_RECENT, /*not*/1); - } - else if (!strcmp(criteria.s, "older")) { /* RFC 5032 */ - if (c != ' ') goto missingarg; - c = getint32(pin, (int32_t *)&u); - if (c == EOF) goto badinterval; - e = search_expr_new(parent, SEOP_LE); - e->attr = search_attr_find("internaldate"); - e->value.u = now - u; - } - else if (!strcmp(criteria.s, "on")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = get_search_date(pin, &start, &end); - if (c == EOF) goto baddate; - date_range(parent, "internaldate", start, end); - } - else goto badcri; - break; - - case 'r': - if (!strcmp(criteria.s, "recent")) { /* RFC 3501 */ - indexflag_match(parent, MESSAGE_RECENT, /*not*/0); - } - else if ((base->state & GETSEARCH_RETURN) && - !strcmp(criteria.s, "return")) { /* RFC 4731 */ - c = get_search_return_opts(pin, pout, base); - if (c == EOF) return EOF; - keep_charset = 1; - } - else goto badcri; - break; - - case 's': - if (!strcmp(criteria.s, "seen")) { /* RFC 3501 */ - indexflag_match(parent, MESSAGE_SEEN, /*not*/0); - } - else if (!strcmp(criteria.s, "sentbefore")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = get_search_date(pin, &start, &end); - if (c == EOF) goto baddate; - e = search_expr_new(parent, SEOP_LT); - e->attr = search_attr_find("sentdate"); - e->value.u = start; - } - else if (!strcmp(criteria.s, "senton")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = get_search_date(pin, &start, &end); - if (c == EOF) goto baddate; - date_range(parent, "sentdate", start, end); - } - else if (!strcmp(criteria.s, "sentsince")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = get_search_date(pin, &start, &end); - if (c == EOF) goto baddate; - e = search_expr_new(parent, SEOP_GE); - e->attr = search_attr_find("sentdate"); - e->value.u = start; - } - else if (!strcmp(criteria.s, "since")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = get_search_date(pin, &start, &end); - if (c == EOF) goto baddate; - e = search_expr_new(parent, SEOP_GE); - e->attr = search_attr_find("internaldate"); - e->value.u = start; - } - else if (!strcmp(criteria.s, "smaller")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getint32(pin, (int32_t *)&u); - if (c == EOF) goto badnumber; - e = search_expr_new(parent, SEOP_LT); - e->attr = search_attr_find("size"); - e->value.u = u; - } - else if (!strcmp(criteria.s, "subject")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c == EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else goto badcri; - break; - - case 't': - if (!strcmp(criteria.s, "to")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c == EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else if (!strcmp(criteria.s, "text")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c == EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else goto badcri; - break; - - case 'u': - if (!strcmp(criteria.s, "uid")) { /* RFC 3501 */ - struct seqset *seq; - if (c != ' ') goto missingarg; - c = getword(pin, &arg); - if (!imparse_issequence(arg.s)) goto badcri; - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find(criteria.s); - seq = seqset_parse(arg.s, NULL, /*maxval*/0); - if (!seq) goto badcri; - seqset_free(seq); - e->value.s = xstrdup(arg.s); - } - else if (!strcmp(criteria.s, "unseen")) { /* RFC 3501 */ - indexflag_match(parent, MESSAGE_SEEN, /*not*/1); - } - else if (!strcmp(criteria.s, "unanswered")) { /* RFC 3501 */ - systemflag_match(parent, FLAG_ANSWERED, /*not*/1); - } - else if (!strcmp(criteria.s, "undeleted")) { /* RFC 3501 */ - systemflag_match(parent, FLAG_DELETED, /*not*/1); - } - else if (!strcmp(criteria.s, "undraft")) { /* RFC 3501 */ - systemflag_match(parent, FLAG_DRAFT, /*not*/1); - } - else if (!strcmp(criteria.s, "unflagged")) { /* RFC 3501 */ - systemflag_match(parent, FLAG_FLAGGED, /*not*/1); - } - else if (!strcmp(criteria.s, "unkeyword")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getword(pin, &arg); - if (!imparse_isatom(arg.s)) goto badflag; - e = search_expr_new(parent, SEOP_NOT); - e = search_expr_new(e, SEOP_MATCH); - e->attr = search_attr_find("keyword"); - e->value.s = xstrdup(arg.s); - } - else goto badcri; - break; - - case 'x': - if (!strcmp(criteria.s, "xlistid")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c == EOF) goto missingarg; - string_match(parent, arg.s, "listid", base); - } - else if (!strcmp(criteria.s, "xcontenttype")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); - if (c == EOF) goto missingarg; - string_match(parent, arg.s, "contenttype", base); - } - else goto badcri; - break; - - case 'y': - if (!strcmp(criteria.s, "younger")) { /* RFC 5032 */ - if (c != ' ') goto missingarg; - c = getint32(pin, (int32_t *)&u); - if (c == EOF) goto badinterval; - e = search_expr_new(parent, SEOP_GE); - e->attr = search_attr_find("internaldate"); - e->value.u = now - u; - } - else goto badcri; - break; - - default: - badcri: - prot_printf(pout, "%s BAD Invalid Search criteria\r\n", base->tag); - if (c != EOF) prot_ungetc(c, pin); - return EOF; - } - - if (!keep_charset) - base->state &= ~GETSEARCH_CHARSET_KEYWORD; - base->state &= ~GETSEARCH_RETURN; - - return c; - - missingarg: - prot_printf(pout, "%s BAD Missing required argument to Search %s\r\n", - base->tag, criteria.s); - if (c != EOF) prot_ungetc(c, pin); - return EOF; - - badflag: - prot_printf(pout, "%s BAD Invalid flag name %s in Search command\r\n", - base->tag, arg.s); - if (c != EOF) prot_ungetc(c, pin); - return EOF; - - baddate: - prot_printf(pout, "%s BAD Invalid date in Search command\r\n", - base->tag); - if (c != EOF) prot_ungetc(c, pin); - return EOF; - - badnumber: - prot_printf(pout, "%s BAD Invalid number in Search command\r\n", - base->tag); - if (c != EOF) prot_ungetc(c, pin); - return EOF; - - badinterval: - prot_printf(pout, "%s BAD Invalid interval in Search command\r\n", - base->tag); - if (c != EOF) prot_ungetc(c, pin); - return EOF; - - missingcharset: - prot_printf(pout, "%s BAD Missing charset\r\n", - base->tag); - if (c != EOF) prot_ungetc(c, pin); - return EOF; - - badcharset: - prot_printf(pout, "%s BAD %s\r\n", base->tag, - error_message(IMAP_UNRECOGNIZED_CHARSET)); - if (c != EOF) prot_ungetc(c, pin); - return EOF; -} - -/* - * Parse a search program - */ -EXPORTED int get_search_program(struct protstream *pin, - struct protstream *pout, - struct searchargs *searchargs) -{ - int c; - - searchargs->root = search_expr_new(NULL, SEOP_AND); - - do { - c = get_search_criterion(pin, pout, searchargs->root, searchargs); - } while (c == ' '); - - return c; -} -
View file
cyrus-imapd-2.5.tar.gz/imap/imapparse.h
Deleted
@@ -1,94 +0,0 @@ -/* imapparse.h -- Header for IMAP parsing functions - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __CYRUS_IMAP_PARSE_H__ -#define __CYRUS_IMAP_PARSE_H__ - -#include "libconfig.h" -#include "prot.h" -#include "index.h" - -/* imap parsing functions (imapparse.c) */ -int getword(struct protstream *in, struct buf *buf); - -/* Flags for getxstring() */ -/* IMAP_BIN_ASTRING is an IMAP_ASTRING that does not perform the - * does-not-contain-a-NULL check (in the case of a literal) */ -enum getxstring_flags { - GXS_ATOM = (1<<0), /* result may be a bare atom */ - GXS_QUOTED = (1<<1), /* result may be "quoted" */ - GXS_LITERAL = (1<<2), /* result may be {N}literal */ - GXS_NIL = (1<<3), /* result may be the special atom NIL */ - GXS_BINARY = (1<<4), /* result may contain embedded NULs */ - - IMAP_ASTRING = GXS_ATOM|GXS_QUOTED|GXS_LITERAL, - IMAP_BIN_ASTRING = IMAP_ASTRING|GXS_BINARY, - IMAP_NSTRING = GXS_NIL|GXS_QUOTED|GXS_LITERAL, - IMAP_BIN_NSTRING = IMAP_NSTRING|GXS_BINARY, - IMAP_QSTRING = GXS_QUOTED, - IMAP_STRING = GXS_QUOTED|GXS_LITERAL, - - /* note: there's some consistency issues here... the special - * value "NIL" must be quoted to get returned as a string */ - IMAP_NASTRING = GXS_NIL|GXS_ATOM|GXS_QUOTED|GXS_LITERAL, -}; - -int getxstring(struct protstream *pin, struct protstream *pout, - struct buf *buf, enum getxstring_flags); -#define getastring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_ASTRING) -#define getbastring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_BIN_ASTRING) -#define getnstring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_NSTRING) -#define getbnstring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_BIN_NSTRING) -#define getqstring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_QSTRING) -#define getstring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_STRING) -#define getnastring(pin, pout, buf) getxstring((pin), (pout), (buf), IMAP_NASTRING) -#define getcharset(pin, pout, buf) getxstring((pin), (pout), (buf), GXS_ATOM|GXS_QUOTED) -int getint32(struct protstream *pin, int *num); -int getsint32(struct protstream *pin, int *num); -int getuint32(struct protstream *pin, unsigned int *num); -int getmodseq(struct protstream *pin, modseq_t *num); - -void eatline(struct protstream *pin, int c); - -int get_search_program(struct protstream *pin, struct protstream *pout, struct searchargs *searchargs); - -#endif /* __CYRUS_IMAP_PARSE_H__ */
View file
cyrus-imapd-2.5.tar.gz/imap/index.c
Changed
@@ -46,7 +46,6 @@ #include <string.h> #include <sys/types.h> #include <sys/stat.h> -#include <errno.h> #include <fcntl.h> #include <netinet/in.h> #include <syslog.h> @@ -58,12 +57,9 @@ #include "append.h" #include "assert.h" #include "charset.h" -#include "conversations.h" -#include "dlist.h" #include "exitcodes.h" #include "hash.h" -#include "hashu64.h" -#include "imap_err.h" +#include "imap/imap_err.h" #include "global.h" #include "times.h" #include "imapd.h" @@ -73,14 +69,10 @@ #include "message.h" #include "parseaddr.h" #include "search_engines.h" -#include "search_query.h" #include "seen.h" #include "statuscache.h" #include "strhash.h" -#include "user.h" #include "util.h" -#include "xstats.h" -#include "ptrarray.h" #include "xmalloc.h" #include "xstrlcpy.h" @@ -88,7 +80,7 @@ #include "sync_log.h" /* Forward declarations */ -static void index_refresh_locked(struct index_state *state); +static void index_refresh(struct index_state *state); static void index_tellexists(struct index_state *state); static int index_lock(struct index_state *state); static void index_unlock(struct index_state *state); @@ -103,13 +95,15 @@ bit32 removed_user_flagsMAX_USER_FLAGS/32; }; +static void index_checkflags(struct index_state *state, int dirty); + static int index_writeseen(struct index_state *state); static void index_fetchmsg(struct index_state *state, - const struct buf *msg, + const char *msg_base, unsigned long msg_size, unsigned offset, unsigned size, unsigned start_octet, unsigned octet_count); static int index_fetchsection(struct index_state *state, const char *resp, - const struct buf *msg, + const char *msg_base, unsigned long msg_size, char *section, const char *cachestr, unsigned size, unsigned start_octet, unsigned octet_count); @@ -120,6 +114,8 @@ unsigned start_octet, unsigned octet_count); static char *index_readheader(const char *msg_base, unsigned long msg_size, unsigned offset, unsigned size); +static void index_pruneheader(char *buf, const strarray_t *headers, + const strarray_t *headers_not); static void index_fetchheader(struct index_state *state, const char *msg_base, unsigned long msg_size, unsigned size, @@ -130,13 +126,25 @@ unsigned octet_count); static void index_listflags(struct index_state *state); static void index_fetchflags(struct index_state *state, uint32_t msgno); +static int index_search_evaluate(struct index_state *state, + const struct searchargs *searchargs, + uint32_t msgno, struct mapfile *msgfile); +static int index_searchmsg(char *substr, comp_pat *pat, + struct mapfile *msgfile, + int skipheader, const char *cachestr); +static int index_searchheader(char *name, char *substr, comp_pat *pat, + struct mapfile *msgfile, + int size); +static int index_searchcacheheader(struct index_state *state, uint32_t msgno, char *name, char *substr, + comp_pat *pat); +static int _index_search(unsigned **msgno_list, struct index_state *state, + const struct searchargs *searchargs, + modseq_t *highestmodseq); -static int index_copysetup(struct index_state *state, uint32_t msgno, - struct copyargs *copyargs, int is_same_user); +static int index_copysetup(struct index_state *state, uint32_t msgno, struct copyargs *copyargs); static int index_storeflag(struct index_state *state, - struct index_modified_flags *modified_flags, - uint32_t msgno, struct index_record *record, - struct storeargs *storeargs); + struct index_modified_flags *modified_flags, + uint32_t msgno, struct storeargs *storeargs); static int index_store_annotation(struct index_state *state, uint32_t msgno, struct storeargs *storeargs); static int index_fetchreply(struct index_state *state, uint32_t msgno, @@ -149,24 +157,29 @@ static char *_index_extract_subject(char *s, int *is_refwd); static void index_get_ids(MsgData *msgdata, char *envtokens, const char *headers, unsigned size); +static MsgData *index_msgdata_load(struct index_state *state, unsigned *msgno_list, int n, + const struct sortcrit *sortcrit); +static struct seqset *_index_vanished(struct index_state *state, + struct vanished_params *params); +static void *index_sort_getnext(MsgData *node); +static void index_sort_setnext(MsgData *node, MsgData *next); static int index_sort_compare(MsgData *md1, MsgData *md2, const struct sortcrit *call_data); -static int index_sort_compare_qsort(const void *v1, const void *v2); +static void index_msgdata_free(MsgData *md); static void *index_thread_getnext(Thread *thread); static void index_thread_setnext(Thread *thread, Thread *next); static int index_thread_compare(Thread *t1, Thread *t2, const struct sortcrit *call_data); static void index_thread_orderedsubj(struct index_state *state, - unsigned *msgno_list, unsigned int nmsg, + unsigned *msgno_list, int nmsg, int usinguid); static void index_thread_sort(Thread *root, const struct sortcrit *sortcrit); static void index_thread_print(struct index_state *state, Thread *threads, int usinguid); static void index_thread_ref(struct index_state *state, - unsigned *msgno_list, unsigned int nmsg, - int usinguid); + unsigned *msgno_list, int nmsg, int usinguid); static struct seqset *_parse_sequence(struct index_state *state, const char *sequence, int usinguid); @@ -216,7 +229,6 @@ /* restore mutable fields */ recordp->modseq = im->modseq; recordp->system_flags = im->system_flags; - recordp->cache_offset = im->cache_offset; for (i = 0; i < MAX_USER_FLAGS/32; i++) recordp->user_flagsi = im->user_flagsi; @@ -239,7 +251,6 @@ /* update tracking of mutable fields */ im->modseq = recordp->modseq; im->system_flags = recordp->system_flags; - im->cache_offset = recordp->cache_offset; for (i = 0; i < MAX_USER_FLAGS/32; i++) im->user_flagsi = recordp->user_flagsi; @@ -255,7 +266,6 @@ state->mailbox = NULL; /* should be done by close anyway */ } } -static struct sortcrit *the_sortcrit; /* * A mailbox is about to be closed. @@ -296,7 +306,6 @@ state->out = init->out; state->qresync = init->qresync; state->userid = xstrdupnull(init->userid); - state->want_expunged = init->want_expunged; if (state->examining) { r = mailbox_open_irl(state->mboxname, &state->mailbox); @@ -320,17 +329,13 @@ if (r) goto fail; } - if (state->mailbox->mbtype & MBTYPES_NONIMAP) { - r = IMAP_MAILBOX_BADTYPE; - goto fail; - } - /* initialise the index_state */ - index_refresh_locked(state); + index_refresh(state); /* have to get the vanished list while we're still locked */ - if (init) - init->vanishedlist = index_vanished(state, &init->vanished); + if (init && init->vanished.uidvalidity == state->mailbox->i.uidvalidity) { + init->vanishedlist = _index_vanished(state, &init->vanished); + } index_unlock(state); @@ -339,7 +344,6 @@ return 0; fail: - mailbox_close(&state->mailbox); free(state->mboxname); free(state->userid); free(state); @@ -356,7 +360,6 @@ struct index_record record; int numexpunged = 0; struct mboxevent *mboxevent = NULL; - modseq_t oldmodseq; r = index_lock(state); if (r) return r; @@ -386,8 +389,6 @@ if (index_reload_record(state, msgno, &record)) continue; - oldmodseq = im->modseq; - if (!im->isseen) { state->numunseen--; im->isseen = 1; @@ -401,15 +402,10 @@ /* set the flags */ record.system_flags |= FLAG_DELETED | FLAG_EXPUNGED; numexpunged++; - state->num_expunged++; r = index_rewrite_record(state, msgno, &record); if (r) break; - /* avoid telling again (equivalent to STORE FLAGS.SILENT) */ - if (im->told_modseq == oldmodseq) - im->told_modseq = im->modseq; - mboxevent_extract_record(mboxevent, state->mailbox, &record); } @@ -574,19 +570,18 @@ return seenlist; } -static void index_refresh_locked(struct index_state *state) +void index_refresh(struct index_state *state) { struct mailbox *mailbox = state->mailbox; struct index_record record; - uint32_t recno = 1; + uint32_t recno; uint32_t msgno = 1; uint32_t firstnotseen = 0; uint32_t numrecent = 0; uint32_t numunseen = 0; - uint32_t num_expunged = 0; - uint32_t recentuid = 0; - modseq_t delayed_modseq = 0; + uint32_t recentuid; struct index_map *im; + modseq_t delayed_modseq = 0; uint32_t need_records; struct seqset *seenlist; int i; @@ -601,10 +596,6 @@ if (state->last_uid) { need_records = state->exists + (mailbox->i.last_uid - state->last_uid); } - else if (state->want_expunged) { - /* could need the lot! */ - need_records = mailbox->i.num_records; - } else { /* init case */ need_records = mailbox->i.exists; @@ -650,16 +641,12 @@ * find the file */ im->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED; im = &state->mapmsgno++; - - /* this one is expunged */ - num_expunged++; } /* expunged record not in map, can skip immediately. It's * never been told to this connection, so it doesn't need to * get its own msgno */ - if (!state->want_expunged - && (msgno > state->exists || record.uid < im->uid) + if ((msgno > state->exists || record.uid < im->uid) && (record.system_flags & FLAG_EXPUNGED)) continue; @@ -675,12 +662,11 @@ im->recno = recno; im->modseq = record.modseq; im->system_flags = record.system_flags; - im->cache_offset = record.cache_offset; for (i = 0; i < MAX_USER_FLAGS/32; i++) im->user_flagsi = record.user_flagsi; /* for expunged records, just track the modseq */ - if (!state->want_expunged && (im->system_flags & FLAG_EXPUNGED)) { + if (im->system_flags & FLAG_EXPUNGED) { /* http://www.rfc-editor.org/errata_search.php?rfc=5162 * Errata ID: 1809 - if there are expunged records we * aren't telling about, need to make the highestmodseq @@ -720,9 +706,6 @@ } } - if (im->system_flags & FLAG_EXPUNGED) - num_expunged++; - msgno++; /* make sure we don't overflow the memory we mapped */ @@ -749,7 +732,6 @@ im->recno = 0; im->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED; im = &state->mapmsgno++; - num_expunged++; } seqset_free(seenlist); @@ -763,7 +745,6 @@ state->uidvalidity = mailbox->i.uidvalidity; state->last_uid = mailbox->i.last_uid; state->num_records = mailbox->i.num_records; - state->num_expunged = num_expunged; state->firstnotseen = firstnotseen; state->numunseen = numunseen; state->numrecent = numrecent; @@ -781,7 +762,7 @@ index_tellexists(state); /* always print flags */ - index_checkflags(state, 1, 1); + index_checkflags(state, 1); if (state->firstnotseen) prot_printf(state->out, "* OK UNSEEN %u Ok\r\n", @@ -849,6 +830,7 @@ syslog(LOG_WARNING, "Mailbox %s has been (re)moved out from under client", state->mboxname); + mailbox_close(&state->mailbox); fatal("Mailbox has been (re)moved", EC_IOERR); } @@ -890,8 +872,8 @@ /* * Perform UID FETCH (VANISHED) on a sequence. */ -struct seqset *index_vanished(struct index_state *state, - struct vanished_params *params) +static struct seqset *_index_vanished(struct index_state *state, + struct vanished_params *params) { struct mailbox *mailbox = state->mailbox; struct index_record record; @@ -899,14 +881,6 @@ struct seqset *seq; uint32_t recno; - /* check uidvalidity match */ - if (params->uidvalidity_is_max) { - if (params->uidvalidity < mailbox->i.uidvalidity) return NULL; - } - else { - if (params->uidvalidity != mailbox->i.uidvalidity) return NULL; - } - /* No recently expunged messages */ if (params->modseq >= state->highestmodseq) return NULL; @@ -1040,7 +1014,7 @@ } /* seq can be NULL - means "ALL" */ -EXPORTED void index_fetchresponses(struct index_state *state, +void index_fetchresponses(struct index_state *state, struct seqset *seq, int usinguid, const struct fetchargs *fetchargs, @@ -1138,13 +1112,12 @@ if (fetchargs->vanished) { struct vanished_params v; - v.sequence = sequence;; - v.uidvalidity = state->mailbox->i.uidvalidity; + v.sequence = sequence; v.modseq = fetchargs->changedsince; v.match_seq = fetchargs->match_seq; v.match_uid = fetchargs->match_uid; /* XXX - return error unless usinguid? */ - vanishedlist = index_vanished(state, &v); + vanishedlist = _index_vanished(state, &v); } index_unlock(state); @@ -1153,7 +1126,7 @@ mboxevent_notify(mboxevent); mboxevent_free(&mboxevent); - index_checkflags(state, 1, 0); + index_checkflags(state, 0); if (vanishedlist && vanishedlist->len) { char *vanished = seqset_cstring(vanishedlist); @@ -1243,7 +1216,7 @@ case STORE_ADD_FLAGS: case STORE_REMOVE_FLAGS: case STORE_REPLACE_FLAGS: - r = index_storeflag(state, &modified_flags, msgno, &record, storeargs); + r = index_storeflag(state, &modified_flags, msgno, storeargs); if (r) break; @@ -1306,8 +1279,7 @@ struct mailbox *mailbox = state->mailbox; struct index_map *im; uint32_t msgno; - const char *fname; - struct index_record record; + char *fname; syslog(LOG_ERR, "Prefetching initial parts of messages\n"); @@ -1316,10 +1288,7 @@ if (!seqset_ismember(seq, usinguid ? im->uid : msgno)) continue; - if (index_reload_record(state, msgno, &record)) - continue; - - fname = mailbox_record_fname(mailbox, &record); + fname = mailbox_message_fname(mailbox, im->uid); if (!fname) continue; @@ -1400,138 +1369,6 @@ return r; } -EXPORTED int index_warmup(struct mboxlist_entry *mbentry, - unsigned int warmup_flags, - struct seqset *uids) -{ - const char *fname = NULL; - char *tofree1 = NULL; - char *tofree2 = NULL; - unsigned int uid; - strarray_t files = STRARRAY_INITIALIZER; - int i; - int r = 0; - - if (warmup_flags & WARMUP_INDEX) { - fname = mboxname_metapath(mbentry->partition, mbentry->name, META_INDEX, 0); - r = warmup_file(fname, 0, 0); - if (r) goto out; - } - if (warmup_flags & WARMUP_CONVERSATIONS) { - if (config_getswitch(IMAPOPT_CONVERSATIONS)) { - fname = tofree1 = conversations_getmboxpath(mbentry->name); - r = warmup_file(fname, 0, 0); - if (r) goto out; - } - } - if (warmup_flags & WARMUP_ANNOTATIONS) { - fname = mboxname_metapath(mbentry->partition, mbentry->name, META_ANNOTATIONS, 0); - r = warmup_file(fname, 0, 0); - if (r) goto out; - } - if (warmup_flags & WARMUP_FOLDERSTATUS) { - if (config_getswitch(IMAPOPT_STATUSCACHE)) { - fname = tofree2 = statuscache_filename(); - r = warmup_file(fname, 0, 0); - if (r) goto out; - } - } - if (warmup_flags & WARMUP_SEARCH) { - const char *userid = mboxname_to_userid(mbentry->name); - r = search_list_files(userid, &files); - if (r) goto out; - for (i = 0 ; i < files.count ; i++) { - fname = strarray_nth(&files, i); - r = warmup_file(fname, 0, 0); - if (r) goto out; - } - } - while ((uid = seqset_getnext(uids))) { - fname = mboxname_datapath(mbentry->partition, mbentry->name, uid); - r = warmup_file(fname, 0, 0); - if (r) goto out; - } - -out: - if (r == ENOENT || r == ENOSYS) - r = 0; - if (r) - syslog(LOG_ERR, "IOERROR: unable to warmup file %s: %s", - fname, error_message(r)); - free(tofree1); - free(tofree2); - strarray_fini(&files); - return r; -} - -static void build_query(search_builder_t *bx, - search_expr_t *e, - int remove, - int *nmatchesp) -{ - search_expr_t *child; - int bop = -1; - - switch (e->op) { - - case SEOP_NOT: - bop = SEARCH_OP_NOT; - break; - - case SEOP_AND: - bop = SEARCH_OP_AND; - break; - - case SEOP_OR: - bop = SEARCH_OP_OR; - break; - - case SEOP_FUZZYMATCH: - if (e->attr && e->attr->part >= 0) { - bx->match(bx, e->attr->part, e->value.s); - (*nmatchesp)++; - if (remove && e->attr->part != SEARCH_PART_HEADERS) { - /* - * We're relying on the search engine to correctly - * find matching messages, so we don't need to - * keep this node in the expression tree anymore. - * Rather than remove it we neuter it. - */ - search_expr_neutralise(e); - } - } - return; - - default: - return; - } - - if (e->children) { - assert(bop != -1); - bx->begin_boolean(bx, bop); - for (child = e->children ; child ; child = child->next) - build_query(bx, child, remove, nmatchesp); - bx->end_boolean(bx, bop); - } -} - -static int index_prefilter_messages(unsigned* msg_list, - struct index_state *state, - struct searchargs *searchargs __attribute((unused))) -{ - unsigned int msgno; - - xstats_inc(SEARCH_TRIVIAL); - - /* Just put in all possible messages. This falls back to Cyrus' default - * search. */ - - for (msgno = 1; msgno <= state->exists; msgno++) - msg_listmsgno-1 = msgno; - - return state->exists; -} - static int index_scan_work(const char *s, unsigned long len, const char *match, unsigned long min) { @@ -1556,8 +1393,10 @@ int listindex; int listcount; struct searchargs searchargs; + struct strlist strlist; unsigned long length; struct mailbox *mailbox = state->mailbox; + struct index_map *im; if (!(contents && contents0)) return(0); @@ -1569,51 +1408,137 @@ length = strlen(contents); memset(&searchargs, 0, sizeof(struct searchargs)); - searchargs.root = search_expr_new(NULL, SEOP_MATCH); - searchargs.root->attr = search_attr_find("text"); + searchargs.text = &strlist; /* Use US-ASCII to emulate fgrep */ - searchargs.root->value.s = charset_convert(contents, charset_lookupname("US-ASCII"), + strlist.s = charset_convert(contents, charset_lookupname("US-ASCII"), charset_flags); - - search_expr_internalise(state, searchargs.root); + strlist.p = charset_compilepat(strlist.s); + strlist.next = NULL; msgno_list = (unsigned *) xmalloc(state->exists * sizeof(unsigned)); - listcount = index_prefilter_messages(msgno_list, state, &searchargs); + listcount = search_prefilter_messages(msgno_list, state, &searchargs); for (listindex = 0; !n && listindex < listcount; listindex++) { - struct buf msgfile = BUF_INITIALIZER; - struct index_record record; + struct mapfile msgfile = MAPFILE_INITIALIZER; msgno = msgno_listlistindex; - if (index_reload_record(state, msgno, &record)) - continue; + im = &state->mapmsgno-1; - if (mailbox_map_record(mailbox, &record, &msgfile)) + if (mailbox_map_message(mailbox, im->uid, + &msgfile.base, &msgfile.size)) continue; - n += index_scan_work(msgfile.s, msgfile.len, contents, length); + n += index_scan_work(msgfile.base, msgfile.size, contents, length); - buf_free(&msgfile); + mailbox_unmap_message(mailbox, im->uid, + &msgfile.base, &msgfile.size); } - search_expr_free(searchargs.root); + free(strlist.s); + free(strlist.p); free(msgno_list); return n; } -EXPORTED message_t *index_get_message(struct index_state *state, uint32_t msgno) +/* + * Guts of the SEARCH command. + * + * Returns message numbers in an array. This function is used by + * SEARCH, SORT and THREAD. + */ +static int _index_search(unsigned **msgno_list, struct index_state *state, + const struct searchargs *searchargs, + modseq_t *highestmodseq) { - struct index_map *im = &state->mapmsgno-1; - struct index_record record; - uint32_t indexflags = 0; - if (im->isseen) indexflags |= MESSAGE_SEEN; - if (im->isrecent) indexflags |= MESSAGE_RECENT; - if (index_reload_record(state, msgno, &record)) - return NULL; - return message_new_from_index(state->mailbox, &record, - msgno, indexflags); + uint32_t msgno; + int n = 0; + int listindex, min; + int listcount; + struct index_map *im; + + if (state->exists <= 0) return 0; + + *msgno_list = (unsigned *) xmalloc(state->exists * sizeof(unsigned)); + + /* OK, so I'm being a bit clever here. We fill the msgno list with + a list of message IDs returned by the search engine. Then we + scan through the list and store matching message IDs back into the + list. This is OK because we only overwrite message IDs that we've + already looked at. */ + listcount = search_prefilter_messages(*msgno_list, state, searchargs); + + if (searchargs->returnopts == SEARCH_RETURN_MAX) { + /* If we only want MAX, then skip forward search, + and do complete reverse search */ + listindex = listcount; + min = 0; + } else { + /* Otherwise use forward search, potentially skipping reverse search */ + listindex = 0; + min = listcount; + } + + /* Forward search. Used for everything other than MAX-only */ + for (; listindex < listcount; listindex++) { + msgno = (*msgno_list)listindex; + im = &state->mapmsgno-1; + + /* expunged messages never match */ + if (im->system_flags & FLAG_EXPUNGED) + continue; + + if (index_search_evaluate(state, searchargs, msgno, NULL)) { + (*msgno_list)n++ = msgno; + if (highestmodseq && im->modseq > *highestmodseq) { + *highestmodseq = im->modseq; + } + + /* See if we should short-circuit + (we want MIN, but NOT COUNT or ALL) */ + if ((searchargs->returnopts & SEARCH_RETURN_MIN) && + !(searchargs->returnopts & SEARCH_RETURN_COUNT) && + !(searchargs->returnopts & SEARCH_RETURN_ALL)) { + + if (searchargs->returnopts & SEARCH_RETURN_MAX) { + /* If we want MAX, setup for reverse search */ + min = listindex; + } + /* We're done */ + listindex = listcount; + if (highestmodseq) + *highestmodseq = im->modseq; + } + } + } + + /* Reverse search. Stops at previously found MIN (if any) */ + for (listindex = listcount; listindex > min; listindex--) { + msgno = (*msgno_list)listindex-1; + im = &state->mapmsgno-1; + + /* expunged messages never match */ + if (im->system_flags & FLAG_EXPUNGED) + continue; + + if (index_search_evaluate(state, searchargs, msgno, NULL)) { + (*msgno_list)n++ = msgno; + if (highestmodseq && im->modseq > *highestmodseq) { + *highestmodseq = im->modseq; + } + /* We only care about MAX, so we're done on first match */ + listindex = 0; + } + } + + /* if we didn't find any matches, free msgno_list */ + if (!n && *msgno_list) { + free(*msgno_list); + *msgno_list = NULL; + } + + return n; } EXPORTED uint32_t index_getuid(struct index_state *state, uint32_t msgno) @@ -1628,21 +1553,21 @@ struct searchargs *searchargs, unsigned **uid_list) { - search_query_t *query = NULL; - search_folder_t *folder = NULL; - int r; - int n = 0; + unsigned *msgno_list; + int i, n; - query = search_query_new(state, searchargs); - r = search_query_run(query); - if (r) goto out; - folder = search_query_find_folder(query, index_mboxname(state)); - if (!folder) goto out; + n = _index_search(&msgno_list, state, searchargs, NULL); + if (n == 0) { + *uid_list = NULL; + return 0; + } - n = search_folder_get_array(folder, uid_list); + *uid_list = msgno_list; + + /* filthy in-place replacement */ + for (i = 0; i < n; i++) + (*uid_list)i = index_getuid(state, msgno_listi); -out: - search_query_free(query); return n; } @@ -1680,7 +1605,7 @@ /* if highestmodseq has changed or file is repacked, read updates */ if (state->highestmodseq != state->mailbox->i.highestmodseq || state->generation != state->mailbox->i.generation_no) - index_refresh_locked(state); + index_refresh(state); return 0; } @@ -1689,24 +1614,8 @@ { int items = STATUS_MESSAGES | STATUS_UIDNEXT | STATUS_UIDVALIDITY | STATUS_HIGHESTMODSEQ | STATUS_RECENT | STATUS_UNSEEN; - int r; - - r = index_refresh(state); - if (r) return r; - statuscache_fill(sdata, state->userid, state->mailbox, items, state->numrecent, state->numunseen); - - return 0; -} - -EXPORTED int index_refresh(struct index_state *state) -{ - int r; - - r = index_lock(state); /* calls index_refresh_locked */ - if (r) return r; - index_unlock(state); return 0; } @@ -1723,124 +1632,79 @@ } /* - * RFC 4551 says: - * If client specifies a MODSEQ criterion in a SEARCH command - * and the server returns a non-empty SEARCH result, the server - * MUST also append (to the end of the untagged SEARCH response) - * the highest mod-sequence for all messages being returned. - */ -static int needs_modseq(const struct searchargs *searchargs, - const struct sortcrit *sortcrit) -{ - int i; - - if (search_expr_uses_attr(searchargs->root, "modseq")) - return 1; - - if (sortcrit) { - for (i = 0 ; sortcriti.key != SORT_SEQUENCE ; i++) - if (sortcriti.key == SORT_MODSEQ) - return 1; - } - - return 0; -} - -/* * Performs a SEARCH command. - * This is a wrapper around the search_query API which simply prints the results. + * This is a wrapper around _index_search() which simply prints the results. */ -EXPORTED int index_search(struct index_state *state, - struct searchargs *searchargs, - int usinguid) -{ - search_query_t *query = NULL; - search_folder_t *folder; - int nmsg = 0; - int i; +EXPORTED int index_search(struct index_state *state, struct searchargs *searchargs, + int usinguid) +{ + unsigned *list = NULL; + int i, n; modseq_t highestmodseq = 0; - int r; /* update the index */ if (index_check(state, 0, 0)) return 0; - highestmodseq = needs_modseq(searchargs, NULL); - - query = search_query_new(state, searchargs); - r = search_query_run(query); - if (r) goto out; /* search failed */ - folder = search_query_find_folder(query, index_mboxname(state)); - - if (folder) { - if (!usinguid) - search_folder_use_msn(folder, state); - if (highestmodseq) - highestmodseq = search_folder_get_highest_modseq(folder); - nmsg = search_folder_get_count(folder); - } - else - nmsg = 0; + /* now do the search */ + n = _index_search(&list, state, searchargs, + searchargs->modseq ? &highestmodseq : NULL); + + /* replace the values now */ + if (usinguid) + for (i = 0; i < n; i++) + listi = state->maplisti-1.uid; if (searchargs->returnopts) { - /* - * Implement RFC 4731 return options. - */ prot_printf(state->out, "* ESEARCH"); if (searchargs->tag) { prot_printf(state->out, " (TAG \"%s\")", searchargs->tag); } - if (nmsg) { + if (n) { if (usinguid) prot_printf(state->out, " UID"); if (searchargs->returnopts & SEARCH_RETURN_MIN) - prot_printf(state->out, " MIN %u", search_folder_get_min(folder)); + prot_printf(state->out, " MIN %u", list0); if (searchargs->returnopts & SEARCH_RETURN_MAX) - prot_printf(state->out, " MAX %u", search_folder_get_max(folder)); + prot_printf(state->out, " MAX %u", listn-1); if (highestmodseq) prot_printf(state->out, " MODSEQ " MODSEQ_FMT, highestmodseq); if (searchargs->returnopts & SEARCH_RETURN_ALL) { - struct seqset *seq = search_folder_get_seqset(folder); + struct seqset *seq; + char *str; + + /* Create a sequence-set */ + seq = seqset_init(0, SEQ_SPARSE); + for (i = 0; i < n; i++) + seqset_add(seq, listi, 1); if (seq->len) { - char *str = seqset_cstring(seq); + str = seqset_cstring(seq); prot_printf(state->out, " ALL %s", str); free(str); } seqset_free(seq); } - if (searchargs->returnopts & SEARCH_RETURN_RELEVANCY) { - prot_printf(state->out, " RELEVANCY ("); - for (i = 0; i < nmsg; i++) { - if (i) prot_putc(' ', state->out); - /* for now all messages have relevancy=100 */ - prot_printf(state->out, "%u", 100); - } - prot_printf(state->out, ")"); - } } if (searchargs->returnopts & SEARCH_RETURN_COUNT) { - prot_printf(state->out, " COUNT %u", nmsg); + prot_printf(state->out, " COUNT %u", n); } } else { prot_printf(state->out, "* SEARCH"); - if (nmsg) { - search_folder_foreach(folder, i) { - prot_printf(state->out, " %u", i); - } - } + for (i = 0; i < n; i++) + prot_printf(state->out, " %u", listi); if (highestmodseq) prot_printf(state->out, " (MODSEQ " MODSEQ_FMT ")", highestmodseq); } + if (n) free(list); + prot_printf(state->out, "\r\n"); -out: - search_query_free(query); - return nmsg; + return n; } /* @@ -1850,852 +1714,66 @@ const struct sortcrit *sortcrit, struct searchargs *searchargs, int usinguid) { - int i; - int nmsg = 0; + unsigned *msgno_list; + MsgData *msgdata = NULL, *freeme = NULL; + int nmsg; modseq_t highestmodseq = 0; - search_query_t *query = NULL; - search_folder_t *folder = NULL; - int r; + int i, modseq = 0; /* update the index */ if (index_check(state, 0, 0)) return 0; - highestmodseq = needs_modseq(searchargs, NULL); - - /* Search for messages based on the given criteria */ - query = search_query_new(state, searchargs); - query->sortcrit = sortcrit; - r = search_query_run(query); - if (r) goto out; /* search failed */ - folder = search_query_find_folder(query, index_mboxname(state)); - - if (folder) { - if (highestmodseq) - highestmodseq = search_folder_get_highest_modseq(folder); - nmsg = search_folder_get_count(folder); - } - - prot_printf(state->out, "* SORT"); - - if (nmsg) { - /* Output the sorted messages */ - for (i = 0 ; i < query->merged_msgdata.count ; i++) { - MsgData *md = ptrarray_nth(&query->merged_msgdata, i); - prot_printf(state->out, " %u", - (usinguid ? md->uid : md->msgno)); - } - } - - if (highestmodseq) - prot_printf(state->out, " (MODSEQ " MODSEQ_FMT ")", highestmodseq); - - prot_printf(state->out, "\r\n"); - -out: - search_query_free(query); - return nmsg; -} - -static int is_mutable_sort(struct sortcrit *sortcrit) -{ - int i; - - if (!sortcrit) return 0; - - for (i = 0; sortcriti.key; i++) { - switch (sortcriti.key) { - /* these are the mutable fields */ - case SORT_ANNOTATION: - case SORT_MODSEQ: - case SORT_HASFLAG: - case SORT_CONVMODSEQ: - case SORT_CONVEXISTS: - case SORT_CONVSIZE: - case SORT_HASCONVFLAG: - return 1; - default: - break; - } - } - - return 0; -} - -/* This function will return a TRUE value if anything in the - * sort or search criteria returns a MUTABLE ordering, i.e. - * the user can take actions which will change the order in - * which the results are returned. For example, the base - * case of UID sort and all messages is NOT mutable */ -static int is_mutable_ordering(struct sortcrit *sortcrit, - struct searchargs *searchargs) -{ - if (is_mutable_sort(sortcrit)) - return 1; - if (search_expr_is_mutable(searchargs->root)) - return 1; - return 0; -} - -#define UNPREDICTABLE (-1) -static int search_predict_total(struct index_state *state, - struct conversations_state *cstate, - const struct searchargs *searchargs, - int conversations, - modseq_t *xconvmodseqp) -{ - conv_status_t convstatus = CONV_STATUS_INIT; - uint32_t exists; - - if (conversations) { - conversation_getstatus(cstate, index_mboxname(state), &convstatus); - /* always grab xconvmodseq, so we report a growing - * highestmodseq to all callers */ - if (xconvmodseqp) *xconvmodseqp = convstatus.modseq; - exists = convstatus.exists; - } + if (searchargs->modseq) modseq = 1; else { - if (xconvmodseqp) *xconvmodseqp = state->highestmodseq; - /* we may be in xconvupdates, where expunged are present */ - exists = state->exists - state->num_expunged; - } - - switch (search_expr_get_countability(searchargs->root)) { - case SEC_EXISTS: - return exists; - - case SEC_EXISTS|SEC_NOT: - return 0; - - /* we don't try to optimise searches on \Recent */ - case SEC_SEEN: - assert(state->exists >= state->numunseen); - return state->exists - state->numunseen; - - case SEC_SEEN|SEC_NOT: - return state->numunseen; - - case SEC_CONVSEEN: - assert(conversations); - assert(convstatus.exists >= convstatus.unseen); - return convstatus.exists - convstatus.unseen; - - case SEC_CONVSEEN|SEC_NOT: - assert(conversations); - return convstatus.unseen; - - default: - return UNPREDICTABLE; - } -} - -/* - * Performs a XCONVSORT command - */ -EXPORTED int index_convsort(struct index_state *state, - struct sortcrit *sortcrit, - struct searchargs *searchargs, - const struct windowargs *windowargs) -{ - MsgData **msgdata = NULL; - unsigned int mi; - modseq_t xconvmodseq = 0; - int i; - hashu64_table seen_cids = HASHU64_TABLE_INITIALIZER; - uint32_t pos = 0; - int found_anchor = 0; - uint32_t anchor_pos = 0; - uint32_t first_pos = 0; - unsigned int ninwindow = 0; - ptrarray_t results = PTRARRAY_INITIALIZER; - int total = 0; - int r = 0; - struct conversations_state *cstate = NULL; - - assert(windowargs); - assert(!windowargs->changedsince); - assert(!windowargs->upto); - - /* Check the client didn't specify MULTIANCHOR. */ - if (windowargs->anchor && windowargs->anchorfolder) - return IMAP_PROTOCOL_BAD_PARAMETERS; - - /* make sure \Deleted messages are expunged. Will also lock the - * mailbox state and read any new information */ - r = index_expunge(state, NULL, 1); - if (r) return r; - - if (windowargs->conversations) { - cstate = conversations_get_mbox(index_mboxname(state)); - if (!cstate) - return IMAP_INTERNAL; - } - - search_expr_internalise(state, searchargs->root); - - /* this works both with and without conversations */ - total = search_predict_total(state, cstate, searchargs, - windowargs->conversations, - &xconvmodseq); - /* not going to match anything? bonus */ - if (!total) - goto out; - - construct_hashu64_table(&seen_cids, state->exists/4+4, 0); - - /* Create/load the msgdata array. - * load data for ALL messages always. We sort before searching so - * we can take advantage of the window arguments to stop searching - * early */ - msgdata = index_msgdata_load(state, NULL, state->exists, sortcrit, - windowargs->anchor, &found_anchor); - if (windowargs->anchor && !found_anchor) { - r = IMAP_ANCHOR_NOT_FOUND; - goto out; - } - - /* Sort the messages based on the given criteria */ - index_msgdata_sort(msgdata, state->exists, sortcrit); - - /* One pass through the message list */ - for (mi = 0 ; mi < state->exists ; mi++) { - MsgData *msg = msgdatami; - struct index_map *im = &state->mapmsg->msgno-1; - - /* can happen if we didn't "tellchanges" yet */ - if (im->system_flags & FLAG_EXPUNGED) - continue; - - /* run the search program against all messages */ - if (!index_search_evaluate(state, searchargs->root, msg->msgno)) - continue; - - /* figure out whether this message is an exemplar */ - if (windowargs->conversations) { - /* in conversations mode => only the first message seen - * with each unique CID is an exemplar */ - if (hashu64_lookup(msg->cid, &seen_cids)) - continue; - hashu64_insert(msg->cid, (void *)1, &seen_cids); - } - /* else not in conversations mode => all messages are exemplars */ - - pos++; - - if (!anchor_pos && - windowargs->anchor == msg->uid) { - /* we've found the anchor's position, rejoice! */ - anchor_pos = pos; - } - - if (windowargs->anchor) { - if (!anchor_pos) - continue; - if (pos < anchor_pos + windowargs->offset) - continue; - } - else if (windowargs->position) { - if (pos < windowargs->position) - continue; - } - if (windowargs->limit && - ++ninwindow > windowargs->limit) { - if (total == UNPREDICTABLE) { - /* the total was not predictable, so we need to keep - * going over the whole list to count it */ - continue; - } - break; - } - - if (!first_pos) - first_pos = pos; - ptrarray_append(&results, msg); - } - - if (total == UNPREDICTABLE) { - /* the total was not predictable prima facie */ - total = pos; - } - - if (windowargs->anchor && !anchor_pos) { - /* the anchor was present but not an exemplar */ - assert(results.count == 0); - r = IMAP_ANCHOR_NOT_FOUND; - goto out; - } - - /* Print the resulting list */ - - /* Yes, we could use a seqset here, but apparently the most common - * sort order seen in the field is reverse date, which is basically - * the worst case for seqset. So we don't bother */ - if (results.count) { - prot_printf(state->out, "* SORT"); /* uids */ - for (i = 0 ; i < results.count ; i++) { - MsgData *msg = results.datai; - prot_printf(state->out, " %u", msg->uid); - } - prot_printf(state->out, "\r\n"); - } - -out: - if (!r) { - if (first_pos) - prot_printf(state->out, "* OK POSITION %u\r\n", first_pos); - - prot_printf(state->out, "* OK HIGHESTMODSEQ " MODSEQ_FMT "\r\n", - MAX(xconvmodseq, state->mailbox->i.highestmodseq)); - prot_printf(state->out, "* OK UIDVALIDITY %u\r\n", - state->mailbox->i.uidvalidity); - prot_printf(state->out, "* OK UIDNEXT %u\r\n", - state->mailbox->i.last_uid + 1); - prot_printf(state->out, "* OK TOTAL %u\r\n", - total); - } - - /* free all our temporary data */ - index_msgdata_free(msgdata, state->exists); - ptrarray_fini(&results); - free_hashu64_table(&seen_cids, NULL); - - return r; -} - -/* - * Performs a XCONVMULTISORT command - */ -EXPORTED int index_convmultisort(struct index_state *state, - struct sortcrit *sortcrit, - struct searchargs *searchargs, - const struct windowargs *windowargs) -{ - int mi; - int fi; - int i; - hashu64_table seen_cids = HASHU64_TABLE_INITIALIZER; - uint32_t pos = 0; - uint32_t anchor_pos = 0; - uint32_t first_pos = 0; - unsigned int ninwindow = 0; - /* array of (arrays of msgdata* with the same CID) */ - ptrarray_t results = PTRARRAY_INITIALIZER; - /* Used as a placeholder which provides a non-NULL entry in the - * seen_cids hashtable for conversations which are outside the - * specified window. */ - ptrarray_t dummy_response; - int total = UNPREDICTABLE; - int r = 0; - char extnameMAX_MAILBOX_BUFFER; - modseq_t hms; - search_query_t *query = NULL; - search_folder_t *folder = NULL; - search_folder_t *anchor_folder = NULL; - - assert(windowargs); - assert(!windowargs->changedsince); - assert(!windowargs->upto); - - /* Client needs to have specified MULTIANCHOR which includes - * the folder name instead of just ANCHOR. Check that here - * 'cos it's easier than doing so during parsing */ - if (windowargs->anchor && !windowargs->anchorfolder) - return IMAP_PROTOCOL_BAD_PARAMETERS; - - /* make sure folder still exists and map in data */ - r = index_refresh(state); - if (r) return r; - - hms = mboxname_readmodseq(index_mboxname(state)); - query = search_query_new(state, searchargs); - query->multiple = 1; - query->need_ids = 1; - query->need_expunge = 1; - query->sortcrit = sortcrit; - r = search_query_run(query); - if (r) return r; - - if (windowargs->anchorfolder) { - anchor_folder = search_query_find_folder(query, windowargs->anchorfolder); - if (!anchor_folder) { - r = IMAP_ANCHOR_NOT_FOUND; - goto out; - } - } - - /* going to need to do conversation-level breakdown */ - if (windowargs->conversations) - construct_hashu64_table(&seen_cids, query->merged_msgdata.count/4+4, 0); - /* no need */ - else - total = query->merged_msgdata.count; - - /* Another pass through the merged message list */ - for (mi = 0 ; mi < query->merged_msgdata.count ; mi++) { - MsgData *md = ptrarray_nth(&query->merged_msgdata, mi); - ptrarray_t *response = NULL; - - /* figure out whether this message is an exemplar */ - if (windowargs->conversations) { - response = hashu64_lookup(md->cid, &seen_cids); - /* in conversations mode => only the first message seen - * with each unique CID is an exemplar */ - if (response) { - if (response != &dummy_response) - ptrarray_append(response, md); - continue; - } - hashu64_insert(md->cid, &dummy_response, &seen_cids); - } - /* else not in conversations mode => all messages are exemplars */ - - pos++; - - if (!anchor_pos && - windowargs->anchor == md->uid && - anchor_folder == md->folder) { - /* we've found the anchor's position, rejoice! */ - anchor_pos = pos; - } - - if (windowargs->anchor) { - if (!anchor_pos) - continue; - if (pos < anchor_pos + windowargs->offset) - continue; - } - else if (windowargs->position) { - if (pos < windowargs->position) - continue; - } - if (windowargs->limit && - ++ninwindow > windowargs->limit) { - if (total == UNPREDICTABLE) { - /* the total was not predictable, so we need to keep - * going over the whole list to count it */ - continue; - } - break; - } - - if (!first_pos) - first_pos = pos; - - /* the message is the exemplar of a conversation which is inside - * the specified window, so record a non-dummy seen_cids entry - * and a results entry */ - response = ptrarray_new(); - ptrarray_push(response, md); - ptrarray_push(&results, response); - - if (windowargs->conversations) { - hashu64_insert(md->cid, response, &seen_cids); - } - } - - if (total == UNPREDICTABLE) { - /* the total was not predictable prima facie */ - total = pos; - } - - if (windowargs->anchor && !anchor_pos) { - /* the anchor was not found */ - assert(results.count == 0); - r = IMAP_ANCHOR_NOT_FOUND; - goto out; - } - - /* Print the resulting list */ - - xstats_add(SEARCH_RESULT, results.count); - if (results.count) { - /* The untagged reponse would be XCONVMULTISORT but - * Mail::IMAPTalk has an undocumented hack whereby any untagged - * response matching /sort/i is assumed to be a sequence of - * numeric uids. Meh. */ - prot_printf(state->out, "* XCONVMULTI ("); - for (fi = 0 ; fi < query->folders_by_id.count ; fi++) { - folder = ptrarray_nth(&query->folders_by_id, fi); - - searchargs->namespace->mboxname_toexternal(searchargs->namespace, - folder->mboxname, - searchargs->userid, - extname); - if (fi) - prot_printf(state->out, " "); - prot_printf(state->out, "("); - prot_printstring(state->out, extname); - prot_printf(state->out, " %u)", folder->uidvalidity); - } - prot_printf(state->out, ") ("); - for (i = 0 ; i < results.count ; i++) { - ptrarray_t *response = ptrarray_nth(&results, i); - int j; - if (i) - prot_printf(state->out, " "); - for (j = 0; j < response->count; j++) { - MsgData *md = ptrarray_nth(response, j); - if (!j) - prot_printf(state->out, "(%s" , conversation_id_encode(md->cid)); - prot_printf(state->out, " (%u %u)", md->folder->id, md->uid); + for (i = 0; sortcriti.key != SORT_SEQUENCE; i++) { + if (sortcriti.key == SORT_MODSEQ) { + modseq = 1; + break; } - prot_printf(state->out, ")"); } - prot_printf(state->out, ")\r\n"); } -out: - if (!r) { - if (first_pos) - prot_printf(state->out, "* OK POSITION %u\r\n", first_pos); - - prot_printf(state->out, "* OK HIGHESTMODSEQ " MODSEQ_FMT "\r\n", - hms); -#if 0 - prot_printf(state->out, "* OK UIDNEXT %u\r\n", - state->mailbox->i.last_uid + 1); -#endif - prot_printf(state->out, "* OK TOTAL %u\r\n", - total); - } - - /* free all our temporary data */ - free_hashu64_table(&seen_cids, NULL); - for (i = 0 ; i < results.count ; i++) { - ptrarray_t *response = ptrarray_nth(&results, i); - ptrarray_free(response); - } - ptrarray_fini(&results); - search_query_free(query); - - return r; -} - -struct snippet_rock { - struct protstream *out; - struct namespace *namespace; - const char *userid; -}; - -static int emit_snippet(struct mailbox *mailbox, uint32_t uid, - int part, const char *snippet, void *rock) -{ - struct snippet_rock *sr = (struct snippet_rock *)rock; - const char *partname = search_part_as_string(part); - int r; - char extnameMAX_MAILBOX_BUFFER; - - if (!partname) return 0; - - r = sr->namespace->mboxname_toexternal(sr->namespace, mailbox->name, - sr->userid, extname); - if (r) return r; - - prot_printf(sr->out, "* SNIPPET "); - prot_printstring(sr->out, extname); - prot_printf(sr->out, " %u %u %s ", mailbox->i.uidvalidity, uid, partname); - prot_printstring(sr->out, snippet); - prot_printf(sr->out, "\r\n"); - return 0; -} - -EXPORTED int index_snippets(struct index_state *state, - const struct snippetargs *snippetargs, - struct searchargs *searchargs) -{ - void *intquery = NULL; - search_builder_t *bx = NULL; - search_text_receiver_t *rx = NULL; - struct mailbox *mailbox = NULL; - int i; - int r = 0; - int nmatches = 0; - struct snippet_rock srock; - - /* reload index */ - r = index_refresh(state); - if (r) return r; - - bx = search_begin_search(state->mailbox, SEARCH_MULTIPLE); - if (!bx) { - r = IMAP_INTERNAL; - goto out; - } - - build_query(bx, searchargs->root, 0, &nmatches); - if (!bx->get_internalised) goto out; - intquery = bx->get_internalised(bx); - search_end_search(bx); - if (!intquery) goto out; - - srock.out = state->out; - srock.namespace = searchargs->namespace; - srock.userid = searchargs->userid; - rx = search_begin_snippets(intquery, 0/*verbose*/, - emit_snippet, &srock); - if (!rx) goto out; - - for ( ; snippetargs ; snippetargs = snippetargs->next) { - - mailbox = NULL; - if (!strcmp(snippetargs->mboxname, index_mboxname(state))) { - mailbox = state->mailbox; - } - else { - r = mailbox_open_iwl(snippetargs->mboxname, &mailbox); - if (r) goto out; - } - - if (snippetargs->uidvalidity && - snippetargs->uidvalidity != mailbox->i.uidvalidity) { - r = IMAP_NOTFOUND; - goto out; - } - - r = rx->begin_mailbox(rx, mailbox, /*incremental*/0); - - for (i = 0 ; i < snippetargs->uids.count ; i++) { - uint32_t uid = snippetargs->uids.datai; - struct index_record record; - message_t *msg; - - /* This UID didn't appear in the old index file */ - r = mailbox_find_index_record(mailbox, uid, &record, NULL); - if (r) continue; - - msg = message_new_from_record(mailbox, &record); - index_getsearchtext(msg, rx, /*snippet*/1); - message_unref(&msg); - } - - r = rx->end_mailbox(rx, mailbox); - if (r) goto out; - if (mailbox != state->mailbox) - mailbox_close(&mailbox); - } - -out: - if (rx) search_end_snippets(rx); - if (intquery) search_free_internalised(intquery); - if (mailbox != state->mailbox) - mailbox_close(&mailbox); - return r; -} - -static modseq_t get_modseq_of(MsgData *msg, - struct conversations_state *cstate) -{ - modseq_t modseq = 0; - - if (cstate) { - conversation_get_modseq(cstate, msg->cid, &modseq); - /* TODO: error handling dammit */ - } else { - modseq = msg->modseq; - } - return modseq; -} - -/* - * Performs a XCONVUPDATES command - */ -EXPORTED int index_convupdates(struct index_state *state, - struct sortcrit *sortcrit, - struct searchargs *searchargs, - const struct windowargs *windowargs) -{ - MsgData **msgdata = NULL; - modseq_t xconvmodseq = 0; - unsigned int mi; - int i; - hashu64_table seen_cids = HASHU64_TABLE_INITIALIZER; - hashu64_table old_seen_cids = HASHU64_TABLE_INITIALIZER; - int32_t pos = 0; - uint32_t upto_pos = 0; - ptrarray_t added = PTRARRAY_INITIALIZER; - ptrarray_t removed = PTRARRAY_INITIALIZER; - ptrarray_t changed = PTRARRAY_INITIALIZER; - int total = 0; - struct conversations_state *cstate = NULL; - int search_is_mutable = is_mutable_ordering(sortcrit, searchargs); - int r = 0; - - assert(windowargs); - assert(windowargs->changedsince); - assert(windowargs->offset == 0); - assert(!windowargs->position); - - /* make sure \Deleted messages are expunged. Will also lock the - * mailbox state and read any new information */ - r = index_expunge(state, NULL, 1); - if (r) return r; - - if (windowargs->conversations) { - cstate = conversations_get_mbox(index_mboxname(state)); - if (!cstate) - return IMAP_INTERNAL; - } - - search_expr_internalise(state, searchargs->root); - - total = search_predict_total(state, cstate, searchargs, - windowargs->conversations, - &xconvmodseq); - /* If there are no current and no expunged messages, we won't - * have any results at all and can short circuit the main loop; - * note that is a righter criterion than for XCONVSORT. */ - if (!total && !state->exists) - goto out; - - construct_hashu64_table(&seen_cids, state->exists/4+4, 0); - construct_hashu64_table(&old_seen_cids, state->exists/4+4, 0); - - /* Create/load the msgdata array - * initial list - load data for ALL messages always */ - msgdata = index_msgdata_load(state, NULL, state->exists, sortcrit, 0, NULL); - - /* Sort the messages based on the given criteria */ - index_msgdata_sort(msgdata, state->exists, sortcrit); - - /* Discover exemplars */ - for (mi = 0 ; mi < state->exists ; mi++) { - MsgData *msg = msgdatami; - struct index_map *im = &state->mapmsg->msgno-1; - int was_old_exemplar = 0; - int is_new_exemplar = 0; - int is_deleted = 0; - int is_new = 0; - int was_deleted = 0; - int is_changed = 0; - int in_search = 0; - - in_search = index_search_evaluate(state, searchargs->root, msg->msgno); - is_deleted = !!(im->system_flags & FLAG_EXPUNGED); - is_new = (im->uid >= windowargs->uidnext); - is_changed = (im->modseq > windowargs->modseq); - was_deleted = is_deleted && !is_changed; - - /* is this message a current exemplar? */ - if (!is_deleted && - in_search && - (!windowargs->conversations || !hashu64_lookup(msg->cid, &seen_cids))) { - is_new_exemplar = 1; - pos++; - if (windowargs->conversations) - hashu64_insert(msg->cid, (void *)1, &seen_cids); - } - - /* optimisation for when the total is - * not known but we've hit 'upto' */ - if (upto_pos) - continue; + /* Search for messages based on the given criteria */ + nmsg = _index_search(&msgno_list, state, searchargs, + modseq ? &highestmodseq : NULL); - /* was this message an old exemplar, or in the case of mutable - * searches, possible an old exemplar? */ - if (!is_new && - !was_deleted && - (in_search || search_is_mutable) && - (!windowargs->conversations || !hashu64_lookup(msg->cid, &old_seen_cids))) { - was_old_exemplar = 1; - if (windowargs->conversations) - hashu64_insert(msg->cid, (void *)1, &old_seen_cids); - } - - if (was_old_exemplar && !is_new_exemplar) { - ptrarray_push(&removed, msg); - } else if (!was_old_exemplar && is_new_exemplar) { - msg->msgno = pos; /* hacky: reuse ->msgno for pos */ - ptrarray_push(&added, msg); - } else if (was_old_exemplar && is_new_exemplar) { - modseq_t modseq = get_modseq_of(msg, cstate); - if (modseq > windowargs->modseq) { - ptrarray_push(&changed, msg); - if (search_is_mutable) { - /* is the search is mutable, we're in a whole world of - * uncertainty about the client's state, so we just - * report the exemplar in all three lists and let the - * client sort it out. */ - ptrarray_push(&removed, msg); - msg->msgno = pos; /* hacky: reuse ->msgno for pos */ - ptrarray_push(&added, msg); - } - } - } + prot_printf(state->out, "* SORT"); - /* if this is the last message the client cares about ('upto') - * then we can break early...unless its a mutable search or - * we need to keep going to calculate an accurate total */ - if (!search_is_mutable && - !upto_pos && - msg->uid == windowargs->upto) { - if (total != UNPREDICTABLE) - break; - upto_pos = pos; - } - } + if (nmsg) { + /* Create/load the msgdata array */ + freeme = msgdata = index_msgdata_load(state, msgno_list, nmsg, sortcrit); + free(msgno_list); - /* unlike 'anchor', the case of not finding 'upto' is not an error */ + /* Sort the messages based on the given criteria */ + msgdata = lsort(msgdata, + (void * (*)(void*)) index_sort_getnext, + (void (*)(void*,void*)) index_sort_setnext, + (int (*)(void*,void*,void*)) index_sort_compare, + (void *)sortcrit); - if (total == UNPREDICTABLE) { - /* the total was not predictable prima facie */ - total = pos; - } + /* Output the sorted messages */ + while (msgdata) { + unsigned no = usinguid ? state->mapmsgdata->msgno-1.uid + : msgdata->msgno; + prot_printf(state->out, " %u", no); - /* Print the resulting lists */ + /* free contents of the current node */ + index_msgdata_free(msgdata); - if (added.count) { - prot_printf(state->out, "* ADDED"); /* (uid pos) tuples */ - for (i = 0 ; i < added.count ; i++) { - MsgData *msg = added.datai; - prot_printf(state->out, " (%u %u)", - msg->uid, msg->msgno); + msgdata = msgdata->next; } - prot_printf(state->out, "\r\n"); - } - if (removed.count) { - prot_printf(state->out, "* REMOVED"); /* uids */ - for (i = 0 ; i < removed.count ; i++) { - MsgData *msg = removed.datai; - prot_printf(state->out, " %u", msg->uid); - } - prot_printf(state->out, "\r\n"); + /* free the msgdata array */ + free(freeme); } - if (changed.count) { - prot_printf(state->out, "* CHANGED"); /* cids or uids */ - for (i = 0 ; i < changed.count ; i++) { - MsgData *msg = changed.datai; - if (windowargs->conversations) - prot_printf(state->out, " %s", - conversation_id_encode(msg->cid)); - else - prot_printf(state->out, " %u", msg->uid); - } - prot_printf(state->out, "\r\n"); - } + if (highestmodseq) + prot_printf(state->out, " (MODSEQ " MODSEQ_FMT ")", highestmodseq); -out: - if (!r) { - prot_printf(state->out, "* OK HIGHESTMODSEQ " MODSEQ_FMT "\r\n", - MAX(xconvmodseq, state->mailbox->i.highestmodseq)); - prot_printf(state->out, "* OK UIDVALIDITY %u\r\n", - state->mailbox->i.uidvalidity); - prot_printf(state->out, "* OK UIDNEXT %u\r\n", - state->mailbox->i.last_uid + 1); - prot_printf(state->out, "* OK TOTAL %u\r\n", - total); - } - - /* free all our temporary data */ - index_msgdata_free(msgdata, state->exists); - ptrarray_fini(&added); - ptrarray_fini(&removed); - ptrarray_fini(&changed); - free_hashu64_table(&seen_cids, NULL); - free_hashu64_table(&old_seen_cids, NULL); + prot_printf(state->out, "\r\n"); - return r; + return nmsg; } /* @@ -2704,35 +1782,21 @@ EXPORTED int index_thread(struct index_state *state, int algorithm, struct searchargs *searchargs, int usinguid) { - search_query_t *query = NULL; - search_folder_t *folder; unsigned *msgno_list; - int nmsg = 0; + int nmsg; clock_t start; modseq_t highestmodseq = 0; - int r; /* update the index */ if (index_check(state, 0, 0)) return 0; - - highestmodseq = needs_modseq(searchargs, NULL); - + if(CONFIG_TIMING_VERBOSE) start = clock(); /* Search for messages based on the given criteria */ - query = search_query_new(state, searchargs); - r = search_query_run(query); - if (r) goto out; /* search failed */ - folder = search_query_find_folder(query, index_mboxname(state)); - - if (folder) { - search_folder_use_msn(folder, state); - if (highestmodseq) - highestmodseq = search_folder_get_highest_modseq(folder); - nmsg = search_folder_get_array(folder, &msgno_list); - } + nmsg = _index_search(&msgno_list, state, searchargs, + searchargs->modseq ? &highestmodseq : NULL); if (nmsg) { /* Thread messages using given algorithm */ @@ -2757,8 +1821,6 @@ (clock() - start) / (double) CLOCKS_PER_SEC); } -out: - search_query_free(query); return nmsg; } @@ -2774,10 +1836,9 @@ int nolink, struct namespace *namespace, int isadmin, - int ismove, - int ignorequota) + int ismove) { - struct copyargs copyargs; + static struct copyargs copyargs; int i; quota_t qdiffsQUOTA_NUMRESOURCES = QUOTA_DIFFS_INITIALIZER; quota_t *qptr = NULL; @@ -2786,27 +1847,18 @@ uint32_t msgno, checkval; long docopyuid; struct seqset *seq; - struct mailbox *srcmailbox = NULL; + struct mailbox *mailbox; struct mailbox *destmailbox = NULL; struct index_map *im; - int is_same_user; *copyuidp = NULL; - memset(©args, 0, sizeof(struct copyargs)); - - /* let's just see how common this is... */ - if (!strcmp(index_mboxname(state), name)) - syslog(LOG_NOTICE, "same mailbox copy %s (%s)", name, sequence); - - is_same_user = mboxname_same_userid(index_mboxname(state), name); - if (is_same_user < 0) - return is_same_user; + copyargs.nummsg = 0; r = index_check(state, usinguid, usinguid); if (r) return r; - srcmailbox = state->mailbox; + mailbox = state->mailbox; seq = _parse_sequence(state, sequence, usinguid); @@ -2815,36 +1867,33 @@ checkval = usinguid ? im->uid : msgno; if (!seqset_ismember(seq, checkval)) continue; - index_copysetup(state, msgno, ©args, is_same_user); + index_copysetup(state, msgno, ©args); } seqset_free(seq); - if (copyargs.nummsg == 0) { - r = IMAP_NO_NOSUCHMSG; - goto done; - } + if (copyargs.nummsg == 0) return IMAP_NO_NOSUCHMSG; r = mailbox_open_iwl(name, &destmailbox); - if (r) goto done; + if (r) return r; /* not moving or different quota root - need to check quota */ - if (!ismove || strcmpsafe(srcmailbox->quotaroot, destmailbox->quotaroot)) { + if (!ismove || strcmpsafe(mailbox->quotaroot, destmailbox->quotaroot)) { for (i = 0; i < copyargs.nummsg; i++) - qdiffsQUOTA_STORAGE += copyargs.copymsgi.record.size; + qdiffsQUOTA_STORAGE += copyargs.copymsgi.size; qdiffsQUOTA_MESSAGE = copyargs.nummsg; qptr = qdiffs; } r = append_setup_mbox(&appendstate, destmailbox, state->userid, state->authstate, ACL_INSERT, - ignorequota ? NULL : qptr, namespace, isadmin, + qptr, namespace, isadmin, ismove ? EVENT_MESSAGE_MOVE : EVENT_MESSAGE_COPY); if (r) goto done; docopyuid = (appendstate.myrights & ACL_READ); - r = append_copy(srcmailbox, &appendstate, copyargs.nummsg, + r = append_copy(mailbox, &appendstate, copyargs.nummsg, copyargs.copymsg, nolink); if (r) { append_abort(&appendstate); @@ -2866,7 +1915,7 @@ seq = seqset_init(0, SEQ_SPARSE); for (i = 0; i < copyargs.nummsg; i++) - seqset_add(seq, copyargs.copymsgi.olduid, 1); + seqset_add(seq, copyargs.copymsgi.uid, 1); source = seqset_cstring(seq); @@ -2892,13 +1941,9 @@ /* we log the first name to get GUID-copy magic */ if (!r) - sync_log_mailbox_double(index_mboxname(state), name); + sync_log_mailbox_double(mailbox->name, name); done: - for (i = 0; i < copyargs.nummsg; i++) { - strarray_fini(©args.copymsgi.flags); - } - free(copyargs.copymsg); mailbox_close(&destmailbox); return r; @@ -2911,7 +1956,8 @@ struct protstream *pout) { struct mailbox *mailbox = state->mailbox; - struct buf msg = BUF_INITIALIZER; + const char *msg_base = 0; + size_t msg_size = 0; unsigned flag, flagmask = 0; char datebufRFC3501_DATETIME_MAX+1; char sepchar = '('; @@ -2922,7 +1968,7 @@ if (r) return r; /* Open the message file */ - if (mailbox_map_record(mailbox, &record, &msg)) + if (mailbox_map_message(mailbox, record.uid, &msg_base, &msg_size)) return IMAP_NO_MSGGONE; /* start the individual append */ @@ -2966,10 +2012,11 @@ prot_printf(pout, ") \"%s\" ", datebuf); /* message literal */ - index_fetchmsg(state, &msg, 0, record.size, 0, 0); + index_fetchmsg(state, msg_base, msg_size, 0, record.size, 0, 0); /* close the message file */ - buf_free(&msg); + if (msg_base) + mailbox_unmap_message(mailbox, record.uid, &msg_base, &msg_size); return 0; } @@ -3053,8 +2100,8 @@ * further constrained by 'start_octet' and 'octet_count' as per the * IMAP command PARTIAL. */ -void index_fetchmsg(struct index_state *state, const struct buf *msg, - unsigned offset, +void index_fetchmsg(struct index_state *state, const char *msg_base, + unsigned long msg_size, unsigned offset, unsigned size, /* this is the correct size for a news message after having LF translated to CRLF */ unsigned start_octet, unsigned octet_count) @@ -3062,7 +2109,7 @@ unsigned n, domain; /* If no data, output NIL */ - if (!msg || !msg->s) { + if (!msg_base) { prot_printf(state->out, "NIL"); return; } @@ -3087,9 +2134,9 @@ /* Seek over PARTIAL constraint */ offset += start_octet; n = size; - if (offset + size > msg->len) { - if (msg->len > offset) { - n = msg->len - offset; + if (offset + size > msg_size) { + if (msg_size > offset) { + n = msg_size - offset; } else { prot_printf(state->out, "\"\""); @@ -3098,7 +2145,7 @@ } /* Get domain of the data */ - domain = data_domain(msg->s + offset, n); + domain = data_domain(msg_base + offset, n); if (domain == DOMAIN_BINARY) { /* Write size of literal8 */ @@ -3111,7 +2158,7 @@ /* Non-text literal -- tell the protstream about it */ if (domain != DOMAIN_7BIT) prot_data_boundary(state->out); - prot_write(state->out, msg->s + offset, n); + prot_write(state->out, msg_base + offset, n); while (n++ < size) { /* File too short, resynch client. * @@ -3129,7 +2176,7 @@ * Helper function to fetch a body section */ static int index_fetchsection(struct index_state *state, const char *resp, - const struct buf *inmsg, + const char *msg_base, unsigned long msg_size, char *section, const char *cachestr, unsigned size, unsigned start_octet, unsigned octet_count) { @@ -3138,9 +2185,6 @@ int fetchmime = 0; unsigned offset = 0; char *decbuf = NULL; - struct buf msg = BUF_INITIALIZER; - - buf_init_ro(&msg, inmsg->s, inmsg->len); p = section; @@ -3150,7 +2194,7 @@ prot_printf(state->out, "%s%u", resp, size); } else { prot_printf(state->out, "%s", resp); - index_fetchmsg(state, &msg, 0, size, + index_fetchmsg(state, msg_base, msg_size, 0, size, start_octet, octet_count); } return 0; @@ -3217,21 +2261,21 @@ offset = CACHE_ITEM_BIT32(cachestr); size = CACHE_ITEM_BIT32(cachestr + CACHE_ITEM_SIZE_SKIP); - if (msg.s && (p = strstr(resp, "BINARY"))) { + if (msg_base && (p = strstr(resp, "BINARY"))) { /* BINARY or BINARY.SIZE */ int encoding = CACHE_ITEM_BIT32(cachestr + 2 * 4) & 0xff; size_t newsize; /* check that the offset isn't corrupt */ - if (offset + size > msg.len) { - syslog(LOG_ERR, "invalid part offset in %s", index_mboxname(state)); + if (offset + size > msg_size) { + syslog(LOG_ERR, "invalid part offset in %s", state->mboxname); return IMAP_IOERROR; } - msg.s = (char *)charset_decode_mimebody(msg.s + offset, size, encoding, - &decbuf, &newsize); + msg_base = charset_decode_mimebody(msg_base + offset, size, encoding, + &decbuf, &newsize); - if (!msg.s) { + if (!msg_base) { /* failed to decode */ if (decbuf) free(decbuf); return IMAP_NO_UNKNOWN_CTE; @@ -3246,13 +2290,13 @@ /* BINARY */ offset = 0; size = newsize; - msg.len = newsize; + msg_size = newsize; } } /* Output body part */ prot_printf(state->out, "%s", resp); - index_fetchmsg(state, &msg, offset, size, + index_fetchmsg(state, msg_base, msg_size, offset, size, start_octet, octet_count); if (decbuf) free(decbuf); @@ -3327,10 +2371,10 @@ CACHE_ITEM_BIT32(cachestr+CACHE_ITEM_SIZE_SKIP)); if (fields_not) { - message_pruneheader(buf, 0, fsection->fields); + index_pruneheader(buf, 0, fsection->fields); } else { - message_pruneheader(buf, fsection->fields, 0); + index_pruneheader(buf, fsection->fields, 0); } size = strlen(buf); @@ -3382,7 +2426,8 @@ static char *index_readheader(const char *msg_base, unsigned long msg_size, unsigned offset, unsigned size) { - static struct buf buf = BUF_INITIALIZER; + static char *buf; + static unsigned bufsize; if (offset + size > msg_size) { /* Message file is too short, truncate request */ @@ -3394,9 +2439,85 @@ } } - buf_reset(&buf); - buf_appendmap(&buf, msg_base+offset, size); - return (char *)buf_cstring(&buf); + if (bufsize < size+2) { + bufsize = size+100; + buf = xrealloc(buf, bufsize); + } + + msg_base += offset; + + memcpy(buf, msg_base, size); + bufsize = '\0'; + + return buf; +} + +/* + * Prune the header section in buf to include only those headers + * listed in headers or (if headers_not is non-empty) those headers + * not in headers_not. + */ +static void index_pruneheader(char *buf, const strarray_t *headers, + const strarray_t *headers_not) +{ + char *p, *colon, *nextheader; + int goodheader; + char *endlastgood = buf; + char **l; + int count = 0; + int maxlines = config_getint(IMAPOPT_MAXHEADERLINES); + + p = buf; + while (*p && *p != '\r') { + colon = strchr(p, ':'); + if (colon && headers_not && headers_not->count) { + goodheader = 1; + for (l = headers_not->data ; *l ; l++) { + if ((size_t) (colon - p) == strlen(*l) && + !strncasecmp(p, *l, colon - p)) { + goodheader = 0; + break; + } + } + } else { + goodheader = 0; + } + if (colon && headers && headers->count) { + for (l = headers->data ; *l ; l++) { + if ((size_t) (colon - p) == strlen(*l) && + !strncasecmp(p, *l, colon - p)) { + goodheader = 1; + break; + } + } + } + + nextheader = p; + do { + nextheader = strchr(nextheader, '\n'); + if (nextheader) nextheader++; + else nextheader = p + strlen(p); + } while (*nextheader == ' ' || *nextheader == '\t'); + + if (goodheader) { + if (endlastgood != p) { + /* memmove and not strcpy since this is all within a + * single buffer */ + memmove(endlastgood, p, strlen(p) + 1); + nextheader -= p - endlastgood; + } + endlastgood = nextheader; + } + p = nextheader; + + /* stop giant headers causing massive loops */ + if (maxlines) { + count++; + if (count > maxlines) break; + } + } + + *endlastgood = '\0'; } /* @@ -3420,7 +2541,7 @@ buf = index_readheader(msg_base, msg_size, 0, size); - message_pruneheader(buf, headers, headers_not); + index_pruneheader(buf, headers, headers_not); size = strlen(buf); prot_printf(state->out, "{%u}\r\n%s\r\n", size+2, buf); @@ -3435,7 +2556,8 @@ const strarray_t *headers, unsigned start_octet, unsigned octet_count) { - static struct buf buf = BUF_INITIALIZER; + static char *buf; + static unsigned bufsize; unsigned size; unsigned crlf_start = 0; unsigned crlf_size = 2; @@ -3447,12 +2569,17 @@ return; } - buf_setmap(&buf, cacheitem_base(record, CACHE_HEADERS), - cacheitem_size(record, CACHE_HEADERS)); - buf_cstring(&buf); + size = cacheitem_size(record, CACHE_HEADERS); + if (bufsize < size+2) { + bufsize = size+100; + buf = xrealloc(buf, bufsize); + } - message_pruneheader(buf.s, headers, 0); - size = strlen(buf.s); /* not buf.len, it has been pruned */ + memcpy(buf, cacheitem_base(record, CACHE_HEADERS), size); + bufsize = '\0'; + + index_pruneheader(buf, headers, 0); + size = strlen(buf); /* partial fetch: adjust 'size' */ if (octet_count) { @@ -3484,7 +2611,7 @@ } else { prot_printf(state->out, "{%u}\r\n", size + crlf_size); - prot_write(state->out, buf.s + start_octet, size); + prot_write(state->out, buf + start_octet, size); prot_write(state->out, "\r\n" + crlf_start, crlf_size); } } @@ -3534,7 +2661,7 @@ prot_printf(state->out, ") Ok\r\n"); } -EXPORTED void index_checkflags(struct index_state *state, int print, int dirty) +static void index_checkflags(struct index_state *state, int dirty) { struct mailbox *mailbox = state->mailbox; unsigned i; @@ -3560,7 +2687,7 @@ dirty = 1; } - if (dirty && print) + if (dirty) index_listflags(state); } @@ -3580,7 +2707,6 @@ /* inform about expunges */ if (im->system_flags & FLAG_EXPUNGED) { state->exists--; - state->num_expunged--; /* they never knew about this one, skip */ if (msgno > state->oldexists) continue; @@ -3628,12 +2754,16 @@ if (state->oldexists != state->exists) index_tellexists(state); - index_checkflags(state, 1, 0); + index_checkflags(state, 0); /* print any changed message flags */ for (msgno = 1; msgno <= state->exists; msgno++) { im = &state->mapmsgno-1; + /* we don't report flag updates if it's been expunged */ + if (im->system_flags & FLAG_EXPUNGED) + continue; + /* report if it's changed since last told */ if (im->modseq > im->told_modseq) index_printflags(state, msgno, printuid, printmodseq); @@ -3782,7 +2912,8 @@ { struct mailbox *mailbox = state->mailbox; int fetchitems = fetchargs->fetchitems; - struct buf msg = BUF_INITIALIZER; + const char *msg_base = NULL; + size_t msg_size = 0; struct octetinfo *oi = NULL; int sepchar = '('; int started = 0; @@ -3809,19 +2940,12 @@ return 0; } - /* Check against the CID list filter */ - if (fetchargs->cidhash) { - const char *key = conversation_id_encode(record.cid); - if (!hash_lookup(key, fetchargs->cidhash)) - return 0; - } - /* Open the message file if we're going to need it */ - if ((fetchitems & (FETCH_HEADER|FETCH_TEXT|FETCH_SHA1|FETCH_RFC822)) || + if ((fetchitems & (FETCH_HEADER|FETCH_TEXT|FETCH_RFC822)) || fetchargs->cache_atleast > record.cache_version || fetchargs->binsections || fetchargs->sizesections || fetchargs->bodysections) { - if (mailbox_map_record(mailbox, &record, &msg)) { + if (mailbox_map_message(mailbox, record.uid, &msg_base, &msg_size)) { prot_printf(state->out, "* OK "); prot_printf(state->out, error_message(IMAP_NO_MSGGONE), msgno); prot_printf(state->out, "\r\n"); @@ -3844,12 +2968,6 @@ prot_printf(state->out, "%cUID %u", sepchar, record.uid); sepchar = ' '; } - if (fetchitems & FETCH_GUID) { - prot_printf(state->out, "%cDIGEST.SHA1 %s", sepchar, - message_guid_encode(&record.guid)); - sepchar = ' '; - } - if (fetchitems & FETCH_INTERNALDATE) { time_t msgdate = record.internaldate; char datebufRFC3501_DATETIME_MAX+1; @@ -3877,55 +2995,6 @@ prot_printf(state->out, ")"); sepchar = ' '; } - if (fetchitems & FETCH_FILESIZE) { - unsigned int msg_size = msg.len; - if (!msg.s) { - const char *fname = mailbox_record_fname(mailbox, &record); - struct stat sbuf; - /* Find the size of the message file */ - if (stat(fname, &sbuf) == -1) - syslog(LOG_ERR, "IOERROR: stat on %s: %m", fname); - else - msg_size = sbuf.st_size; - } - prot_printf(state->out, "%cRFC822.FILESIZE %lu", sepchar, - (long unsigned)msg_size); - sepchar = ' '; - } - if (fetchitems & FETCH_SHA1) { - struct message_guid tmpguid; - message_guid_generate(&tmpguid, msg.s, msg.len); - prot_printf(state->out, "%cRFC822.SHA1 %s", sepchar, message_guid_encode(&tmpguid)); - sepchar = ' '; - } - if ((fetchitems & FETCH_CID) && - config_getswitch(IMAPOPT_CONVERSATIONS)) { - struct buf buf = BUF_INITIALIZER; - if (!record.cid) - buf_appendcstr(&buf, "NIL"); - else - buf_printf(&buf, CONV_FMT, record.cid); - prot_printf(state->out, "%cCID %s", sepchar, buf_cstring(&buf)); - buf_free(&buf); - sepchar = ' '; - } - if ((fetchitems & FETCH_FOLDER)) { - struct namespace *ns = fetchargs->namespace; - char mboxnameMAX_MAILBOX_PATH+1; - r = ns->mboxname_toexternal(ns, index_mboxname(state), - fetchargs->userid, mboxname); - if (!r) { - prot_printf(state->out, "%cFOLDER ", sepchar); - prot_printastring(state->out, mboxname); - sepchar = ' '; - } - r = 0; - } - if ((fetchitems & FETCH_UIDVALIDITY)) { - prot_printf(state->out, "%cUIDVALIDITY %u", sepchar, - state->mailbox->i.uidvalidity); - sepchar = ' '; - } if (fetchitems & FETCH_ENVELOPE) { if (!mailbox_cacherecord(mailbox, &record)) { prot_printf(state->out, "%cENVELOPE ", sepchar); @@ -3951,7 +3020,7 @@ if (fetchitems & FETCH_HEADER) { prot_printf(state->out, "%cRFC822.HEADER ", sepchar); sepchar = ' '; - index_fetchmsg(state, &msg, 0, + index_fetchmsg(state, msg_base, msg_size, 0, record.header_size, (fetchitems & FETCH_IS_PARTIAL) ? fetchargs->start_octet : 0, @@ -3962,7 +3031,7 @@ prot_printf(state->out, "%cRFC822.HEADER ", sepchar); sepchar = ' '; if (fetchargs->cache_atleast > record.cache_version) { - index_fetchheader(state, msg.s, msg.len, + index_fetchheader(state, msg_base, msg_size, record.header_size, &fetchargs->headers, &fetchargs->headers_not); } else { @@ -3973,7 +3042,7 @@ if (fetchitems & FETCH_TEXT) { prot_printf(state->out, "%cRFC822.TEXT ", sepchar); sepchar = ' '; - index_fetchmsg(state, &msg, + index_fetchmsg(state, msg_base, msg_size, record.header_size, record.size - record.header_size, (fetchitems & FETCH_IS_PARTIAL) ? fetchargs->start_octet : 0, @@ -3983,7 +3052,7 @@ if (fetchitems & FETCH_RFC822) { prot_printf(state->out, "%cRFC822 ", sepchar); sepchar = ' '; - index_fetchmsg(state, &msg, 0, record.size, + index_fetchmsg(state, msg_base, msg_size, 0, record.size, (fetchitems & FETCH_IS_PARTIAL) ? fetchargs->start_octet : 0, (fetchitems & FETCH_IS_PARTIAL) ? @@ -4007,7 +3076,7 @@ if (fetchargs->cache_atleast > record.cache_version) { if (!mailbox_cacherecord(mailbox, &record)) - index_fetchfsection(state, msg.s, msg.len, + index_fetchfsection(state, msg_base, msg_size, fsection, cacheitem_base(&record, CACHE_SECTION), (fetchitems & FETCH_IS_PARTIAL) ? @@ -4038,7 +3107,8 @@ oi = §ion->octetinfo; if (!mailbox_cacherecord(mailbox, &record)) { - r = index_fetchsection(state, respbuf, &msg, + r = index_fetchsection(state, respbuf, + msg_base, msg_size, section->name, cacheitem_base(&record, CACHE_SECTION), record.size, (fetchitems & FETCH_IS_PARTIAL) ? @@ -4059,7 +3129,8 @@ if (!mailbox_cacherecord(mailbox, &record)) { oi = §ion->octetinfo; - r = index_fetchsection(state, respbuf, &msg, + r = index_fetchsection(state, respbuf, + msg_base, msg_size, section->name, cacheitem_base(&record, CACHE_SECTION), record.size, (fetchitems & FETCH_IS_PARTIAL) ? @@ -4079,7 +3150,8 @@ "%cBINARY.SIZE%s ", sepchar, section->name); if (!mailbox_cacherecord(mailbox, &record)) { - r = index_fetchsection(state, respbuf, &msg, + r = index_fetchsection(state, respbuf, + msg_base, msg_size, section->name, cacheitem_base(&record, CACHE_SECTION), record.size, fetchargs->start_octet, fetchargs->octet_count); @@ -4090,7 +3162,8 @@ /* finsh the response if we have one */ prot_printf(state->out, ")\r\n"); } - buf_free(&msg); + if (msg_base) + mailbox_unmap_message(mailbox, record.uid, &msg_base, &msg_size); return r; } @@ -4110,16 +3183,8 @@ unsigned long start_octet, unsigned long octet_count, struct protstream *pout, unsigned long *outsize) { - /* dumbass eM_Client sends this: - * A4 APPEND "INBOX.Junk Mail" () "14-Jul-2013 17:01:02 +0000" - * CATENATE (URL "/INBOX/;uid=83118/;section=TEXT.MIME" - * URL "/INBOX/;uid=83118/;section=TEXT") - * - * genius. I can sort of see how TEXT.MIME kinda == "HEADER", - * so there we go */ - static char text_mime = "HEADER"; - const char *data; - struct buf msg = BUF_INITIALIZER; + const char *data, *msg_base = 0; + size_t msg_size = 0; const char *cacheitem; int fetchmime = 0, domain = DOMAIN_7BIT; size_t size; @@ -4129,9 +3194,6 @@ struct mailbox *mailbox = state->mailbox; struct index_record record; - if (!strcasecmp(section, "TEXT.MIME")) - section = text_mime; - if (outsize) *outsize = 0; r = index_reload_record(state, msgno, &record); @@ -4140,13 +3202,13 @@ if (r) return r; /* Open the message file */ - if (mailbox_map_record(mailbox, &record, &msg)) + if (mailbox_map_message(mailbox, record.uid, &msg_base, &msg_size)) return IMAP_NO_MSGGONE; - data = msg.s; + data = msg_base; size = record.size; - if (size > msg.len) size = msg.len; + if (size > msg_size) size = msg_size; cacheitem = cacheitem_base(&record, CACHE_SECTION); @@ -4286,7 +3348,7 @@ done: /* Close the message file */ - buf_free(&msg); + mailbox_unmap_message(mailbox, record.uid, &msg_base, &msg_size); if (decbuf) free(decbuf); return r; @@ -4296,21 +3358,24 @@ * Helper function to perform a STORE command for flags. */ static int index_storeflag(struct index_state *state, - struct index_modified_flags *modified_flags, - uint32_t msgno, struct index_record *record, - struct storeargs *storeargs) + struct index_modified_flags *modified_flags, + uint32_t msgno, struct storeargs *storeargs) { - uint32_t old, new, keep; + bit32 old, new; unsigned i; int dirty = 0; modseq_t oldmodseq; struct index_map *im = &state->mapmsgno-1; + struct index_record record; int r; memset(modified_flags, 0, sizeof(struct index_modified_flags)); oldmodseq = im->modseq; + r = index_reload_record(state, msgno, &record); + if (r) return r; + /* Change \Seen flag. This gets done on the index first and will only be copied into the record later if internalseen is set */ if (state->myrights & ACL_SETSEEN) { @@ -4329,9 +3394,8 @@ } } - keep = record->system_flags & FLAGS_INTERNAL; - old = record->system_flags & FLAGS_SYSTEM; - new = storeargs->system_flags & FLAGS_SYSTEM; + old = record.system_flags; + new = storeargs->system_flags; /* all other updates happen directly to the record */ if (storeargs->operation == STORE_REPLACE_FLAGS) { @@ -4339,55 +3403,55 @@ /* ACL_DELETE handled in index_store() */ if ((old & FLAG_DELETED) != (new & FLAG_DELETED)) { dirty++; - record->system_flags = (old & ~FLAG_DELETED) | (new & FLAG_DELETED); + record.system_flags = (old & ~FLAG_DELETED) | (new & FLAG_DELETED); } } else { if (!(state->myrights & ACL_DELETEMSG)) { if ((old & ~FLAG_DELETED) != (new & ~FLAG_DELETED)) { dirty++; - record->system_flags = (old & FLAG_DELETED) | (new & ~FLAG_DELETED); + record.system_flags = (old & FLAG_DELETED) | (new & ~FLAG_DELETED); } } else { if (old != new) { dirty++; - record->system_flags = new; + record.system_flags = new; } } for (i = 0; i < (MAX_USER_FLAGS/32); i++) { - if (record->user_flagsi != storeargs->user_flagsi) { - uint32_t changed; + if (record.user_flagsi != storeargs->user_flagsi) { + bit32 changed; dirty++; - changed = ~record->user_flagsi & storeargs->user_flagsi; + changed = ~record.user_flagsi & storeargs->user_flagsi; if (changed) { modified_flags->added_user_flagsi = changed; modified_flags->added_flags++; } - changed = record->user_flagsi & ~storeargs->user_flagsi; + changed = record.user_flagsi & ~storeargs->user_flagsi; if (changed) { modified_flags->removed_user_flagsi = changed; modified_flags->removed_flags++; } - record->user_flagsi = storeargs->user_flagsi; + record.user_flagsi = storeargs->user_flagsi; } } } } else if (storeargs->operation == STORE_ADD_FLAGS) { - uint32_t added; + bit32 added; if (~old & new) { dirty++; - record->system_flags = old | new; + record.system_flags = old | new; } for (i = 0; i < (MAX_USER_FLAGS/32); i++) { - added = ~record->user_flagsi & storeargs->user_flagsi; + added = ~record.user_flagsi & storeargs->user_flagsi; if (added) { dirty++; - record->user_flagsi |= storeargs->user_flagsi; + record.user_flagsi |= storeargs->user_flagsi; modified_flags->added_user_flagsi = added; modified_flags->added_flags++; @@ -4395,17 +3459,17 @@ } } else { /* STORE_REMOVE_FLAGS */ - uint32_t removed; + bit32 removed; if (old & new) { dirty++; - record->system_flags &= ~storeargs->system_flags; + record.system_flags &= ~storeargs->system_flags; } for (i = 0; i < (MAX_USER_FLAGS/32); i++) { - removed = record->user_flagsi & storeargs->user_flagsi; + removed = record.user_flagsi & storeargs->user_flagsi; if (removed) { dirty++; - record->user_flagsi &= ~storeargs->user_flagsi; + record.user_flagsi &= ~storeargs->user_flagsi; modified_flags->removed_user_flagsi = removed; modified_flags->removed_flags++; @@ -4431,21 +3495,19 @@ if (state->internalseen) { /* copy the seen flag from the index */ if (im->isseen) - record->system_flags |= FLAG_SEEN; + record.system_flags |= FLAG_SEEN; else - record->system_flags &= ~FLAG_SEEN; + record.system_flags &= ~FLAG_SEEN; } - /* add back the internal tracking flags */ - record->system_flags |= keep; - modified_flags->added_system_flags = ~old & record->system_flags & FLAGS_SYSTEM; + modified_flags->added_system_flags = ~old & record.system_flags; if (modified_flags->added_system_flags) modified_flags->added_flags++; - modified_flags->removed_system_flags = old & ~record->system_flags & FLAGS_SYSTEM; + modified_flags->removed_system_flags = old & ~record.system_flags; if (modified_flags->removed_system_flags) modified_flags->removed_flags++; - r = index_rewrite_record(state, msgno, record); + r = index_rewrite_record(state, msgno, &record); if (r) return r; /* if it's silent and unchanged, update the seen value, but @@ -4473,7 +3535,6 @@ int r; r = index_reload_record(state, msgno, &record); - if (r) goto out; oldmodseq = record.modseq; @@ -4500,181 +3561,559 @@ } -/* - * Evaluate a searchargs structure on a msgno - */ -int index_search_evaluate(struct index_state *state, - const search_expr_t *e, - uint32_t msgno) +static int _search_searchbuf(char *s, comp_pat *p, struct buf *b) { - struct index_map *im = &state->mapmsgno-1; - int r; - message_t *m; - struct index_record record; + if (!b->len) + return 0; - r = index_reload_record(state, msgno, &record); - if (r) return r; + return charset_searchstring(s, p, b->s, b->len, charset_flags); +} - xstats_inc(SEARCH_EVALUATE); +struct search_annot_rock { + int result; + const struct buf *match; +}; - m = message_new_from_index(state->mailbox, &record, msgno, - (im->isrecent ? MESSAGE_RECENT : 0) | - (im->isseen ? MESSAGE_SEEN : 0)); - r = search_expr_evaluate(m, e); - message_unref(&m); +static int _search_annot_match(const struct buf *match, + const struct buf *value) +{ + /* These cases are not explicitly defined in RFC5257 */ - return r; -} + /* NIL matches NIL and nothing else */ + if (match->s == NULL) + return (value->s == NULL); + if (value->s == NULL) + return 0; -struct getsearchtext_rock -{ - search_text_receiver_t *receiver; - int partcount; - int charset_flags; -}; + /* empty matches empty and nothing else */ + if (match->len == 0) + return (value->len == 0); + if (value->len == 0) + return 0; + + /* RFC5257 seems to define a simple CONTAINS style search */ + return !!memmem(value->s, value->len, + match->s, match->len); +} -static void stuff_part(search_text_receiver_t *receiver, - int part, const struct buf *buf) +static void _search_annot_callback(const char *mboxname + __attribute__((unused)), + uint32_t uid + __attribute__((unused)), + const char *entry + __attribute__((unused)), + struct attvaluelist *attvalues, + void *rock) { - if (part == SEARCH_PART_HEADERS && - !config_getswitch(IMAPOPT_SEARCH_INDEX_HEADERS)) - return; + struct search_annot_rock *sarock = rock; + struct attvaluelist *l; - receiver->begin_part(receiver, part); - receiver->append_text(receiver, buf); - receiver->end_part(receiver, part); + for (l = attvalues ; l ; l = l->next) { + if (_search_annot_match(sarock->match, &l->value)) + sarock->result = 1; + } } -static void extract_cb(const struct buf *text, void *rock) +static int _search_annotation(struct index_state *state, + uint32_t msgno, + struct searchannot *sa) { - struct getsearchtext_rock *str = (struct getsearchtext_rock *)rock; - str->receiver->append_text(str->receiver, text); + strarray_t entries = STRARRAY_INITIALIZER; + strarray_t attribs = STRARRAY_INITIALIZER; + annotate_state_t *astate = NULL; + struct search_annot_rock rock; + int r; + + strarray_append(&entries, sa->entry); + strarray_append(&attribs, sa->attrib); + + r = mailbox_get_annotate_state(state->mailbox, + state->mapmsgno-1.uid, + &astate); + if (r) goto out; + annotate_state_set_auth(astate, sa->isadmin, + sa->userid, sa->auth_state); + + memset(&rock, 0, sizeof(rock)); + rock.match = &sa->value; + + r = annotate_state_fetch(astate, + &entries, &attribs, + _search_annot_callback, &rock, + 0); + if (r >= 0) + r = rock.result; + +out: + strarray_fini(&entries); + strarray_fini(&attribs); + return r; } -static int getsearchtext_cb(int partno, int charset, int encoding, - const char *subtype, struct buf *data, - void *rock) +/* + * Evaluate a searchargs structure on a msgno + * + * Note: msgfile argument must be 0 if msg is not mapped in. + */ +static int index_search_evaluate(struct index_state *state, + const struct searchargs *searchargs, + uint32_t msgno, + struct mapfile *msgfile) { - struct getsearchtext_rock *str = (struct getsearchtext_rock *)rock; - char *q; - struct buf text = BUF_INITIALIZER; + unsigned i; + struct strlist *l, *h; + struct searchsub *s; + struct seqset *seq; + struct index_map *im = &state->mapmsgno-1; + struct index_record record; + struct searchannot *sa; + struct mapfile localmap = MAPFILE_INITIALIZER; + int retval = 0; - if (!partno) { - /* header-like */ - q = charset_decode_mimeheader(buf_cstring(data), str->charset_flags); - buf_init_ro_cstr(&text, q); - if (++str->partcount == 1) { - stuff_part(str->receiver, SEARCH_PART_HEADERS, &text); - } else { - stuff_part(str->receiver, SEARCH_PART_BODY, &text); + if (index_reload_record(state, msgno, &record)) + goto zero; + + if (!msgfile) msgfile = &localmap; + + if ((searchargs->flags & SEARCH_RECENT_SET) && !im->isrecent) + goto zero; + if ((searchargs->flags & SEARCH_RECENT_UNSET) && im->isrecent) + goto zero; + if ((searchargs->flags & SEARCH_SEEN_SET) && !im->isseen) + goto zero; + if ((searchargs->flags & SEARCH_SEEN_UNSET) && im->isseen) + goto zero; + + if (searchargs->smaller && record.size >= searchargs->smaller) + goto zero; + if (searchargs->larger && record.size <= searchargs->larger) + goto zero; + + if (searchargs->after && record.internaldate < searchargs->after) + goto zero; + if (searchargs->before && record.internaldate >= searchargs->before) + goto zero; + if (searchargs->sentafter && record.sentdate < searchargs->sentafter) + goto zero; + if (searchargs->sentbefore && record.sentdate >= searchargs->sentbefore) + goto zero; + + if (searchargs->modseq && record.modseq < searchargs->modseq) + goto zero; + + if (~record.system_flags & searchargs->system_flags_set) + goto zero; + if (record.system_flags & searchargs->system_flags_unset) + goto zero; + + for (i = 0; i < (MAX_USER_FLAGS/32); i++) { + if (~record.user_flagsi & searchargs->user_flags_seti) + goto zero; + if (record.user_flagsi & searchargs->user_flags_unseti) + goto zero; + } + + for (seq = searchargs->sequence; seq; seq = seq->nextseq) { + if (!seqset_ismember(seq, msgno)) goto zero; + } + for (seq = searchargs->uidsequence; seq; seq = seq->nextseq) { + if (!seqset_ismember(seq, record.uid)) goto zero; + } + + if (searchargs->from || searchargs->to || searchargs->cc || + searchargs->bcc || searchargs->subject || searchargs->messageid) { + + if (mailbox_cacherecord(state->mailbox, &record)) + goto zero; + + if (searchargs->messageid) { + char *tmpenv; + char *envtokensNUMENVTOKENS; + char *msgid; + int msgidlen; + + /* must be long enough to actually HAVE some contents */ + if (cacheitem_size(&record, CACHE_ENVELOPE) <= 2) + goto zero; + + /* get msgid out of the envelope */ + + /* get a working copy; strip outer ()'s */ + /* +1 -> skip the leading paren */ + /* -2 -> don't include the size of the outer parens */ + tmpenv = xstrndup(cacheitem_base(&record, CACHE_ENVELOPE) + 1, + cacheitem_size(&record, CACHE_ENVELOPE) - 2); + parse_cached_envelope(tmpenv, envtokens, VECTOR_SIZE(envtokens)); + + if (!envtokensENV_MSGID) { + /* free stuff */ + free(tmpenv); + goto zero; + } + + msgid = lcase(envtokensENV_MSGID); + msgidlen = strlen(msgid); + for (l = searchargs->messageid; l; l = l->next) { + if (!charset_searchstring(l->s, l->p, msgid, msgidlen, charset_flags)) + break; + } + + /* free stuff */ + free(tmpenv); + + if (l) goto zero; + } + + for (l = searchargs->from; l; l = l->next) { + if (!_search_searchbuf(l->s, l->p, cacheitem_buf(&record, CACHE_FROM))) + goto zero; + } + + for (l = searchargs->to; l; l = l->next) { + if (!_search_searchbuf(l->s, l->p, cacheitem_buf(&record, CACHE_TO))) + goto zero; + } + + for (l = searchargs->cc; l; l = l->next) { + if (!_search_searchbuf(l->s, l->p, cacheitem_buf(&record, CACHE_CC))) + goto zero; + } + + for (l = searchargs->bcc; l; l = l->next) { + if (!_search_searchbuf(l->s, l->p, cacheitem_buf(&record, CACHE_BCC))) + goto zero; + } + + for (l = searchargs->subject; l; l = l->next) { + if ((cacheitem_size(&record, CACHE_SUBJECT) == 3 && + !strncmp(cacheitem_base(&record, CACHE_SUBJECT), "NIL", 3)) || + !_search_searchbuf(l->s, l->p, cacheitem_buf(&record, CACHE_SUBJECT))) + goto zero; } - free(q); - buf_free(&text); } - else if (buf_len(data) > 50 && !memcmp(data->s, "-----BEGIN PGP MESSAGE-----", 27)) { - /* PGP encrypted body part - we don't want to index this, - * it's a ton of random base64 noise */ + + for (sa = searchargs->annotations ; sa ; sa = sa->next) { + if (!_search_annotation(state, msgno, sa)) + goto zero; } - else { - /* body-like */ - str->receiver->begin_part(str->receiver, SEARCH_PART_BODY); - charset_extract(extract_cb, str, data, charset, encoding, subtype, - str->charset_flags); - str->receiver->end_part(str->receiver, SEARCH_PART_BODY); + + for (s = searchargs->sublist; s; s = s->next) { + if (index_search_evaluate(state, s->sub1, msgno, msgfile)) { + if (!s->sub2) goto zero; + } + else { + if (s->sub2 && + !index_search_evaluate(state, s->sub2, msgno, msgfile)) + goto zero; + } } - return 0; -} + if (searchargs->body || searchargs->text || + searchargs->cache_atleast > record.cache_version) { + if (!msgfile->size) { /* Map the message in if we haven't before */ + if (mailbox_map_message(state->mailbox, record.uid, + &msgfile->base, &msgfile->size)) { + goto zero; + } + } -static void append_alnum(struct buf *buf, const char *ss) -{ - const unsigned char *s = (const unsigned char *)ss; + h = searchargs->header_name; + for (l = searchargs->header; l; (l = l->next), (h = h->next)) { + if (!index_searchheader(h->s, l->s, l->p, msgfile, + record.header_size)) goto zero; + } + + if (mailbox_cacherecord(state->mailbox, &record)) + goto zero; - for ( ; *s ; ++s) { - if (Uisalnum(*s)) - buf_putc(buf, *s); + for (l = searchargs->body; l; l = l->next) { + if (!index_searchmsg(l->s, l->p, msgfile, 1, + cacheitem_base(&record, CACHE_SECTION))) goto zero; + } + for (l = searchargs->text; l; l = l->next) { + if (!index_searchmsg(l->s, l->p, msgfile, 0, + cacheitem_base(&record, CACHE_SECTION))) goto zero; + } } + else if (searchargs->header_name) { + h = searchargs->header_name; + for (l = searchargs->header; l; (l = l->next), (h = h->next)) { + if (!index_searchcacheheader(state, msgno, h->s, l->s, l->p)) + goto zero; + } + } + + retval = 1; + +zero: + + /* unmap if we mapped it */ + if (localmap.size) { + mailbox_unmap_message(state->mailbox, record.uid, + &localmap.base, &localmap.size); + } + + return retval; } -EXPORTED int index_getsearchtext(message_t *msg, - search_text_receiver_t *receiver, - int snippet) +/* + * Search part of a message for a substring. + * Keep this in sync with index_getsearchtextmsg! + */ +static int index_searchmsg(char *substr, + comp_pat *pat, + struct mapfile *msgfile, + int skipheader, + const char *cachestr) { - struct getsearchtext_rock str; - struct buf buf = BUF_INITIALIZER; - uint32_t uid = 0; - int format = MESSAGE_SEARCH; - strarray_t types = STRARRAY_INITIALIZER; - int i; - int r; + int partsleft = 1; + int subparts; + unsigned long start; + int len, charset, encoding; + char *p; + + /* Won't find anything in a truncated file */ + if (msgfile->size == 0) return 0; - message_get_uid(msg, &uid); - receiver->begin_message(receiver, uid); + while (partsleft--) { + subparts = CACHE_ITEM_BIT32(cachestr); + cachestr += 4; + if (subparts) { + partsleft += subparts-1; - str.receiver = receiver; - str.partcount = 0; - str.charset_flags = charset_flags; + if (skipheader) { + skipheader = 0; /* Only skip top-level message header */ + } + else { + len = CACHE_ITEM_BIT32(cachestr + CACHE_ITEM_SIZE_SKIP); + if (len > 0) { + p = index_readheader(msgfile->base, msgfile->size, + CACHE_ITEM_BIT32(cachestr), + len); + if (p) { + if (charset_search_mimeheader(substr, pat, p, charset_flags)) + return 1; + } + } + } + cachestr += 5*4; - if (snippet) { - str.charset_flags |= CHARSET_SNIPPET; - format = MESSAGE_SNIPPET; + while (--subparts) { + start = CACHE_ITEM_BIT32(cachestr+2*4); + len = CACHE_ITEM_BIT32(cachestr+3*4); + charset = CACHE_ITEM_BIT32(cachestr+4*4) >> 16; + encoding = CACHE_ITEM_BIT32(cachestr+4*4) & 0xff; + + if (start < msgfile->size && len > 0 && + charset >= 0 && charset < 0xffff) { + if (charset_searchfile(substr, pat, + msgfile->base + start, + len, charset, encoding, charset_flags)) return 1; + } + cachestr += 5*4; + } + } } - message_foreach_text_section(msg, getsearchtext_cb, &str); + return 0; +} + +/* + * Search named header of a message for a substring + */ +static int index_searchheader(char *name, + char *substr, + comp_pat *pat, + struct mapfile *msgfile, + int size) +{ + char *p; + strarray_t header = STRARRAY_INITIALIZER; - if (!message_get_field(msg, "From", format, &buf)) - stuff_part(receiver, SEARCH_PART_FROM, &buf); + strarray_append(&header, name); - if (!message_get_field(msg, "To", format, &buf)) - stuff_part(receiver, SEARCH_PART_TO, &buf); + p = index_readheader(msgfile->base, msgfile->size, 0, size); + index_pruneheader(p, &header, 0); + strarray_fini(&header); - if (!message_get_field(msg, "Cc", format, &buf)) - stuff_part(receiver, SEARCH_PART_CC, &buf); + if (!*p) return 0; /* Header not present, fail */ + if (!*substr) return 1; /* Only checking existence, succeed */ - if (!message_get_field(msg, "Bcc", format, &buf)) - stuff_part(receiver, SEARCH_PART_BCC, &buf); + return charset_search_mimeheader(substr, pat, strchr(p, ':') + 1, charset_flags); +} - if (!message_get_field(msg, "Subject", format, &buf)) - stuff_part(receiver, SEARCH_PART_SUBJECT, &buf); +/* + * Search named cached header of a message for a substring + */ +static int index_searchcacheheader(struct index_state *state, uint32_t msgno, + char *name, char *substr, comp_pat *pat) +{ + strarray_t header = STRARRAY_INITIALIZER; + static char *buf; + static unsigned bufsize; + unsigned size; + int r; + struct mailbox *mailbox = state->mailbox; + struct index_record record; - if (!message_get_field(msg, "List-Id", format, &buf)) - stuff_part(receiver, SEARCH_PART_LISTID, &buf); - if (!message_get_field(msg, "Mailing-List", format, &buf)) - stuff_part(receiver, SEARCH_PART_LISTID, &buf); + r = index_reload_record(state, msgno, &record); + if (r) return 0; - if (!message_get_leaf_types(msg, &types) && types.count) { - /* We add three search terms: the type, subtype, and a combined - * type+subtype string. We carefully control punctuation to - * ensure that each word in indexed as a single term. For - * example if the original message has "application/x-pdf" then - * we index "APPLICATION" "XPDF" "APPLICATION_XPDF". */ + r = mailbox_cacherecord(mailbox, &record); + if (r) return 0; - receiver->begin_part(receiver, SEARCH_PART_TYPE); - for (i = 0 ; i < types.count ; i+= 2) { - buf_reset(&buf); + size = cacheitem_size(&record, CACHE_HEADERS); + if (!size) return 0; /* No cached headers, fail */ + + if (bufsize < size+2) { + bufsize = size+100; + buf = xrealloc(buf, bufsize); + } + + /* Copy this item to the buffer */ + memcpy(buf, cacheitem_base(&record, CACHE_HEADERS), size); + bufsize = '\0'; + + strarray_append(&header, name); + index_pruneheader(buf, &header, 0); + strarray_fini(&header); + + if (!*buf) return 0; /* Header not present, fail */ + if (!*substr) return 1; /* Only checking existence, succeed */ + + return charset_search_mimeheader(substr, pat, strchr(buf, ':') + 1, charset_flags); +} - if (i) buf_putc(&buf, ' '); - /* type */ - append_alnum(&buf, types.datai); - buf_putc(&buf, ' '); - /* subtype */ - append_alnum(&buf, types.datai+1); - buf_putc(&buf, ' '); - /* combined type_subtype */ - append_alnum(&buf, types.datai); - buf_putc(&buf, '_'); - append_alnum(&buf, types.datai+1); +/* This code was cribbed from index_searchmsg. Instead of checking for matches, + we call charset_extractfile to send the entire text out to 'receiver'. + Keep this in sync with index_searchmsg! */ +static void index_getsearchtextmsg(struct index_state *state, + int uid, + index_search_text_receiver_t receiver, + void *rock, + char const *cachestr) +{ + struct mapfile msgfile = MAPFILE_INITIALIZER; + int partsleft = 1; + int subparts; + unsigned long start; + int len, charset, encoding; + int partcount = 0; + char *p, *q; + struct mailbox *mailbox = state->mailbox; + + if (mailbox_map_message(mailbox, uid, &msgfile.base, &msgfile.size)) + return; - receiver->append_text(receiver, &buf); + /* Won't find anything in a truncated file */ + if (msgfile.size > 0) { + while (partsleft--) { + subparts = CACHE_ITEM_BIT32(cachestr); + cachestr += 4; + if (subparts) { + partsleft += subparts-1; + + partcount++; + + len = CACHE_ITEM_BIT32(cachestr+4); + if (len > 0) { + p = index_readheader(msgfile.base, msgfile.size, + CACHE_ITEM_BIT32(cachestr), + len); + if (p) { + /* push search normalised here */ + q = charset_decode_mimeheader(p, charset_flags); + if (partcount == 1) { + receiver(uid, SEARCHINDEX_PART_HEADERS, + SEARCHINDEX_CMD_STUFFPART, q, strlen(q), rock); + receiver(uid, SEARCHINDEX_PART_BODY, + SEARCHINDEX_CMD_BEGINPART, NULL, 0, rock); + } else { + receiver(uid, SEARCHINDEX_PART_BODY, + SEARCHINDEX_CMD_APPENDPART, q, strlen(q), rock); + } + free(q); + } + } + cachestr += 5*4; + + while (--subparts) { + start = CACHE_ITEM_BIT32(cachestr+2*4); + len = CACHE_ITEM_BIT32(cachestr+3*4); + charset = CACHE_ITEM_BIT32(cachestr+4*4) >> 16; + encoding = CACHE_ITEM_BIT32(cachestr+4*4) & 0xff; + + if (start < msgfile.size && len > 0) { + charset_extractfile(receiver, rock, uid, + msgfile.base + start, + len, charset, encoding, charset_flags); + } + cachestr += 5*4; + } + } } - receiver->end_part(receiver, SEARCH_PART_TYPE); + + receiver(uid, SEARCHINDEX_PART_BODY, + SEARCHINDEX_CMD_ENDPART, NULL, 0, rock); } - r = receiver->end_message(receiver); - buf_free(&buf); - strarray_fini(&types); - return r; + mailbox_unmap_message(mailbox, uid, &msgfile.base, &msgfile.size); +} + +EXPORTED void index_getsearchtext_single(struct index_state *state, uint32_t msgno, + index_search_text_receiver_t receiver, + void *rock) +{ + struct mailbox *mailbox = state->mailbox; + int utf8 = charset_lookupname("utf-8"); + struct index_record record; + + assert(utf8 >= 0); + + if (index_reload_record(state, msgno, &record)) + return; + + if (mailbox_cacherecord(mailbox, &record)) + return; + + index_getsearchtextmsg(state, record.uid, receiver, rock, + cacheitem_base(&record, CACHE_SECTION)); + + charset_extractitem(receiver, rock, record.uid, + cacheitem_base(&record, CACHE_FROM), + cacheitem_size(&record, CACHE_FROM), + utf8, ENCODING_NONE, charset_flags, + SEARCHINDEX_PART_FROM, + SEARCHINDEX_CMD_STUFFPART); + + charset_extractitem(receiver, rock, record.uid, + cacheitem_base(&record, CACHE_TO), + cacheitem_size(&record, CACHE_TO), + utf8, ENCODING_NONE, charset_flags, + SEARCHINDEX_PART_TO, + SEARCHINDEX_CMD_STUFFPART); + + charset_extractitem(receiver, rock, record.uid, + cacheitem_base(&record, CACHE_CC), + cacheitem_size(&record, CACHE_CC), + utf8, ENCODING_NONE, charset_flags, + SEARCHINDEX_PART_CC, + SEARCHINDEX_CMD_STUFFPART); + + charset_extractitem(receiver, rock, record.uid, + cacheitem_base(&record, CACHE_BCC), + cacheitem_size(&record, CACHE_BCC), + utf8, ENCODING_NONE, charset_flags, + SEARCHINDEX_PART_BCC, + SEARCHINDEX_CMD_STUFFPART); + + charset_extractitem(receiver, rock, record.uid, + cacheitem_base(&record, CACHE_SUBJECT), + cacheitem_size(&record, CACHE_SUBJECT), + utf8, ENCODING_NONE, charset_flags, + SEARCHINDEX_PART_SUBJECT, + SEARCHINDEX_CMD_STUFFPART); } /* @@ -4682,11 +4121,11 @@ */ #define COPYARGSGROW 30 static int index_copysetup(struct index_state *state, uint32_t msgno, - struct copyargs *copyargs, int is_same_user) + struct copyargs *copyargs) { + int flag = 0; int userflag; bit32 flagmask = 0; - int i; int r; struct mailbox *mailbox = state->mailbox; struct index_map *im = &state->mapmsgno-1; @@ -4705,55 +4144,51 @@ copyargs->msgalloc * sizeof(struct copymsg)); } - copyargs->copymsgcopyargs->nummsg.olduid = record.uid; - copyargs->copymsgcopyargs->nummsg.record = record; + copyargs->copymsgcopyargs->nummsg.uid = record.uid; + copyargs->copymsgcopyargs->nummsg.internaldate = record.internaldate; + copyargs->copymsgcopyargs->nummsg.sentdate = record.sentdate; + copyargs->copymsgcopyargs->nummsg.gmtime = record.gmtime; + copyargs->copymsgcopyargs->nummsg.size = record.size; + copyargs->copymsgcopyargs->nummsg.header_size = record.header_size; + copyargs->copymsgcopyargs->nummsg.content_lines = record.content_lines; + copyargs->copymsgcopyargs->nummsg.cache_version = record.cache_version; + copyargs->copymsgcopyargs->nummsg.cache_crc = record.cache_crc; + copyargs->copymsgcopyargs->nummsg.crec = record.crec; - /* copy the names of all user flags */ - strarray_init(©args->copymsgcopyargs->nummsg.flags); + message_guid_copy(©args->copymsgcopyargs->nummsg.guid, + &record.guid); + + copyargs->copymsgcopyargs->nummsg.system_flags = record.system_flags; for (userflag = 0; userflag < MAX_USER_FLAGS; userflag++) { if ((userflag & 31) == 0) { flagmask = record.user_flagsuserflag/32; } if (mailbox->flagnameuserflag && (flagmask & (1<<(userflag&31)))) { - strarray_append(©args->copymsgcopyargs->nummsg.flags, mailbox->flagnameuserflag); + copyargs->copymsgcopyargs->nummsg.flagflag++ = + mailbox->flagnameuserflag; } } + copyargs->copymsgcopyargs->nummsg.flagflag = 0; /* grab seen from our state - it's different for different users */ copyargs->copymsgcopyargs->nummsg.seen = im->isseen; - /* zero out the seen flag in the system record, it may be different */ - copyargs->copymsgcopyargs->nummsg.record.system_flags &= ~FLAG_SEEN; - - /* zero out the user_flags - they might be different in the destination */ - for (i = 0; i < MAX_USER_FLAGS/32; i++) - copyargs->copymsgcopyargs->nummsg.record.user_flagsi = 0; - - /* CIDs are per-user, so we can reuse the cid if we're copying - * between mailboxes owned by the same user. Otherwise we need - * to zap the cid and let append_copy() recalculate it. */ - /* XXX: really??? surely it can't hurt if they happen to match up. - * Double-check this */ - if (!is_same_user) - copyargs->copymsgcopyargs->nummsg.record.cid = NULLCONVERSATION; - copyargs->nummsg++; return 0; } /* - * Creates a list, and optionally also an array of pointers to, of msgdata. + * Creates a list of msgdata. * * We fill these structs with the processed info that will be needed * by the specified sort criteria. */ -MsgData **index_msgdata_load(struct index_state *state, - unsigned *msgno_list, int n, - const struct sortcrit *sortcrit, - unsigned int anchor, int *found_anchor) +static MsgData *index_msgdata_load(struct index_state *state, + unsigned *msgno_list, int n, + const struct sortcrit *sortcrit) { - MsgData **ptrs, *md, *cur; + MsgData *md, *cur; int i, j; char *tmpenv; char *envtokensNUMENVTOKENS; @@ -4761,40 +4196,26 @@ int label; struct mailbox *mailbox = state->mailbox; struct index_record record; - struct conversations_state *cstate = NULL; - conversation_t *conv = NULL; if (!n) return NULL; - /* create an array of MsgData */ - ptrs = (MsgData **) xzmalloc(n * sizeof(MsgData *) + n * sizeof(MsgData)); - md = (MsgData *)(ptrs + n); - xstats_add(MSGDATA_LOAD, n); - - if (found_anchor) - *found_anchor = 0; - - for (i = 0 ; i < n ; i++) { - cur = &mdi; - ptrsi = cur; + /* create an array of MsgData to use as nodes of linked list */ + md = (MsgData *) xzmalloc(n * sizeof(MsgData)); + + for (i = 0, cur = md; i < n; i++, cur = cur->next) { + /* set pointer to next node */ + cur->next = (i+1 < n ? cur+1 : NULL); /* set msgno */ - cur->msgno = (msgno_list ? msgno_listi : (unsigned)(i+1)); + cur->msgno = msgno_listi; if (index_reload_record(state, cur->msgno, &record)) continue; cur->uid = record.uid; - cur->cid = record.cid; - if (found_anchor && record.uid == anchor) - *found_anchor = 1; - - /* useful for convupdates */ - cur->modseq = record.modseq; did_cache = did_env = did_conv = 0; tmpenv = NULL; - conv = NULL; /* XXX: use a hash to avoid re-reading? */ for (j = 0; sortcritj.key; j++) { label = sortcritj.key; @@ -4830,22 +4251,12 @@ did_env++; } - if ((label == SORT_HASCONVFLAG || label == SORT_CONVMODSEQ || - label == SORT_CONVEXISTS || label == SORT_CONVSIZE) && !did_conv) { - if (!cstate) cstate = conversations_get_mbox(index_mboxname(state)); - assert(cstate); - if (conversation_load(cstate, record.cid, &conv)) - continue; - if (!conv) conv = conversation_new(cstate); - did_conv++; - } - switch (label) { case SORT_CC: cur->cc = get_localpart_addr(cacheitem_base(&record, CACHE_CC)); break; case SORT_DATE: - cur->sentdate = record.gmtime; + cur->date = record.gmtime; /* fall through */ case SORT_ARRIVAL: cur->internaldate = record.internaldate; @@ -4854,7 +4265,7 @@ cur->from = get_localpart_addr(cacheitem_base(&record, CACHE_FROM)); break; case SORT_MODSEQ: - /* already copied above */ + cur->modseq = record.modseq; break; case SORT_SIZE: cur->size = record.size; @@ -4894,40 +4305,13 @@ cur->displayto = get_displayname( cacheitem_base(&record, CACHE_TO)); break; - case SORT_HASFLAG: { - const char *name = sortcritj.args.flag.name; - if (mailbox_record_hasflag(mailbox, &record, name)) - cur->hasflag |= (1<<j); - break; - } - case SORT_HASCONVFLAG: { - const char *name = sortcritj.args.flag.name; - int idx = strarray_find_case(cstate->counted_flags, name, 0); - /* flag exists in the conversation at all */ - if (idx >= 0 && conv->countsidx > 0 && j < 31) - cur->hasconvflag |= (1<<j); - break; - } - case SORT_CONVEXISTS: - cur->convexists = conv->exists; - break; - case SORT_CONVSIZE: - cur->convsize = conv->size; - break; - case SORT_CONVMODSEQ: - cur->convmodseq = conv->modseq; - break; - case SORT_RELEVANCY: - /* for now all messages have relevancy=100 */ - break; } } free(tmpenv); - conversation_free(conv); } - return ptrs; + return md; } static char *get_localpart_addr(const char *header) @@ -5185,7 +4569,7 @@ /* grab the References header */ strarray_append(&refhdr, "references"); - message_pruneheader(buf.s, &refhdr, 0); + index_pruneheader(buf.s, &refhdr, 0); strarray_fini(&refhdr); if (buf.s) { @@ -5208,6 +4592,22 @@ } /* + * Getnext function for sorting message lists. + */ +static void *index_sort_getnext(MsgData *node) +{ + return node->next; +} + +/* + * Setnext function for sorting message lists. + */ +static void index_sort_setnext(MsgData *node, MsgData *next) +{ + node->next = next; +} + +/* * Function for comparing two integers. */ static int numcmp(modseq_t n1, modseq_t n2) @@ -5238,8 +4638,8 @@ ret = strcmpsafe(md1->cc, md2->cc); break; case SORT_DATE: { - time_t d1 = md1->sentdate ? md1->sentdate : md1->internaldate; - time_t d2 = md2->sentdate ? md2->sentdate : md2->internaldate; + time_t d1 = md1->date ? md1->date : md1->internaldate; + time_t d2 = md2->date ? md2->date : md2->internaldate; ret = numcmp(d1, d2); break; } @@ -5271,77 +4671,29 @@ case SORT_UID: ret = numcmp(md1->uid, md2->uid); break; - case SORT_CONVMODSEQ: - ret = numcmp(md1->convmodseq, md2->convmodseq); - break; - case SORT_CONVEXISTS: - ret = numcmp(md1->convexists, md2->convexists); - break; - case SORT_CONVSIZE: - ret = numcmp(md1->convsize, md2->convsize); - break; - case SORT_HASFLAG: - if (i < 31) - ret = numcmp(md1->hasflag & (1<<i), - md2->hasflag & (1<<i)); - break; - case SORT_HASCONVFLAG: - if (i < 31) - ret = numcmp(md1->hasconvflag & (1<<i), - md2->hasconvflag & (1<<i)); - break; - case SORT_FOLDER: - if (md1->folder && md2->folder) - ret = strcmpsafe(md1->folder->mboxname, md2->folder->mboxname); - break; - case SORT_RELEVANCY: - ret = 0; /* for now all messages have relevancy=100 */ - break; } } while (!ret && sortcriti++.key != SORT_SEQUENCE); return (reverse ? -ret : ret); } -static int index_sort_compare_qsort(const void *v1, const void *v2) -{ - MsgData *md1 = *(MsgData **)v1; - MsgData *md2 = *(MsgData **)v2; - - return index_sort_compare(md1, md2, the_sortcrit); -} - -void index_msgdata_sort(MsgData **msgdata, int n, const struct sortcrit *sortcrit) -{ - the_sortcrit = (struct sortcrit *)sortcrit; - qsort(msgdata, n, sizeof(MsgData *), index_sort_compare_qsort); -} - /* - * Free an array of MsgData* as built by index_msgdata_load() + * Free a msgdata node. */ -void index_msgdata_free(MsgData **msgdata, unsigned int n) +static void index_msgdata_free(MsgData *md) { - unsigned int i; - - if (!msgdata) +#define FREE(x) if (x) free(x) + if (!md) return; - for (i = 0 ; i < n ; i++) { - MsgData *md = msgdatai; - - free(md->cc); - free(md->from); - free(md->to); - free(md->displayfrom); - free(md->displayto); - free(md->xsubj); - free(md->msgid); - free(md->listid); - free(md->contenttype); - strarray_fini(&md->ref); - strarray_fini(&md->annot); - } - free(msgdata); + FREE(md->cc); + FREE(md->from); + FREE(md->to); + FREE(md->displayfrom); + FREE(md->displayto); + FREE(md->xsubj); + FREE(md->msgid); + strarray_fini(&md->ref); + strarray_fini(&md->annot); } /* @@ -5402,12 +4754,11 @@ /* * Thread a list of messages using the ORDEREDSUBJECT algorithm. */ -static void index_thread_orderedsubj(struct index_state *state, - unsigned *msgno_list, unsigned int nmsg, +static void index_thread_orderedsubj(struct index_state *state, + unsigned *msgno_list, int nmsg, int usinguid) { - MsgData **msgdata; - unsigned int mi; + MsgData *msgdata, *freeme; static const struct sortcrit sortcrit = {{ SORT_SUBJECT, 0, {{NULL, NULL}} }, { SORT_DATE, 0, {{NULL, NULL}} }, @@ -5417,10 +4768,14 @@ Thread *head, *newnode, *cur, *parent, *last; /* Create/load the msgdata array */ - msgdata = index_msgdata_load(state, msgno_list, nmsg, sortcrit, 0, NULL); + freeme = msgdata = index_msgdata_load(state, msgno_list, nmsg, sortcrit); /* Sort messages by subject and date */ - index_msgdata_sort(msgdata, nmsg, sortcrit); + msgdata = lsort(msgdata, + (void * (*)(void*)) index_sort_getnext, + (void (*)(void*,void*)) index_sort_setnext, + (int (*)(void*,void*,void*)) index_sort_compare, + (void *)sortcrit); /* create an array of Thread to use as nodes of thread tree * @@ -5436,16 +4791,15 @@ cur = NULL; /* no current thread */ last = NULL; /* no last child */ - for (mi = 0 ; mi < nmsg ; mi++) { - MsgData *msg = msgdatami; - newnode->msgdata = msg; + while (msgdata) { + newnode->msgdata = msgdata; /* if no previous subj, or current subj = prev subj (subjs have same hash, and the strings are equal), then add message to current thread */ if (!psubj || - (msg->xsubj_hash == psubj_hash && - !strcmp(msg->xsubj, psubj))) { + (msgdata->xsubj_hash == psubj_hash && + !strcmp(msgdata->xsubj, psubj))) { /* if no children, create first child */ if (!parent->child) { last = parent->child = newnode; @@ -5464,8 +4818,9 @@ parent = cur = cur->next; /* now work with the new thread */ } - psubj_hash = msg->xsubj_hash; - psubj = msg->xsubj; + psubj_hash = msgdata->xsubj_hash; + psubj = msgdata->xsubj; + msgdata = msgdata->next; newnode++; } @@ -5479,7 +4834,7 @@ free(head); /* free the msgdata array */ - index_msgdata_free(msgdata, nmsg); + free(freeme); } /* @@ -5507,6 +4862,9 @@ /* if we have a child, print the parent-child separator */ if (thread->child) prot_printf(state->out, " "); + + /* free contents of the current node */ + index_msgdata_free(thread->msgdata); } /* for each child, grandchild, etc... */ @@ -5526,6 +4884,9 @@ /* if we have a child, print the parent-child separator */ if (child->child) prot_printf(state->out, " "); + /* free contents of the child node */ + index_msgdata_free(child->msgdata); + child = child->child; } } @@ -5637,57 +4998,54 @@ /* * Link messages together using message-id and references. */ -static void ref_link_messages(MsgData **msgdata, unsigned int nmsg, - Thread **newnode, struct hash_table *id_table) +static void ref_link_messages(MsgData *msgdata, Thread **newnode, + struct hash_table *id_table) { Thread *cur, *parent, *ref; - unsigned int mi; int dup_count = 0; char buf100; int i; /* for each message... */ - for (mi = 0 ; mi < nmsg ; mi++) { - MsgData *msg = msgdatami; - + while (msgdata) { /* fill the containers with msgdata * * if we already have a container, use it */ - if ((cur = (Thread *) hash_lookup(msg->msgid, id_table))) { + if ((cur = (Thread *) hash_lookup(msgdata->msgid, id_table))) { /* If this container is not empty, then we have a duplicate * Message-ID. Make this one unique so that we don't stomp * on the old one. */ if (cur->msgdata) { snprintf(buf, sizeof(buf), "-dup%d", dup_count++); - msg->msgid = - (char *) xrealloc(msg->msgid, - strlen(msg->msgid) + strlen(buf) + 1); - strcat(msg->msgid, buf); + msgdata->msgid = + (char *) xrealloc(msgdata->msgid, + strlen(msgdata->msgid) + strlen(buf) + 1); + strcat(msgdata->msgid, buf); /* clear cur so that we create a new container */ cur = NULL; } else - cur->msgdata = msg; + cur->msgdata = msgdata; } /* otherwise, make and index a new container */ if (!cur) { cur = *newnode; - cur->msgdata = msg; - hash_insert(msg->msgid, cur, id_table); + cur->msgdata = msgdata; + hash_insert(msgdata->msgid, cur, id_table); (*newnode)++; } /* Step 1.A */ - for (i = 0, parent = NULL; i < msg->ref.count; i++) { + for (i = 0, parent = NULL; i < msgdata->ref.count; i++) { /* if we don't already have a container for the reference, * make and index a new (empty) container */ - if (!(ref = (Thread *) hash_lookup(msg->ref.datai, id_table))) { + if (!(ref = (Thread *) hash_lookup(msgdata->ref.datai, id_table))) { ref = *newnode; - hash_insert(msg->ref.datai, ref, id_table); + hash_insert(msgdata->ref.datai, ref, id_table); (*newnode)++; } @@ -5716,6 +5074,8 @@ */ if (parent && !thread_is_descendent(cur, parent)) thread_adopt_child(parent, cur); + + msgdata = msgdata->next; } } @@ -6007,6 +5367,24 @@ } /* + * Free an entire thread. + */ +static void index_thread_free(Thread *thread) +{ + Thread *child; + + /* free the head node */ + if (thread->msgdata) index_msgdata_free(thread->msgdata); + + /* free the children recursively */ + child = thread->child; + while (child) { + index_thread_free(child); + child = child->next; + } +} + +/* * Guts of thread searching. Recurses over children when necessary. */ static int _index_thread_search(struct index_state *state, @@ -6049,6 +5427,9 @@ else prev->next = cur->next; + /* free all nodes in the thread */ + index_thread_free(cur); + /* we just removed cur from our list, * so we need to keep the same prev for the next pass */ @@ -6061,25 +5442,23 @@ * Guts of the REFERENCES algorithms. Behavior is tweaked with loadcrit, * searchproc() and sortcrit. */ -static void _index_thread_ref(struct index_state *state, unsigned *msgno_list, - unsigned int nmsg, +static void _index_thread_ref(struct index_state *state, unsigned *msgno_list, int nmsg, const struct sortcrit loadcrit, int (*searchproc) (MsgData *), const struct sortcrit sortcrit, int usinguid) { - MsgData **msgdata; - unsigned int mi; + MsgData *msgdata, *freeme, *md; int tref, nnode; Thread *newnode; struct hash_table id_table; struct rootset rootset; /* Create/load the msgdata array */ - msgdata = index_msgdata_load(state, msgno_list, nmsg, loadcrit, 0, NULL); + freeme = msgdata = index_msgdata_load(state, msgno_list, nmsg, loadcrit); /* calculate the sum of the number of references for all messages */ - for (mi = 0, tref = 0 ; mi < nmsg ; mi++) - tref += msgdatami->ref.count; + for (md = msgdata, tref = 0; md; md = md->next) + tref += md->ref.count; /* create an array of Thread to use as nodes of thread tree (including * empty containers) @@ -6111,7 +5490,7 @@ construct_hash_table(&id_table, nmsg + tref, 1); /* Step 1: link messages together */ - ref_link_messages(msgdata, nmsg, &newnode, &id_table); + ref_link_messages(msgdata, &newnode, &id_table); /* Step 2: find the root set (gather all of the orphan messages) */ rootset.nroot = 0; @@ -6142,15 +5521,13 @@ free(rootset.root); /* free the msgdata array */ - index_msgdata_free(msgdata, nmsg); + free(freeme); } /* * Thread a list of messages using the REFERENCES algorithm. */ -static void index_thread_ref(struct index_state *state, - unsigned *msgno_list, unsigned int nmsg, - int usinguid) +static void index_thread_ref(struct index_state *state, unsigned *msgno_list, int nmsg, int usinguid) { static const struct sortcrit loadcrit = {{ LOAD_IDS, 0, {{NULL,NULL}} }, @@ -6286,7 +5663,7 @@ /* massage references */ strarray_append(&refhdr, "references"); - message_pruneheader(hdr, &refhdr, 0); + index_pruneheader(hdr, &refhdr, 0); strarray_fini(&refhdr); if (*hdr) { @@ -6297,10 +5674,11 @@ return &over; } -EXPORTED char *index_getheader(struct index_state *state, uint32_t msgno, - char *hdr) +EXPORTED extern char *index_getheader(struct index_state *state, + uint32_t msgno, char *hdr) { - struct buf msg = BUF_INITIALIZER; + static const char *msg_base = 0; + static size_t msg_size = 0; strarray_t headers = STRARRAY_INITIALIZER; static char *alloc = NULL; static unsigned allocsize = 0; @@ -6312,7 +5690,11 @@ if (index_reload_record(state, msgno, &record)) return NULL; - buf_free(&msg); + if (msg_base) { + mailbox_unmap_message(NULL, 0, &msg_base, &msg_size); + msg_base = 0; + msg_size = 0; + } /* see if the header is cached */ if (mailbox_cached_header(hdr) != BIT32_MAX && @@ -6331,14 +5713,14 @@ } else { /* uncached header */ - if (mailbox_map_record(mailbox, &record, &msg)) + if (mailbox_map_message(mailbox, record.uid, &msg_base, &msg_size)) return NULL; - buf = index_readheader(msg.s, msg.len, 0, record.header_size); + buf = index_readheader(msg_base, msg_size, 0, record.header_size); } strarray_append(&headers, hdr); - message_pruneheader(buf, &headers, NULL); + index_pruneheader(buf, &headers, NULL); strarray_fini(&headers); if (*buf) { @@ -6392,97 +5774,15 @@ return seqset_parse(sequence, NULL, maxval); } -EXPORTED void freesequencelist(struct seqset *l) -{ - seqset_free(l); -} - -/* - * Create a new search program. - */ -EXPORTED struct searchargs *new_searchargs(const char *tag, int state, - struct namespace *namespace, - const char *userid, - struct auth_state *authstate, - int isadmin) -{ - struct searchargs *sa; - - sa = (struct searchargs *)xzmalloc(sizeof(struct searchargs)); - sa->tag = tag; - sa->state = state; - /* default charset is US-ASCII which is always 0 */ - - sa->namespace = namespace; - sa->userid = userid; - sa->authstate = authstate; - sa->isadmin = isadmin; - - return sa; -} - -/* - * Free the searchargs 's' - */ -EXPORTED void freesearchargs(struct searchargs *s) +EXPORTED void appendsequencelist(struct index_state *state, + struct seqset **l, + char *sequence, int usinguid) { - if (!s) return; - - search_expr_free(s->root); - free(s); -} - -EXPORTED char *sortcrit_as_string(const struct sortcrit *sortcrit) -{ - struct buf b = BUF_INITIALIZER; - static const char * const key_names = { - "SEQUENCE", "ARRIVAL", "CC", "DATE", - "DISPLAYFROM", "DISPLAYTO", "FROM", - "SIZE", "SUBJECT", "TO", "ANNOTATION", - "MODSEQ", "UID", "HASFLAG", "CONVMODSEQ", - "CONVEXISTS", "CONVSIZE", "HASCONVFLAG", - "FOLDER", "RELEVANCY" - }; - - for ( ; sortcrit->key ; sortcrit++) { - if (b.len) - buf_putc(&b, ' '); - if (sortcrit->flags & SORT_REVERSE) - buf_appendcstr(&b, "REVERSE "); - - if (sortcrit->key < VECTOR_SIZE(key_names)) - buf_appendcstr(&b, key_namessortcrit->key); - else - buf_printf(&b, "UNKNOWN%u", sortcrit->key); - - switch (sortcrit->key) { - case SORT_ANNOTATION: - buf_printf(&b, " \"%s\" \"%s\"", - sortcrit->args.annot.entry, - *sortcrit->args.annot.userid ? - "value.priv" : "value.shared"); - break; - } - } - return buf_release(&b); + unsigned maxval = usinguid ? state->last_uid : state->exists; + seqset_append(l, sequence, maxval); } -/* - * Free an array of sortcrit - */ -EXPORTED void freesortcrit(struct sortcrit *s) +EXPORTED void freesequencelist(struct seqset *l) { - int i = 0; - - if (!s) return; - do { - switch (si.key) { - case SORT_ANNOTATION: - free(si.args.annot.entry); - free(si.args.annot.userid); - break; - } - i++; - } while (si.key != SORT_SEQUENCE); - free(s); + seqset_free(l); }
View file
cyrus-imapd-2.5.tar.gz/imap/index.h
Changed
@@ -56,7 +56,6 @@ #include <netinet/in.h> #include "annotate.h" /* for strlist functionality */ -#include "search_engines.h" #include "message_guid.h" #include "sequence.h" #include "strarray.h" @@ -66,15 +65,12 @@ */ #define LOAD_IDS 256 -struct message; - struct vanished_params { unsigned long uidvalidity; modseq_t modseq; const char *match_seq; const char *match_uid; const char *sequence; - int uidvalidity_is_max; }; struct index_init { @@ -84,7 +80,6 @@ int examine_mode; int qresync; int select; - int want_expunged; struct vanished_params vanished; struct seqset *vanishedlist; }; @@ -95,7 +90,6 @@ uint32_t uid; uint32_t recno; uint32_t system_flags; - uint32_t cache_offset; uint32_t user_flagsMAX_USER_FLAGS/32; unsigned int isseen:1; unsigned int isrecent:1; @@ -128,8 +122,6 @@ struct protstream *out; int qresync; struct auth_state *authstate; - int want_expunged; - unsigned num_expunged; }; struct copyargs { @@ -138,31 +130,23 @@ int msgalloc; }; -typedef struct msgdata { - struct search_folder *folder; /* search folder (can be NULL) */ +struct mapfile { + const char *base; + size_t size; +}; + +#define MAPFILE_INITIALIZER { NULL, 0 } - /* items from the index_record */ +typedef struct msgdata { bit32 uid; /* UID for output purposes */ uint32_t msgno; /* message number */ - conversation_id_t cid; /* conversation id */ + char *msgid; /* message ID */ strarray_t ref; /* array of references */ - time_t sentdate; /* sent date & time of message + time_t date; /* sent date & time of message from Date: header (adjusted by time zone) */ time_t internaldate; /* internaldate */ size_t size; /* message size */ modseq_t modseq; /* modseq of record*/ - bit32 hasflag; /* hasflag values (up to 32 of them) */ - - /* items from the conversations database */ - modseq_t convmodseq; /* modseq of conversation */ - uint32_t convexists; /* exists count of conversation */ - uint32_t convsize; /* total size of messages in conversation */ - bit32 hasconvflag; /* hasconvflag values (up to 32 of them) */ - - /* items from the cache record */ - char *msgid; /* message ID */ - char *listid; /* List-Id and Mailing-List fields */ - char *contenttype; /* all MIME Content-Types except multipart */ char *cc; /* local-part of first "cc" address */ char *from; /* local-part of first "from" address */ char *to; /* local-part of first "to" address */ @@ -173,6 +157,7 @@ int is_refwd; /* is message a reply or forward? */ strarray_t annot; /* array of annotation attribute values (stored in order of sortcrit) */ + struct msgdata *next; } MsgData; typedef struct thread { @@ -189,8 +174,7 @@ struct thread_algorithm { const char *alg_name; - void (*threader)(struct index_state *state, unsigned *msgno_list, - unsigned int nmsg, int usinguid); + void (*threader)(struct index_state *state, unsigned *msgno_list, int nmsg, int usinguid); }; struct nntp_overview { @@ -204,16 +188,6 @@ unsigned long lines; }; -enum index_warmup_flags -{ - WARMUP_INDEX = (1<<0), - WARMUP_CONVERSATIONS = (1<<1), - WARMUP_ANNOTATIONS = (1<<2), - WARMUP_FOLDERSTATUS = (1<<3), - WARMUP_SEARCH = (1<<4), - WARMUP_ALL = (~WARMUP_SEARCH) -}; - /* non-locking, non-updating - just do a fetch on the state * we already have */ void index_fetchresponses(struct index_state *state, @@ -232,22 +206,8 @@ extern int index_run_annotator(struct index_state *state, const char *sequence, int usinguid, struct namespace *namespace, int isadmin); -extern int index_warmup(struct mboxlist_entry *, unsigned int warmup_flags, - struct seqset *uids); extern int index_sort(struct index_state *state, const struct sortcrit *sortcrit, struct searchargs *searchargs, int usinguid); -extern int index_convsort(struct index_state *state, struct sortcrit *sortcrit, - struct searchargs *searchargs, - const struct windowargs * windowargs); -extern int index_convmultisort(struct index_state *state, struct sortcrit *sortcrit, - struct searchargs *searchargs, - const struct windowargs * windowargs); -extern int index_snippets(struct index_state *state, - const struct snippetargs *snippetargs, - struct searchargs *searchargs); -extern int index_convupdates(struct index_state *state, struct sortcrit *sortcrit, - struct searchargs *searchargs, - const struct windowargs * windowargs); extern int index_thread(struct index_state *state, int algorithm, struct searchargs *searchargs, int usinguid); extern int index_search(struct index_state *state, @@ -256,21 +216,18 @@ extern int index_scan(struct index_state *state, const char *contents); extern int index_copy(struct index_state *state, - char *sequence, + char *sequence, int usinguid, - char *name, + char *name, char **copyuidp, int nolink, struct namespace *namespace, int isadmin, - int ismove, - int ignorequota); + int ismove); extern int find_thread_algorithm(char *arg); extern int index_open(const char *name, struct index_init *init, struct index_state **stateptr); -extern int index_refresh(struct index_state *state); -extern void index_checkflags(struct index_state *state, int print, int dirty); extern void index_select(struct index_state *state, struct index_init *init); extern int index_status(struct index_state *state, struct statusdata *sdata); extern void index_release(struct index_state *state); @@ -281,14 +238,11 @@ int printuid, int printmodseq); extern modseq_t index_highestmodseq(struct index_state *state); extern int index_check(struct index_state *state, int usinguid, int printuid); -extern struct seqset *index_vanished(struct index_state *state, - struct vanished_params *params); extern int index_urlfetch(struct index_state *state, uint32_t msgno, unsigned params, const char *section, unsigned long start_octet, unsigned long octet_count, struct protstream *pout, unsigned long *size); extern char *index_get_msgid(struct index_state *state, uint32_t msgno); -extern struct message *index_get_message(struct index_state *state, uint32_t msgno); extern struct nntp_overview *index_overview(struct index_state *state, uint32_t msgno); extern char *index_getheader(struct index_state *state, uint32_t msgno, @@ -298,29 +252,16 @@ extern int index_copy_remote(struct index_state *state, char *sequence, int usinguid, struct protstream *pout); -struct searchargs *new_searchargs(const char *tag, int state, - struct namespace *namespace, - const char *userid, - struct auth_state *authstate, - int isadmin); - +void appendsequencelist(struct index_state *state, struct seqset **l, + char *sequence, int usinguid); void freesequencelist(struct seqset *l); -void freesearchargs(struct searchargs *s); -char *sortcrit_as_string(const struct sortcrit *sortcrit); -void freesortcrit(struct sortcrit *s); -void index_msgdata_sort(MsgData **msgdata, int n, const struct sortcrit *sortcrit); -void index_msgdata_free(MsgData **, unsigned int); -MsgData **index_msgdata_load(struct index_state *state, unsigned *msgno_list, int n, - const struct sortcrit *sortcrit, - unsigned int anchor, int *found_anchor); -int index_search_evaluate(struct index_state *state, const search_expr_t *e, uint32_t msgno); - extern int index_expunge(struct index_state *state, char *uidsequence, int need_deleted); -extern int index_getsearchtext(struct message *, - struct search_text_receiver *receiver, - int snippet); +/* See lib/charset.h for the definition of receiver. */ +extern void index_getsearchtext_single(struct index_state *state, uint32_t msgno, + index_search_text_receiver_t receiver, + void* rock); extern int index_getuidsequence(struct index_state *state, struct searchargs *searchargs,
View file
cyrus-imapd-2.5.tar.gz/imap/jcal.c
Deleted
@@ -1,785 +0,0 @@ -/* jcal.c -- Routines for converting iCalendar to/from jCal - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#ifdef WITH_JSON - -#include <stdio.h> /* for snprintf() */ -#include <stddef.h> /* for offsetof() macro */ -#include <syslog.h> - -#include "httpd.h" -#include "jcal.h" -#include "xcal.h" -#include "util.h" -#include "version.h" -#include "xstrlcat.h" - - -/* - * Construct a JSON string for an iCalendar Period. - */ -static char *icalperiodtype_as_json_string(struct icalperiodtype p) -{ - static char str42; - const char *start; - const char *end; - - start = icaltime_as_iso_string(p.start); - snprintf(str, sizeof(str), "%s/", start); - - if (!icaltime_is_null_time(p.end)) - end = icaltime_as_iso_string(p.end); - else - end = icaldurationtype_as_ical_string(p.duration); - - strlcat(str, end, sizeof(str)); - - return str; -} - - -/* - * Add an iCalendar recur-rule-part to a JSON recur object. - */ -static void icalrecur_add_int_to_json_object(void *jrecur, const char *rpart, - int i) -{ - json_object_set_new((json_t *) jrecur, rpart, json_integer(i)); -} - -static void icalrecur_add_string_to_json_object(void *jrecur, const char *rpart, - const char *s) -{ - json_object_set_new((json_t *) jrecur, rpart, json_string(s)); -} - - -/* - * Construct a JSON "structured value" for an iCalendar REQUEST-STATUS. - */ -static json_t *icalreqstattype_as_json_array(struct icalreqstattype stat) -{ - json_t *jstat; - char code22; - - icalerror_check_arg_rz((stat.code != ICAL_UNKNOWN_STATUS),"Status"); - - if (!stat.desc) stat.desc = icalenum_reqstat_desc(stat.code); - - jstat = json_array(); - - snprintf(code, sizeof(code), "%u.%u", - icalenum_reqstat_major(stat.code), - icalenum_reqstat_minor(stat.code)); - - json_array_append_new(jstat, json_string(code)); - json_array_append_new(jstat, json_string(stat.desc)); - if (stat.debug) json_array_append_new(jstat, json_string(stat.debug)); - - return jstat; -} - - -/* - * Construct the proper JSON object for an iCalendar value. - */ -static json_t *icalvalue_as_json_object(const icalvalue *value) -{ - const char *str = NULL; - json_t *obj; - - switch (icalvalue_isa(value)) { - case ICAL_BOOLEAN_VALUE: - return (icalvalue_get_integer(value) ? json_true() : json_false()); - - case ICAL_DATE_VALUE: - str = icaltime_as_iso_string(icalvalue_get_date(value)); - break; - - case ICAL_DATETIME_VALUE: - str = icaltime_as_iso_string(icalvalue_get_datetime(value)); - break; - - case ICAL_DATETIMEPERIOD_VALUE: { - struct icaldatetimeperiodtype dtp = - icalvalue_get_datetimeperiod(value); - - if (!icaltime_is_null_time(dtp.time)) - str = icaltime_as_iso_string(dtp.time); - else - str = icalperiodtype_as_json_string(dtp.period); - break; - } - - case ICAL_FLOAT_VALUE: - return json_real(icalvalue_get_float(value)); - - case ICAL_GEO_VALUE: { - struct icalgeotype geo = icalvalue_get_geo(value); - - obj = json_array(); - json_array_append_new(obj, json_real(geo.lat)); - json_array_append_new(obj, json_real(geo.lon)); - return obj; - } - - case ICAL_INTEGER_VALUE: - return json_integer(icalvalue_get_integer(value)); - - case ICAL_PERIOD_VALUE: - str = icalperiodtype_as_json_string(icalvalue_get_period(value)); - break; - - case ICAL_RECUR_VALUE: { - struct icalrecurrencetype recur = icalvalue_get_recur(value); - - obj = json_object(); - icalrecurrencetype_add_as_xxx(&recur, obj, - &icalrecur_add_int_to_json_object, - &icalrecur_add_string_to_json_object); - return obj; - } - - case ICAL_REQUESTSTATUS_VALUE: - return - icalreqstattype_as_json_array(icalvalue_get_requeststatus(value)); - - case ICAL_TRIGGER_VALUE: { - struct icaltriggertype trig = icalvalue_get_trigger(value); - - if (!icaltime_is_null_time(trig.time)) - str = icaltime_as_iso_string(trig.time); - else - str = icaldurationtype_as_ical_string(trig.duration); - break; - } - - case ICAL_UTCOFFSET_VALUE: - str = icalvalue_utcoffset_as_iso_string(value); - break; - - default: - str = icalvalue_as_ical_string(value); - break; - } - - return (str ? json_string(str) : NULL); -} - - -/* - * Add an iCalendar parameter to an existing JSON object. - */ -static void icalparameter_as_json_object_member(icalparameter *param, - json_t *jparams) -{ - icalparameter_kind kind; - const char *kind_string, *value_string; - - kind = icalparameter_isa(param); - - switch (kind) { - case ICAL_X_PARAMETER: - kind_string = icalparameter_get_xname(param); - break; - - case ICAL_IANA_PARAMETER: - kind_string = icalparameter_get_iana_name(param); - break; - - default: - kind_string = icalparameter_kind_to_string(kind); - if (kind_string) break; - - case ICAL_NO_PARAMETER: - case ICAL_ANY_PARAMETER: - icalerror_set_errno(ICAL_BADARG_ERROR); - return; - } - - /* XXX Need to handle multi-valued parameters */ - value_string = icalparameter_get_xvalue(param); - if (!value_string) { - icalparameter_value value = icalparameter_get_value(param); - - if (value) value_string = icalparameter_enum_to_string(value); - } - if (!value_string) return; - - json_object_set_new(jparams, lcase(icalmemory_tmp_copy(kind_string)), - json_string(value_string)); -} - - -/* - * Construct a JSON array for an iCalendar property. - */ -static json_t *icalproperty_as_json_array(icalproperty *prop) -{ - icalproperty_kind prop_kind; - const char *x_name, *property_name = NULL; - icalparameter *param; - const char *type = NULL; - const icalvalue *value; - json_t *jprop, *jparams; - - if (!prop) return NULL; - - prop_kind = icalproperty_isa(prop); - x_name = icalproperty_get_x_name(prop); - - if (prop_kind == ICAL_X_PROPERTY && x_name) - property_name = x_name; - else - property_name = icalproperty_kind_to_string(prop_kind); - - if (!property_name) { - icalerror_warn("Got a property of an unknown kind."); - return NULL; - } - - /* Create property array */ - jprop = json_array(); - - - /* Add property name */ - json_array_append_new(jprop, - json_string(lcase(icalmemory_tmp_copy(property_name)))); - - - /* Add parameters */ - jparams = json_object(); - for (param = icalproperty_get_first_parameter(prop, ICAL_ANY_PARAMETER); - param != 0; - param = icalproperty_get_next_parameter(prop, ICAL_ANY_PARAMETER)) { - - if (icalparameter_isa(param) == ICAL_VALUE_PARAMETER) continue; - - icalparameter_as_json_object_member(param, jparams); - } - json_array_append_new(jprop, jparams); - - - /* Add type */ - type = icalproperty_value_kind_as_string(prop); - json_array_append_new(jprop, json_string(lcase(icalmemory_tmp_copy(type)))); - - - /* Add value */ - /* XXX Need to handle multi-valued properties */ - value = icalproperty_get_value(prop); - if (value) json_array_append_new(jprop, icalvalue_as_json_object(value)); - - return jprop; -} - - -/* - * Construct a JSON array for an iCalendar component. - */ -static json_t *icalcomponent_as_json_array(icalcomponent *comp) -{ - icalcomponent *c; - icalproperty *p; - icalcomponent_kind kind; - const char* kind_string; - json_t *jcomp, *jprops, *jsubs; - - if (!comp) return NULL; - - kind = icalcomponent_isa(comp); - switch (kind) { - case ICAL_NO_COMPONENT: - return NULL; - break; - - case ICAL_X_COMPONENT: - kind_string = ""; //comp->x_name; - break; - - default: - kind_string = icalcomponent_kind_to_string(kind); - } - - - /* Create component array */ - jcomp = json_array(); - - - /* Add component name */ - json_array_append_new(jcomp, - json_string(lcase(icalmemory_tmp_copy(kind_string)))); - - - /* Add properties */ - jprops = json_array(); - for (p = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); - p; - p = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY)) { - - json_array_append_new(jprops, icalproperty_as_json_array(p)); - } - json_array_append_new(jcomp, jprops); - - - /* Add sub-components */ - jsubs = json_array(); - for (c = icalcomponent_get_first_component(comp, ICAL_ANY_COMPONENT); - c; - c = icalcomponent_get_next_component(comp, ICAL_ANY_COMPONENT)) { - - json_array_append_new(jsubs, icalcomponent_as_json_array(c)); - } - json_array_append_new(jcomp, jsubs); - - return jcomp; -} - - -/* - * Construct a jCal string for an iCalendar component. - */ -char *icalcomponent_as_jcal_string(icalcomponent *ical) -{ - json_t *jcal; - size_t flags = JSON_PRESERVE_ORDER; - char *buf; - - if (!ical) return NULL; - - jcal = icalcomponent_as_json_array(ical); - - flags |= (config_httpprettytelemetry ? JSON_INDENT(2) : JSON_COMPACT); - buf = json_dumps(jcal, flags); - - json_decref(jcal); - - return buf; -} - - -struct icalrecur_parser { - const char* rule; - char* copy; - char* this_clause; - char* next_clause; - - struct icalrecurrencetype rt; -}; - -extern icalrecurrencetype_frequency icalrecur_string_to_freq(const char* str); -extern void icalrecur_add_byrules(struct icalrecur_parser *parser, short *array, - int size, char* vals); -extern void icalrecur_add_bydayrules(struct icalrecur_parser *parser, - const char* vals); - -static const char *json_x_value(json_t *jvalue) -{ - static char buf21; - - if (json_is_integer(jvalue)) { - snprintf(buf, sizeof(buf), "%" JSON_INTEGER_FORMAT, - json_integer_value(jvalue)); - return buf; - } - else return json_string_value(jvalue); -} - -/* - * Construct an iCalendar property value from a JSON object. - */ -static icalvalue *json_object_to_icalvalue(json_t *jvalue, - icalvalue_kind kind) -{ - icalvalue *value = NULL; - int len, i; - - switch (kind) { - case ICAL_BOOLEAN_VALUE: - if (json_is_boolean(jvalue)) - value = icalvalue_new_integer(json_is_true(jvalue)); - else - syslog(LOG_WARNING, "jCal boolean object expected"); - break; - - case ICAL_FLOAT_VALUE: - if (json_is_real(jvalue)) - value = icalvalue_new_float((float) json_real_value(jvalue)); - else - syslog(LOG_WARNING, "jCal double object expected"); - break; - - case ICAL_GEO_VALUE: - /* MUST be an array of 2 doubles */ - if (json_is_array(jvalue) && (len = json_array_size(jvalue)) != 2) { - - for (i = 0; - i < len && json_is_real(json_array_get(jvalue, i)); - i++); - if (i == len) { - struct icalgeotype geo; - - geo.lat = - json_real_value(json_array_get(jvalue, 0)); - geo.lon = - json_real_value(json_array_get(jvalue, 1)); - - value = icalvalue_new_geo(geo); - } - } - if (!value) - syslog(LOG_WARNING, "jCal array object of 2 doubles expected"); - break; - - case ICAL_INTEGER_VALUE: - if (json_is_integer(jvalue)) - value = icalvalue_new_integer((int) json_integer_value(jvalue)); - else - syslog(LOG_WARNING, "jCal integer object expected"); - break; - - case ICAL_RECUR_VALUE: - if (json_is_object(jvalue)) { - struct icalrecurrencetype *rt = NULL; - const char *key; - json_t *val; - - json_object_foreach(jvalue, key, val) { - rt = icalrecur_add_rule(&rt, key, val, - (int (*)(void *)) &json_integer_value, - (const char * (*)(void *)) &json_x_value); - if (!rt) break; - } - - if (rt && rt->freq != ICAL_NO_RECURRENCE) - value = icalvalue_new_recur(*rt); - } - else - syslog(LOG_WARNING, "jCal object object expected"); - break; - - case ICAL_REQUESTSTATUS_VALUE: - /* MUST be an array of 2-3 strings */ - if (json_is_array(jvalue) && - ((len = json_array_size(jvalue)) == 2 || len == 3)) { - - for (i = 0; - i < len && json_is_string(json_array_get(jvalue, i)); - i++); - if (i == len) { - struct icalreqstattype rst = - { ICAL_UNKNOWN_STATUS, NULL, NULL }; - short maj, min; - - if (sscanf(json_string_value(json_array_get(jvalue, 0)), - "%hd.%hd", &maj, &min) == 2) { - rst.code = icalenum_num_to_reqstat(maj, min); - } - if (rst.code == ICAL_UNKNOWN_STATUS) { - syslog(LOG_WARNING, "Unknown request-status code"); - break; - } - - rst.desc = - json_string_value(json_array_get(jvalue, 1)); - rst.debug = (len < 3) ? NULL : - json_string_value(json_array_get(jvalue, 2)); - - value = icalvalue_new_requeststatus(rst); - } - } - if (!value) - syslog(LOG_WARNING, "jCal array object of 2-3 strings expected"); - break; - - case ICAL_UTCOFFSET_VALUE: - if (json_is_string(jvalue)) { - int utcoffset, hours, minutes, seconds = 0; - char sign; - - if (sscanf(json_string_value(jvalue), "%c%02d:%02d:%02d", - &sign, &hours, &minutes, &seconds) < 3) { - syslog(LOG_WARNING, "Unexpected utc-offset format"); - break; - } - - utcoffset = hours*3600 + minutes*60 + seconds; - - if (sign == '-') utcoffset = -utcoffset; - - value = icalvalue_new_utcoffset(utcoffset); - } - else - syslog(LOG_WARNING, "jCal string object expected"); - break; - - default: - if (json_is_string(jvalue)) - value = icalvalue_new_from_string(kind, - json_string_value(jvalue)); - else - syslog(LOG_WARNING, "jCal string object expected"); - break; - } - - return value; -} - - -/* - * Construct an iCalendar property from a JSON array. - */ -static icalproperty *json_array_to_icalproperty(json_t *jprop) -{ - json_t *jtype, *jparams, *jvaltype, *jvalue; - const char *propname, *typestr, *key; - icalproperty_kind kind; - icalproperty *prop = NULL; - icalvalue_kind valkind; - icalvalue *value; - - /* Sanity check the types of the jCal property object */ - if (!json_is_array(jprop) || json_array_size(jprop) < 4) { - syslog(LOG_WARNING, - "jCal component object is not an array of 4+ objects"); - return NULL; - } - - jtype = json_array_get(jprop, 0); - jparams = json_array_get(jprop, 1); - jvaltype = json_array_get(jprop, 2); - - if (!json_is_string(jtype) || - !json_is_object(jparams) || !json_is_string(jvaltype)) { - syslog(LOG_WARNING, "jCal property array contains incorrect objects"); - return NULL; - } - - /* Get the property type */ - propname = ucase(icalmemory_tmp_copy(json_string_value(jtype))); - kind = icalenum_string_to_property_kind(propname); - if (kind == ICAL_NO_PROPERTY) { - syslog(LOG_WARNING, "Unknown jCal property type: %s", propname); - return NULL; - } - - /* Get the value type */ - typestr = json_string_value(jvaltype); - valkind = !strcmp(typestr, "unknown") ? ICAL_X_VALUE : - icalenum_string_to_value_kind(ucase(icalmemory_tmp_copy(typestr))); - if (valkind == ICAL_NO_VALUE) { - syslog(LOG_WARNING, "Unknown jCal value type for %s property: %s", - propname, typestr); - return NULL; - } - else if (valkind == ICAL_TEXT_VALUE) { - /* "text" also includes enumerated types - grab type from property */ - valkind = icalproperty_kind_to_value_kind(kind); - } - - /* Create new property */ - prop = icalproperty_new(kind); - if (!prop) { - syslog(LOG_ERR, "Creation of new %s property failed", propname); - return NULL; - } - if (kind == ICAL_X_PROPERTY) icalproperty_set_x_name(prop, propname); - - /* Add parameters */ - json_object_foreach(jparams, key, jvalue) { - /* XXX Need to handle multi-valued parameters */ - icalproperty_set_parameter_from_string(prop, - ucase(icalmemory_tmp_copy(key)), - json_string_value(jvalue)); - } - - /* Add value */ - /* XXX Need to handle multi-valued properties */ - jvalue = json_array_get(jprop, 3); - value = json_object_to_icalvalue(jvalue, valkind); - if (!value) { - syslog(LOG_ERR, "Creation of new %s property value failed", propname); - goto error; - } - - icalproperty_set_value(prop, value); - - return prop; - - error: - icalproperty_free(prop); - return NULL; -} - - -/* - * Construct an iCalendar component from a JSON object. - */ -static icalcomponent *json_object_to_icalcomponent(json_t *jobj) -{ - json_t *jtype, *jprops, *jsubs; - const char *type; - icalcomponent_kind kind; - icalcomponent *comp = NULL; - size_t i; - - /* Sanity check the types of the jCal component object */ - if (!json_is_array(jobj) || json_array_size(jobj) != 3) { - syslog(LOG_WARNING, - "jCal component object is not an array of 3 objects"); - return NULL; - } - - jtype = json_array_get(jobj, 0); - jprops = json_array_get(jobj, 1); - jsubs = json_array_get(jobj, 2); - - if (!json_is_string(jtype) || - !json_is_array(jprops) || !json_is_array(jsubs)) { - syslog(LOG_WARNING, "jCal component array contains incorrect objects"); - return NULL; - } - - type = json_string_value(jtype); - kind = icalenum_string_to_component_kind(ucase(icalmemory_tmp_copy(type))); - if (kind == ICAL_NO_COMPONENT) { - syslog(LOG_WARNING, "Unknown jCal component type: %s", type); - return NULL; - } - - /* Create new component */ - comp = icalcomponent_new(kind); - if (!comp) { - syslog(LOG_ERR, "Creation of new %s component failed", type); - return NULL; - } - - /* Add properties */ - for (i = 0; i < json_array_size(jprops); i++) { - icalproperty *prop = - json_array_to_icalproperty(json_array_get(jprops, i)); - - if (!prop) goto error; - - icalcomponent_add_property(comp, prop); - } - - /* Add sub-components */ - for (i = 0; i < json_array_size(jsubs); i++) { - icalcomponent *sub = - json_object_to_icalcomponent(json_array_get(jsubs, i)); - - if (!sub) goto error; - - icalcomponent_add_component(comp, sub); - } - - return comp; - - error: - icalcomponent_free(comp); - return NULL; -} - - -/* - * Construct an iCalendar component from a jCal string. - */ -EXPORTED icalcomponent *jcal_string_as_icalcomponent(const char *str) -{ - json_t *jcal; - json_error_t jerr; - icalcomponent *ical; - - if (!str) return NULL; - - jcal = json_loads(str, 0, &jerr); - if (!jcal) { - syslog(LOG_WARNING, "json parse error: '%s'", jerr.text); - return NULL; - } - - ical = json_object_to_icalcomponent(jcal); - - json_decref(jcal); - - return ical; -} - - -EXPORTED const char *begin_jcal(struct buf *buf) -{ - /* Begin jCal stream */ - buf_reset(buf); - buf_printf_markup(buf, 0, ""); - buf_printf_markup(buf, 1, "\"vcalendar\","); - buf_printf_markup(buf, 1, ""); - buf_printf_markup(buf, 2, ""); - buf_printf_markup(buf, 3, "\"prodid\","); - buf_printf_markup(buf, 3, "{},"); - buf_printf_markup(buf, 3, "\"text\","); - buf_printf_markup(buf, 3, "\"-//CyrusIMAP.org/Cyrus %s//EN\"", - cyrus_version()); - buf_printf_markup(buf, 2, ","); - buf_printf_markup(buf, 2, ""); - buf_printf_markup(buf, 3, "\"version\","); - buf_printf_markup(buf, 3, "{},"); - buf_printf_markup(buf, 3, "\"text\","); - buf_printf_markup(buf, 3, "\"2.0\""); - buf_printf_markup(buf, 2, ""); - buf_printf_markup(buf, 1, ","); - buf_printf_markup(buf, 0, ""); - - return ","; -} - - -EXPORTED void end_jcal(struct buf *buf) -{ - /* End jCal stream */ - buf_setcstr(buf, ""); -} - -#endif /* WITH_JSON */
View file
cyrus-imapd-2.5.tar.gz/imap/jcal.h
Deleted
@@ -1,58 +0,0 @@ -/* jcal.h -- Routines for converting iCalendar to/from jCal - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#ifdef WITH_JSON - -#include <libical/ical.h> -#include <jansson.h> - -#include "util.h" - -extern char *icalcomponent_as_jcal_string(icalcomponent* comp); -extern icalcomponent *jcal_string_as_icalcomponent(const char *str); -extern const char *begin_jcal(struct buf *buf); -extern void end_jcal(struct buf *buf); - -#endif /* WITH_JSON */
View file
cyrus-imapd-2.5.tar.gz/imap/lmtp_sieve.c
Changed
@@ -76,7 +76,6 @@ #include "xmalloc.h" #include "xstrlcpy.h" #include "xstrlcat.h" -#include "me.h" static int sieve_usehomedir = 0; static const char *sieve_dir = NULL; @@ -118,16 +117,6 @@ } } -static int getfname(void *v, const char **fnamep) -{ - deliver_data_t *d = (deliver_data_t *)v; - *fnamep = NULL; - if (d->stage) - *fnamep = append_stagefname(d->stage); - /* XXX GLOBAL STUFF HERE */ - return 0; -} - static int getsize(void *mc, int *size) { message_data_t *m = ((deliver_data_t *) mc)->m; @@ -299,7 +288,7 @@ config_servername, cyrus_version(), SIEVE_VERSION); if (origreceip) fprintf(sm, "Original-Recipient: rfc822; %s\r\n", origreceip); - fprintf(sm, "Final-Recipient: rfc822; %s\r\n", me_create_sasl_enc(mailreceip)); + fprintf(sm, "Final-Recipient: rfc822; %s\r\n", mailreceip); if (origid) fprintf(sm, "Original-Message-ID: %s\r\n", origid); fprintf(sm, "Disposition: " @@ -472,7 +461,7 @@ return SIEVE_OK; } - body = msg_getheader(md, "x-delivered-to"); + body = msg_getheader(md, "original-recipient"); origreceip = body ? body0 : NULL; if ((res = send_rejection(md->id, md->return_path, origreceip, sd->username, @@ -497,7 +486,7 @@ static int sieve_fileinto(void *ac, void *ic __attribute__((unused)), void *sc, - void *mc, + void *mc __attribute__((unused)), const char **errmsg __attribute__((unused))) { sieve_fileinto_context_t *fc = (sieve_fileinto_context_t *) ac; @@ -583,9 +572,9 @@ /* "default" is a magic value that implies the default */ notify(!strcmp("default",nc->method) ? notifier : nc->method, "SIEVE", nc->priority, sd->username, NULL, - nopt, nc->options, nc->message, nc->fname); + nopt, nc->options, nc->message); } - + return SIEVE_OK; } @@ -791,7 +780,6 @@ sieve_register_notify(interp, &sieve_notify); sieve_register_size(interp, &getsize); sieve_register_header(interp, &getheader); - sieve_register_fname(interp, &getfname); sieve_register_envelope(interp, &getenvelope); sieve_register_body(interp, &getbody);
View file
cyrus-imapd-2.5.tar.gz/imap/lmtpd.c
Changed
@@ -638,7 +638,7 @@ config_virtdomains ? strcspn(userbuf, "@") : 0); notify(notifier, "MAIL", NULL, userbuf, namebuf, 0, NULL, - notifyheader ? notifyheader : "", /*fname*/NULL); + notifyheader ? notifyheader : ""); } } @@ -975,8 +975,7 @@ if (stage) append_removestage(stage); syslog(LOG_ERR, "FATAL: %s", s); - - abort(); + /* shouldn't return */ shut_down(code);
View file
cyrus-imapd-2.5.tar.gz/imap/lmtpengine.c
Changed
@@ -715,10 +715,8 @@ /* now, using our header cache, fill in the data that we want */ - /* first check x-me-message-id, then resent-message-id */ - if ((body = msg_getheader(m, "x-me-message-id")) && body00) { - m->id = xstrdup(body0); - } else if ((body = msg_getheader(m, "resent-message-id")) && body00) { + /* first check resent-message-id */ + if ((body = msg_getheader(m, "resent-message-id")) && body00) { m->id = xstrdup(body0); } else if ((body = msg_getheader(m, "message-id")) && body00) { m->id = xstrdup(body0);
View file
cyrus-imapd-2.5.tar.gz/imap/mailbox.c
Changed
@@ -51,9 +51,6 @@ #elif defined(HAVE_STDINT_H) # include <stdint.h> #endif -#ifdef WITH_DAV -#include <libical/vcc.h> -#endif #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -83,11 +80,6 @@ #include "annotate.h" #include "assert.h" -#ifdef WITH_DAV -#include "caldav_db.h" -#include "caldav_alarm.h" -#include "carddav_db.h" -#endif #include "crc32.h" #include "md5.h" #include "exitcodes.h" @@ -96,12 +88,10 @@ #include "imparse.h" #include "cyr_lock.h" #include "mailbox.h" -#include "mappedfile.h" #include "message.h" #include "map.h" #include "mboxevent.h" #include "mboxlist.h" -#include "parseaddr.h" #include "proc.h" #include "retry.h" #include "seen.h" @@ -113,8 +103,6 @@ #include "sync_log.h" #include "xmalloc.h" #include "xstrlcpy.h" -#include "xstrlcat.h" -#include "xstats.h" struct mailboxlist { struct mailboxlist *next; @@ -127,6 +115,7 @@ #define zeromailbox(m) { memset(&m, 0, sizeof(struct mailbox)); \ (m).index_fd = -1; \ + (m).cache_fd = -1; \ (m).header_fd = -1; } static int mailbox_index_unlink(struct mailbox *mailbox); @@ -182,7 +171,7 @@ fatal("didn't find item in list", EC_SOFTWARE); } -EXPORTED const char *mailbox_meta_fname(struct mailbox *mailbox, int metafile) +EXPORTED char *mailbox_meta_fname(struct mailbox *mailbox, int metafile) { static char fnamebufMAX_MAILBOX_PATH; const char *src; @@ -194,7 +183,7 @@ return fnamebuf; } -EXPORTED const char *mailbox_meta_newfname(struct mailbox *mailbox, int metafile) +EXPORTED char *mailbox_meta_newfname(struct mailbox *mailbox, int metafile) { static char fnamebufMAX_MAILBOX_PATH; const char *src; @@ -208,8 +197,8 @@ EXPORTED int mailbox_meta_rename(struct mailbox *mailbox, int metafile) { - const char *fname = mailbox_meta_fname(mailbox, metafile); - const char *newfname = mailbox_meta_newfname(mailbox, metafile); + char *fname = mailbox_meta_fname(mailbox, metafile); + char *newfname = mailbox_meta_newfname(mailbox, metafile); if (rename(newfname, fname)) return IMAP_IOERROR; @@ -217,26 +206,19 @@ return 0; } -static const char *mailbox_spool_fname(struct mailbox *mailbox, uint32_t uid) +EXPORTED char *mailbox_message_fname(struct mailbox *mailbox, unsigned long uid) { - return mboxname_datapath(mailbox->part, mailbox->name, uid); -} + static char localbufMAX_MAILBOX_PATH; + const char *src; -static const char *mailbox_archive_fname(struct mailbox *mailbox, uint32_t uid) -{ - return mboxname_archivepath(mailbox->part, mailbox->name, uid); -} + src = mboxname_datapath(mailbox->part, mailbox->name, uid); + if (!src) return NULL; -EXPORTED const char *mailbox_record_fname(struct mailbox *mailbox, - struct index_record *record) -{ - if (record->system_flags & FLAG_ARCHIVED) - return mailbox_archive_fname(mailbox, record->uid); - else - return mailbox_spool_fname(mailbox, record->uid); + xstrncpy(localbuf, src, MAX_MAILBOX_PATH); + return localbuf; } -EXPORTED const char *mailbox_datapath(struct mailbox *mailbox) +EXPORTED char *mailbox_datapath(struct mailbox *mailbox) { static char localbufMAX_MAILBOX_PATH; const char *src; @@ -281,14 +263,16 @@ /* for efficient FastMail interface display */ { "x-spam-score", 3 }, + { "x-spam-hits", 3 }, + { "x-spam-source", 3 }, { "x-resolved-to", 3 }, { "x-delivered-to", 3 }, { "x-mail-from", 3 }, + { "x-truedomain", 3 }, + { "x-truedomain-dkim", 3 }, + { "x-truedomain-spf", 3 }, { "x-truedomain-domain", 3 }, - /* for conversations */ - { "x-me-message-id", 4 }, - /* things to never cache */ { "bcc", BIT32_MAX }, { "cc", BIT32_MAX }, @@ -376,7 +360,7 @@ /* Scan for header */ for (i=0; i < (MAX_CACHED_HEADER_SIZE - 1); i++) { if (!texti || texti == '\r' || texti == '\n') break; - + if (texti == ':') { bufi = '\0'; return is_cached_header(buf); @@ -390,7 +374,7 @@ const char *cache_base(struct index_record *record) { - const char *base = record->crec.buf->s; + const char *base = record->crec.base->s; return base + record->crec.offset; } @@ -412,7 +396,7 @@ EXPORTED const char *cacheitem_base(struct index_record *record, int field) { - const char *base = record->crec.buf->s; + const char *base = record->crec.base->s; return base + record->crec.itemfield.offset; } @@ -432,28 +416,27 @@ return &staticbuf; } + /* parse a single cache record from the mapped file - creates buf * records which point into the map, so you can't free it while * you still have them around! */ -static int cache_parserecord(struct mappedfile *cachefile, size_t cache_offset, +static int cache_parserecord(struct buf *cachebase, size_t cache_offset, struct cacherecord *crec) { - const struct buf *buf = mappedfile_buf(cachefile); - size_t buf_size = mappedfile_size(cachefile); - const char *cacheitem, *next; - size_t offset; int cache_ent; + size_t offset; + const char *cacheitem, *next; offset = cache_offset; - if (offset >= buf_size) { + if (offset >= cachebase->len) { syslog(LOG_ERR, "IOERROR: offset greater than cache size %lu %lu", - offset, buf_size); + offset, cachebase->len); return IMAP_IOERROR; } for (cache_ent = 0; cache_ent < NUM_CACHE_FIELDS; cache_ent++) { - cacheitem = buf->s + offset; + cacheitem = cachebase->s + offset; /* copy locations */ crec->itemcache_ent.len = CACHE_ITEM_LEN(cacheitem); crec->itemcache_ent.offset = offset + CACHE_ITEM_SIZE_SKIP; @@ -465,17 +448,17 @@ return IMAP_IOERROR; } - offset = next - buf->s; - if (offset > buf_size) { + offset = next - cachebase->s; + if (offset > cachebase->len) { syslog(LOG_ERR, "IOERROR: offset greater than cache size " SIZE_T_FMT " " SIZE_T_FMT "(%d)", - offset, buf_size, cache_ent); + offset, cachebase->len, cache_ent); return IMAP_IOERROR; } } /* all fit within the cache, it's gold as far as we can tell */ - crec->buf = buf; + crec->base = cachebase; crec->len = offset - cache_offset; crec->offset = cache_offset; @@ -513,217 +496,202 @@ return msgid; } -static int mailbox_index_islocked(struct mailbox *mailbox, int write) +HIDDEN int mailbox_ensure_cache(struct mailbox *mailbox, size_t len) { - if (mailbox->index_locktype == LOCK_EXCLUSIVE) return 1; - if (mailbox->index_locktype == LOCK_SHARED && !write) return 1; - return 0; -} - -static int cache_append_record(struct mappedfile *mf, struct index_record *record) -{ - const struct buf *buf = cache_buf(record); - size_t offset = mappedfile_size(mf); - int n; - - n = mappedfile_pwritebuf(mf, buf, offset); - if (n < 0) { - syslog(LOG_ERR, "failed to append " SIZE_T_FMT " bytes to cache", buf->len); - return IMAP_IOERROR; - } + struct stat sbuf; + unsigned generation; + int retry = 0; + int openflags = mailbox->is_readonly ? O_RDONLY : O_RDWR; - record->cache_offset = offset; + retry: + /* open the file */ + if (mailbox->cache_fd == -1) { + char *fname; + + /* it's bogus to be dirty here */ + if (mailbox->cache_dirty) + abort(); - return 0; -} - -static struct mappedfile *cache_getfile(ptrarray_t *list, const char *fname, - int readonly, uint32_t generation) -{ - struct mappedfile *cachefile = NULL; - int openflags = readonly ? 0 : MAPPEDFILE_CREATE | MAPPEDFILE_RW; - int i; - char buf4; + fname = mailbox_meta_fname(mailbox, META_CACHE); + mailbox->cache_fd = open(fname, openflags, 0); + if (mailbox->cache_fd == -1) + goto fail; - for (i = 0; i < list->count; i++) { - cachefile = ptrarray_nth(list, i); - if (!strcmp(fname, mappedfile_fname(cachefile))) - return cachefile; + if (mailbox->cache_buf.s) + map_free((const char **)&mailbox->cache_buf.s, &mailbox->cache_len); + mailbox->cache_buf.len = 0; } - /* guess we didn't find it - open a new one */ - cachefile = NULL; - if (mappedfile_open(&cachefile, fname, openflags)) { - syslog(LOG_ERR, "IOERROR: failed to open cache file %s", fname); - return NULL; - } + if (len >= mailbox->cache_buf.len) { + /* get the size and inode */ + if (fstat(mailbox->cache_fd, &sbuf) == -1) { + syslog(LOG_ERR, "IOERROR: fstating cache %s: %m", mailbox->name); + goto fail; + } + mailbox->cache_buf.len = sbuf.st_size; + if (mailbox->cache_buf.len < 4) + goto fail; - if (!readonly && !mappedfile_size(cachefile)) { - /* zero byte file? Set the generation */ - *((uint32_t *)buf) = htonl(generation); - mappedfile_pwrite(cachefile, buf, 4, 0); - mappedfile_commit(cachefile); + map_refresh(mailbox->cache_fd, 0, (const char **)&mailbox->cache_buf.s, + &mailbox->cache_len, mailbox->cache_buf.len, "cache", + mailbox->name); + + generation = ntohl(*((bit32 *)(mailbox->cache_buf.s))); + if (generation < mailbox->i.generation_no && !retry) { + /* try a rename - maybe we got killed between renames in repack */ + map_free((const char **)&mailbox->cache_buf.s, &mailbox->cache_len); + xclose(mailbox->cache_fd); + syslog(LOG_NOTICE, "WARNING: trying to rename cache file %s (%d < %d)", + mailbox->name, generation, mailbox->i.generation_no); + mailbox_meta_rename(mailbox, META_CACHE); + retry = 1; + goto retry; + } + if (generation != mailbox->i.generation_no) { + map_free((const char **)&mailbox->cache_buf.s, &mailbox->cache_len); + goto fail; + } } - ptrarray_append(list, cachefile); - - return cachefile; -} - -static struct mappedfile *mailbox_cachefile(struct mailbox *mailbox, - struct index_record *record) -{ - const char *fname; - - if (record->system_flags & FLAG_ARCHIVED) - fname = mailbox_meta_fname(mailbox, META_ARCHIVECACHE); - else - fname = mailbox_meta_fname(mailbox, META_CACHE); + return 0; - return cache_getfile(&mailbox->caches, fname, mailbox->is_readonly, mailbox->i.generation_no); +fail: + syslog(LOG_ERR, "IOERROR: failed to load cache for %s", mailbox->name); + return IMAP_IOERROR; } -static struct mappedfile *repack_cachefile(struct mailbox_repack *repack, - struct index_record *record) +static int mailbox_index_islocked(struct mailbox *mailbox, int write) { - const char *fname; - - if (record->system_flags & FLAG_ARCHIVED) - fname = mailbox_meta_newfname(repack->mailbox, META_ARCHIVECACHE); - else - fname = mailbox_meta_newfname(repack->mailbox, META_CACHE); - - return cache_getfile(&repack->caches, fname, /*readonly*/0, repack->i.generation_no); + if (mailbox->index_locktype == LOCK_EXCLUSIVE) return 1; + if (mailbox->index_locktype == LOCK_SHARED && !write) return 1; + return 0; } /* return the offset for the start of the record! */ -static int mailbox_append_cache(struct mailbox *mailbox, - struct index_record *record) +int mailbox_append_cache(struct mailbox *mailbox, + struct index_record *record) { - struct mappedfile *cachefile; int r; assert(mailbox_index_islocked(mailbox, 1)); + /* no cache content */ + if (!record->crec.len) + return 0; + /* already been written */ if (record->cache_offset) return 0; - /* no cache content */ - if (!record->crec.len) { - /* make one! */ - const char *fname = mailbox_record_fname(mailbox, record); - syslog(LOG_ERR, "IOERROR: no cache for %s %u, parsing and saving", - mailbox->name, record->uid); - r = message_parse(fname, record); - if (r) return r; - mailbox_index_dirty(mailbox); - mailbox->i.options |= OPT_MAILBOX_NEEDS_REPACK; - } - - cachefile = mailbox_cachefile(mailbox, record); - if (!cachefile) { + /* ensure we have a cache fd */ + r = mailbox_ensure_cache(mailbox, 0); + if (r) { syslog(LOG_ERR, "Failed to open cache to %s for %u", mailbox->name, record->uid); - return IMAP_IOERROR; /* unable to append */ + return r; /* unable to append */ } - r = cache_append_record(cachefile, record); + r = cache_append_record(mailbox->cache_fd, record); if (r) { syslog(LOG_ERR, "Failed to append cache to %s for %u", mailbox->name, record->uid); return r; } + mailbox->cache_dirty = 1; + + /* and now read it straight back in to ensure we're always + * fresh */ + r = mailbox_ensure_cache(mailbox, record->cache_offset + record->crec.len); + if (r) return r; + + /* try to parse the cache record */ + r = cache_parserecord(&mailbox->cache_buf, + record->cache_offset, &record->crec); + if (r) return r; + + if (record->cache_crc != crc32_buf(cache_buf(record))) + return IMAP_MAILBOX_CHECKSUM; + return 0; } EXPORTED int mailbox_cacherecord(struct mailbox *mailbox, - struct index_record *record) + struct index_record *record) { - struct mappedfile *cachefile; + uint32_t crc; int r = 0; /* do we already have a record loaded? */ if (record->crec.len) return 0; - /* make sure there's a file to read from */ - cachefile = mailbox_cachefile(mailbox, record); - if (!cachefile) - goto err; - - /* do we have an offset? */ if (!record->cache_offset) - goto err; + r = IMAP_IOERROR; + if (r) goto done; + + r = mailbox_ensure_cache(mailbox, record->cache_offset + record->crec.len); + if (r) goto done; /* try to parse the cache record */ - r = cache_parserecord(cachefile, record->cache_offset, &record->crec); + r = cache_parserecord(&mailbox->cache_buf, + record->cache_offset, &record->crec); - /* check the checksum */ - if (!r && crc32_buf(cache_buf(record)) != record->cache_crc) + if (r) goto done; + crc = crc32_buf(cache_buf(record)); + if (crc != record->cache_crc) r = IMAP_MAILBOX_CHECKSUM; - if (r) goto err; - return 0; - -err: - if (!cachefile) - syslog(LOG_ERR, "IOERROR: missing cache file for %s uid %u", - mailbox->name, record->uid); - else if (!record->cache_offset) - syslog(LOG_ERR, "IOERROR: missing cache offset for %s uid %u", - mailbox->name, record->uid); - else if (r) +done: + if (r) syslog(LOG_ERR, "IOERROR: invalid cache record for %s uid %u (%s)", mailbox->name, record->uid, error_message(r)); - /* parse the file again */ - { - /* parse directly into the cache for this record */ - const char *fname = mailbox_record_fname(mailbox, record); - if (!fname) { - syslog(LOG_ERR, "IOERROR: no spool file for %s uid %u", - mailbox->name, record->uid); - return IMAP_IOERROR; - } + return r; +} - r = message_parse(fname, record); - if (r) { - syslog(LOG_ERR, "IOERROR: failed to parse message for %s uid %u", - mailbox->name, record->uid); - return r; - } +int cache_append_record(int fd, struct index_record *record) +{ + size_t offset; + size_t len = cache_len(record); + int n; - /* if we can add it, do that now */ - if (cachefile && mailbox_index_islocked(mailbox, 1)) { - r = cache_append_record(cachefile, record); - if (!r) r = mailbox_rewrite_index_record(mailbox, record); - if (r) { - syslog(LOG_ERR, "IOERROR: failed to append cache to %s for %u", - mailbox->name, record->uid); - /* but ignore, we have a valid read at least */ - } - else { - /* mark for repack */ - mailbox_index_dirty(mailbox); - mailbox->i.options |= OPT_MAILBOX_NEEDS_REPACK; - } - } + /* no parsed cache present */ + if (!record->crec.len) + return 0; + + /* cache offset already there - probably already been written */ + if (record->cache_offset) + return 0; + + if (record->cache_crc != crc32_buf(cache_buf(record))) + return IMAP_MAILBOX_CHECKSUM; + + offset = lseek(fd, 0L, SEEK_END); + n = retry_write(fd, cache_base(record), len); + if (n < 0) { + syslog(LOG_ERR, "failed to append " SIZE_T_FMT " bytes to cache", len); + return IMAP_IOERROR; } + record->cache_offset = offset; + return 0; } static int mailbox_commit_cache(struct mailbox *mailbox) { - int i; + if (!mailbox->cache_dirty) + return 0; - for (i = 0; i < mailbox->caches.count; i++) { - struct mappedfile *cachefile = ptrarray_nth(&mailbox->caches, i); - int r = mappedfile_commit(cachefile); - if (r) return r; - } + mailbox->cache_dirty = 0; + + /* not open! That's bad */ + if (mailbox->cache_fd == -1) + abort(); + + /* just fsync is all that's needed to commit */ + (void)fsync(mailbox->cache_fd); return 0; } @@ -791,19 +759,17 @@ } /* - * Maps in the content for the message with UID 'uid' in 'mailbox', - * into a read-only struct buf. Use buf_free() to free the data. + * Maps in the content for the message with UID 'uid' in 'mailbox'. + * Returns map in 'basep' and 'lenp' */ -EXPORTED int mailbox_map_record(struct mailbox *mailbox, - struct index_record *record, - struct buf *data) +EXPORTED int mailbox_map_message(struct mailbox *mailbox, unsigned long uid, + const char **basep, size_t *lenp) { int msgfd; - const char *fname; + char *fname; struct stat sbuf; - xstats_inc(MESSAGE_MAP); - fname = mailbox_record_fname(mailbox, record); + fname = mailbox_message_fname(mailbox, uid); msgfd = open(fname, O_RDONLY, 0666); if (msgfd == -1) return errno; @@ -812,18 +778,27 @@ syslog(LOG_ERR, "IOERROR: fstat on %s: %m", fname); fatal("can't fstat message file", EC_OSFILE); } - buf_free(data); - buf_init_mmap(data, /*onceonly*/1, msgfd, fname, sbuf.st_size, mailbox->name); + *basep = 0; + *lenp = 0; + map_refresh(msgfd, 1, basep, lenp, sbuf.st_size, fname, mailbox->name); close(msgfd); return 0; } -static void mailbox_release_resources(struct mailbox *mailbox) +/* + * Releases the buffer obtained from mailbox_map_message() + */ +EXPORTED void mailbox_unmap_message(struct mailbox *mailbox __attribute__((unused)), + unsigned long uid __attribute__((unused)), + const char **basep, size_t *lenp) { - int i; + map_free(basep, lenp); +} - if (mailbox->i.dirty) +static void mailbox_release_resources(struct mailbox *mailbox) +{ + if (mailbox->i.dirty || mailbox->cache_dirty) abort(); /* just close the header */ @@ -834,12 +809,11 @@ if (mailbox->index_base) map_free(&mailbox->index_base, &mailbox->index_len); - /* release caches */ - for (i = 0; i < mailbox->caches.count; i++) { - struct mappedfile *cachefile = ptrarray_nth(&mailbox->caches, i); - mappedfile_close(&cachefile); - } - ptrarray_fini(&mailbox->caches); + /* release and unmap cache */ + xclose(mailbox->cache_fd); + if (mailbox->cache_buf.s) + map_free((const char **)&mailbox->cache_buf.s, &mailbox->cache_len); + mailbox->cache_buf.len = 0; } /* @@ -848,7 +822,7 @@ static int mailbox_open_index(struct mailbox *mailbox) { struct stat sbuf; - const char *fname; + char *fname; int openflags = mailbox->is_readonly ? O_RDONLY : O_RDWR; mailbox_release_resources(mailbox); @@ -917,7 +891,7 @@ return IMAP_MAILBOX_LOCKED; /* can't reuse an already locked index */ if (listitem->m.index_locktype) - return IMAP_MAILBOX_LOCKED; + return IMAP_MAILBOX_LOCKED; listitem->nopen++; mailbox = &listitem->m; @@ -966,7 +940,17 @@ } lockindex: - r = mailbox_lock_index(mailbox, index_locktype); + /* this will open, map and parse the header file */ + r = mailbox_lock_index_internal(mailbox, index_locktype); + if (r) { + syslog(LOG_ERR, "IOERROR: locking index %s: %s", + mailbox->name, error_message(r)); + goto done; + } + + /* oops, a race, it got deleted meanwhile. That's OK */ + if (mailbox->i.options & OPT_MAILBOX_DELETED) + r = IMAP_MAILBOX_NONEXISTENT; done: if (r) mailbox_close(&mailbox); @@ -987,15 +971,6 @@ mailboxptr); } -EXPORTED int mailbox_open_irlnb(const char *name, struct mailbox **mailboxptr) -{ - return mailbox_open_advanced(name, - LOCK_SHARED|LOCK_NONBLOCK, - /* cannot do nonblocking lock on index...why? */ - LOCK_SHARED, - mailboxptr); -} - HIDDEN int mailbox_open_exclusive(const char *name, struct mailbox **mailboxptr) { return mailbox_open_advanced(name, LOCK_EXCLUSIVE, LOCK_EXCLUSIVE, @@ -1015,8 +990,7 @@ if (mailbox->modseq_dirty) return; - mailbox->i.highestmodseq = mboxname_nextmodseq(mailbox->name, - mailbox->i.highestmodseq); + mailbox->i.highestmodseq++; mailbox->last_updated = time(0); mailbox->modseq_dirty = 1; mailbox_index_dirty(mailbox); @@ -1030,6 +1004,7 @@ int flag; struct mailbox *mailbox = *mailboxptr; struct mailboxlist *listitem; + int expunge_days = config_getint(IMAPOPT_EXPUNGE_DAYS); /* be safe against double-close */ if (!mailbox) return; @@ -1046,6 +1021,20 @@ return; } + /* auto-cleanup */ + if (mailbox->i.first_expunged && + (mailbox->index_locktype == LOCK_EXCLUSIVE)) { + time_t floor = time(NULL) - (expunge_days * 86400); + /* but only if we're more than a full week older than + * the expunge time, * so it doesn't turn into lots + * of bitty rewrites. + * Also, cyr_expire can get first bite if it's been set + * to run... */ + if (mailbox->i.first_expunged < floor - (8 * 86400)) { + mailbox_expunge_cleanup(mailbox, floor, NULL); + /* XXX - handle error code? */ + } + } /* get a re-read of the options field for cleanup purposes */ if (mailbox->index_fd != -1) { if (!mailbox->index_locktype) @@ -1549,134 +1538,34 @@ return r; } -EXPORTED int mailbox_has_conversations(struct mailbox *mailbox) -{ - char *path; - - /* not needed */ - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) - return 0; - - /* we never store data about deleted mailboxes */ - if (mboxname_isdeletedmailbox(mailbox->name, NULL)) - return 0; - - path = conversations_getmboxpath(mailbox->name); - if (!path) return 0; - free(path); - - return 1; -} - -static int mailbox_lock_conversations(struct mailbox *mailbox) -{ - /* does this mailbox have conversations? */ - if (!mailbox_has_conversations(mailbox)) - return 0; - - /* already locked */ - if (conversations_get_mbox(mailbox->name)) - return 0; - - return conversations_open_mbox(mailbox->name, &mailbox->local_cstate); -} - /* - * bsearch() comparison function for searching on UID. + * bsearch() function to compare two index record buffers by UID */ static int rec_compar(const void *key, const void *mem) { uint32_t uid = *((uint32_t *) key); - uint32_t recuid = ntohl(*((bit32 *)((const char *)mem+OFFSET_UID))); - if (uid < recuid) return -1; - return (uid > recuid); -} - -static uint32_t mailbox_getuid(struct mailbox *mailbox, uint32_t recno) -{ struct index_record record; - record.uid = 0; - /* XXX - cheaper memory-access reads? */ - mailbox_read_index_record(mailbox, recno, &record); - return record.uid; -} + int r; + if ((r = mailbox_buf_to_index_record(mem, &record))) return r; -/* - * Returns the recno of the message with UID 'uid'. - * If no message with UID 'uid', returns the message with - * the higest UID not greater than 'uid'. - */ -EXPORTED uint32_t mailbox_finduid(struct mailbox *mailbox, uint32_t uid) -{ - uint32_t low = 1; - uint32_t high = mailbox->i.num_records; - uint32_t mid; - uint32_t miduid; - - while (low <= high) { - mid = (high - low)/2 + low; - miduid = mailbox_getuid(mailbox, mid); - if (miduid == uid) - return mid; - else if (miduid > uid) - high = mid - 1; - else - low = mid + 1; - } - return high; + if (uid < record.uid) return -1; + return (uid > record.uid); } /* - * Perform a binary search on the mailbox index file to read the record - * for uid 'uid' into 'record'. If 'oldrecord' is not NULL then it is - * assumed to point a correct and current index record from an earlier - * call, and the search is bounded by that record. Returns 0 on success - * or an IMAP error code on failure. + * Find the index record in mailbox corresponding to UID */ EXPORTED int mailbox_find_index_record(struct mailbox *mailbox, uint32_t uid, - struct index_record *record, - const struct index_record *oldrecord) + struct index_record *record) { const char *mem, *base = mailbox->index_base + mailbox->i.start_offset; - const char *low = base; size_t num_records = mailbox->i.num_records; size_t size = mailbox->i.record_size; int r; - if (uid > mailbox->i.last_uid) return IMAP_NOTFOUND; - - if (oldrecord) { - const char *oldmem = base + (oldrecord->recno-1) * size; - if (uid == oldrecord->uid) { - /* already found it */ - if (record != oldrecord) - memcpy(record, oldrecord, sizeof(struct index_record)); - return 0; - } - else if (uid == oldrecord->uid+1) { - /* are we at the end? */ - if (oldrecord->recno == mailbox->i.num_records) - return IMAP_NOTFOUND; - /* Optimise for the common case of moving up by one uid. - * The index file is in UID order so the record we want - * is either the next one or is not present. */ - low = oldmem + size; - num_records = 1; - } - else if (uid < oldrecord->uid) { - /* target is before the old record */ - num_records = oldrecord->recno-1; - } - else { - /* target is after the old record */ - low = oldmem + size; - num_records -= oldrecord->recno; - } - } - - mem = bsearch(&uid, low, num_records, size, rec_compar); - if (!mem) return IMAP_NOTFOUND; + mem = bsearch(&uid, base, num_records, size, rec_compar); + if (!mem) return CYRUSDB_NOTFOUND; if ((r = mailbox_buf_to_index_record(mem, record))) return r; @@ -1699,7 +1588,6 @@ assert(!mailbox->index_locktype); restart: - r = 0; if (locktype == LOCK_EXCLUSIVE) { /* handle read-only case cleanly - we need to re-open read-write first! */ @@ -1713,7 +1601,6 @@ r = lock_shared(mailbox->index_fd, index_fname); } else { - /* this function does not support nonblocking locks */ fatal("invalid locktype for index", EC_SOFTWARE); } @@ -1827,16 +1714,7 @@ EXPORTED int mailbox_lock_index(struct mailbox *mailbox, int locktype) { - int r = 0; - - /* XXX: only lock convdb if we're in read-write mode. This is kinda - * bogus really, but there's no way to get a read lock on convdb */ - if (locktype != LOCK_SHARED) { - r = mailbox_lock_conversations(mailbox); - if (r) return r; - } - - r = mailbox_lock_index_internal(mailbox, locktype); + int r = mailbox_lock_index_internal(mailbox, locktype); if (r) return r; /* otherwise, sanity checks for regular use, but not for internal @@ -1874,7 +1752,6 @@ if (updatenotifier) updatenotifier(mailbox->name); sync_log_mailbox(mailbox->name); statuscache_invalidate(mailbox->name, sdata); - mailbox->has_changed = 0; } else if (sdata) { @@ -1888,30 +1765,12 @@ mailbox->name); mailbox->index_locktype = 0; } - gettimeofday(&endtime, 0); timediff = timesub(&mailbox->starttime, &endtime); if (timediff > 1.0) { syslog(LOG_NOTICE, "mailbox: longlock %s for %0.1f seconds", mailbox->name, timediff); } - - if (mailbox->local_cstate) { - int r = conversations_commit(&mailbox->local_cstate); - if (r) - syslog(LOG_ERR, "Error committing to conversations database for mailbox %s: %s", - mailbox->name, error_message(r)); - } -} - -EXPORTED int mailbox_yield_index(struct mailbox *mailbox) -{ - int locktype = mailbox->index_locktype; - - if (!locktype) return 0; - - mailbox_unlock_index(mailbox, NULL); - return mailbox_lock_index(mailbox, locktype); } /* @@ -2083,8 +1942,6 @@ return 0; } - - /* * Write the index header for 'mailbox' */ @@ -2125,12 +1982,6 @@ return IMAP_IOERROR; } - if (config_auditlog && mailbox->modseq_dirty) - syslog(LOG_NOTICE, "auditlog: modseq sessionid=<%s> " - "mailbox=<%s> uniqueid=<%s> highestmodseq=<" MODSEQ_FMT ">", - session_id(), mailbox->name, mailbox->uniqueid, - mailbox->i.highestmodseq); - /* remove all dirty flags! */ mailbox->i.dirty = 0; mailbox->modseq_dirty = 0; @@ -2374,11 +2225,6 @@ MD5Update(&ctx, message_guid_encode(&record->guid), 2*MESSAGE_GUID_SIZE); - MD5Update(&ctx, " ", 1); - - n = snprintf(buf, sizeof(buf), "%llu", record->cid); - MD5Update(&ctx, buf, n); - MD5Final(result.md5, &ctx); return ntohl(result.b32); @@ -2401,8 +2247,7 @@ MD5Update(&ctx, " ", 1); MD5Update(&ctx, entry, strlen(entry)); MD5Update(&ctx, " ", 1); - if (userid) - MD5Update(&ctx, userid, strlen(userid)); + MD5Update(&ctx, userid, strlen(userid)); MD5Update(&ctx, " ", 1); MD5Update(&ctx, value->s, value->len); @@ -2657,488 +2502,6 @@ return r; } -#ifdef WITH_DAV -static int mailbox_update_carddav(struct mailbox *mailbox, - struct index_record *old, - struct index_record *new) -{ - const char *userid = mboxname_to_userid(mailbox->name); - struct carddav_db *carddavdb = NULL; - struct param *param; - struct body *body = NULL; - struct carddav_data *cdata = NULL; - const char *resource = NULL; - int r = 0; - - /* conditions in which there's nothing to do */ - if (!new) goto done; - if (!userid) goto done; - - /* phantom record - never really existed here */ - if (!old && (new->system_flags & FLAG_EXPUNGED)) - goto done; - - r = mailbox_cacherecord(mailbox, new); - if (r) goto done; - - /* Get resource URL from filename param in Content-Disposition header */ - message_read_bodystructure(new, &body); - for (param = body->disposition_params; param; param = param->next) { - if (!strcmp(param->attribute, "FILENAME")) { - resource = param->value; - } - } - - assert(resource); - - carddavdb = carddav_open_mailbox(mailbox, 0); - - /* Find existing record for this resource */ - carddav_lookup_resource(carddavdb, mailbox->name, resource, 1, &cdata); - - /* XXX - if not matching by UID, skip - this record doesn't refer to the current item */ - - if (new->system_flags & FLAG_EXPUNGED) { - /* is there an existing record? */ - if (!cdata) goto done; - - /* does it still come from this UID? */ - if (cdata->dav.imap_uid != new->uid) goto done; - - /* delete entry */ - r = carddav_delete(carddavdb, cdata->dav.rowid, 0); - } - else { - struct buf msg_buf = BUF_INITIALIZER; - VObjectIterator i; - VObject *vcard; - - /* already seen this message, so do we update it? No */ - if (old) goto done; - - /* Load message containing the resource and parse vcard data */ - r = mailbox_map_record(mailbox, new, &msg_buf); - if (r) goto done; - - vcard = Parse_MIME(buf_base(&msg_buf) + new->header_size, - new->size - new->header_size); - buf_free(&msg_buf); - if (!vcard) goto done; - - /* Create mapping entry from resource name to UID */ - cdata->dav.mailbox = mailbox->name; - cdata->dav.resource = resource; - cdata->dav.imap_uid = new->uid; - - if (!cdata->dav.creationdate) - cdata->dav.creationdate = new->internaldate; - - initPropIterator(&i, vcard); - while (moreIteration(&i)) { - VObject *prop = nextVObject(&i); - const char *name = vObjectName(prop); - const wchar_t *propval = vObjectUStringZValue(prop); - - if (!name) continue; - if (!propval) continue; - - if (!strcmp(name, "UID")) { - cdata->vcard_uid = fakeCString(propval); - } - else if (!strcmp(name, "N")) { - cdata->name = fakeCString(propval); - } - else if (!strcmp(name, "FN")) { - cdata->fullname = fakeCString(propval); - } - else if (!strcmp(name, "NICKNAME")) { - cdata->nickname = fakeCString(propval); - } - else if (!strcmp(name, "EMAIL")) { - /* XXX - insert if primary */ - strarray_append(&cdata->emails, fakeCString(propval)); - } - else if (!strcmp(name, "X-ADDRESSBOOKSERVER-MEMBER")) { - const char *item = fakeCString(propval); - if (!strncmp(item, "urn:uuid:", 9)) - strarray_append(&cdata->member_uids, item+9); - } - } - - r = carddav_write(carddavdb, cdata, 0); - } - -done: - if (carddavdb) { - carddav_commit(carddavdb); - carddav_close(carddavdb); - } - - return r; -} - -static int mailbox_update_caldav(struct mailbox *mailbox, - struct index_record *old, - struct index_record *new) -{ - const char *userid = mboxname_to_userid(mailbox->name); - struct caldav_db *caldavdb = NULL; - struct param *param; - struct body *body = NULL; - struct caldav_data *cdata = NULL; - const char *resource = NULL; - const char *sched_tag = NULL; - int r = 0; - - /* conditions in which there's nothing to do */ - if (!new) goto done; - if (!userid) goto done; - - /* phantom record - never really existed here */ - if (!old && (new->system_flags & FLAG_EXPUNGED)) - goto done; - - r = mailbox_cacherecord(mailbox, new); - if (r) goto done; - - /* Get resource URL from filename param in Content-Disposition header */ - message_read_bodystructure(new, &body); - for (param = body->disposition_params; param; param = param->next) { - if (!strcmp(param->attribute, "FILENAME")) { - resource = param->value; - } - else if (!strcmp(param->attribute, "SCHEDULE-TAG")) { - sched_tag = param->value; - } - } - - caldavdb = caldav_open_mailbox(mailbox, 0); - - /* Find existing record for this resource */ - caldav_lookup_resource(caldavdb, mailbox->name, resource, 1, &cdata); - - /* XXX - if not matching by UID, skip - this record doesn't refer to the current item */ - - if (new->system_flags & FLAG_EXPUNGED) { - /* is there an existing record? */ - if (!cdata) goto done; - - /* does it still come from this UID? */ - if (cdata->dav.imap_uid != new->uid) goto done; - - /* prepare alarm data for removal */ - struct caldav_alarm_data alarmdata = { - .mailbox = cdata->dav.mailbox, - .resource = cdata->dav.resource, - }; - - /* delete entry */ - r = caldav_delete(caldavdb, cdata->dav.rowid, 0); - - /* and associated alarms */ - struct caldav_alarm_db *alarmdb = caldav_alarm_open(); - caldav_alarm_delete_all(alarmdb, &alarmdata); - caldav_alarm_close(alarmdb); - } - else { - struct buf msg_buf = BUF_INITIALIZER; - icalcomponent *ical = NULL; - - /* already seen this message, so do we update it? No */ - if (old) goto done; - - r = mailbox_map_record(mailbox, new, &msg_buf); - if (r) goto done; - - ical = icalparser_parse_string(buf_base(&msg_buf) + new->header_size); - buf_free(&msg_buf); - if (!ical) goto done; - - cdata->dav.creationdate = new->internaldate; - cdata->dav.mailbox = mailbox->name; - cdata->dav.imap_uid = new->uid; - cdata->dav.resource = resource; - cdata->sched_tag = sched_tag; - - caldav_make_entry(ical, cdata); - - r = caldav_write(caldavdb, cdata, 0); - if (r) { - icalcomponent_free(ical); - goto done; - } - - struct caldav_alarm_db *alarmdb = caldav_alarm_open(); - caldav_alarm_begin(alarmdb); - - struct caldav_alarm_data alarmdata = { - .mailbox = cdata->dav.mailbox, - .resource = cdata->dav.resource, - }; - - /* remove old ones */ - int rc = caldav_alarm_delete_all(alarmdb, &alarmdata); - - int i; - for (i = CALDAV_ALARM_ACTION_FIRST; i <= CALDAV_ALARM_ACTION_LAST; i++) { - /* prepare alarm data */ - if (!rc && - !caldav_alarm_prepare(ical, &alarmdata, i, - icaltime_current_time_with_zone(icaltimezone_get_utc_timezone()))) { - rc = caldav_alarm_add(alarmdb, &alarmdata); - caldav_alarm_fini(&alarmdata); - } - } - - if (rc) - caldav_alarm_rollback(alarmdb); - else - caldav_alarm_commit(alarmdb); - caldav_alarm_close(alarmdb); - - icalcomponent_free(ical); - } - -done: - message_free_body(body); - free(body); - - if (caldavdb) { - caldav_commit(caldavdb); - caldav_close(caldavdb); - } - - return r; -} - -static int mailbox_update_dav(struct mailbox *mailbox, - struct index_record *old, - struct index_record *new) -{ - if (mailbox->mbtype & MBTYPE_ADDRESSBOOK) - return mailbox_update_carddav(mailbox, old, new); - if (mailbox->mbtype & MBTYPE_CALENDAR) - return mailbox_update_caldav(mailbox, old, new); - return 0; -} -#endif //WITH_DAV - -EXPORTED int mailbox_update_conversations(struct mailbox *mailbox, - struct index_record *old, - struct index_record *new) -{ - int r = 0; - struct mboxname_parts parts; - conversation_t *conv = NULL; - int delta_num_records = 0; - int delta_exists = 0; - int delta_unseen = 0; - int is_trash = 0; - int delta_size = 0; - int *delta_counts = NULL; - int i; - modseq_t modseq = 0; - struct index_record *record = NULL; - struct conversations_state *cstate = NULL; - - if (!mailbox_has_conversations(mailbox)) - return 0; - - cstate = conversations_get_mbox(mailbox->name); - if (!cstate) - return IMAP_CONVERSATIONS_NOT_OPEN; - - /* IRIS-2534: check if it's the trash folder - XXX - should be separate - * conversation root or similar more useful method in future */ - if (mboxname_to_parts(mailbox->name, &parts)) - return IMAP_MAILBOX_BADNAME; - - if (!strcmpsafe(parts.box, "Trash")) - is_trash = 1; - - mboxname_free_parts(&parts); - - /* handle unlinked items as if they didn't exist */ - if (old && (old->system_flags & FLAG_UNLINKED)) old = NULL; - if (new && (new->system_flags & FLAG_UNLINKED)) new = NULL; - - if (!old && !new) - return 0; - - if (old && new) { - assert(old->uid == new->uid); - assert(old->modseq <= new->modseq); - /* this flag cannot go away */ - if ((old->system_flags & FLAG_EXPUNGED)) - assert((new->system_flags & FLAG_EXPUNGED)); - - if (old->cid != new->cid) { - /* handle CID being renamed, by calling ourselves. Always remove - * BEFORE adding so that the old 'B' record can be deleted, and - * hence the old CID be cleaned up in a rename case */ - r = mailbox_update_conversations(mailbox, old, NULL); - if (!r && new->cid) /* handle ctl_conversationdb -z correctly */ - r = mailbox_update_conversations(mailbox, NULL, new); - return r; - } - } - - if (new && !old) { - /* add the conversation */ - mailbox_cacherecord(mailbox, new); /* make sure it's loaded */ - r = message_update_conversations(cstate, new, &conv); - if (r) return r; - record = new; - /* possible if silent (i.e. replica) */ - if (!record->cid) return 0; - } - else { - record = new ? new : old; - /* skip out on non-CIDed records */ - if (!record->cid) return 0; - - r = conversation_load(cstate, record->cid, &conv); - if (r) - return r; - if (!conv) { - if (!new) { - /* We're trying to delete a conversation that's already - * gone...don't try to hard */ - syslog(LOG_NOTICE, "conversation "CONV_FMT" already " - "deleted, ignoring", record->cid); - return 0; - } - conv = conversation_new(cstate); - } - } - - if (cstate->counted_flags) - delta_counts = xzmalloc(sizeof(int) * cstate->counted_flags->count); - - /* calculate the changes */ - if (old) { - /* decrease any relevent counts */ - if (!(old->system_flags & FLAG_EXPUNGED)) { - delta_exists--; - delta_size -= old->size; - /* drafts don't update the 'unseen' counter so that - * they never turn a conversation "unread" */ - if (!is_trash && !(old->system_flags & (FLAG_SEEN|FLAG_DRAFT))) - delta_unseen--; - if (cstate->counted_flags) { - for (i = 0; i < cstate->counted_flags->count; i++) { - const char *flag = strarray_nth(cstate->counted_flags, i); - if (mailbox_record_hasflag(mailbox, old, flag)) - delta_countsi--; - } - } - } - delta_num_records--; - modseq = MAX(modseq, old->modseq); - } - if (new) { - /* add any counts */ - if (!(new->system_flags & FLAG_EXPUNGED)) { - delta_exists++; - delta_size += new->size; - /* drafts don't update the 'unseen' counter so that - * they never turn a conversation "unread" */ - if (!is_trash && !(new->system_flags & (FLAG_SEEN|FLAG_DRAFT))) - delta_unseen++; - if (cstate->counted_flags) { - for (i = 0; i < cstate->counted_flags->count; i++) { - const char *flag = strarray_nth(cstate->counted_flags, i); - if (mailbox_record_hasflag(mailbox, new, flag)) - delta_countsi++; - } - } - } - delta_num_records++; - modseq = MAX(modseq, new->modseq); - } - - /* XXX - combine this with the earlier cache parsing */ - if (!mailbox_cacherecord(mailbox, record)) { - char *env = NULL; - char *envtokensNUMENVTOKENS; - struct address addr = { NULL, NULL, NULL, NULL, NULL, NULL }; - - /* Need to find the sender */ - - /* +1 -> skip the leading paren */ - env = xstrndup(cacheitem_base(record, CACHE_ENVELOPE) + 1, - cacheitem_size(record, CACHE_ENVELOPE) - 1); - - parse_cached_envelope(env, envtokens, VECTOR_SIZE(envtokens)); - - if (envtokensENV_FROM) - message_parse_env_address(envtokensENV_FROM, &addr); - - /* XXX - internaldate vs gmtime? */ - conversation_update_sender(conv, - addr.name, addr.route, - addr.mailbox, addr.domain, - record->gmtime, delta_exists); - free(env); - } - - conversation_update(cstate, conv, mailbox->name, - delta_num_records, - delta_exists, delta_unseen, - delta_size, delta_counts, modseq); - - r = conversation_save(cstate, record->cid, conv); - - conversation_free(conv); - free(delta_counts); - return r; -} - -EXPORTED int mailbox_get_xconvmodseq(struct mailbox *mailbox, modseq_t *modseqp) -{ - conv_status_t status = CONV_STATUS_INIT; - int r; - - if (modseqp) - *modseqp = 0; - - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) - return 0; - - if (!mailbox->local_cstate) - return IMAP_INTERNAL; - - r = conversation_getstatus(mailbox->local_cstate, mailbox->name, &status); - if (r) return r; - - *modseqp = status.modseq; - - return 0; -} - -/* Used in replication */ -EXPORTED int mailbox_update_xconvmodseq(struct mailbox *mailbox, modseq_t newmodseq, int force) -{ - conv_status_t status = CONV_STATUS_INIT; - int r; - - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) - return 0; - - if (!mailbox->local_cstate) - return IMAP_INTERNAL; - - r = conversation_getstatus(mailbox->local_cstate, mailbox->name, &status); - if (r) return r; - - if (newmodseq > status.modseq || (force && newmodseq < status.modseq)) { - status.modseq = newmodseq; - r = conversation_setstatus(mailbox->local_cstate, mailbox->name, &status); - } - - return r; -} - /* NOTE: maybe make this able to return error codes if we have * support for transactional mailbox updates later. For now, * we expect callers to have already done all sanity checking */ @@ -3146,18 +2509,6 @@ struct index_record *old, struct index_record *new) { - int r = 0; - -#ifdef WITH_DAV - r = mailbox_update_dav(mailbox, old, new); - if (r) return r; -#endif - - r = mailbox_update_conversations(mailbox, old, new); - if (r) return r; - - /* NOTE - we do these last, once the counts are updated */ - if (old) mailbox_index_update_counts(mailbox, old, 0); if (new) @@ -3171,7 +2522,7 @@ * necessary tracking fields automatically. */ EXPORTED int mailbox_rewrite_index_record(struct mailbox *mailbox, - struct index_record *record) + struct index_record *record) { int n; int r; @@ -3208,18 +2559,9 @@ assert(record->system_flags & FLAG_EXPUNGED); } - if (oldrecord.system_flags & FLAG_ARCHIVED) { - /* it is also a sin to unarchive a message, except in the - * the very odd case of a reconstruct. So let's see about - * that */ - if (!(record->system_flags & FLAG_ARCHIVED)) - syslog(LOG_ERR, "IOERROR: bogus removal of archived flag for %s %u", - mailbox->name, record->uid); - } - /* handle immediate expunges here... */ if (immediate && (record->system_flags & FLAG_EXPUNGED)) - record->system_flags |= FLAG_UNLINKED | FLAG_NEEDS_CLEANUP; + record->system_flags |= FLAG_UNLINKED; /* make sure highestmodseq gets updated unless we're * being silent about it (i.e. marking an already EXPUNGED @@ -3241,11 +2583,15 @@ mailbox->i.options |= OPT_MAILBOX_NEEDS_UNLINK; } else { - /* rewrite the cache record if required anyway */ + /* write the cache record before buffering the message, it + * will set the cache_offset field. */ r = mailbox_append_cache(mailbox, record); if (r) return r; } + /* remove the counts for the old copy, and add them for + * the new copy */ + r = mailbox_update_indexes(mailbox, &oldrecord, record); if (r) return r; @@ -3280,10 +2626,9 @@ if (config_auditlog) syslog(LOG_NOTICE, "auditlog: expunge sessionid=<%s> " - "mailbox=<%s> uniqueid=<%s> uid=<%u> guid=<%s> cid=<%s>", + "mailbox=<%s> uniqueid=<%s> uid=<%u> guid=<%s>", session_id(), mailbox->name, mailbox->uniqueid, - record->uid, message_guid_encode(&record->guid), - conversation_id_encode(record->cid)); + record->uid, message_guid_encode(&record->guid)); } return mailbox_refresh_index_map(mailbox); @@ -3340,20 +2685,10 @@ record->sentdate = mktime(tm); } - /* update the highestmodseq if needed */ - if (record->silent) { - mailbox_index_dirty(mailbox); - } - else { - mailbox_modseq_dirty(mailbox); - record->modseq = mailbox->i.highestmodseq; - record->last_updated = mailbox->last_updated; - } - if (!(record->system_flags & FLAG_UNLINKED)) { /* make the file timestamp correct */ settime.actime = settime.modtime = record->internaldate; - if (utime(mailbox_record_fname(mailbox, record), &settime) == -1) + if (utime(mailbox_message_fname(mailbox, record->uid), &settime) == -1) return IMAP_IOERROR; /* write the cache record before buffering the message, it @@ -3362,6 +2697,16 @@ if (r) return r; } + /* update the highestmodseq if needed */ + if (record->silent) { + mailbox_index_dirty(mailbox); + } + else { + mailbox_modseq_dirty(mailbox); + record->modseq = mailbox->i.highestmodseq; + record->last_updated = mailbox->last_updated; + } + r = mailbox_update_indexes(mailbox, NULL, record); if (r) return r; @@ -3391,10 +2736,9 @@ mailbox->index_size += INDEX_RECORD_SIZE; if (config_auditlog) - syslog(LOG_NOTICE, "auditlog: append sessionid=<%s> mailbox=<%s> uniqueid=<%s> uid=<%u> guid=<%s> cid=<%s>", + syslog(LOG_NOTICE, "auditlog: append sessionid=<%s> mailbox=<%s> uniqueid=<%s> uid=<%u> guid=<%s>", session_id(), mailbox->name, mailbox->uniqueid, record->uid, - message_guid_encode(&record->guid), - conversation_id_encode(record->cid)); + message_guid_encode(&record->guid)); /* expunged tracking */ if (record->system_flags & FLAG_EXPUNGED) { @@ -3404,10 +2748,9 @@ if (config_auditlog) syslog(LOG_NOTICE, "auditlog: expunge sessionid=<%s> " - "mailbox=<%s> uniqueid=<%s> uid=<%u> guid=<%s> cid=<%s>", + "mailbox=<%s> uniqueid=<%s> uid=<%u> guid=<%s>", session_id(), mailbox->name, mailbox->uniqueid, - record->uid, message_guid_encode(&record->guid), - conversation_id_encode(record->cid)); + record->uid, message_guid_encode(&record->guid)); } /* yep, it could even be pre-unlinked in 'default' expunge mode, joy */ @@ -3422,58 +2765,33 @@ return mailbox_refresh_index_map(mailbox); } -static void mailbox_message_cleanup(struct mailbox *mailbox, - struct index_record *record) +static void mailbox_message_unlink(struct mailbox *mailbox, uint32_t uid) { - const char *spoolfname = mailbox_spool_fname(mailbox, record->uid); - const char *archivefname = mailbox_archive_fname(mailbox, record->uid); + const char *fname = mailbox_message_fname(mailbox, uid); int r; - if (record->system_flags & FLAG_UNLINKED) { - /* try to delete both */ - - if (unlink(spoolfname) == 0) { - if (config_auditlog) - syslog(LOG_NOTICE, "auditlog: unlink sessionid=<%s> " - "mailbox=<%s> uniqueid=<%s> uid=<%u>", - session_id(), mailbox->name, mailbox->uniqueid, record->uid); - } - - if (unlink(archivefname) == 0) { - if (config_auditlog) - syslog(LOG_NOTICE, "auditlog: unlinkarchive sessionid=<%s> " - "mailbox=<%s> uniqueid=<%s> uid=<%u>", - session_id(), mailbox->name, mailbox->uniqueid, record->uid); - } + /* XXX - reports errors other than ENOENT ? */ - r = mailbox_get_annotate_state(mailbox, record->uid, NULL); - if (r) { - syslog(LOG_ERR, "IOERROR: failed to open annotations %s %u: %s", - mailbox->name, record->uid, error_message(r)); - return; - } - - r = annotate_msg_cleanup(mailbox, record->uid); - if (r) { - syslog(LOG_ERR, "IOERROR: failed to cleanup annotations %s %u: %s", - mailbox->name, record->uid, error_message(r)); - return; - } + /* no error, we removed a file */ + if (unlink(fname) == 0) { + if (config_auditlog) + syslog(LOG_NOTICE, "auditlog: unlink sessionid=<%s> " + "mailbox=<%s> uniqueid=<%s> uid=<%u>", + session_id(), mailbox->name, mailbox->uniqueid, uid); } - /* nothing to cleanup if it's the same file! */ - if (!strcmp(spoolfname, archivefname)) + r = mailbox_get_annotate_state(mailbox, uid, NULL); + if (r) { + syslog(LOG_ERR, "IOERROR: failed to open annotations %s %u: %s", + mailbox->name, uid, error_message(r)); return; - - /* otherwise check for archived/nonarchived */ - else if (record->system_flags & FLAG_ARCHIVED) { - /* XXX - stat to make sure the other file exists first? - we mostly - * trust that we didn't do stupid things everywhere else, so maybe not */ - unlink(spoolfname); } - else { - unlink(archivefname); + r = annotate_msg_cleanup(mailbox, uid); + if (r) { + syslog(LOG_ERR, "IOERROR: failed to cleanup annotations %s %u: %s", + mailbox->name, uid, error_message(r)); + return; } } @@ -3486,25 +2804,15 @@ syslog(LOG_INFO, "Unlinking files in mailbox %s", mailbox->name); - /* NOTE: this gets called for two different cases: - * 1) file is actually ready for unlinking (immediate expunge or - * cyr_expire). - * 2) file has been archived/unarchived, and the other one needs - * to be removed. - */ + /* note: this may try to unlink the same files more than once, + * but them's the breaks - the alternative is yet another + * system flag which gets updated once done! */ for (recno = 1; recno <= mailbox->i.num_records; recno++) { r = mailbox_read_index_record(mailbox, recno, &record); if (r) return r; - /* still gotta check for FLAG_UNLINKED, because it may have been - * created by old code. Woot */ - if (record.system_flags & (FLAG_NEEDS_CLEANUP | FLAG_UNLINKED)) { - mailbox_message_cleanup(mailbox, &record); - record.system_flags &= ~FLAG_NEEDS_CLEANUP; - record.silent = 1; - /* XXX - error handling */ - mailbox_rewrite_index_record(mailbox, &record); - } + if (record.system_flags & FLAG_UNLINKED) + mailbox_message_unlink(mailbox, record.uid); } /* need to clear the flag, even if nothing needed unlinking! */ @@ -3516,7 +2824,7 @@ } HIDDEN int mailbox_repack_setup(struct mailbox *mailbox, - struct mailbox_repack **repackptr) + struct mailbox_repack **repackptr) { struct mailbox_repack *repack = xzmalloc(sizeof(struct mailbox_repack)); const char *fname; @@ -3528,6 +2836,7 @@ repack->mailbox = mailbox; repack->i = mailbox->i; /* struct copy */ repack->newindex_fd = -1; + repack->newcache_fd = -1; /* new files */ fname = mailbox_meta_newfname(mailbox, META_INDEX); @@ -3537,6 +2846,13 @@ goto fail; } + fname = mailbox_meta_newfname(mailbox, META_CACHE); + repack->newcache_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666); + if (repack->newcache_fd == -1) { + syslog(LOG_ERR, "IOERROR: failed to create %s: %m", fname); + goto fail; + } + /* update the generation number */ repack->i.generation_no++; @@ -3560,6 +2876,10 @@ /* prepare initial header buffer */ mailbox_index_header_to_buf(&repack->i, buf); + /* write initial headers */ + n = retry_write(repack->newcache_fd, buf, 4); + if (n == -1) goto fail; + n = retry_write(repack->newindex_fd, buf, INDEX_HEADER_SIZE); if (n == -1) goto fail; @@ -3572,20 +2892,17 @@ } HIDDEN int mailbox_repack_add(struct mailbox_repack *repack, - struct index_record *record) + struct index_record *record) { - struct mappedfile *cachefile; - indexbuffer_t ibuf; - unsigned char *buf = ibuf.buf; int r; int n; - - cachefile = repack_cachefile(repack, record); + indexbuffer_t ibuf; + unsigned char *buf = ibuf.buf; /* write out the new cache record - need to clear the cache_offset * so it gets reset in the new record */ record->cache_offset = 0; - r = cache_append_record(cachefile, record); + r = cache_append_record(repack->newcache_fd, record); if (r) return r; /* update counters */ @@ -3606,38 +2923,21 @@ HIDDEN void mailbox_repack_abort(struct mailbox_repack **repackptr) { struct mailbox_repack *repack = *repackptr; - int i; - if (!repack) return; /* safe against double-free */ - - /* close and remove index */ + xclose(repack->newcache_fd); + unlink(mailbox_meta_newfname(repack->mailbox, META_CACHE)); xclose(repack->newindex_fd); unlink(mailbox_meta_newfname(repack->mailbox, META_INDEX)); - - /* close and remove all new caches */ - for (i = 0; i < repack->caches.count; i++) { - struct mappedfile *cachefile = ptrarray_nth(&repack->caches, i); - char *fname = xstrdup(mappedfile_fname(cachefile)); - mappedfile_commit(cachefile); /* gotta commit to clear the dirty flag. Alternative would be an unlink function */ - mappedfile_unlock(cachefile); - mappedfile_close(&cachefile); - unlink(fname); - free(fname); - } - ptrarray_fini(&repack->caches); - free(repack); *repackptr = NULL; } HIDDEN int mailbox_repack_commit(struct mailbox_repack **repackptr) { - strarray_t cachefiles = STRARRAY_INITIALIZER; indexbuffer_t ibuf; unsigned char *buf = ibuf.buf; struct mailbox_repack *repack = *repackptr; int r = IMAP_IOERROR; - int i; assert(repack); @@ -3659,51 +2959,24 @@ if (fsync(repack->newindex_fd) < 0) goto fail; - xclose(repack->newindex_fd); + if (fsync(repack->newcache_fd) < 0) + goto fail; - /* NOTE: cache files need commiting before index is renamed */ - for (i = 0; i < repack->caches.count; i++) { - struct mappedfile *cachefile = ptrarray_nth(&repack->caches, i); - r = mappedfile_commit(cachefile); - if (r) goto fail; - } + xclose(repack->newcache_fd); + xclose(repack->newindex_fd); /* rename index first - loader will handle un-renamed cache if * the generation is lower */ r = mailbox_meta_rename(repack->mailbox, META_INDEX); if (r) goto fail; - /* which cache files might currently exist? */ - strarray_add(&cachefiles, mailbox_meta_fname(repack->mailbox, META_CACHE)); - strarray_add(&cachefiles, mailbox_meta_fname(repack->mailbox, META_ARCHIVECACHE)); - - /* now the cache files can be renamed */ - for (i = 0; i < repack->caches.count; i++) { - struct mappedfile *cachefile = ptrarray_nth(&repack->caches, i); - char *newname = xstrdup(mappedfile_fname(cachefile)); - size_t len = strlen(newname)-4; - assert(!strcmp(newname+len, ".NEW")); - newnamelen = '\0'; /* STRIP .NEW */ - mappedfile_rename(cachefile, newname); - mappedfile_close(&cachefile); - strarray_remove_all(&cachefiles, newname); - free(newname); - } - ptrarray_fini(&repack->caches); - - for (i = 0; i < cachefiles.count; i++) { - const char *fname = strarray_nth(&cachefiles, i); - if (!unlink(fname)) - syslog(LOG_NOTICE, "Removed unused cache file %s", fname); - } + mailbox_meta_rename(repack->mailbox, META_CACHE); - strarray_fini(&cachefiles); free(repack); *repackptr = NULL; return 0; fail: - strarray_fini(&cachefiles); mailbox_repack_abort(repackptr); return r; } @@ -3728,17 +3001,11 @@ /* been marked for removal, just skip */ if (!record.uid) continue; - /* better handle the cleanup just in case it's unlinked too */ - /* still gotta check for FLAG_UNLINKED, because it may have been - * created by old code. Woot */ - if (record.system_flags & (FLAG_NEEDS_CLEANUP | FLAG_UNLINKED)) { - mailbox_message_cleanup(mailbox, &record); - record.system_flags &= ~FLAG_NEEDS_CLEANUP; - /* no need to rewrite - it's already being written to the new file */ - } - /* we aren't keeping unlinked files, that's kind of the point */ if (record.system_flags & FLAG_UNLINKED) { + /* just in case it was left lying around */ + mailbox_message_unlink(mailbox, record.uid); + /* track the modseq for QRESYNC purposes */ if (record.modseq > repack->i.deletedmodseq) repack->i.deletedmodseq = record.modseq; @@ -3792,123 +3059,13 @@ } /* - * Move messages between spool and archive partition - * function pointed to by 'decideproc' is called (with 'deciderock') to - * determine which messages to move. If deciderock return 0, the message - * should be in the spool - if 1, the message should be in the archive. - */ -EXPORTED void mailbox_archive(struct mailbox *mailbox, - mailbox_decideproc_t *decideproc, void *deciderock) -{ - int r; - int dirtycache = 0; - uint32_t recno; - struct index_record record; - const char *srcname; - const char *destname; - char *spoolcache = xstrdup(mailbox_meta_fname(mailbox, META_CACHE)); - char *archivecache = xstrdup(mailbox_meta_fname(mailbox, META_ARCHIVECACHE)); - int differentcache = strcmp(spoolcache, archivecache); - free(spoolcache); - free(archivecache); - - assert(mailbox_index_islocked(mailbox, 1)); - assert(decideproc); - - for (recno = 1; recno <= mailbox->i.num_records; recno++) { - const char *action = NULL; - /* skip damaged records */ - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) { - syslog(LOG_ERR, "IOERROR archive %s recno %u failed to read index record: %s", - mailbox->name, recno, error_message(r)); - continue; - } - - /* skip already unlinked records */ - if (record.system_flags & FLAG_UNLINKED) - continue; - - if (decideproc(mailbox, &record, deciderock)) { - if (record.system_flags & FLAG_ARCHIVED) - continue; - srcname = mailbox_spool_fname(mailbox, record.uid); - destname = mailbox_archive_fname(mailbox, record.uid); - - /* load cache before changing the flags */ - r = mailbox_cacherecord(mailbox, &record); - if (r) { - syslog(LOG_ERR, "IOERROR archive %s %u failed to read cache: %s", - mailbox->name, record.uid, error_message(r)); - continue; - } - - record.system_flags |= FLAG_ARCHIVED | FLAG_NEEDS_CLEANUP; - action = "archive"; - } - else { - if (!(record.system_flags & FLAG_ARCHIVED)) - continue; - destname = mailbox_spool_fname(mailbox, record.uid); - srcname = mailbox_archive_fname(mailbox, record.uid); - - /* load cache before changing the flags */ - r = mailbox_cacherecord(mailbox, &record); - if (r) { - syslog(LOG_ERR, "IOERROR archive %s %u failed to read cache: %s", - mailbox->name, record.uid, error_message(r)); - continue; - } - - record.system_flags &= ~FLAG_ARCHIVED | FLAG_NEEDS_CLEANUP; - action = "unarchive"; - } - - /* got a file to copy! */ - if (strcmp(srcname, destname)) { - r = cyrus_copyfile(srcname, destname, COPYFILE_MKDIR); - if (r) { - syslog(LOG_ERR, "IOERROR archive %s %u failed to copyfile (%s => %s): %s", - mailbox->name, record.uid, srcname, destname, error_message(r)); - continue; - } - } - - /* got a new cache record to write */ - if (differentcache) { - dirtycache = 1; - record.cache_offset = 0; - if (mailbox_append_cache(mailbox, &record)) - continue; - } - - /* rewrite the index record */ - record.silent = 1; - if (mailbox_rewrite_index_record(mailbox, &record)) - continue; - mailbox->i.options |= OPT_MAILBOX_NEEDS_UNLINK; - - if (config_auditlog) - syslog(LOG_NOTICE, "auditlog: %s sessionid=<%s> mailbox=<%s> uniqueid=<%s> uid=<%u> guid=<%s> cid=<%s>", - action, session_id(), mailbox->name, mailbox->uniqueid, record.uid, - message_guid_encode(&record.guid), conversation_id_encode(record.cid)); - } - - /* if we have stale cache records, we'll need a repack */ - if (dirtycache) { - mailbox_index_dirty(mailbox); - mailbox->i.options |= OPT_MAILBOX_NEEDS_REPACK; - } -} - -/* * Perform an expunge operation on 'mailbox'. If nonzero, the * function pointed to by 'decideproc' is called (with 'deciderock') to * determine which messages to expunge. If 'decideproc' is a null pointer, * then messages with the \Deleted flag are expunged. * - * event_type - the event among MessageExpunge, MessageExpire (zero means - * don't send notification) + * event_type - the event among MessageExpunge, MessageExpire (zero means + * don't send notification) */ EXPORTED int mailbox_expunge(struct mailbox *mailbox, mailbox_decideproc_t *decideproc, void *deciderock, @@ -4081,7 +3238,6 @@ const char *uniqueid, int options, unsigned uidvalidity, - modseq_t highestmodseq, struct mailbox **mailboxptr) { int r = 0; @@ -4090,7 +3246,8 @@ const char *fname; struct mailbox *mailbox = NULL; int n; - int createfnames = { META_INDEX, META_HEADER, 0 }; + uint32_t generation_buf; + int createfnames = { META_INDEX, META_CACHE, META_HEADER, 0 }; struct mailboxlist *listitem; strarray_t *initial_flags = NULL; @@ -4128,7 +3285,7 @@ } /* ensure we can fit the longest possible file name */ - fname = mailbox_datapath(mailbox); + fname = mailbox_message_fname(mailbox, UINT32_MAX); if (!fname) { syslog(LOG_ERR, "IOERROR: Mailbox name too long (%s)", mailbox->name); r = IMAP_MAILBOX_BADNAME; @@ -4160,14 +3317,20 @@ goto done; } mailbox->index_locktype = LOCK_EXCLUSIVE; - r = mailbox_lock_conversations(mailbox); - if (r) { - syslog(LOG_ERR, "IOERROR: locking conversations %s %s", - mailbox->name, error_message(r)); + + fname = mailbox_meta_fname(mailbox, META_CACHE); + if (!fname) { + syslog(LOG_ERR, "IOERROR: Mailbox name too long (%s)", mailbox->name); + r = IMAP_MAILBOX_BADNAME; + goto done; + } + mailbox->cache_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666); + if (mailbox->cache_fd == -1) { + syslog(LOG_ERR, "IOERROR: creating %s: %m", fname); r = IMAP_IOERROR; goto done; } - + if (hasquota) { mailbox_set_quotaroot(mailbox, quotaroot); memset(mailbox->quota_previously_used, 0, sizeof(mailbox->quota_previously_used)); @@ -4176,24 +3339,15 @@ /* ensure a UIDVALIDITY is set */ if (!uidvalidity) - uidvalidity = mboxname_nextuidvalidity(name, time(0)); - else - mboxname_setuidvalidity(mailbox->name, uidvalidity); - - /* and highest modseq */ - if (!highestmodseq) - highestmodseq = mboxname_nextmodseq(mailbox->name, 0); - else - mboxname_setmodseq(mailbox->name, highestmodseq); - + uidvalidity = time(0); /* init non-zero fields */ mailbox_index_dirty(mailbox); mailbox->i.minor_version = MAILBOX_MINOR_VERSION; mailbox->i.start_offset = INDEX_HEADER_SIZE; mailbox->i.record_size = INDEX_RECORD_SIZE; - mailbox->i.options = options; mailbox->i.uidvalidity = uidvalidity; - mailbox->i.highestmodseq = highestmodseq; + mailbox->i.options = options; + mailbox->i.highestmodseq = 1; mailbox->i.sync_crc_vers = MAILBOX_CRC_VERSION_MAX; /* initialise header size field so appends calculate the @@ -4221,6 +3375,16 @@ } } + /* write out the initial generation number to the cache file */ + generation_buf = htonl(mailbox->i.generation_no); + n = retry_write(mailbox->cache_fd, (char *)&generation_buf, 4); + if (n != 4 || fsync(mailbox->cache_fd)) { + syslog(LOG_ERR, "IOERROR: writing initial cache for %s: %m", + mailbox->name); + r = IMAP_IOERROR; + goto done; + } + r = seen_create_mailbox(NULL, mailbox); if (r) goto done; r = mailbox_commit(mailbox); @@ -4246,7 +3410,7 @@ /* * Remove all files in directory */ -static void mailbox_delete_files(const char *path) +static void mailbox_delete_files(char *path) { DIR *dirp; struct dirent *f; @@ -4312,82 +3476,11 @@ return r; } -#ifdef WITH_DAV -EXPORTED int mailbox_add_dav(struct mailbox *mailbox) -{ - struct index_record record; - uint32_t recno; - int r = 0; - - if (!(mailbox->mbtype & (MBTYPES_DAV))) - return 0; - - for (recno = 1; recno <= mailbox->i.num_records; recno++) { - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) return r; - - r = mailbox_update_dav(mailbox, NULL, &record); - if (r) return r; - } - - return 0; -} -#endif - -EXPORTED int mailbox_add_conversations(struct mailbox *mailbox) -{ - struct index_record record; - uint32_t recno; - int r = 0; - - if (!mailbox_has_conversations(mailbox)) - return 0; - - for (recno = 1; recno <= mailbox->i.num_records; recno++) { - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) return r; - - /* not assigned, skip */ - if (!record.cid) - continue; - - r = mailbox_update_conversations(mailbox, NULL, &record); - if (r) return r; - } - - return 0; -} - -static int mailbox_delete_conversations(struct mailbox *mailbox) -{ - struct conversations_state *cstate; - struct index_record record; - uint32_t recno; - int r; - - if (!mailbox_has_conversations(mailbox)) - return 0; - - cstate = conversations_get_mbox(mailbox->name); - if (!cstate) - return IMAP_CONVERSATIONS_NOT_OPEN; - - for (recno = 1; recno <= mailbox->i.num_records; recno++) { - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) return r; - - /* not assigned, skip */ - if (!record.cid) - continue; - - r = mailbox_update_conversations(mailbox, &record, NULL); - if (r) return r; - } - - return conversations_rename_folder(cstate, mailbox->name, NULL); -} - -static int mailbox_delete_internal(struct mailbox **mailboxptr) +/* + * Delete and close the mailbox 'mailbox'. Closes 'mailbox' whether + * or not the deletion was successful. Requires a locked mailbox. + */ +EXPORTED int mailbox_delete(struct mailbox **mailboxptr) { int r = 0; struct mailbox *mailbox = *mailboxptr; @@ -4431,88 +3524,6 @@ return 0; } -#ifdef WITH_DAV -static int mailbox_delete_caldav(struct mailbox *mailbox) -{ - struct caldav_db *caldavdb = NULL; - - caldavdb = caldav_open_mailbox(mailbox, 0); - if (caldavdb) { - int r = caldav_delmbox(caldavdb, mailbox->name, 0); - caldav_close(caldavdb); - if (r) return r; - } - - struct caldav_alarm_db *alarmdb = caldav_alarm_open(); - if (alarmdb) { - int r = caldav_alarm_delmbox(alarmdb, mailbox->name); - caldav_alarm_close(alarmdb); - if (r) return r; - } - - return 0; -} - -static int mailbox_delete_carddav(struct mailbox *mailbox) -{ - struct carddav_db *carddavdb = NULL; - - carddavdb = carddav_open_mailbox(mailbox, 0); - if (carddavdb) { - int r = carddav_delmbox(carddavdb, mailbox->name, 0); - carddav_close(carddavdb); - if (r) return r; - } - - return 0; -} - -static int mailbox_delete_dav(struct mailbox *mailbox) -{ - if (mailbox->mbtype & MBTYPE_ADDRESSBOOK) - return mailbox_delete_carddav(mailbox); - if (mailbox->mbtype & MBTYPE_CALENDAR) - return mailbox_delete_caldav(mailbox); - return 0; -} -#endif //WITH_DAV - -/* - * Delete and close the mailbox 'mailbox'. Closes 'mailbox' whether - * or not the deletion was successful. Requires a locked mailbox. - */ -EXPORTED int mailbox_delete(struct mailbox **mailboxptr) -{ - struct mailbox *mailbox = *mailboxptr; - int r; - - r = mailbox_delete_conversations(mailbox); - if (r) return r; - -#ifdef WITH_DAV - r = mailbox_delete_dav(mailbox); - if (r) return r; -#endif //WITH_DAV - - return mailbox_delete_internal(mailboxptr); -} - -struct meta_file { - unsigned long metaflag; - int optional; - int nolink; -}; - -static struct meta_file meta_files = { - { META_HEADER, 0, 1 }, - { META_INDEX, 0, 1 }, - { META_CACHE, 1, 1 }, - { META_SQUAT, 1, 0 }, - { META_ANNOTATIONS, 1, 1 }, - { META_ARCHIVECACHE, 1, 1 }, - { 0, 0, 0 } -}; - /* XXX - move this part of cleanup into mboxlist. Really * needs to be done with mailboxes.db locked so nobody can * try to create a mailbox while the delete is underway. @@ -4520,36 +3531,31 @@ /* we need an exclusive namelock for this */ HIDDEN int mailbox_delete_cleanup(const char *part, const char *name) { - strarray_t paths = STRARRAY_INITIALIZER; - int i; + char nbufMAX_MAILBOX_BUFFER; + char pbufMAX_MAILBOX_PATH+1, mbufMAX_MAILBOX_PATH+1; + char *ntail, *ptail, *mtail = NULL; + char *path, *mpath; mbentry_t *mbentry; - struct meta_file *mf; int r; - char nbufMAX_MAILBOX_NAME; - char *ntail; - char *p; - - strncpy(nbuf, name, sizeof(nbuf)); - ntail = nbuf + strlen(nbuf); - - /* XXX - double XXX - this is a really ugly function. It should be - * using mboxname_parts and walking back up the 'boxes' list */ - strarray_add(&paths, mboxname_datapath(part, name, 0)); + /* XXX - use explicit paths to each type of file */ - /* find the directory for every one of the meta files */ - for (mf = meta_files; mf->metaflag; mf++) { - char *fname = xstrdup(mboxname_metapath(part, name, mf->metaflag, 0)); - p = strrchr(fname, '/'); - if (p) *p = '\0'; - strarray_add(&paths, fname); - free(fname); + /* Flush data (message file) directory */ + path = mboxname_datapath(part, name, 0); + mailbox_delete_files(path); + strlcpy(pbuf, path, sizeof(pbuf)); + ptail = pbuf + strlen(pbuf); + + /* Flush metadata directory */ + mpath = mboxname_metapath(part, name, 0, 0); + if (strcmp(path, mpath)) { + mailbox_delete_files(mpath); + strlcpy(mbuf, mpath, sizeof(mbuf)); + mtail = mbuf + strlen(mbuf); } - for (i = 0; i < paths.count; i++) { - const char *path = strarray_nth(&paths, i); - mailbox_delete_files(path); - } + strlcpy(nbuf, name, sizeof(nbuf)); + ntail = nbuf + strlen(nbuf); do { /* Check if the mailbox has children */ @@ -4557,16 +3563,23 @@ r = mboxlist_findall(NULL, nbuf, 1, NULL, NULL, chkchildren, (void *)part); if (r != 0) break; /* We short-circuit with CYRUSDB_DONE */ - /* no children, remove the directories */ - for (i = 0; i < paths.count; i++) { - char *path = paths.datai; /* need direct reference, because we're fiddling */ - r = rmdir(path); - if (r && errno != ENOENT) + /* No children, remove mailbox spool dir(s) */ + if (rmdir(pbuf)) { + syslog(LOG_NOTICE, + "Remove of supposedly empty directory %s failed: %m", + pbuf); + } + ptail = strrchr(pbuf, '/'); + *ptail ='\0'; + + if (mtail) { + if (rmdir(mbuf)) { syslog(LOG_NOTICE, "Remove of supposedly empty directory %s failed: %m", - path); - p = strrchr(path, '/'); - if (p) *p = '\0'; + mbuf); + } + mtail = strrchr(mbuf, '/'); + *mtail ='\0'; } /* Check if parent mailbox exists */ @@ -4593,13 +3606,26 @@ } } while (r == IMAP_MAILBOX_NONEXISTENT); - strarray_fini(&paths); - return 0; } +struct meta_file { + unsigned long metaflag; + int optional; + int nolink; +}; + +static struct meta_file meta_files = { + { META_HEADER, 0, 1 }, + { META_INDEX, 0, 1 }, + { META_CACHE, 0, 1 }, + { META_SQUAT, 1, 0 }, + { META_ANNOTATIONS, 1, 0 }, + { 0, 0, 0 } +}; + EXPORTED int mailbox_copy_files(struct mailbox *mailbox, const char *newpart, - const char *newname) + const char *newname) { char oldbufMAX_MAILBOX_PATH, newbufMAX_MAILBOX_PATH; struct meta_file *mf; @@ -4631,14 +3657,10 @@ if (record.system_flags & FLAG_UNLINKED) continue; - xstrncpy(oldbuf, mailbox_record_fname(mailbox, &record), + xstrncpy(oldbuf, mailbox_message_fname(mailbox, record.uid), + MAX_MAILBOX_PATH); + xstrncpy(newbuf, mboxname_datapath(newpart, newname, record.uid), MAX_MAILBOX_PATH); - if (record.system_flags & FLAG_ARCHIVED) - xstrncpy(newbuf, mboxname_archivepath(newpart, newname, record.uid), - MAX_MAILBOX_PATH); - else - xstrncpy(newbuf, mboxname_datapath(newpart, newname, record.uid), - MAX_MAILBOX_PATH); r = mailbox_copyfile(oldbuf, newbuf, 0); if (r) return r; @@ -4661,29 +3683,18 @@ { int r; struct mailbox *newmailbox = NULL; - struct conversations_state *oldcstate = NULL; - struct conversations_state *newcstate = NULL; char *newquotaroot = NULL; assert(mailbox_index_islocked(oldmailbox, 1)); - /* we can't rename back from a deleted mailbox, because the conversations - * information will be wrong. Ideally we might re-calculate, but for now - * we just throw a big fat error */ - if (mboxname_isdeletedmailbox(oldmailbox->name, NULL)) { - syslog(LOG_ERR, "can't rename a deleted mailbox %s", oldmailbox->name); - return IMAP_MAILBOX_BADNAME; - } - /* create uidvalidity if not explicitly requested */ if (!uidvalidity) - uidvalidity = mboxname_nextuidvalidity(newname, oldmailbox->i.uidvalidity); + uidvalidity = time(0); /* Create new mailbox */ r = mailbox_create(newname, oldmailbox->mbtype, newpartition, oldmailbox->acl, (userid ? NULL : oldmailbox->uniqueid), - oldmailbox->i.options, uidvalidity, - oldmailbox->i.highestmodseq, &newmailbox); + oldmailbox->i.options, uidvalidity, &newmailbox); if (r) return r; @@ -4736,33 +3747,6 @@ /* and bump the modseq too */ mailbox_modseq_dirty(newmailbox); - /* NOTE: in the case of renaming a user to another user, we - * don't rename the conversations DB - instead we re-create - * the records in the target user. Sorry, was too complex - * otherwise handling all the special cases */ - if (mailbox_has_conversations(oldmailbox)) { - oldcstate = conversations_get_mbox(oldmailbox->name); - assert(oldcstate); - } - - if (mailbox_has_conversations(newmailbox)) { - newcstate = conversations_get_mbox(newmailbox->name); - assert(newcstate); - } - - if (oldcstate && newcstate && !strcmp(oldcstate->path, newcstate->path)) { - /* we can just rename within the same user */ - r = conversations_rename_folder(oldcstate, oldmailbox->name, newname); - } - else { - /* have to handle each one separately */ - if (oldcstate) - r = mailbox_delete_conversations(oldmailbox); - if (newcstate) - r = mailbox_add_conversations(newmailbox); - } - if (r) goto fail; - /* commit the index changes */ r = mailbox_commit(newmailbox); if (r) goto fail; @@ -4803,7 +3787,7 @@ if (!r) r = mailbox_commit(oldmailbox); mailbox_close(mailboxptr); } else { - r = mailbox_delete_internal(mailboxptr); + r = mailbox_delete(mailboxptr); } if (r) { @@ -4837,13 +3821,8 @@ #define UIDGROW 300 -struct found_uid { - uint32_t uid; - unsigned isarchive:1; -}; - struct found_uids { - struct found_uid *found; + unsigned long *uids; unsigned nalloc; unsigned nused; unsigned pos; @@ -4851,31 +3830,25 @@ #define FOUND_UIDS_INITIALIZER \ { NULL, 0, 0, 0 } -static int sort_found(const void *a, const void *b) +static int sort_uid(const void *a, const void *b) { - struct found_uid *fa = (struct found_uid *)a; - struct found_uid *fb = (struct found_uid *)b; - if (fa->uid != fb->uid) - return fa->uid - fb->uid; - return fa->isarchive - fb->isarchive; + return *(unsigned long *)a - *(unsigned long *)b; } -static void add_found(struct found_uids *ff, uint32_t uid, int isarchive) +static void add_found(struct found_uids *ff, unsigned long uid) { /* make sure there's space */ if (ff->nused >= ff->nalloc) { ff->nalloc += UIDGROW; - ff->found = xrealloc(ff->found, ff->nalloc * sizeof(struct found_uid)); + ff->uids = xrealloc(ff->uids, ff->nalloc * sizeof(unsigned long)); } - ff->foundff->nused.uid = uid; - ff->foundff->nused.isarchive = !!isarchive; - ff->nused++; + ff->uidsff->nused++ = uid; } static void free_found(struct found_uids *ff) { - free(ff->found); - ff->found = NULL; + free(ff->uids); + ff->uids = NULL; ff->nalloc = 0; ff->nused = 0; ff->pos = 0; @@ -4901,7 +3874,7 @@ static int find_files(struct mailbox *mailbox, struct found_uids *files, int flags) { - strarray_t paths = STRARRAY_INITIALIZER; + const char *dirpath; DIR *dirp; struct dirent *dirent; uint32_t uid; @@ -4909,55 +3882,57 @@ char bufMAX_MAILBOX_PATH; struct stat sbuf; int r; - int i; - strarray_add(&paths, mailbox_datapath(mailbox)); - strarray_add(&paths, mboxname_archivepath(mailbox->part, mailbox->name, 0)); + dirpath = mailbox_datapath(mailbox); + if (!dirpath) return IMAP_MAILBOX_BADNAME; - for (i = 0; i < paths.count; i++) { - const char *dirpath = strarray_nth(&paths, i); - int isarchive = strcmp(dirpath, mailbox_datapath(mailbox)); - - dirp = opendir(dirpath); - if (!dirp) continue; - - /* data directory is fine */ - while ((dirent = readdir(dirp)) != NULL) { - p = dirent->d_name; - if (*p == '.') continue; /* dot files */ - if (!strncmp(p, "cyrus.", 6)) continue; /* cyrus.* files */ + dirp = opendir(dirpath); + if (!dirp) { + printf("%s data directory is missing %s\n", mailbox->name, dirpath); + /* need to re-create data directory */ + if (cyrus_mkdir(dirpath, 0755) == -1) + return IMAP_IOERROR; + if (mkdir(dirpath, 0755) == -1) + return IMAP_IOERROR; + return 0; + } - r = parse_datafilename(p, &uid); + /* data directory is fine */ + while ((dirent = readdir(dirp)) != NULL) { + p = dirent->d_name; + if (*p == '.') continue; /* dot files */ + if (!strncmp(p, "cyrus.", 6)) continue; /* cyrus.* files */ - if (r) { - /* check if it's a directory */ - snprintf(buf, MAX_MAILBOX_PATH, "%s/%s", dirpath, dirent->d_name); - if (stat(buf, &sbuf) == -1) continue; /* ignore ephemeral */ - if (!S_ISDIR(sbuf.st_mode)) { - if (!(flags & RECONSTRUCT_IGNORE_ODDFILES)) { - printf("%s odd file %s\n", mailbox->name, buf); - syslog(LOG_ERR, "%s odd file %s", mailbox->name, buf); - if (flags & RECONSTRUCT_REMOVE_ODDFILES) - unlink(buf); - else { - printf("run reconstruct with -O to remove odd files\n"); - syslog(LOG_ERR, "run reconstruct with -O to " - "remove odd files"); - } + r = parse_datafilename(p, &uid); + + if (r) { + /* check if it's a directory */ + snprintf(buf, MAX_MAILBOX_PATH, "%s/%s", dirpath, dirent->d_name); + if (stat(buf, &sbuf) == -1) continue; /* ignore ephemeral */ + if (!S_ISDIR(sbuf.st_mode)) { + if (!(flags & RECONSTRUCT_IGNORE_ODDFILES)) { + printf("%s odd file %s\n", mailbox->name, buf); + syslog(LOG_ERR, "%s odd file %s", mailbox->name, buf); + if (flags & RECONSTRUCT_REMOVE_ODDFILES) + unlink(buf); + else { + printf("run reconstruct with -O to remove odd files\n"); + syslog(LOG_ERR, "run reconstruct with -O to " + "remove odd files"); } } } - else { - /* it's one of ours :) */ - add_found(files, uid, isarchive); - } } - - closedir(dirp); + else { + /* it's one of ours :) */ + add_found(files, uid); + } } + closedir(dirp); + /* make sure UIDs are sorted for comparison */ - qsort(files->found, files->nused, sizeof(unsigned long), sort_found); + qsort(files->uids, files->nused, sizeof(unsigned long), sort_uid); return 0; } @@ -5015,7 +3990,7 @@ for (erecno = 1; erecno <= expunge_num; erecno++) { bufp = expunge_base + eoffset + (erecno-1)*expungerecord_size; uid = ntohl(*((bit32 *)(bufp+OFFSET_UID))); - fname = mboxname_datapath(mailbox->part, mailbox->name, uid); + fname = mailbox_message_fname(mailbox, uid); unlink(fname); count++; } @@ -5076,7 +4051,7 @@ * no point trying to rescue anything else... */ mailbox_close(&mailbox); r = mailbox_create(name, mbentry->mbtype, mbentry->partition, mbentry->acl, - NULL, options, 0, 0, mbptr); + NULL, options, 0, mbptr); mboxlist_entry_free(&mbentry); return r; } @@ -5228,7 +4203,7 @@ int flags, int have_file, struct found_uids *discovered) { - const char *fname = mailbox_record_fname(mailbox, record); + char *fname = mailbox_message_fname(mailbox, record->uid); int r = 0; int i; struct index_record copy; @@ -5241,7 +4216,7 @@ /* does the file actually exist? */ if (have_file && do_stat) { - if (stat(fname, &sbuf) == -1 || (sbuf.st_size == 0)) { + if (stat(fname, &sbuf) == -1 || (sbuf.st_size == 0)) { have_file = 0; } else if (record->size != (unsigned) sbuf.st_size) { @@ -5249,7 +4224,7 @@ } did_stat = 1; } - + if (!have_file) { /* well, that's OK if it's supposed to be missing! */ if (record->system_flags & FLAG_UNLINKED) @@ -5312,7 +4287,7 @@ } else if (flags & RECONSTRUCT_GUID_REWRITE) { /* treat this file as discovered */ - add_found(discovered, record->uid, record->system_flags & FLAG_ARCHIVED); + add_found(discovered, record->uid); printf("%s uid %u marking for uid upgrade\n", mailbox->name, record->uid); syslog(LOG_ERR, "%s uid %u marking for uid upgrade", @@ -5343,21 +4318,6 @@ } } - if (!record->size) { - /* dang, guess it failed to parse */ - - printf("%s uid %u failed to parse\n", mailbox->name, record->uid); - syslog(LOG_ERR, "%s uid %u failed to parse", mailbox->name, record->uid); - - if (!make_changes) return 0; - - /* otherwise we have issues, mark it unlinked */ - unlink(fname); - record->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED; - mailbox->i.options |= OPT_MAILBOX_NEEDS_REPACK; - return mailbox_rewrite_index_record(mailbox, record); - } - /* get internaldate from the file if not set */ if (!record->internaldate) { if (did_stat || stat(fname, &sbuf) != -1) @@ -5373,8 +4333,7 @@ syslog(LOG_ERR, "%s uid %u future modseq " MODSEQ_FMT " found", mailbox->name, record->uid, record->modseq); mailbox_index_dirty(mailbox); - mailbox->i.highestmodseq = mboxname_setmodseq(mailbox->name, - record->modseq); + mailbox->i.highestmodseq = record->modseq; } if (record->uid > mailbox->i.last_uid) { @@ -5411,28 +4370,20 @@ return mailbox_rewrite_index_record(mailbox, record); } -static int mailbox_reconstruct_append(struct mailbox *mailbox, uint32_t uid, int isarchive, +static int mailbox_reconstruct_append(struct mailbox *mailbox, uint32_t uid, int flags) { - /* XXX - support archived */ - const char *fname; + char *fname = mailbox_message_fname(mailbox, uid); int r = 0; struct index_record record; struct stat sbuf; int make_changes = flags & RECONSTRUCT_MAKE_CHANGES; - if (isarchive) - fname = mboxname_archivepath(mailbox->part, mailbox->name, uid); - else - fname = mboxname_datapath(mailbox->part, mailbox->name, uid); - /* possible if '0.' file exists */ if (!uid) { /* filthy hack - copy the path to '1.' and replace 1 with 0 */ - char *hack; - fname = mboxname_datapath(mailbox->part, mailbox->name, 1); - hack = (char *)fname; - hackstrlen(fname)-2 = '0'; + fname = xstrdup(mailbox_message_fname(mailbox, 1)); + fnamestrlen(fname)-2 = '0'; } if (stat(fname, &sbuf) == -1) r = IMAP_MAILBOX_NONEXISTENT; @@ -5452,9 +4403,6 @@ r = message_parse(fname, &record); if (r) return r; - if (isarchive) - record.system_flags |= FLAG_ARCHIVED; - /* copy the timestamp from the file if not calculated */ if (!record.internaldate) record.internaldate = sbuf.st_mtime; @@ -5476,7 +4424,7 @@ if (!make_changes) return 0; oldfname = xstrdup(fname); - newfname = xstrdup(mailbox_record_fname(mailbox, &record)); + newfname = xstrdup(mailbox_message_fname(mailbox, record.uid)); r = rename(oldfname, newfname); free(oldfname); free(newfname); @@ -5613,9 +4561,8 @@ /* take advantage of the guarantee that all annotations with the same UID * will be together in a 'foreach' response */ - if (!annots->nused || annots->foundannots->nused-1.uid != uid) { - /* we don't support an archive annotations DB yet */ - add_found(annots, uid, /*isarchive*/0); + if (!annots->nused || annots->uidsannots->nused-1 != uid) { + add_found(annots, uid); } return 0; @@ -5631,7 +4578,7 @@ if (r) return r; /* make sure UIDs are sorted for comparison */ - qsort(annots->found, annots->nused, sizeof(unsigned long), sort_found); + qsort(annots->uids, annots->nused, sizeof(unsigned long), sort_uid); return 0; } @@ -5651,7 +4598,7 @@ } while (delannots->pos < delannots->nused) { - uint32_t uid = delannots->founddelannots->pos.uid; + unsigned uid = delannots->uidsdelannots->pos; syslog(LOG_NOTICE, "removing stale annotations for %u", uid); printf("removing stale annotations for %u\n", uid); if (make_changes) { @@ -5723,6 +4670,29 @@ valid_user_flagsflag/32 |= 1<<(flag&31); } + r = mailbox_ensure_cache(mailbox, 0); + if (r) { + const char *fname = mailbox_meta_fname(mailbox, META_CACHE); + uint32_t buf; + int n; + + printf("%s: missing cache file, recreating\n", + mailbox->name); + syslog(LOG_ERR, "%s: missing cache file, recreating", + mailbox->name); + + if (!make_changes) goto close; + + if (cyrus_mkdir(fname, 0755)) goto close; + mailbox->cache_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666); + if (mailbox->cache_fd == -1) goto close; + + /* set the generation number */ + buf = htonl(mailbox->i.generation_no); + n = retry_write(mailbox->cache_fd, (char *)&buf, 4); + if (n != 4) goto close; + } + /* find cyrus.expunge file if present */ cleanup_stale_expunged(mailbox); @@ -5751,50 +4721,26 @@ last_seen_uid = record.uid; /* bogus annotations? */ - while (annots.pos < annots.nused && annots.foundannots.pos.uid < record.uid) { - add_found(&delannots, annots.foundannots.pos.uid, /*isarchive*/0); + while (annots.pos < annots.nused && annots.uidsannots.pos < record.uid) { + add_found(&delannots, annots.uidsannots.pos); annots.pos++; } /* skip over current */ - while (annots.pos < annots.nused && annots.foundannots.pos.uid == record.uid) { + if (annots.pos < annots.nused && annots.uidsannots.pos == record.uid) { annots.pos++; } /* lower UID file exists */ - while (files.pos < files.nused && files.foundfiles.pos.uid < record.uid) { - add_found(&discovered, files.foundfiles.pos.uid, files.foundfiles.pos.isarchive); + while (files.pos < files.nused && files.uidsfiles.pos < record.uid) { + add_found(&discovered, files.uidsfiles.pos); files.pos++; } /* if they match, advance the pointer */ have_file = 0; - while (files.pos < files.nused && files.foundfiles.pos.uid == record.uid) { - if (have_file) { - /* we can just unlink this one, already processed one copy */ - const char *fname = mboxname_archivepath(mailbox->part, mailbox->name, record.uid); - printf("Removing duplicate archive file %s\n", fname); - unlink(fname); - } - else { - if (files.foundfiles.pos.isarchive) { - if (!(record.system_flags & FLAG_ARCHIVED)) { - /* oops, it's really archived - let's fix that right now */ - record.system_flags |= FLAG_ARCHIVED; - printf("Marking file as archived %s %u\n", mailbox->name, record.uid); - mailbox_rewrite_index_record(mailbox, &record); - } - } - else { - if (record.system_flags & FLAG_ARCHIVED) { - /* oops, non-archived copy exists, let's use that */ - record.system_flags &= ~FLAG_ARCHIVED; - printf("Marking file as not archived %s %u\n", mailbox->name, record.uid); - mailbox_rewrite_index_record(mailbox, &record); - } - } - have_file = 1; - } + if (files.pos < files.nused && files.uidsfiles.pos == record.uid) { + have_file = 1; files.pos++; } @@ -5806,44 +4752,42 @@ } /* add discovered messages before last_uid to the list in order */ - while (files.pos < files.nused && files.foundfiles.pos.uid <= mailbox->i.last_uid) { - add_found(&discovered, files.foundfiles.pos.uid, files.foundfiles.pos.isarchive); + while (files.pos < files.nused && files.uidsfiles.pos <= mailbox->i.last_uid) { + add_found(&discovered, files.uidsfiles.pos); files.pos++; } /* messages AFTER last_uid can keep the same UID (see also, restore * from lost .index file) - so don't bother moving those */ while (files.pos < files.nused) { - uint32_t uid = files.foundfiles.pos.uid; - r = mailbox_reconstruct_append(mailbox, files.foundfiles.pos.uid, - files.foundfiles.pos.isarchive, flags); + unsigned uid = files.uidsfiles.pos; + r = mailbox_reconstruct_append(mailbox, files.uidsfiles.pos, flags); if (r) goto close; files.pos++; /* we can keep this annotation too... */ /* bogus annotations? */ - while (annots.pos < annots.nused && annots.foundannots.pos.uid < uid) { - add_found(&delannots, annots.foundannots.pos.uid, /*isarchive*/0); + while (annots.pos < annots.nused && annots.uidsannots.pos < uid) { + add_found(&delannots, annots.uidsannots.pos); annots.pos++; } /* skip over current */ - while (annots.pos < annots.nused && annots.foundannots.pos.uid == uid) { + if (annots.pos < annots.nused && annots.uidsannots.pos == uid) { annots.pos++; } } /* bogus annotations after the end? */ while (annots.pos < annots.nused) { - add_found(&delannots, annots.foundannots.pos.uid, /*isarchive*/0); + add_found(&delannots, annots.uidsannots.pos); annots.pos++; } - + /* handle new list - note, we don't copy annotations for these */ while (discovered.pos < discovered.nused) { - r = mailbox_reconstruct_append(mailbox, discovered.founddiscovered.pos.uid, - discovered.founddiscovered.pos.isarchive, flags); + r = mailbox_reconstruct_append(mailbox, discovered.uidsdiscovered.pos, flags); if (r) goto close; discovered.pos++; } @@ -5866,17 +4810,17 @@ reconstruct_compare_headers(mailbox, &old_header, &mailbox->i); /* fix up 2.4.0 bug breakage */ - if (!mailbox->i.uidvalidity) { + if (mailbox->i.uidvalidity == 0) { if (make_changes) { - mailbox->i.uidvalidity = mboxname_nextuidvalidity(mailbox->name, time(0)); + mailbox->i.uidvalidity = time(0); mailbox_index_dirty(mailbox); } syslog(LOG_ERR, "%s: zero uidvalidity", mailbox->name); } - if (!mailbox->i.highestmodseq) { + if (mailbox->i.highestmodseq == 0) { if (make_changes) { mailbox_index_dirty(mailbox); - mailbox->i.highestmodseq = mboxname_nextmodseq(mailbox->name, 0); + mailbox->i.highestmodseq = 1; } syslog(LOG_ERR, "%s: zero highestmodseq", mailbox->name); } @@ -5889,6 +4833,7 @@ * write any changes */ mailbox->i.dirty = 0; mailbox->quota_dirty = 0; + mailbox->cache_dirty = 0; mailbox->modseq_dirty = 0; mailbox->header_dirty = 0; } @@ -5945,46 +4890,3 @@ return 0; } - -int mailbox_cid_rename(struct mailbox *mailbox, - conversation_id_t from_cid, - conversation_id_t to_cid) -{ - uint32_t recno, num_records; - struct index_record record; - int r; - - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) - return 0; - - num_records = mailbox->i.num_records; - for (recno = 1; recno <= num_records; recno++) { - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) { - syslog(LOG_ERR, "mailbox_cid_rename: error " - "reading record %u, mailbox %s: %s", - recno, mailbox->name, error_message(r)); - return r; - } - - if (record.cid != from_cid) - continue; - - /* - * Just rename the CID in place - injecting a copy at the end - * messes with clients that just use UID ordering, like Apple's - * IOS email client */ - - record.cid = to_cid; - r = mailbox_rewrite_index_record(mailbox, &record); - - if (r) { - syslog(LOG_ERR, "mailbox_cid_rename: error " - "rewriting record %u, mailbox %s: %s from %llu to %llu", - recno, mailbox->name, error_message(r), from_cid, to_cid); - return r; - } - } - - return 0; -}
View file
cyrus-imapd-2.5.tar.gz/imap/mailbox.h
Changed
@@ -49,9 +49,7 @@ #include <config.h> #include "byteorder64.h" -#include "conversations.h" #include "message_guid.h" -#include "ptrarray.h" #include "quota.h" #include "sequence.h" #include "util.h" @@ -76,7 +74,6 @@ #define FNAME_SQUAT "/cyrus.squat" #define FNAME_EXPUNGE "/cyrus.expunge" #define FNAME_ANNOTATIONS "/cyrus.annotations" -#define FNAME_DAV "/cyrus.dav" enum meta_filename { META_HEADER = 1, @@ -84,9 +81,7 @@ META_CACHE, META_SQUAT, META_EXPUNGE, - META_ANNOTATIONS, - META_DAV, - META_ARCHIVECACHE + META_ANNOTATIONS }; #define MAILBOX_FNAME_LEN 256 @@ -94,8 +89,7 @@ #define LOCK_NONE 0 #define LOCK_SHARED 1 #define LOCK_EXCLUSIVE 2 -#define LOCK_NONBLOCK 4 /* flag to OR in */ -#define LOCK_NONBLOCKING (LOCK_NONBLOCK|LOCK_EXCLUSIVE) +#define LOCK_NONBLOCKING 3 #define NUM_CACHE_FIELDS 10 @@ -105,7 +99,7 @@ }; struct cacherecord { - const struct buf *buf; + struct buf *base; unsigned offset; unsigned len; struct cacheitem itemNUM_CACHE_FIELDS; @@ -115,17 +109,14 @@ const char *userid; unsigned statusitems; - uint32_t messages; - uint32_t recent; - uint32_t uidnext; - uint32_t uidvalidity; - uint32_t unseen; + unsigned messages; + unsigned recent; + unsigned uidnext; + unsigned uidvalidity; + unsigned unseen; modseq_t highestmodseq; - conv_status_t xconv; }; -#define STATUSDATA_INIT { NULL, 0, 0, 0, 0, 0, 0, 0, CONV_STATUS_INIT } - struct index_record { uint32_t uid; time_t internaldate; @@ -135,13 +126,13 @@ time_t gmtime; uint32_t cache_offset; time_t last_updated; - uint32_t system_flags; - uint32_t user_flagsMAX_USER_FLAGS/32; + bit32 system_flags; + bit32 user_flagsMAX_USER_FLAGS/32; uint32_t content_lines; uint32_t cache_version; struct message_guid guid; modseq_t modseq; - conversation_id_t cid; + bit64 cid; bit32 cache_crc; bit32 record_crc; @@ -194,12 +185,14 @@ struct mailbox { int index_fd; + int cache_fd; int lock_fd; int header_fd; - ptrarray_t caches; const char *index_base; size_t index_len; /* mapped size */ + struct buf cache_buf; + size_t cache_len; /* mapped size */ int index_locktype; /* 0 = none, 1 = shared, 2 = exclusive */ int is_readonly; /* true = open index and cache files readonly */ @@ -229,12 +222,10 @@ /* annotations */ struct annotate_state *annot_state; - /* conversations */ - struct conversations_state *local_cstate; - /* change management */ int modseq_dirty; int header_dirty; + int cache_dirty; int quota_dirty; int has_changed; time_t last_updated; /* for appends*/ @@ -315,17 +306,9 @@ #define FLAG_DELETED (1<<2) #define FLAG_DRAFT (1<<3) #define FLAG_SEEN (1<<4) -#define FLAG_NEEDS_CLEANUP (1<<28) -#define FLAG_ARCHIVED (1<<29) #define FLAG_UNLINKED (1<<30) #define FLAG_EXPUNGED (1U<<31) -#define FLAGS_SYSTEM (FLAG_ANSWERED|FLAG_FLAGGED|FLAG_DELETED|FLAG_DRAFT|FLAG_SEEN) -#define FLAGS_INTERNAL (FLAG_NEEDS_CLEANUP|FLAG_ARCHIVED|FLAG_UNLINKED|FLAG_EXPUNGED) -/* for replication */ -#define FLAGS_LOCAL (FLAG_NEEDS_CLEANUP|FLAG_ARCHIVED|FLAG_UNLINKED) -#define FLAGS_GLOBAL (FLAGS_SYSTEM|FLAG_EXPUNGED) - #define OPT_POP3_NEW_UIDL (1<<0) /* added for Outlook stupidity */ /* NOTE: not used anymore - but don't reuse it */ #define OPT_IMAP_CONDSTORE (1<<1) /* added for CONDSTORE extension */ @@ -428,21 +411,28 @@ /* file names on disk */ #define META_FNAME_NEW 1 -extern const char *mailbox_meta_fname(struct mailbox *mailbox, int metafile); -extern const char *mailbox_meta_newfname(struct mailbox *mailbox, int metafile); +extern char *mailbox_meta_fname(struct mailbox *mailbox, int metafile); +extern char *mailbox_meta_newfname(struct mailbox *mailbox, int metafile); extern int mailbox_meta_rename(struct mailbox *mailbox, int metafile); -extern const char *mailbox_record_fname(struct mailbox *mailbox, - struct index_record *record); -extern const char *mailbox_datapath(struct mailbox *mailbox); +extern char *mailbox_message_fname(struct mailbox *mailbox, + unsigned long uid); +extern char *mailbox_datapath(struct mailbox *mailbox); -extern int mailbox_map_record(struct mailbox *mailbox, - struct index_record *record, - struct buf *data); +/* map individual messages in */ +extern int mailbox_map_message(struct mailbox *mailbox, unsigned long uid, + const char **basep, size_t *lenp); +extern void mailbox_unmap_message(struct mailbox *mailbox, + unsigned long uid, + const char **basep, size_t *lenp); /* cache record API */ +int mailbox_ensure_cache(struct mailbox *mailbox, size_t len); int mailbox_cacherecord(struct mailbox *mailbox, struct index_record *record); +int cache_append_record(int fd, struct index_record *record); +int mailbox_append_cache(struct mailbox *mailbox, + struct index_record *record); char *mailbox_cache_get_msgid(struct mailbox *mailbox, struct index_record *record); const char *cacheitem_base(struct index_record *record, int field); @@ -455,7 +445,6 @@ /* opening and closing */ extern int mailbox_open_iwl(const char *name, struct mailbox **mailboxptr); -extern int mailbox_open_irlnb(const char *name, struct mailbox **); extern int mailbox_open_irl(const char *name, struct mailbox **mailboxptr); extern int mailbox_open_exclusive(const char *name, @@ -477,9 +466,7 @@ extern int mailbox_append_index_record(struct mailbox *mailbox, struct index_record *record); extern int mailbox_find_index_record(struct mailbox *mailbox, uint32_t uid, - struct index_record *record, - const struct index_record *oldrecord); -extern uint32_t mailbox_finduid(struct mailbox *mailbox, uint32_t uid); + struct index_record *record); extern int mailbox_set_acl(struct mailbox *mailbox, const char *acl, int dirty_modseq); @@ -505,24 +492,19 @@ extern int mailbox_expunge(struct mailbox *mailbox, mailbox_decideproc_t *decideproc, void *deciderock, unsigned *nexpunged, int event_type); -extern void mailbox_archive(struct mailbox *mailbox, - mailbox_decideproc_t *decideproc, void *deciderock); extern int mailbox_cleanup(struct mailbox *mailbox, int iscurrentdir, mailbox_decideproc_t *decideproc, void *deciderock); extern void mailbox_unlock_index(struct mailbox *mailbox, struct statusdata *sd); -/* unlock and immediately lock again with the same type */ -extern int mailbox_yield_index(struct mailbox *mailbox); extern int mailbox_create(const char *name, uint32_t mbtype, const char *part, const char *acl, const char *uniqueid, int options, unsigned uidvalidity, - modseq_t highestmodseq, struct mailbox **mailboxptr); extern int mailbox_copy_files(struct mailbox *mailbox, const char *newpart, const char *newname); extern int mailbox_delete_cleanup(const char *part, const char *name); -extern int mailbox_rename_copy(struct mailbox *oldmailbox, +extern int mailbox_rename_copy(struct mailbox *oldmailbox, const char *newname, const char *newpart, unsigned uidvalidity, const char *userid, int ignorequota, @@ -546,7 +528,7 @@ struct mailbox *mailbox; struct index_header i; int newindex_fd; - ptrarray_t caches; + int newcache_fd; }; #define MAILBOX_CRC_VERSION_MIN 1 @@ -578,20 +560,4 @@ uint32_t mailbox_sync_crc(struct mailbox *mailbox, unsigned vers, int recalc); unsigned mailbox_best_crcvers(unsigned minvers, unsigned maxvers); -#ifdef WITH_DAV -extern int mailbox_add_dav(struct mailbox *mailbox); -#endif - -/* Rename a CID. Note - this is just one mailbox! */ -extern int mailbox_cid_rename(struct mailbox *mailbox, - conversation_id_t from_cid, - conversation_id_t to_cid); -extern int mailbox_update_conversations(struct mailbox *mailbox, - struct index_record *old, - struct index_record *new); -extern int mailbox_add_conversations(struct mailbox *mailbox); -extern int mailbox_get_xconvmodseq(struct mailbox *mailbox, modseq_t *); -extern int mailbox_update_xconvmodseq(struct mailbox *mailbox, modseq_t, int force); -extern int mailbox_has_conversations(struct mailbox *mailbox); - #endif /* INCLUDED_MAILBOX_H */
View file
cyrus-imapd-2.5.tar.gz/imap/mbdump.c
Changed
@@ -59,12 +59,10 @@ #include <utime.h> #include "annotate.h" -#include "dav_util.h" #include "exitcodes.h" #include "global.h" #include "imap/imap_err.h" #include "map.h" -#include "mappedfile.h" #include "mbdump.h" #include "mboxkey.h" #include "mboxlist.h" @@ -459,12 +457,11 @@ { META_INDEX, "cyrus.index" }, { META_CACHE, "cyrus.cache" }, { META_EXPUNGE, "cyrus.expunge" }, - { META_DAV, "cyrus.dav" }, { 0, NULL } }; -enum { SEEN_DB = 0, SUBS_DB = 1, MBOXKEY_DB = 2, DAV_DB = 3 }; -static int NUM_USER_DATA_FILES = 4; +enum { SEEN_DB = 0, SUBS_DB = 1, MBOXKEY_DB = 2 }; +static int NUM_USER_DATA_FILES = 3; EXPORTED int dump_mailbox(const char *tag, struct mailbox *mailbox, uint32_t uid_start, int oldversion, @@ -481,11 +478,8 @@ struct quota q; struct data_file *df; struct seqset *expunged_seq = NULL; - const char *dirpath = mailbox_datapath(mailbox); - /* XXX - archivepath */ - - mbdir = opendir(dirpath); + mbdir = opendir(mailbox_datapath(mailbox)); if (!mbdir && errno == EACCES) { syslog(LOG_ERR, "could not dump mailbox %s (permission denied)", mailbox->name); @@ -509,7 +503,7 @@ r = quota_read(&q, NULL, 0); if (!r) { - prot_printf(pout, QUOTA_T_FMT, q.limitsQUOTA_STORAGE); + prot_printf(pout, "%d", q.limitsQUOTA_STORAGE); } else { prot_printf(pout, "NIL"); if (r == IMAP_QUOTAROOT_NONEXISTENT) r = 0; @@ -532,13 +526,9 @@ break; case META_CACHE: - { - /* XXX - multi-cache-file support */ - struct mappedfile *cachefile = ptrarray_nth(&mailbox->caches, 0); - if (cachefile) { - fbase = mappedfile_base(cachefile); - flen = mappedfile_size(cachefile); - } + if (mailbox->cache_buf.s) { + fbase = mailbox->cache_buf.s; + flen = mailbox->cache_buf.len; } break; @@ -568,7 +558,6 @@ while ((next = readdir(mbdir)) != NULL) { char *name = next->d_name; /* Alias */ char *p = name; - char *fullpath; uint32_t uid; /* special case for '.' (well, it gets '..' too) */ @@ -588,10 +577,9 @@ continue; /* construct path/filename */ - fullpath = strconcat(dirpath, "/", name, (char *)NULL); - r = dump_file(0, !tag, pin, pout, fullpath, name, NULL, 0); - free(fullpath); + fname = mailbox_message_fname(mailbox, uid); + r = dump_file(0, !tag, pin, pout, fname, name, NULL, 0); if (r) goto done; } @@ -630,16 +618,6 @@ fname = mboxkey_getpath(userid); ftag = "MBOXKEY"; break; -#ifdef WITH_DAV - case DAV_DB: { - struct buf dav_file = BUF_INITIALIZER; - - dav_getpath_byuserid(&dav_file, userid); - fname = (char *) buf_cstring(&dav_file); - ftag = "DAV"; - break; - } -#endif // WITH_DAV default: fatal("unknown user data file", EC_OSFILE); } @@ -727,7 +705,7 @@ } prot_printliteral(pout, quota_namesres, strlen(quota_namesres)); prot_putc(' ', pout); - prot_printf(pout, QUOTA_T_FMT, q.limitsres); + prot_printf(pout, "%d", q.limitsres); } prot_putc(')', pout); } @@ -835,8 +813,8 @@ char *mboxkey_file = NULL; quota_t old_quota_usageQUOTA_NUMRESOURCES; int res; - quota_t newquotasQUOTA_NUMRESOURCES; - quota_t quotalimit = -1; + int newquotasQUOTA_NUMRESOURCES; + int quotalimit = -1; annotate_state_t *astate = NULL; memset(&file, 0, sizeof(file)); @@ -863,7 +841,7 @@ if (!strcmp(data.s, "NIL")) { /* Remove any existing quotaroot */ mboxlist_unsetquota(mbname); - } else if (sscanf(data.s, QUOTA_T_FMT, "alimit) == 1) { + } else if (sscanf(data.s, "%d", "alimit) == 1) { /* Set a Quota (may be -1 for "unlimited") */ for (res = 0; res < QUOTA_NUMRESOURCES; res++) { newquotasres = QUOTA_UNLIMITED; @@ -891,7 +869,7 @@ r = mboxlist_lookup(mbname, &mbentry, NULL); if (!r) r = mailbox_create(mbname, mbentry->mbtype, mbentry->partition, mbentry->acl, - NULL, 0, 0, 0, &mailbox); + NULL, 0, 0, &mailbox); mboxlist_entry_free(&mbentry); } if(r) goto done; @@ -1074,14 +1052,6 @@ char *s = user_hash_subs(userid); strlcpy(fnamebuf, s, sizeof(fnamebuf)); free(s); -#ifdef WITHDAV - } else if (userid && !strcmp(file.s, "DAV")) { - /* overwriting this outright is absolutely what we want to do */ - struct buf dav_file = BUF_INITIALIZER; - dav_getpath_byuserid(&dav_file, userid); - strlcpy(fnamebuf, buf_cstring(&dav_file), sizeof(fnamebuf)); - buf_free(&dav_file); -#endif // WITH_DAV } else if (userid && !strcmp(file.s, "SEEN")) { seen_file = seen_getpath(userid); @@ -1128,7 +1098,7 @@ /* Non-fatal, let's get the file transferred if we can */ } - + } } } else { @@ -1145,7 +1115,7 @@ if (!parseuint32(file.s, &ptr, &uid)) { /* is it really a data file? */ if (ptr && ptr0 == '.' && ptr1 == '\0') - path = mboxname_datapath(mailbox->part, mailbox->name, uid); + path = mailbox_message_fname(mailbox, uid); } } if (!path) { @@ -1291,7 +1261,7 @@ if (r) continue; if (record.system_flags & FLAG_UNLINKED) continue; /* no file! */ - fname = mailbox_record_fname(mailbox, &record); + fname = mailbox_message_fname(mailbox, record.uid); settime.actime = settime.modtime = record.internaldate; if (utime(fname, &settime) == -1) { r = IMAP_IOERROR;
View file
cyrus-imapd-2.5.tar.gz/imap/mbexamine.c
Changed
@@ -362,9 +362,9 @@ struct mailbox *mailbox = NULL; struct index_record record; quota_t total = 0; - const char *fname; + char *fname; struct stat sbuf; - + signals_poll(); /* Convert internal name to external */ @@ -388,7 +388,7 @@ if (record.system_flags & FLAG_EXPUNGED) continue; - fname = mailbox_record_fname(mailbox, &record); + fname = mailbox_message_fname(mailbox, record.uid); if (stat(fname, &sbuf) != 0) { syslog(LOG_WARNING,
View file
cyrus-imapd-2.5.tar.gz/imap/mboxevent.c
Changed
@@ -53,10 +53,6 @@ #include "annotate.h" #include "assert.h" -#ifdef WITH_DAV -#include "caldav_db.h" -#include "carddav_db.h" -#endif #include "exitcodes.h" #include "imapurl.h" #include "libconfig.h" @@ -64,11 +60,10 @@ #include "times.h" #include "xmalloc.h" -#include "map.h" -#include "mboxevent.h" -#include "mboxname.h" -#include "notify.h" -#include "global.h" +#include "imap/mboxevent.h" +#include "imap/mboxname.h" +#include "imap/notify.h" + #define MESSAGE_EVENTS (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_EXPIRE|\ EVENT_MESSAGE_EXPUNGE|EVENT_MESSAGE_NEW|\ @@ -84,9 +79,6 @@ #define QUOTA_EVENTS (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN|EVENT_QUOTA_CHANGE) -#ifdef WITH_DAV -#define CALENDAR_EVENTS (EVENT_CALENDAR_ALARM) -#endif //WITH_DAV #define FILL_STRING_PARAM(e,p,v) e->paramsp.value = (uint64_t)v; \ e->paramsp.type = EVENT_PARAM_STRING; \ @@ -101,8 +93,6 @@ static const char *notifier = NULL; static struct namespace namespace; -static const char *client_id = NULL; - static strarray_t *excluded_flags; static strarray_t *excluded_specialuse; static int enable_subfolder = 1; @@ -115,7 +105,6 @@ /* ordered to optimize the parsing of the notification message */ { { EVENT_TIMESTAMP, "timestamp", EVENT_PARAM_STRING, 0, 0 }, { EVENT_SERVICE, "service", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_SERVERFQDN, "serverFQDN", EVENT_PARAM_STRING, 0, 0 }, { EVENT_SERVER_ADDRESS, "serverAddress", EVENT_PARAM_STRING, 0, 0 }, { EVENT_CLIENT_ADDRESS, "clientAddress", EVENT_PARAM_STRING, 0, 0 }, { EVENT_OLD_MAILBOX_ID, "oldMailboxID", EVENT_PARAM_STRING, 0, 0 }, @@ -126,53 +115,20 @@ { EVENT_DISK_QUOTA, "diskQuota", EVENT_PARAM_INT, 0, 0 }, { EVENT_DISK_USED, "diskUsed", EVENT_PARAM_INT, 0, 0 }, { EVENT_MAX_MESSAGES, "maxMessages", EVENT_PARAM_INT, 0, 0 }, + { EVENT_ACL_SUBJECT, "aclSubject", EVENT_PARAM_STRING, 0, 0 }, + { EVENT_ACL_RIGHTS, "aclRights", EVENT_PARAM_STRING, 0, 0 }, { EVENT_MESSAGES, "messages", EVENT_PARAM_INT, 0, 0 }, { EVENT_UNSEEN_MESSAGES, "vnd.cmu.unseenMessages", EVENT_PARAM_INT, 0, 0 }, - { EVENT_CONVEXISTS, "vnd.fastmail.convExists", EVENT_PARAM_INT, 0, 0 }, - { EVENT_CONVUNSEEN, "vnd.fastmail.convUnseen", EVENT_PARAM_INT, 0, 0 }, { EVENT_UIDNEXT, "uidnext", EVENT_PARAM_INT, 0, 0 }, { EVENT_UIDSET, "uidset", EVENT_PARAM_STRING, 0, 0 }, { EVENT_MIDSET, "vnd.cmu.midset", EVENT_PARAM_STRING, 0, 0 }, { EVENT_FLAG_NAMES, "flagNames", EVENT_PARAM_STRING, 0, 0 }, { EVENT_PID, "pid", EVENT_PARAM_INT, 0, 0 }, - { EVENT_ACL_SUBJECT, "aclSubject", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_ACL_RIGHTS, "aclRights", EVENT_PARAM_STRING, 0, 0 }, { EVENT_USER, "user", EVENT_PARAM_STRING, 0, 0 }, { EVENT_MESSAGE_SIZE, "messageSize", EVENT_PARAM_INT, 0, 0 }, -#ifdef WITH_DAV - { EVENT_MBTYPE, "vnd.cmu.mbtype", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_MAILBOX_ACL, "vnd.cmu.mailboxACL", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_DAV_FILENAME, "vnd.cmu.davFilename", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_DAV_UID, "vnd.cmu.davUid", EVENT_PARAM_STRING, 0, 0 }, -#endif - { EVENT_MESSAGE_CID, "vnd.fastmail.cid", EVENT_PARAM_STRING, 0, 0 }, - -#ifdef WITH_DAV - /* calendar params for calalarmd/notifyd */ - { EVENT_CALENDAR_ALARM_TIME, "alarmTime", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_ALARM_RECIPIENTS, "alarmRecipients", EVENT_PARAM_ARRAY, 0, 0 }, - { EVENT_CALENDAR_USER_ID, "userId", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_CALENDAR_NAME, "calendarName", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_UID, "uid", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_ACTION, "action", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_SUMMARY, "summary", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_DESCRIPTION, "description", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_LOCATION, "location", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_TIMEZONE, "timezone", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_START, "start", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_END, "end", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CALENDAR_ALLDAY, "allDay", EVENT_PARAM_INT, 0, 0 }, - { EVENT_CALENDAR_ATTENDEE_NAMES, "attendeeNames", EVENT_PARAM_ARRAY, 0, 0 }, - { EVENT_CALENDAR_ATTENDEE_EMAILS, "attendeeEmails", EVENT_PARAM_ARRAY, 0, 0 }, - { EVENT_CALENDAR_ATTENDEE_STATUS, "attendeeStatus", EVENT_PARAM_ARRAY, 0, 0 }, - { EVENT_CALENDAR_ORGANIZER, "organizer", EVENT_PARAM_STRING, 0, 0 }, -#endif //WITH_DAV - /* always at end to let the parser to easily truncate this part */ { EVENT_ENVELOPE, "vnd.cmu.envelope", EVENT_PARAM_STRING, 0, 0 }, { EVENT_BODYSTRUCTURE, "bodyStructure", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_CLIENT_ID, "vnd.fastmail.clientId", EVENT_PARAM_STRING, 0, 0 }, - { EVENT_SESSION_ID, "vnd.fastmail.sessionId", EVENT_PARAM_STRING, 0, 0 }, { EVENT_MESSAGE_CONTENT, "messageContent", EVENT_PARAM_STRING, 0, 0 } }, STRARRAY_INITIALIZER, { 0, 0 }, NULL, STRARRAY_INITIALIZER, NULL, NULL, NULL @@ -227,12 +183,6 @@ if (groups & IMAP_ENUM_EVENT_GROUPS_MAILBOX) enabled_events |= MAILBOX_EVENTS; - -#ifdef WITH_DAV - if (groups & IMAP_ENUM_EVENT_GROUPS_CALENDAR) - enabled_events |= CALENDAR_EVENTS; -#endif //WITH_DAV - } EXPORTED void mboxevent_setnamespace(struct namespace *n) @@ -306,12 +256,6 @@ FILL_UNSIGNED_PARAM(mboxevent, EVENT_PID, getpid()); - if (mboxevent_expected_param(type, EVENT_CLIENT_ID) && client_id) - FILL_STRING_PARAM(mboxevent, EVENT_CLIENT_ID, xstrdup(client_id)); - - if (mboxevent_expected_param(type, EVENT_SESSION_ID)) - FILL_STRING_PARAM(mboxevent, EVENT_SESSION_ID, xstrdup(session_id())); - return mboxevent; } @@ -385,42 +329,8 @@ *mboxevent = NULL; } -#ifdef WITH_DAV -static int mboxevent_expected_calendar_param(enum event_param param) -{ - switch (param) { - case EVENT_CALENDAR_ALARM_TIME: - case EVENT_CALENDAR_ALARM_RECIPIENTS: - case EVENT_CALENDAR_USER_ID: - case EVENT_CALENDAR_CALENDAR_NAME: - case EVENT_CALENDAR_UID: - case EVENT_CALENDAR_ACTION: - case EVENT_CALENDAR_SUMMARY: - case EVENT_CALENDAR_DESCRIPTION: - case EVENT_CALENDAR_LOCATION: - case EVENT_CALENDAR_TIMEZONE: - case EVENT_CALENDAR_START: - case EVENT_CALENDAR_END: - case EVENT_CALENDAR_ALLDAY: - case EVENT_CALENDAR_ATTENDEE_NAMES: - case EVENT_CALENDAR_ATTENDEE_EMAILS: - case EVENT_CALENDAR_ATTENDEE_STATUS: - case EVENT_CALENDAR_ORGANIZER: - return 1; - case EVENT_SERVERFQDN: /* needed to see who is master */ - return 1; - default: - return 0; - } -} -#endif //WITH_DAV - static int mboxevent_expected_param(enum event_type type, enum event_param param) { -#ifdef WITH_DAV - if (type == EVENT_CALENDAR_ALARM) - return mboxevent_expected_calendar_param(param); -#endif //WITH_DAV switch (param) { case EVENT_BODYSTRUCTURE: return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_BODYSTRUCTURE) && @@ -445,18 +355,8 @@ return (type & (EVENT_FLAGS_SET|EVENT_FLAGS_CLEAR)) || ((extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_FLAGNAMES) && (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW))); - case EVENT_CLIENT_ID: - return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CLIENTID; - case EVENT_SESSION_ID: - return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_SESSIONID; case EVENT_MAILBOX_ID: return (type & MAILBOX_EVENTS); -#ifdef WITH_DAV - case EVENT_MBTYPE: - return (type & MAILBOX_EVENTS); -#endif - case EVENT_MAILBOX_ACL: - return (type & MAILBOX_EVENTS); case EVENT_MAX_MESSAGES: return type & QUOTA_EVENTS; case EVENT_MESSAGE_CONTENT: @@ -465,17 +365,6 @@ case EVENT_MESSAGE_SIZE: return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGESIZE) && (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)); -#ifdef WITH_DAV - case EVENT_DAV_FILENAME: - return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_DAVFILENAME) && - (type & EVENT_CALENDAR); - case EVENT_DAV_UID: - return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_DAVUID) && - (type & EVENT_CALENDAR); -#endif - case EVENT_MESSAGE_CID: - return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CID) && - (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)); case EVENT_MESSAGES: if (type & (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN)) return 1; @@ -510,8 +399,6 @@ return 1; case EVENT_PID: return 1; - case EVENT_SERVERFQDN: - return 1; case EVENT_USER: return type & (EVENT_MAILBOX_SUBSCRIBE|EVENT_MAILBOX_UNSUBSCRIBE|\ EVENT_LOGIN|EVENT_LOGOUT); @@ -523,14 +410,8 @@ if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_UNSEENMESSAGES)) return 0; break; - case EVENT_CONVEXISTS: - return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CONVEXISTS; - case EVENT_CONVUNSEEN: - return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CONVUNSEEN; case EVENT_OLD_UIDSET: return type & (EVENT_MESSAGE_COPY|EVENT_MESSAGE_MOVE); - default: - return 0; } /* test if the parameter is related to a message event */ @@ -541,50 +422,50 @@ EXPORTED void mboxevent_notify(struct mboxevent *mboxevents) { enum event_type type; - struct mboxevent *event; + struct mboxevent *event, *next; char stimestampTIMESTAMP_MAX+1; char *formatted_message; - const char *fname = NULL; /* nothing to notify */ if (!mboxevents) return; + event = mboxevents; + + /* swap FlagsSet and FlagsClear notification order depending the presence of + * the \Seen flag because it changes the value of vnd.cmu.unseenMessages */ + if (event->type == EVENT_FLAGS_SET && + event->next && + event->next->type == EVENT_FLAGS_CLEAR && + strarray_find_case(&event->next->flagnames, "\\Seen", 0) >= 0) { + + next = event->next; + event->next = next->next; + next->next = event; + event = next; + } + /* loop over the chained list of events */ - for (event = mboxevents; event; event = event->next) { + do { if (event->type == EVENT_CANCELLED) - continue; - - /* swap FlagsSet and FlagsClear notification order depending the presence of - * the \Seen flag because it changes the value of vnd.cmu.unseenMessages. - * kinda bogus because it only finds two next to each other, but hey */ - if (event->type == EVENT_FLAGS_SET && - event->next && - event->next->type == EVENT_FLAGS_CLEAR && - strarray_find_case(&event->next->flagnames, "\\Seen", 0) >= 0) { - - struct mboxevent *other = event->next; - event->next = other->next; - other->next = event; - event = other; - } + goto next; /* verify that at least one message has been added depending the event type */ if (event->type & (MESSAGE_EVENTS|FLAGS_EVENTS)) { if (event->type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND)) { if (!event->paramsEVENT_URI.filled) - continue; + goto next; } else if (event->uidset == NULL) - continue; + goto next; } /* others quota are not supported by RFC 5423 */ if ((event->type & QUOTA_EVENTS) && !event->paramsEVENT_DISK_QUOTA.filled && !event->paramsEVENT_MAX_MESSAGES.filled) - continue; + goto next; /* finish to fill event parameters structure */ @@ -592,10 +473,6 @@ FILL_STRING_PARAM(event, EVENT_SERVICE, xstrdup(config_ident)); } - if (mboxevent_expected_param(event->type, EVENT_SERVERFQDN)) { - FILL_STRING_PARAM(event, EVENT_SERVERFQDN, xstrdup(config_servername)); - } - if (mboxevent_expected_param(event->type, EVENT_TIMESTAMP)) { timeval_to_iso8601(&event->timestamp, timeval_ms, stimestamp, sizeof(stimestamp)); @@ -645,12 +522,16 @@ /* notification is ready to send */ formatted_message = json_formatter(type, event->params); - notify(notifier, "EVENT", NULL, NULL, NULL, 0, NULL, formatted_message, fname); + notify(notifier, "EVENT", NULL, NULL, NULL, 0, NULL, formatted_message); free(formatted_message); } while (strarray_size(&event->flagnames) > 0); + + next: + event = event->next; } + while (event); return; } @@ -714,6 +595,7 @@ char urlMAX_MAILBOX_PATH+1; struct imapurl imapurl; char extnameMAX_MAILBOX_NAME; + char *userbuf; if (!event) return; @@ -730,19 +612,20 @@ imapurl.server = config_servername; if (mailboxname != NULL) { - char *user = (char *)mboxname_to_userid(mailboxname); - /* translate internal mailbox name to external */ assert(namespace.mboxname_toexternal != NULL); - (*namespace.mboxname_toexternal)(&namespace, mailboxname, user, extname); - imapurl.mailbox = extname; + (*namespace.mboxname_toexternal)(&namespace, mailboxname, + mboxname_to_userid(mailboxname), + extname); - if (user) { - /* translate any separators in user */ - mboxname_hiersep_toexternal(&namespace, user, - config_virtdomains ? strcspn(user, "@") : 0); - imapurl.user = user; - } + /* translate any separators in user */ + userbuf = (char *)mboxname_to_userid(mailboxname); + if (userbuf != NULL) + mboxname_hiersep_toexternal(&namespace, userbuf, + config_virtdomains ? strcspn(userbuf, "@") : 0); + + imapurl.mailbox = extname; + imapurl.user = userbuf; } imapurl_toURL(url, &imapurl); @@ -761,17 +644,17 @@ } if (userid && mboxevent_expected_param(event->type, EVENT_USER)) { /* translate any separators in user */ - char *user = xstrdup(userid); - if (user) { - mboxname_hiersep_toexternal(&namespace, user, - config_virtdomains ? strcspn(user, "@") : 0); - } - FILL_STRING_PARAM(event, EVENT_USER, user); + userbuf = xstrdup(userid); + if (userbuf != NULL) + mboxname_hiersep_toexternal(&namespace, userbuf, + config_virtdomains ? strcspn(userbuf, "@") : 0); + + FILL_STRING_PARAM(event, EVENT_USER, userbuf); } } EXPORTED void mboxevent_set_acl(struct mboxevent *event, const char *identifier, - const char *rights) + const char *rights) { if (!event) return; @@ -826,12 +709,6 @@ FILL_UNSIGNED_PARAM(event, EVENT_MESSAGE_SIZE, record->size); } - /* add message CID */ - if (mboxevent_expected_param(event->type, EVENT_MESSAGE_CID)) { - FILL_STRING_PARAM(event, EVENT_MESSAGE_CID, - xstrdup(conversation_id_encode(record->cid))); - } - /* add vnd.cmu.envelope */ if (mboxevent_expected_param(event->type, EVENT_ENVELOPE)) { FILL_STRING_PARAM(event, EVENT_ENVELOPE, @@ -845,133 +722,8 @@ xstrndup(cacheitem_base(record, CACHE_BODYSTRUCTURE), cacheitem_size(record, CACHE_BODYSTRUCTURE))); } - -#ifdef WITH_DAV - /* add caldav items */ - if ((mailbox->mbtype & (MBTYPES_DAV)) && - (mboxevent_expected_param(event->type, EVENT_DAV_FILENAME) || - mboxevent_expected_param(event->type, EVENT_DAV_UID))) { - struct body *body = NULL; - const char *resource = NULL; - struct param *param; - - if (mailbox_cacherecord(mailbox, record)) - return; - message_read_bodystructure(record, &body); - - for (param = body->disposition_params; param; param = param->next) { - if (!strcmp(param->attribute, "FILENAME")) { - resource = param->value; - } - } - - FILL_STRING_PARAM(event, EVENT_DAV_FILENAME, xstrdup(resource)); - - if (mboxevent_expected_param(event->type, EVENT_DAV_UID)) { - if (mailbox->mbtype & MBTYPE_ADDRESSBOOK) { - struct carddav_db *carddavdb = NULL; - struct carddav_data *cdata = NULL; - carddavdb = carddav_open_mailbox(mailbox, 0); - carddav_lookup_resource(carddavdb, mailbox->name, resource, 0, &cdata); - FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->vcard_uid)); - carddav_close(carddavdb); - } - if (mailbox->mbtype & MBTYPE_CALENDAR) { - struct caldav_db *caldavdb = NULL; - struct caldav_data *cdata = NULL; - caldavdb = caldav_open_mailbox(mailbox, 0); - caldav_lookup_resource(caldavdb, mailbox->name, resource, 0, &cdata); - FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->ical_uid)); - caldav_close(caldavdb); - } - } - } -#endif //WITH_DAV } -#ifdef WITH_DAV -EXPORTED void mboxevent_extract_icalcomponent(struct mboxevent *event, - icalcomponent *ical, - const char *userid, - const char *calname, - enum caldav_alarm_action action, - icaltimetype alarmtime, - const char *timezone, - icaltimetype start, - icaltimetype end, - strarray_t *recipients) -{ - icalcomponent *comp = icalcomponent_get_first_real_component(ical); - - icalproperty *prop; - - FILL_STRING_PARAM(event, EVENT_CALENDAR_ALARM_TIME, - xstrdup(icaltime_as_ical_string(alarmtime))); - - FILL_ARRAY_PARAM(event, EVENT_CALENDAR_ALARM_RECIPIENTS, recipients); - - FILL_STRING_PARAM(event, EVENT_CALENDAR_USER_ID, xstrdup(userid)); - FILL_STRING_PARAM(event, EVENT_CALENDAR_CALENDAR_NAME, xstrdup(calname)); - - prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY); - FILL_STRING_PARAM(event, EVENT_CALENDAR_UID, - xstrdup(prop ? icalproperty_get_value_as_string(prop) : "")); - - FILL_STRING_PARAM(event, EVENT_CALENDAR_ACTION, xstrdup( - action == CALDAV_ALARM_ACTION_DISPLAY ? "display" : - action == CALDAV_ALARM_ACTION_EMAIL ? "email" : - "")); - - prop = icalcomponent_get_first_property(comp, ICAL_SUMMARY_PROPERTY); - FILL_STRING_PARAM(event, EVENT_CALENDAR_SUMMARY, - xstrdup(prop ? icalproperty_get_value_as_string(prop) : "")); - - prop = icalcomponent_get_first_property(comp, ICAL_DESCRIPTION_PROPERTY); - FILL_STRING_PARAM(event, EVENT_CALENDAR_DESCRIPTION, - xstrdup(prop ? icalproperty_get_value_as_string(prop) : "")); - - prop = icalcomponent_get_first_property(comp, ICAL_LOCATION_PROPERTY); - FILL_STRING_PARAM(event, EVENT_CALENDAR_LOCATION, - xstrdup(prop ? icalproperty_get_value_as_string(prop) : "")); - - prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); - FILL_STRING_PARAM(event, EVENT_CALENDAR_ORGANIZER, - xstrdup(prop ? icalproperty_get_value_as_string(prop) : "")); - - FILL_STRING_PARAM(event, EVENT_CALENDAR_TIMEZONE, - xstrdup(timezone)); - FILL_STRING_PARAM(event, EVENT_CALENDAR_START, - xstrdup(icaltime_as_ical_string(start))); - FILL_STRING_PARAM(event, EVENT_CALENDAR_END, - xstrdup(icaltime_as_ical_string(end))); - FILL_UNSIGNED_PARAM(event, EVENT_CALENDAR_ALLDAY, - icaltime_is_date(start) ? 1 : 0); - - strarray_t *attendee_names = strarray_new(); - strarray_t *attendee_emails = strarray_new(); - strarray_t *attendee_status = strarray_new(); - prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY); - while (prop) { - const char *email = icalproperty_get_value_as_string(prop); - if (!email) - continue; - strarray_append(attendee_emails, email); - - const char *name = icalproperty_get_parameter_as_string(prop, "CN"); - strarray_append(attendee_names, name ? name : ""); - - const char *partstat = icalproperty_get_parameter_as_string(prop, "PARTSTAT"); - strarray_append(attendee_status, partstat ? partstat : ""); - - prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY); - } - - FILL_ARRAY_PARAM(event, EVENT_CALENDAR_ATTENDEE_NAMES, attendee_names); - FILL_ARRAY_PARAM(event, EVENT_CALENDAR_ATTENDEE_EMAILS, attendee_emails); - FILL_ARRAY_PARAM(event, EVENT_CALENDAR_ATTENDEE_STATUS, attendee_status); -} -#endif //WITH_DAV - void mboxevent_extract_copied_record(struct mboxevent *event, const struct mailbox *mailbox, uint32_t uid) { @@ -1060,6 +812,7 @@ struct imapurl imapurl; char urlMAX_MAILBOX_PATH+1; char extnameMAX_MAILBOX_NAME; + char *userbuf; if (!event) return; @@ -1093,23 +846,22 @@ * quota root specified in RFC 2087. Thus we fill uri with quota root */ if (!event->paramsEVENT_URI.filled && event->type & QUOTA_EVENTS) { - char *user = (char *)mboxname_to_userid(quota->root); - - memset(&imapurl, 0, sizeof(struct imapurl)); - imapurl.server = config_servername; - /* translate internal mailbox name to external */ assert(namespace.mboxname_toexternal != NULL); - (*namespace.mboxname_toexternal)(&namespace, quota->root, user, extname); - imapurl.mailbox = extname; + (*namespace.mboxname_toexternal)(&namespace, quota->root, + mboxname_to_userid(quota->root), + extname); - if (user) { - /* translate any separators in user */ - mboxname_hiersep_toexternal(&namespace, user, - config_virtdomains ? strcspn(user, "@") : 0); - imapurl.user = user; - } + /* translate any separators in user */ + userbuf = (char *)mboxname_to_userid(quota->root); + if (userbuf != NULL) + mboxname_hiersep_toexternal(&namespace, userbuf, + config_virtdomains ? strcspn(userbuf, "@") : 0); + memset(&imapurl, 0, sizeof(struct imapurl)); + imapurl.server = config_servername; + imapurl.mailbox = extname; + imapurl.user = userbuf; imapurl_toURL(url, &imapurl); FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url)); } @@ -1137,7 +889,7 @@ struct imapurl imapurl; char urlMAX_MAILBOX_PATH+1; char extnameMAX_MAILBOX_NAME; - char *user; + char *userbuf; if (!event) return; @@ -1153,23 +905,22 @@ } /* translate internal mailbox name to external */ - user = (char *)mboxname_to_userid(mailbox->name); - memset(&imapurl, 0, sizeof(struct imapurl)); - imapurl.server = config_servername; - imapurl.uidvalidity = mailbox->i.uidvalidity; - assert(namespace.mboxname_toexternal != NULL); - (*namespace.mboxname_toexternal)(&namespace, mailbox->name, user, extname); - imapurl.mailbox = extname; + (*namespace.mboxname_toexternal)(&namespace, mailbox->name, + mboxname_to_userid(mailbox->name), extname); - if (user) { - /* translate any separators in user */ - mboxname_hiersep_toexternal(&namespace, user, - config_virtdomains ? strcspn(user, "@") : 0); - - imapurl.user = user; - } + /* translate any separators in user */ + userbuf = (char *)mboxname_to_userid(mailbox->name); + if (userbuf != NULL) + mboxname_hiersep_toexternal(&namespace, userbuf, + config_virtdomains ? strcspn(userbuf, "@") : 0); + /* all events needs uri parameter */ + memset(&imapurl, 0, sizeof(struct imapurl)); + imapurl.server = config_servername; + imapurl.mailbox = extname; + imapurl.user = userbuf; + imapurl.uidvalidity = mailbox->i.uidvalidity; if (event->type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND) && event->uidset) { imapurl.uid = seqset_first(event->uidset); /* don't add uidset parameter to MessageNew and MessageAppend events */ @@ -1177,17 +928,9 @@ event->uidset = NULL; } - /* all events needs uri parameter */ imapurl_toURL(url, &imapurl); FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url)); -#ifdef WITH_DAV - FILL_STRING_PARAM(event, EVENT_MBTYPE, - xstrdup(mboxlist_mbtype_to_string(mailbox->mbtype))); -#endif - - FILL_STRING_PARAM(event, EVENT_MAILBOX_ACL, xstrdup(mailbox->acl)); - /* mailbox related events also require mailboxID */ if (event->type & MAILBOX_EVENTS) { FILL_STRING_PARAM(event, EVENT_MAILBOX_ID, xstrdup(url)); @@ -1211,20 +954,6 @@ if (mboxevent_expected_param(event->type, EVENT_MESSAGES)) { FILL_UNSIGNED_PARAM(event, EVENT_MESSAGES, mailbox->i.exists); } - - if (mboxevent_expected_param(event->type, EVENT_CONVEXISTS) || - mboxevent_expected_param(event->type, EVENT_CONVUNSEEN)) { - conv_status_t status = CONV_STATUS_INIT; - - if (mailbox->local_cstate) - conversation_getstatus(mailbox->local_cstate, mailbox->name, &status); - - if (mboxevent_expected_param(event->type, EVENT_CONVEXISTS)) - FILL_UNSIGNED_PARAM(event, EVENT_CONVEXISTS, status.exists); - - if (mboxevent_expected_param(event->type, EVENT_CONVUNSEEN)) - FILL_UNSIGNED_PARAM(event, EVENT_CONVUNSEEN, status.unseen); - } } void mboxevent_extract_old_mailbox(struct mboxevent *event, @@ -1233,50 +962,34 @@ struct imapurl imapurl; char urlMAX_MAILBOX_PATH+1; char extnameMAX_MAILBOX_NAME; - char *user; + char *userbuf; if (!event) return; - user = (char *)mboxname_to_userid(mailbox->name); - - memset(&imapurl, 0, sizeof(struct imapurl)); - imapurl.server = config_servername; - imapurl.uidvalidity = mailbox->i.uidvalidity; - /* translate internal mailbox name to external */ assert(namespace.mboxname_toexternal != NULL); - (*namespace.mboxname_toexternal)(&namespace, mailbox->name, user, extname); - imapurl.mailbox = extname; + (*namespace.mboxname_toexternal)(&namespace, mailbox->name, + mboxname_to_userid(mailbox->name), extname); - if (user) { /* translate any separators in user */ - mboxname_hiersep_toexternal(&namespace, user, - config_virtdomains ? strcspn(user, "@") : 0); - imapurl.user = user; - } + userbuf = (char *)mboxname_to_userid(mailbox->name); + if (userbuf != NULL) + mboxname_hiersep_toexternal(&namespace, userbuf, + config_virtdomains ? strcspn(userbuf, "@") : 0); + + memset(&imapurl, 0, sizeof(struct imapurl)); + imapurl.server = config_servername; + imapurl.mailbox = extname; + imapurl.user = userbuf; + imapurl.uidvalidity = mailbox->i.uidvalidity; imapurl_toURL(url, &imapurl); FILL_STRING_PARAM(event, EVENT_OLD_MAILBOX_ID, xstrdup(url)); } -EXPORTED void mboxevent_set_client_id(const char *id) -{ - if (client_id) - free((char *)client_id); - client_id = xstrdupnull(id); -} - static const char *event_to_name(enum event_type type) { -#ifdef WITH_DAV - if (type == EVENT_CALENDAR) - return "MessageNew"; -#endif - - if (type == EVENT_MESSAGE_NEW) - return "MessageNew"; - switch (type) { case EVENT_MESSAGE_APPEND: return "MessageAppend"; @@ -1318,10 +1031,6 @@ return "MailboxSubscribe"; case EVENT_MAILBOX_UNSUBSCRIBE: return "MailboxUnSubscribe"; -#ifdef WITH_DAV - case EVENT_CALENDAR_ALARM: - return "CalendarAlarm"; -#endif case EVENT_ACL_CHANGE: return "AclChange"; default: @@ -1449,10 +1158,6 @@ if (!event->uidset || (seqset_first(event->uidset) == seqset_last(event->uidset))) buf_appendcstr(&missing, " modseq"); break; - case EVENT_CLIENT_ID: - return event->paramsEVENT_CLIENT_ID.filled; - case EVENT_SESSION_ID: - return event->paramsEVENT_SESSION_ID.filled; default: buf_appendcstr(&missing, " "); buf_appendcstr(&missing, event->paramsparam.name); @@ -1568,10 +1273,6 @@ { } -EXPORTED void mboxevent_set_client_id(const char *id) -{ -} - void mboxevent_extract_old_mailbox(struct mboxevent *event __attribute__((unused)), const struct mailbox *mailbox __attribute__((unused))) {
View file
cyrus-imapd-2.5.tar.gz/imap/mboxevent.h
Changed
@@ -49,10 +49,8 @@ #include "mailbox.h" #include "mboxname.h" -#ifdef WITH_DAV -#include "caldav_db.h" -#include "caldav_alarm.h" -#endif //WITH_DAV + + /* * event types defined in RFC 5423 - Internet Message Store Events */ @@ -82,11 +80,14 @@ EVENT_MAILBOX_RENAME = (1<<17), EVENT_MAILBOX_SUBSCRIBE = (1<<18), EVENT_MAILBOX_UNSUBSCRIBE = (1<<19), - EVENT_CALENDAR = (1<<20), - EVENT_ACL_CHANGE = (1<<21), - EVENT_CALENDAR_ALARM = (1<<22) + EVENT_ACL_CHANGE = (1<<20) }; +/* The number representing the last available position in + * event_param, which should always be messageContent. + */ +#define MAX_PARAM 23 + /* * event parameters defined in RFC 5423 - Internet Message Store Events * @@ -95,7 +96,6 @@ enum event_param { EVENT_TIMESTAMP, EVENT_SERVICE, - EVENT_SERVERFQDN, EVENT_SERVER_ADDRESS, /* gather serverDomain and serverPort together */ EVENT_CLIENT_ADDRESS, /* gather clientIP and clientPort together */ EVENT_OLD_MAILBOX_ID, @@ -108,8 +108,6 @@ EVENT_MAX_MESSAGES, EVENT_MESSAGES, EVENT_UNSEEN_MESSAGES, - EVENT_CONVEXISTS, - EVENT_CONVUNSEEN, EVENT_UIDNEXT, EVENT_UIDSET, EVENT_MIDSET, @@ -119,40 +117,11 @@ EVENT_ACL_RIGHTS, EVENT_USER, EVENT_MESSAGE_SIZE, - EVENT_MAILBOX_ACL, - EVENT_MESSAGE_CID, -#ifdef WITH_DAV - EVENT_MBTYPE, - EVENT_DAV_FILENAME, - EVENT_DAV_UID, - EVENT_CALENDAR_ALARM_TIME, - EVENT_CALENDAR_ALARM_RECIPIENTS, - EVENT_CALENDAR_USER_ID, - EVENT_CALENDAR_CALENDAR_NAME, - EVENT_CALENDAR_UID, - EVENT_CALENDAR_ACTION, - EVENT_CALENDAR_SUMMARY, - EVENT_CALENDAR_DESCRIPTION, - EVENT_CALENDAR_LOCATION, - EVENT_CALENDAR_TIMEZONE, - EVENT_CALENDAR_START, - EVENT_CALENDAR_END, - EVENT_CALENDAR_ALLDAY, - EVENT_CALENDAR_ATTENDEE_NAMES, - EVENT_CALENDAR_ATTENDEE_EMAILS, - EVENT_CALENDAR_ATTENDEE_STATUS, - EVENT_CALENDAR_ORGANIZER, -#endif EVENT_ENVELOPE, EVENT_BODYSTRUCTURE, - EVENT_CLIENT_ID, - EVENT_SESSION_ID, EVENT_MESSAGE_CONTENT }; -/* messageContent number that is always the last */ -#define MAX_PARAM EVENT_MESSAGE_CONTENT - enum event_param_type { EVENT_PARAM_INT, EVENT_PARAM_STRING, @@ -313,27 +282,4 @@ */ void mboxevent_extract_old_mailbox(struct mboxevent *event, const struct mailbox *mailbox); - -#ifdef WITH_DAV -/* - * Extract data from the given ical object - */ -void mboxevent_extract_icalcomponent(struct mboxevent *event, - icalcomponent *ical, - const char *userid, - const char *calname, - enum caldav_alarm_action action, - icaltimetype alarmtime, - const char *timezone, - icaltimetype start, - icaltimetype end, - strarray_t *recipients - ); -#endif //WITH_DAV - -/* - * set the client tag used by vnd.fastmail.clientTagj - */ -void mboxevent_set_client_id(const char *); - #endif /* _MBOXEVENT_H */
View file
cyrus-imapd-2.5.tar.gz/imap/mboxlist.c
Changed
@@ -172,10 +172,6 @@ buf_putc(&buf, 'r'); if (mbtype & MBTYPE_RESERVE) buf_putc(&buf, 'z'); - if (mbtype & MBTYPE_CALENDAR) - buf_putc(&buf, 'c'); - if (mbtype & MBTYPE_ADDRESSBOOK) - buf_putc(&buf, 'a'); return buf_cstring(&buf); } @@ -254,6 +250,20 @@ /* never get here */ } +static char *_parse_acl(struct dlist *di) +{ + struct buf buf = BUF_INITIALIZER; + struct dlist *ai; + + /* gotta make it all tabby again */ + for (ai = di->head; ai; ai = ai->next) { + buf_printf(&buf, "%s\t", ai->name); + buf_printf(&buf, "%s\t", dlist_cstring(ai)); + } + + return buf_release(&buf); +} + EXPORTED uint32_t mboxlist_string_to_mbtype(const char *string) { uint32_t mbtype = 0; @@ -262,12 +272,6 @@ for (; *string; string++) { switch (*string) { - case 'a': - mbtype |= MBTYPE_ADDRESSBOOK; - break; - case 'c': - mbtype |= MBTYPE_CALENDAR; - break; case 'd': mbtype |= MBTYPE_DELETED; break; @@ -289,58 +293,6 @@ return mbtype; } -struct parseentry_rock { - struct mboxlist_entry *mbentry; - struct buf *aclbuf; - int doingacl; -}; - -int parseentry_cb(int type, struct dlistsax_data *d) -{ - struct parseentry_rock *rock = (struct parseentry_rock *)d->rock; - - switch(type) { - case DLISTSAX_KVLISTSTART: - if (!strcmp(buf_cstring(&d->kbuf), "A")) { - rock->doingacl = 1; - } - break; - case DLISTSAX_KVLISTEND: - rock->doingacl = 0; - break; - case DLISTSAX_STRING: - if (rock->doingacl) { - buf_append(rock->aclbuf, &d->kbuf); - buf_putc(rock->aclbuf, '\t'); - buf_append(rock->aclbuf, &d->buf); - buf_putc(rock->aclbuf, '\t'); - } - else { - const char *key = buf_cstring(&d->kbuf); - if (!strcmp(key, "I")) { - rock->mbentry->uniqueid = buf_newcstring(&d->buf); - } - else if (!strcmp(key, "M")) { - rock->mbentry->mtime = atoi(buf_cstring(&d->buf)); - } - else if (!strcmp(key, "P")) { - rock->mbentry->partition = buf_newcstring(&d->buf); - } - else if (!strcmp(key, "S")) { - rock->mbentry->server = buf_newcstring(&d->buf); - } - else if (!strcmp(key, "T")) { - rock->mbentry->mbtype = mboxlist_string_to_mbtype(buf_cstring(&d->buf)); - } - else if (!strcmp(key, "V")) { - rock->mbentry->uidvalidity = atoi(buf_cstring(&d->buf)); - } - } - } - - return 0; -} - /* * parse a record read from the mailboxes.db into its parts. * @@ -353,11 +305,10 @@ * T: _t_ype * V: uid_v_alidity */ -EXPORTED int mboxlist_parse_entry(mbentry_t **mbentryptr, - const char *name, size_t namelen, - const char *data, size_t datalen) +static int mboxlist_parse_entry(mbentry_t **mbentryptr, + const char *name, size_t namelen, + const char *data, size_t datalen) { - static struct buf aclbuf; int r = IMAP_MAILBOX_BADFORMAT; char *freeme = NULL; char **target; @@ -375,13 +326,39 @@ /* check for DLIST mboxlist */ if (*data == '%') { - struct parseentry_rock rock; - memset(&rock, 0, sizeof(struct parseentry_rock)); - rock.mbentry = mbentry; - rock.aclbuf = &aclbuf; - aclbuf.len = 0; - r = dlist_parsesax(data, datalen, 0, parseentry_cb, &rock); - if (!r) mbentry->acl = buf_newcstring(&aclbuf); + struct dlist *dl = NULL; + struct dlist *di; + r = dlist_parsemap(&dl, 0, data, datalen); + if (r) goto done; + if (!dl) goto done; + for (di = dl->head; di; di = di->next) { + switch(di->name0) { + case 'A': + mbentry->acl = _parse_acl(di); + break; + case 'I': + mbentry->uniqueid = xstrdupnull(dlist_cstring(di)); + break; + case 'M': + mbentry->mtime = dlist_num(di); + break; + case 'P': + mbentry->partition = xstrdupnull(dlist_cstring(di)); + break; + case 'S': + mbentry->server = xstrdupnull(dlist_cstring(di)); + break; + case 'T': + mbentry->mbtype = mboxlist_string_to_mbtype(dlist_cstring(di)); + break; + case 'V': + mbentry->uidvalidity = dlist_num(di); + break; + } + } + dlist_free(&dl); + + r = 0; goto done; } @@ -830,7 +807,6 @@ int isadmin, const char *userid, struct auth_state *auth_state, int options, unsigned uidvalidity, - modseq_t highestmodseq, const char *copyacl, const char *uniqueid, int localonly, int forceuser, int dbonly, struct mailbox **mboxptr) @@ -871,9 +847,10 @@ if (r) goto done; if (!dbonly && !isremote) { + /* Filesystem Operations */ r = mailbox_create(mboxname, mbtype, newpartition, acl, uniqueid, - options, uidvalidity, highestmodseq, &newmailbox); + options, uidvalidity, &newmailbox); if (r) goto done; /* CREATE failed */ } @@ -943,7 +920,7 @@ r = mboxlist_createmailbox_full(name, mbtype, partition, isadmin, userid, auth_state, - options, 0, 0, NULL, NULL, localonly, + options, 0, NULL, NULL, localonly, forceuser, dbonly, &mailbox); if (notify && !r) { @@ -965,14 +942,12 @@ const char *partition, const char *userid, struct auth_state *auth_state, int options, unsigned uidvalidity, - modseq_t highestmodseq, const char *acl, const char *uniqueid, struct mailbox **mboxptr) { return mboxlist_createmailbox_full(name, mbtype, partition, 1, userid, auth_state, - options, uidvalidity, - highestmodseq, acl, uniqueid, + options, uidvalidity, acl, uniqueid, 0, 1, 0, mboxptr); } @@ -1215,9 +1190,6 @@ r = mboxlist_lookup(name, &mbentry, NULL); if (r) goto done; - if (mbentry->mbtype & MBTYPE_DELETED) - goto done; - isremote = mbentry->mbtype & MBTYPE_REMOTE; /* check if user has Delete right (we've already excluded non-admins @@ -1352,7 +1324,7 @@ /* special case: same mailbox, must be a partition move */ if (!strcmp(oldname, newname)) { - const char *oldpath = mailbox_datapath(oldmailbox); + char *oldpath = mailbox_datapath(oldmailbox); /* Only admin can move mailboxes between partitions */ if (!isadmin) { @@ -1573,8 +1545,6 @@ /* log the rename */ sync_log_mailbox_double(oldname, newname); - /* and log an append so that squatter indexes it */ - sync_log_append(newname); } /* free memory */ @@ -1802,7 +1772,7 @@ mode = ACL_MODE_REMOVE; } /* do not allow non-admin user to remove the admin rights from mailbox owner */ - if (!isadmin && isidentifiermbox && mode != ACL_MODE_ADD) { + if (!isadmin && isidentifiermbox) { int has_admin_rights = mboxlist_have_admin_rights(rights); if ((has_admin_rights && mode == ACL_MODE_REMOVE) || (!has_admin_rights && mode != ACL_MODE_REMOVE)) { @@ -2055,7 +2025,6 @@ struct glob *g = rock->g; long matchlen; mbentry_t *mbentry = NULL; - int ret = 0; /* don't list mailboxes outside of the default domain */ if (!rock->domainlen && !rock->isadmin && memchr(key, '!', keylen)) return 0; @@ -2064,7 +2033,7 @@ if (rock->inboxoffset) { char namebufMAX_MAILBOX_BUFFER; - if (keylen >= (int) sizeof(namebuf)) { + if(keylen >= (int) sizeof(namebuf)) { syslog(LOG_ERR, "oversize keylen in mboxlist.c:find_p()"); return 0; } @@ -2106,6 +2075,16 @@ return 0; } + /* Suppress deleted hierarchy unless admin: overrides ACL_LOOKUP test */ + if (!rock->isadmin) { + char namebufMAX_MAILBOX_BUFFER; + + memcpy(namebuf, key, keylen); + namebufkeylen = '\0'; + if (mboxname_isdeletedmailbox(namebuf, NULL)) + return 0; + } + /* subs DB has empty keys */ if (rock->issubs) return 1; @@ -2114,32 +2093,26 @@ if (mboxlist_parse_entry(&mbentry, key, keylen, data, datalen)) return 0; - /* nobody sees tombstones */ - if (mbentry->mbtype & MBTYPE_DELETED) - goto done; + if (mbentry->mbtype & MBTYPE_DELETED) { + mboxlist_entry_free(&mbentry); + return 0; + } /* check acl */ if (!rock->isadmin) { - /* always suppress deleted for non-admin */ - if (mboxname_isdeletedmailbox(mbentry->name, NULL)) goto done; - - /* also suppress calendar */ - if (mboxname_iscalendarmailbox(mbentry->name, mbentry->mbtype)) goto done; + int rights = cyrus_acl_myrights(rock->auth_state, mbentry->acl); - /* and addressbook */ - if (mboxname_isaddressbookmailbox(mbentry->name, mbentry->mbtype)) goto done; - - /* check the acls */ - if (!(cyrus_acl_myrights(rock->auth_state, mbentry->acl) & ACL_LOOKUP)) goto done; + if (!(rights & ACL_LOOKUP)) { + mboxlist_entry_free(&mbentry); + return 0; + } } + mboxlist_entry_free(&mbentry); + /* if we get here, close enough for us to spend the time acting interested */ - ret = 1; - -done: - mboxlist_entry_free(&mbentry); - return ret; + return 1; } static int check_name(struct find_rock *rock, @@ -2276,15 +2249,19 @@ EXPORTED int mboxlist_allmbox(const char *prefix, foreach_cb *proc, void *rock, int incdel) { + int r; char *search = prefix ? (char *)prefix : ""; - return cyrusdb_foreach(mbdb, search, strlen(search), - incdel ? NULL : skipdel_cb, - proc, rock, 0); + if (incdel) + r = cyrusdb_foreach(mbdb, search, strlen(search), NULL, proc, rock, 0); + else + r = cyrusdb_foreach(mbdb, search, strlen(search), skipdel_cb, proc, rock, 0); + + return r; } EXPORTED int mboxlist_allusermbox(const char *userid, foreach_cb *proc, - void *rock, int incdel) + void *rock, int include_deleted) { char *inbox = mboxname_user_mbox(userid, 0); size_t inboxlen = strlen(inbox); @@ -2294,31 +2271,22 @@ int r; r = cyrusdb_fetch(mbdb, inbox, inboxlen, &data, &datalen, NULL); - if (!r) { - /* process inbox first */ - if (incdel || skipdel_cb(rock, inbox, inboxlen, data, datalen)) - r = proc(rock, inbox, inboxlen, data, datalen); - } - else if (r == CYRUSDB_NOTFOUND) { - /* don't process inbox! */ - r = 0; - } + if (r) goto done; + + /* process inbox first */ + r = proc(rock, inbox, inboxlen, data, datalen); if (r) goto done; /* process all the sub folders */ - r = cyrusdb_foreach(mbdb, search, strlen(search), - incdel ? NULL : skipdel_cb, - proc, rock, 0); + r = cyrusdb_foreach(mbdb, search, strlen(search), NULL, proc, rock, 0); if (r) goto done; /* don't check if delayed delete is enabled, maybe the caller wants to * clean up deleted stuff after it's been turned off */ - if (incdel) { + if (include_deleted) { const char *prefix = config_getstring(IMAPOPT_DELETEDPREFIX); char *name = strconcat(prefix, ".", inbox, ".", (char *)NULL); - r = cyrusdb_foreach(mbdb, name, strlen(name), - incdel ? NULL : skipdel_cb, - proc, rock, 0); + r = cyrusdb_foreach(mbdb, name, strlen(name), NULL, proc, rock, 0); free(name); } @@ -2765,7 +2733,7 @@ * Set all the resource quotas on, or create a quota root. */ EXPORTED int mboxlist_setquotas(const char *root, - quota_t newquotasQUOTA_NUMRESOURCES, int force) + int newquotasQUOTA_NUMRESOURCES, int force) { char patternMAX_MAILBOX_PATH+1; struct quota q;
View file
cyrus-imapd-2.5.tar.gz/imap/mboxlist.h
Changed
@@ -68,11 +68,6 @@ #define MBTYPE_NETNEWS (1<<2) /* Netnews Mailbox - NO LONGER USED */ #define MBTYPE_MOVING (1<<3) /* Mailbox in mid-transfer (part is remotehost!localpart) */ #define MBTYPE_DELETED (1<<4) /* Mailbox has been deleted, but not yet cleaned up */ -#define MBTYPE_CALENDAR (1<<5) /* Calendar Mailbox */ -#define MBTYPE_ADDRESSBOOK (1<<6) /* Addressbook Mailbox */ - -#define MBTYPES_DAV (MBTYPE_CALENDAR|MBTYPE_ADDRESSBOOK) -#define MBTYPES_NONIMAP (MBTYPE_NETNEWS|MBTYPES_DAV) /* master name of the mailboxes file */ #define FNAME_MBOXLIST "/mailboxes.db" @@ -98,10 +93,6 @@ mbentry_t *mboxlist_entry_create(); -EXPORTED int mboxlist_parse_entry(mbentry_t **mbentryptr, - const char *name, size_t namelen, - const char *data, size_t datalen); - void mboxlist_entry_free(mbentry_t **mbentryptr); /* formats a cstring from a mboxlist_entry. Caller must free @@ -151,8 +142,7 @@ int mboxlist_createsync(const char *name, int mbtype, const char *partition, const char *userid, struct auth_state *auth_state, - int options, unsigned uidvalidity, - modseq_t highestmodseq, const char *acl, + int options, unsigned uidvalidity, const char *acl, const char *uniqueid, struct mailbox **mboxptr); /* delated delete */ @@ -233,7 +223,7 @@ /* set or create quota root */ int mboxlist_setquotas(const char *root, - quota_t newquotasQUOTA_NUMRESOURCES, int force); + int newquotasQUOTA_NUMRESOURCES, int force); int mboxlist_unsetquota(const char *root); /* open the mailboxes db */
View file
cyrus-imapd-2.5.tar.gz/imap/mboxname.c
Changed
@@ -42,7 +42,6 @@ #include <config.h> -#include <errno.h> #include <stdio.h> #include <string.h> #include <syslog.h> @@ -56,9 +55,6 @@ #include "global.h" #include "imap/imap_err.h" #include "mailbox.h" -#include "map.h" -#include "retry.h" -#include "user.h" #include "util.h" #include "xmalloc.h" @@ -157,11 +153,8 @@ previtem->next = item->next; else open_mboxlocks = item->next; - if (item->l.lock_fd != -1) { - if (item->l.locktype) - lock_unlock(item->l.lock_fd, item->l.name); + if (item->l.lock_fd != -1) close(item->l.lock_fd); - } free(item->l.name); free(item); return; @@ -174,17 +167,12 @@ /* name locking support */ -EXPORTED int mboxname_lock(const char *mboxname, struct mboxlock **mboxlockptr, - int locktype_and_flags) +HIDDEN int mboxname_lock(const char *mboxname, struct mboxlock **mboxlockptr, + int locktype) { const char *fname; int r = 0; struct mboxlocklist *lockitem; - int nonblock; - int locktype; - - nonblock = !!(locktype_and_flags & LOCK_NONBLOCK); - locktype = (locktype_and_flags & ~LOCK_NONBLOCK); fname = mboxname_lockpath(mboxname); if (!fname) @@ -194,6 +182,8 @@ /* already open? just use this one */ if (lockitem) { + if (locktype == LOCK_NONBLOCKING) + locktype = LOCK_EXCLUSIVE; /* can't change locktype! */ if (lockitem->l.locktype != locktype) return IMAP_MAILBOX_LOCKED; @@ -220,12 +210,23 @@ goto done; } - r = lock_setlock(lockitem->l.lock_fd, - locktype == LOCK_EXCLUSIVE, - nonblock, fname); - if (!r) lockitem->l.locktype = locktype; - else if (errno == EWOULDBLOCK) r = IMAP_MAILBOX_LOCKED; - else r = errno; + switch (locktype) { + case LOCK_SHARED: + r = lock_shared(lockitem->l.lock_fd, fname); + if (!r) lockitem->l.locktype = LOCK_SHARED; + break; + case LOCK_EXCLUSIVE: + r = lock_blocking(lockitem->l.lock_fd, fname); + if (!r) lockitem->l.locktype = LOCK_EXCLUSIVE; + break; + case LOCK_NONBLOCKING: + r = lock_nonblocking(lockitem->l.lock_fd, fname); + if (r == -1) r = IMAP_MAILBOX_LOCKED; + else if (!r) lockitem->l.locktype = LOCK_EXCLUSIVE; + break; + default: + fatal("unknown lock type", EC_SOFTWARE); + } done: if (r) remove_lockitem(lockitem); @@ -234,7 +235,7 @@ return r; } -EXPORTED void mboxname_release(struct mboxlock **mboxlockptr) +HIDDEN void mboxname_release(struct mboxlock **mboxlockptr) { struct mboxlocklist *lockitem; struct mboxlock *lock = *mboxlockptr; @@ -262,48 +263,6 @@ * one end to the other and such. Yay flexibility. */ -EXPORTED int mboxname_parts_to_internal(struct mboxname_parts *parts, - char *result) -{ - char *p = result; - size_t sz = MAX_MAILBOX_NAME; - size_t len; - const char *dp = config_getstring(IMAPOPT_DELETEDPREFIX); - const char *pf = ""; - - if (parts->domain) { - len = snprintf(p, sz, "%s!", parts->domain); - p += len; - sz -= len; - if (!sz) return IMAP_MAILBOX_BADNAME; - } - - if (parts->is_deleted) { - len = snprintf(p, sz, "%s%s", pf, dp); - p += len; - sz -= len; - if (!sz) return IMAP_MAILBOX_BADNAME; - pf = "."; - } - - if (parts->userid) { - len = snprintf(p, sz, "%suser.%s", pf, parts->userid); - p += len; - sz -= len; - if (!sz) return IMAP_MAILBOX_BADNAME; - pf = "."; - } - - if (parts->box) { - len = snprintf(p, sz, "%s%s", pf, parts->box); - p += len; - sz -= len; - if (!sz) return IMAP_MAILBOX_BADNAME; - } - - return 0; -} - /* Handle conversion from the standard namespace to the internal namespace */ static int mboxname_tointernal(struct namespace *namespace, const char *name, const char *userid, char *result) @@ -750,7 +709,7 @@ return 0; } -EXPORTED struct namespace *mboxname_get_adminnamespace() +HIDDEN struct namespace *mboxname_get_adminnamespace() { static struct namespace ns; if (!admin_namespace) { @@ -915,74 +874,6 @@ } /* - * If (internal) mailbox 'name' is a CALENDAR mailbox - * returns boolean - */ -int mboxname_iscalendarmailbox(const char *name, int mbtype) -{ - static const char *calendarprefix = NULL; - static int calendarprefix_len = 0; - const char *p; - const char *start = name; - - if (mbtype & MBTYPE_CALENDAR) return 1; /* Only works on backends */ - - if (!calendarprefix) { - calendarprefix = config_getstring(IMAPOPT_CALENDARPREFIX); - if (calendarprefix) calendarprefix_len = strlen(calendarprefix); - } - - /* if the prefix is blank, then no calendars */ - if (!calendarprefix_len) return 0; - - /* step past the domain part */ - if (config_virtdomains && (p = strchr(start, '!'))) - start = p + 1; - - /* step past the user part */ - if (!strncmp(start, "user.", 5) && (p = strchr(start+5, '.'))) - start = p + 1; - - return ((!strncmp(start, calendarprefix, calendarprefix_len) && - (startcalendarprefix_len == '\0' || - startcalendarprefix_len == '.')) ? 1 : 0); -} - -/* - * If (internal) mailbox 'name' is a ADDRESSBOOK mailbox - * returns boolean - */ -int mboxname_isaddressbookmailbox(const char *name, int mbtype) -{ - static const char *addressbookprefix = NULL; - static int addressbookprefix_len = 0; - const char *p; - const char *start = name; - - if (mbtype & MBTYPE_ADDRESSBOOK) return 1; /* Only works on backends */ - - if (!addressbookprefix) { - addressbookprefix = config_getstring(IMAPOPT_ADDRESSBOOKPREFIX); - if (addressbookprefix) addressbookprefix_len = strlen(addressbookprefix); - } - - /* if the prefix is blank, then no addressbooks */ - if (!addressbookprefix_len) return 0; - - /* step past the domain part */ - if (config_virtdomains && (p = strchr(start, '!'))) - start = p + 1; - - /* step past the user part */ - if (!strncmp(start, "user.", 5) && (p = strchr(start+5, '.'))) - start = p + 1; - - return ((!strncmp(start, addressbookprefix, addressbookprefix_len) && - (startaddressbookprefix_len == '\0' || - startaddressbookprefix_len == '.')) ? 1 : 0); -} - -/* * Translate (internal) inboxname into corresponding userid. */ EXPORTED const char *mboxname_to_userid(const char *mboxname) @@ -1123,7 +1014,7 @@ return 0; } -EXPORTED int mboxname_userid_to_parts(const char *userid, struct mboxname_parts *parts) +int mboxname_userid_to_parts(const char *userid, struct mboxname_parts *parts) { char *b, *e; /* beginning and end of string parts */ @@ -1144,7 +1035,7 @@ return 0; } -EXPORTED void mboxname_init_parts(struct mboxname_parts *parts) +HIDDEN void mboxname_init_parts(struct mboxname_parts *parts) { memset(parts, 0, sizeof(*parts)); } @@ -1423,47 +1314,8 @@ return pathresult; } -/* note: mboxname must be internal */ -EXPORTED char *mboxname_archivepath(const char *partition, const char *mboxname, unsigned long uid) -{ - static char pathresultMAX_MAILBOX_PATH+1; - const char *root; - - if (!partition) return NULL; - - root = config_archivepartitiondir(partition); - if (!root) root = config_partitiondir(partition); - if (!root) return NULL; - - /* XXX - dedup with datapath above - but make sure to keep the results - * in separate buffers and/or audit the callers */ - if (!mboxname) { - xstrncpy(pathresult, root, MAX_MAILBOX_PATH); - return pathresult; - } - - mboxname_hash(pathresult, MAX_MAILBOX_PATH, root, mboxname); - - if (uid) { - int len = strlen(pathresult); - snprintf(pathresult + len, MAX_MAILBOX_PATH - len, "/%lu.", uid); - } - pathresultMAX_MAILBOX_PATH = '\0'; - - if (strlen(pathresult) == MAX_MAILBOX_PATH) - return NULL; - - return pathresult; -} - char *mboxname_lockpath(const char *mboxname) { - return mboxname_lockpath_suffix(mboxname, ".lock"); -} - -char *mboxname_lockpath_suffix(const char *mboxname, - const char *suffix) -{ static char lockresultMAX_MAILBOX_PATH+1; char basepathMAX_MAILBOX_PATH+1; const char *root = config_getstring(IMAPOPT_MBOXNAME_LOCKPATH); @@ -1477,7 +1329,7 @@ mboxname_hash(lockresult, MAX_MAILBOX_PATH, root, mboxname); len = strlen(lockresult); - snprintf(lockresult + len, MAX_MAILBOX_PATH - len, "%s", suffix); + snprintf(lockresult + len, MAX_MAILBOX_PATH - len, "%s", ".lock"); lockresultMAX_MAILBOX_PATH = '\0'; if (strlen(lockresult) == MAX_MAILBOX_PATH) @@ -1491,7 +1343,6 @@ { static char metaresultMAX_MAILBOX_PATH; int metaflag = 0; - int archiveflag = 0; const char *root = NULL; const char *filename = NULL; char confkey256; @@ -1531,17 +1382,6 @@ metaflag = IMAP_ENUM_METAPARTITION_FILES_ANNOTATIONS; filename = FNAME_ANNOTATIONS; break; - case META_DAV: - snprintf(confkey, 256, "metadir-dav-%s", partition); - metaflag = IMAP_ENUM_METAPARTITION_FILES_DAV; - filename = FNAME_DAV; - break; - case META_ARCHIVECACHE: - snprintf(confkey, 256, "metadir-archivecache-%s", partition); - metaflag = IMAP_ENUM_METAPARTITION_FILES_ARCHIVECACHE; - filename = FNAME_CACHE; - archiveflag = 1; - break; case 0: break; default: @@ -1554,9 +1394,6 @@ if (!root && (!metaflag || (config_metapartition_files & metaflag))) root = config_metapartitiondir(partition); - if (!root && archiveflag) - root = config_archivepartitiondir(partition); - if (!root) root = config_partitiondir(partition); @@ -1631,7 +1468,7 @@ /* NOTE: caller must free, which is different from almost every * other interface in the whole codebase. Grr */ -EXPORTED char *mboxname_conf_getpath(struct mboxname_parts *parts, const char *suffix) +HIDDEN char *mboxname_conf_getpath(struct mboxname_parts *parts, const char *suffix) { char *fname = NULL; char c2, d2; @@ -1673,188 +1510,3 @@ return fname; } - -static bit64 mboxname_readval(const char *mboxname, const char *metaname) -{ - bit64 fileval = 0; - struct mboxname_parts parts; - char *fname = NULL; - const char *base = NULL; - size_t len = 0; - int fd = -1; - - mboxname_to_parts(mboxname, &parts); - - fname = mboxname_conf_getpath(&parts, metaname); - if (!fname) goto done; - - fd = open(fname, O_RDONLY); - - /* read the value - note: we don't care if it's being rewritten, - * we'll still get a consistent read on either the old or new - * value */ - if (fd != -1) { - struct stat sbuf; - if (fstat(fd, &sbuf)) { - syslog(LOG_ERR, "IOERROR: failed to stat fd %s: %m", fname); - goto done; - } - map_refresh(fd, 1, &base, &len, sbuf.st_size, metaname, mboxname); - if (len > 0) - parsenum(base, NULL, len, &fileval); - map_free(&base, &len); - } - - done: - if (fd != -1) close(fd); - mboxname_free_parts(&parts); - free(fname); - return fileval; -} - -/* XXX - inform about errors? Any error causes the value of at least - last+1 to be returned. An error only on writing causes - max(last, fileval) + 1 to still be returned */ -static bit64 mboxname_setval(const char *mboxname, const char *metaname, - bit64 last, int add) -{ - int fd = -1; - int newfd = -1; - char *fname = NULL; - char newfnameMAX_MAILBOX_PATH; - struct stat sbuf, fbuf; - const char *base = NULL; - size_t len = 0; - bit64 fileval = 0; - bit64 retval = last + add; - char numbuf30; - struct mboxname_parts parts; - int n; - - mboxname_to_parts(mboxname, &parts); - - fname = mboxname_conf_getpath(&parts, metaname); - if (!fname) goto done; - - /* get a blocking lock on fd */ - for (;;) { - fd = open(fname, O_RDWR | O_CREAT, 0644); - if (fd == -1) { - /* OK to not exist - try creating the directory first */ - if (cyrus_mkdir(fname, 0755)) goto done; - fd = open(fname, O_RDWR | O_CREAT, 0644); - } - if (fd == -1) { - syslog(LOG_ERR, "IOERROR: failed to create %s: %m", fname); - goto done; - } - if (lock_blocking(fd, fname)) { - syslog(LOG_ERR, "IOERROR: failed to lock %s: %m", fname); - goto done; - } - if (fstat(fd, &sbuf)) { - syslog(LOG_ERR, "IOERROR: failed to stat fd %s: %m", fname); - goto done; - } - if (stat(fname, &fbuf)) { - syslog(LOG_ERR, "IOERROR: failed to stat file %s: %m", fname); - goto done; - } - if (sbuf.st_ino == fbuf.st_ino) break; - lock_unlock(fd, fname); - close(fd); - fd = -1; - } - - /* read the old value */ - if (fd != -1) { - map_refresh(fd, 1, &base, &len, sbuf.st_size, metaname, mboxname); - if (len > 0) - parsenum(base, NULL, len, &fileval); - map_free(&base, &len); - if (fileval > last) last = fileval; - } - - retval = last + add; - - /* unchanged, no need to write */ - if (retval == fileval) - goto done; - - snprintf(newfname, MAX_MAILBOX_PATH, "%s.NEW", fname); - newfd = open(newfname, O_CREAT | O_TRUNC | O_WRONLY, 0644); - if (newfd == -1) { - syslog(LOG_ERR, "IOERROR: failed to open for write %s: %m", newfname); - goto done; - } - - snprintf(numbuf, 30, "%llu", retval); - n = retry_write(newfd, numbuf, strlen(numbuf)); - if (n < 0) { - syslog(LOG_ERR, "IOERROR: failed to write %s: %m", newfname); - goto done; - } - - if (fdatasync(newfd)) { - syslog(LOG_ERR, "IOERROR: failed to fdatasync %s: %m", newfname); - goto done; - } - - close(newfd); - newfd = -1; - - if (rename(newfname, fname)) { - syslog(LOG_ERR, "IOERROR: failed to rename %s: %m", newfname); - goto done; - } - - done: - if (newfd != -1) close(newfd); - if (fd != -1) { - lock_unlock(fd, fname); - close(fd); - } - mboxname_free_parts(&parts); - free(fname); - return retval; -} - -EXPORTED modseq_t mboxname_readmodseq(const char *mboxname) -{ - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) - return 0; - return (modseq_t)mboxname_readval(mboxname, "modseq"); -} - -EXPORTED modseq_t mboxname_nextmodseq(const char *mboxname, modseq_t last) -{ - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) - return last + 1; - return (modseq_t)mboxname_setval(mboxname, "modseq", (bit64)last, 1); -} - -EXPORTED modseq_t mboxname_setmodseq(const char *mboxname, modseq_t val) -{ - return (modseq_t)mboxname_setval(mboxname, "modseq", (bit64)val, 0); -} - -EXPORTED uint32_t mboxname_readuidvalidity(const char *mboxname) -{ - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) - return 0; - return (uint32_t)mboxname_readval(mboxname, "uidvalidity"); -} - -EXPORTED uint32_t mboxname_nextuidvalidity(const char *mboxname, uint32_t last) -{ - if (!config_getswitch(IMAPOPT_CONVERSATIONS)) - return last + 1; - return (uint32_t)mboxname_setval(mboxname, "uidvalidity", (bit64)last, 1); -} - -EXPORTED uint32_t mboxname_setuidvalidity(const char *mboxname, uint32_t val) -{ - return (uint32_t)mboxname_setval(mboxname, "uidvalidity", (bit64)val, 0); -} - -
View file
cyrus-imapd-2.5.tar.gz/imap/mboxname.h
Changed
@@ -44,7 +44,6 @@ #define INCLUDED_MBOXNAME_H #include "auth.h" -#include "util.h" #define MAX_NAMESPACE_PREFIX 40 @@ -87,7 +86,7 @@ struct mboxlock { char *name; int lock_fd; - int locktype; /* LOCK_NONE or LOCK_SHARED or LOCK_EXCLUSIVE */ + int locktype; }; struct mboxname_parts { @@ -148,13 +147,6 @@ */ int mboxname_to_parts(const char *mboxname, struct mboxname_parts *parts); int mboxname_userid_to_parts(const char *userid, struct mboxname_parts *parts); - -/* - * Create an (internal) mboxname from parts - */ - -int mboxname_parts_to_internal(struct mboxname_parts *parts, char *target); - /* * Cleanup up a mboxname_parts structure. */ @@ -162,18 +154,6 @@ void mboxname_free_parts(struct mboxname_parts *parts); -/* - * If (internal) mailbox 'name' is a CALENDAR mailbox - * returns boolean - */ -int mboxname_iscalendarmailbox(const char *name, int mbtype); - -/* - * If (internal) mailbox 'name' is a ADDRESSBOOK mailbox - * returns boolean - */ -int mboxname_isaddressbookmailbox(const char *name, int mbtype); - /* check if one mboxname is a parent or same as the other */ int mboxname_is_prefix(const char *longstr, const char *shortstr); @@ -201,20 +181,15 @@ const char *root, const char *name); -char *mboxname_datapath(const char *partition, +char *mboxname_datapath(const char *partition, const char *mboxname, unsigned long uid); -char *mboxname_archivepath(const char *partition, - const char *mboxname, - unsigned long uid); - char *mboxname_metapath(const char *partition, const char *mboxname, int metafile, int isnew); char *mboxname_lockpath(const char *mboxname); -char *mboxname_lockpath_suffix(const char *mboxname, const char *suffix); /* * Return nonzero if (internal) mailbox 'name' consists of legal characters. @@ -236,12 +211,4 @@ char *mboxname_conf_getpath(struct mboxname_parts *parts, const char *suffix); - -modseq_t mboxname_readmodseq(const char *mboxname); -modseq_t mboxname_nextmodseq(const char *mboxname, modseq_t last); -modseq_t mboxname_setmodseq(const char *mboxname, modseq_t val); -uint32_t mboxname_readuidvalidity(const char *mboxname); -uint32_t mboxname_nextuidvalidity(const char *mboxname, uint32_t last); -uint32_t mboxname_setuidvalidity(const char *mboxname, uint32_t val); - #endif
View file
cyrus-imapd-2.5.tar.gz/imap/message.c
Changed
@@ -45,7 +45,6 @@ #ifdef HAVE_UNISTD_H #include <unistd.h> #endif -#include <errno.h> #include <stdio.h> #include <ctype.h> #include <string.h> @@ -56,18 +55,12 @@ #include <netinet/in.h> #include <stdlib.h> -#include "arrayu64.h" -#include "assert.h" #include "crc32.h" -#include "dlist.h" #include "exitcodes.h" #include "imap/imap_err.h" -#include "prot.h" -#include "hash.h" #include "map.h" #include "mailbox.h" #include "message.h" -#include "message_priv.h" #include "message_guid.h" #include "parseaddr.h" #include "charset.h" @@ -76,18 +69,11 @@ #include "xmalloc.h" #include "xstrlcpy.h" #include "strarray.h" -#include "ptrarray.h" #include "global.h" #include "retry.h" #include "imap/rfc822_header.h" -#include "rfc822tok.h" #include "times.h" -static int message_map_file(message_t *m, const char *fname); -static int message_parse_cbodystructure(message_t *m); - -#define DEBUG 0 - /* Message being parsed */ struct msg { const char *base; @@ -96,8 +82,6 @@ int encode; }; -#define MAX_FIELDNAME_LENGTH 256 - /* (draft standard) MIME tspecials */ #define TSPECIALS "()<>@,;:\\\"/?=" @@ -145,8 +129,6 @@ static void message_write_charset(struct buf *buf, const struct body *body); static void message_write_searchaddr(struct buf *buf, const struct address *addrlist); -static int message_need(message_t *m, unsigned int need); -static void message_yield(message_t *m, unsigned int yield); /* * Convert a string to uppercase. Returns the string. @@ -174,7 +156,7 @@ * imapd.conf before calling. */ EXPORTED int message_copy_strict(struct protstream *from, FILE *to, - unsigned size, int allow_null) + unsigned size, int allow_null) { char buf4096+1; unsigned char *p, *endp; @@ -275,7 +257,8 @@ } } -EXPORTED int message_parse(const char *fname, struct index_record *record) +EXPORTED int message_parse2(const char *fname, struct index_record *record, + struct body **bodyp) { struct body *body = NULL; FILE *f; @@ -287,11 +270,16 @@ r = message_parse_file(f, NULL, NULL, &body); if (!r) r = message_create_record(record, body); - fclose(f); + if (f) fclose(f); if (body) { - message_free_body(body); - free(body); + if (!r && bodyp) { + *bodyp = body; + } + else { + message_free_body(body); + free(body); + } } return r; @@ -333,7 +321,7 @@ if (!*msg_base || !*msg_len) return IMAP_IOERROR; /* zero length file? */ - if (!*body) *body = (struct body *) xzmalloc(sizeof(struct body)); + if (!*body) *body = (struct body *) xmalloc(sizeof(struct body)); r = message_parse_mapped(*msg_base, *msg_len, *body); if (unmap) map_free(msg_base, msg_len); @@ -378,7 +366,7 @@ return IMAP_IOERROR; } - if (!*body) *body = (struct body *) xzmalloc(sizeof(struct body)); + if (!*body) *body = (struct body *) xmalloc(sizeof(struct body)); message_parse_body(&msg, *body, DEFAULT_CONTENT_TYPE, (strarray_t *)0); @@ -397,6 +385,7 @@ return 0; } + /* * Parse the message at 'msg_base' of length 'msg_len'. */ @@ -418,74 +407,6 @@ return 0; } -/* - * Prune the header section in buf to include only those headers - * listed in headers or (if headers_not is non-empty) those headers - * not in headers_not. - */ -void message_pruneheader(char *buf, const strarray_t *headers, - const strarray_t *headers_not) -{ - char *p, *colon, *nextheader; - int goodheader; - char *endlastgood = buf; - char **l; - int count = 0; - int maxlines = config_getint(IMAPOPT_MAXHEADERLINES); - - p = buf; - while (*p && *p != '\r') { - colon = strchr(p, ':'); - if (colon && headers_not && headers_not->count) { - goodheader = 1; - for (l = headers_not->data ; *l ; l++) { - if ((size_t) (colon - p) == strlen(*l) && - !strncasecmp(p, *l, colon - p)) { - goodheader = 0; - break; - } - } - } else { - goodheader = 0; - } - if (colon && headers && headers->count) { - for (l = headers->data ; *l ; l++) { - if ((size_t) (colon - p) == strlen(*l) && - !strncasecmp(p, *l, colon - p)) { - goodheader = 1; - break; - } - } - } - - nextheader = p; - do { - nextheader = strchr(nextheader, '\n'); - if (nextheader) nextheader++; - else nextheader = p + strlen(p); - } while (*nextheader == ' ' || *nextheader == '\t'); - - if (goodheader) { - if (endlastgood != p) { - /* memmove and not strcpy since this is all within a - * single buffer */ - memmove(endlastgood, p, strlen(p) + 1); - nextheader -= p - endlastgood; - } - endlastgood = nextheader; - } - p = nextheader; - - /* stop giant headers causing massive loops */ - if (maxlines) { - count++; - if (count > maxlines) break; - } - } - - *endlastgood = '\0'; -} - static void message_find_part(struct body *body, const char *section, const char **content_types, const char *msg_base, unsigned long msg_len, @@ -553,8 +474,8 @@ * The caller MUST free the array of allocated bodypart(s). */ EXPORTED void message_fetch_part(struct message_content *msg, - const char **content_types, - struct bodypart ***parts) + const char **content_types, + struct bodypart ***parts) { int n = 0; /* running count of the number of matching parts */ @@ -825,9 +746,6 @@ } message_parse_string(value, &body->received_date); break; - case RFC822_X_ME_MESSAGE_ID: - message_parse_string(value, &body->x_me_message_id); - break; default: break; } /* switch() */ @@ -943,8 +861,7 @@ if (!body->type || !strcmp(body->type, "TEXT")) { for (param = body->params; param; param = param->next) { if (!strcasecmp(param->attribute, "charset")) { - if (param->value && *param->value) - charset = charset_lookupname(param->value); + charset = charset_lookupname(param->value); break; } } @@ -1129,14 +1046,7 @@ } /* - * Parse a parameter list from a header. - * - * 'hdr' points into the message, and is not expected to - * be nul-terminated. Handles continuation headers. - * - * Malformed parameters are handled by skipping to the - * next ';' or end of line, which should mark the next - * parameter. + * Parse a parameter list from a header */ static void message_parse_params(const char *hdr, struct param **paramp) { @@ -1155,7 +1065,7 @@ /* Find end of attribute */ attribute = hdr; for (; *hdr && !Uisspace(*hdr) && *hdr != '=' && *hdr != '('; hdr++) { - if (*hdr < ' ' || strchr(TSPECIALS, *hdr)) goto skip; + if (*hdr < ' ' || strchr(TSPECIALS, *hdr)) return; } attributelen = hdr - attribute; @@ -1164,7 +1074,7 @@ if (!hdr) return; /* Ignore param if no '=' character */ - if (*hdr++ != '=') goto skip; + if (*hdr++ != '=') return; /* Skip whitespace before value */ message_parse_rfc822space(&hdr); @@ -1180,9 +1090,8 @@ if (!*hdr) return; } if (*hdr == '\r') { - /* check for continuation headers */ if (hdr1 == '\n' && (hdr2 == ' ' || hdr2 == '\t')) hdr += 2; - else return; /* end of header field */ + else return; } hdr++; } @@ -1190,7 +1099,7 @@ } else { for (; *hdr && !Uisspace(*hdr) && *hdr != ';' && *hdr != '('; hdr++) { - if (*hdr < ' ' || strchr(TSPECIALS, *hdr)) goto skip; + if (*hdr < ' ' || strchr(TSPECIALS, *hdr)) return; } } valuelen = hdr - value; @@ -1199,12 +1108,7 @@ message_parse_rfc822space(&hdr); /* Ignore parameter if not at end of header or parameter delimiter */ - if (hdr && *hdr++ != ';') { -skip: - hdr += strcspn(hdr, ";\r\n"); - if (*hdr == ';') hdr++; - continue; - } + if (hdr && *hdr++ != ';') return; /* Save attribute/value pair */ *paramp = param = (struct param *)xzmalloc(sizeof(struct param)); @@ -1446,7 +1350,7 @@ if (*p == '\n') { p++; if (*p != ' ' && *p != '\t') { - *s = 0; /* end of header field, no continuation */ + *s = 0; return; } } @@ -1481,7 +1385,7 @@ else p++; } if (*p == 0) { - *s = 0; /* embedded NUL */ + *s = 0; } else { *s = p; @@ -1530,15 +1434,14 @@ (limit == 0 ? 1 : boundaries->count < limit)) { body->subpart = (struct body *)xrealloc((char *)body->subpart, (body->numparts+1)*sizeof(struct body)); - message_parse_body(msg, &body->subpartbody->numparts, + message_parse_body(msg, &body->subpartbody->numparts++, defaultContentType, boundaries); if (msg->offset == msg->len && - body->subpartbody->numparts.boundary_size == 0) { + body->subpartbody->numparts-1.boundary_size == 0) { /* hit the end of the message, therefore end all pending multiparts */ - strarray_truncate(boundaries, 0); + boundaries->count = 0; } - body->numparts++; } if (boundaries->count == depth-1) { @@ -1794,6 +1697,7 @@ struct buf ibNUM_CACHE_FIELDS; struct body toplevel; char *subject; + int len; int i; /* initialise data structures */ @@ -1831,13 +1735,15 @@ buf_free(&ibi); } + len = buf_len(&cacheitem_buffer); + /* copy the fields into the message */ record->cache_offset = 0; /* calculate on write! */ record->cache_version = MAILBOX_CACHE_MINOR_VERSION; - record->cache_crc = crc32_buf(&cacheitem_buffer); - record->crec.buf = &cacheitem_buffer; + record->cache_crc = crc32_map(cacheitem_buffer.s, len); + record->crec.base = &cacheitem_buffer; record->crec.offset = 0; /* we're at the start of the buffer */ - record->crec.len = buf_len(&cacheitem_buffer); + record->crec.len = len; return 0; } @@ -1876,7 +1782,7 @@ * (if 'newformat' is nonzero) for 'body' to 'buf'. */ HIDDEN void message_write_body(struct buf *buf, const struct body *body, - int newformat) + int newformat) { struct param *param; @@ -2388,7 +2294,6 @@ if (body->bcc) parseaddr_free(body->bcc); if (body->in_reply_to) free(body->in_reply_to); if (body->message_id) free(body->message_id); - if (body->x_me_message_id) free(body->x_me_message_id); if (body->references) free(body->references); if (body->received_date) free(body->received_date); @@ -2950,1293 +2855,3 @@ binbody = cacheitem_base(record, CACHE_SECTION); message_read_binarybody(&toplevel, &binbody); } - -static void de_nstring_buf(struct buf *src, struct buf *dst) -{ - char *p, *q; - - if (src->s && src->len == 3 && !memcmp(src->s, "NIL", 3)) { - buf_free(dst); - return; - } - buf_cstring(src); /* ensure nstring parse doesn't overrun */ - q = src->s; - p = parse_nstring(&q); - buf_setmap(dst, p, q-p); - buf_cstring(dst); -} - -static void message1_get_subject(struct index_record *record, struct buf *buf) -{ - struct buf tmp = BUF_INITIALIZER; - buf_copy(&tmp, cacheitem_buf(record, CACHE_SUBJECT)); - de_nstring_buf(&tmp, buf); - buf_free(&tmp); -} - -/* - * Generate a conversation id from the given message. - * The conversation id is defined as the first 64b of - * the SHA1 of the message, except that an all-zero - * conversation id is not valid. - */ -static conversation_id_t generate_conversation_id( - const struct index_record *record) -{ - conversation_id_t cid = 0; - size_t i; - - assert(record->guid.status == GUID_NONNULL); - - for (i = 0 ; i < sizeof(cid) ; i++) { - cid <<= 8; - cid |= record->guid.valuei; - } - - /* - * We carefully avoid returning NULLCONVERSATION as - * a new cid, as that would confuse matters no end. - */ - if (cid == NULLCONVERSATION) - cid = 1; - - return cid; -} - -/* - * In RFC2822, the In-Reply-To field is explicitly required to contain - * only message-ids, whitespace and commas. The old RFC822 was less - * well specified and allowed all sorts of stuff. We used to be equally - * liberal here in parsing the field. Sadly some versions of the NMH - * mailer will generate In-Reply-To containing email addresses which we - * cannot tell from message-ids, leading to massively confused - * threading. So we have to be slightly stricter. - */ -static int is_valid_rfc2822_inreplyto(const char *p) -{ - if (!p) - return 1; - - /* skip any whitespace */ - while (*p && (isspace(*p) || *p == ',')) - p++; - - return (!*p || *p == '<'); -} - -/* - * Update the conversations database for the given - * mailbox, to account for the given message. - * @body may be NULL, in which case we get everything - * we need out of the cache item in @record. - */ -EXPORTED int message_update_conversations(struct conversations_state *state, - struct index_record *record, - conversation_t **convp) -{ - char *hdrs4; - char *c_refs = NULL, *c_env = NULL, *c_me_msgid = NULL; - struct buf msubject = BUF_INITIALIZER; - strarray_t msgidlist = STRARRAY_INITIALIZER; - arrayu64_t matchlist = ARRAYU64_INITIALIZER; - arrayu64_t emptylist = ARRAYU64_INITIALIZER; - arrayu64_t cids = ARRAYU64_INITIALIZER; - conversation_t *conv = NULL; - const char *msubj = NULL; - int i; - int j; - int r = 0; - char *msgid = NULL; - - /* - * Gather all the msgids mentioned in the message, starting with - * the oldest message in the References: header, then any mesgids - * mentioned in the In-Reply-To: header, and finally the message's - * own Message-Id:. In general this will result in duplicates (a - * correct References: header will contain as its last entry the - * msgid in In-Reply-To:), so we weed those out before proceeding - * to the database. - */ - if (cache_len(record)) { - /* we have cache loaded, get what we need there */ - strarray_t want = STRARRAY_INITIALIZER; - char *envtokensNUMENVTOKENS; - - /* get References from cached headers */ - c_refs = xstrndup(cacheitem_base(record, CACHE_HEADERS), - cacheitem_size(record, CACHE_HEADERS)); - strarray_append(&want, "references"); - message_pruneheader(c_refs, &want, 0); - hdrs0 = c_refs; - - /* get In-Reply-To, Message-ID out of the envelope - * - * get a working copy; strip outer ()'s - * +1 -> skip the leading paren - * -2 -> don't include the size of the outer parens - */ - c_env = xstrndup(cacheitem_base(record, CACHE_ENVELOPE) + 1, - cacheitem_size(record, CACHE_ENVELOPE) - 2); - parse_cached_envelope(c_env, envtokens, NUMENVTOKENS); - hdrs1 = envtokensENV_INREPLYTO; - hdrs2 = envtokensENV_MSGID; - - /* get X-ME-Message-ID from cached headers */ - c_me_msgid = xstrndup(cacheitem_base(record, CACHE_HEADERS), - cacheitem_size(record, CACHE_HEADERS)); - strarray_set(&want, 0, "x-me-message-id"); - message_pruneheader(c_me_msgid, &want, 0); - hdrs3 = c_me_msgid; - - strarray_fini(&want); - - message1_get_subject(record, &msubject); - - /* work around stupid message_guid API */ - message_guid_isnull(&record->guid); - } - else { - /* nope, now we're screwed */ - return IMAP_INTERNAL; - } - - if (!is_valid_rfc2822_inreplyto(hdrs1)) - hdrs1 = NULL; - - /* Note that a NULL subject, e.g. due to a missing Subject: header - * field in the original message, is normalised to "" not NULL */ - conversation_normalise_subject(&msubject); - msubj = buf_cstring(&msubject); - - for (i = 0 ; i < 4 ; i++) { - while ((msgid = find_msgid(hdrsi, &hdrsi)) != NULL) { - /* - * The issue of case sensitivity of msgids is curious. - * RFC2822 seems to imply they're case-insensitive, - * without explicitly stating so. So here we punt - * on that being the case. - * - * Note that the THREAD command elsewhere in Cyrus - * assumes otherwise. - */ - msgid = lcase(msgid); - - /* already seen this one? */ - if (strarray_find(&msgidlist, msgid, 0) >= 0) { - free(msgid); - continue; - } - - strarray_appendm(&msgidlist, msgid); - - /* Lookup the conversations database to work out which - * conversation ids that message belongs to. */ - r = conversations_get_msgid(state, msgid, &cids); - if (r) goto out; - - for (j = 0; j < cids.count; j++) { - conversation_id_t cid = arrayu64_nth(&cids, j); - r = conversation_load(state, cid, &conv); - if (r) goto out; - if (!conv) - arrayu64_add(&emptylist, cid); - /* IRIS-1576 if X-ME-Message-ID says the messages are - * linked, ignore any difference in Subject: header fields. */ - else if (i == 3 || !strcmpsafe(conv->subject, msubj)) - arrayu64_add(&matchlist, cid); - } - - conversation_free(conv); - conv = NULL; - } - } - - if (!record->silent) { - conversation_id_t newcid = record->cid; - conversation_id_t val; - - /* Work out the new CID this message will belong to. - * Use the MAX of any CIDs found - as NULLCONVERSATION is - * numerically zero this will be the only non-NULL CID or - * the MAX of two or more non-NULL CIDs */ - val = arrayu64_max(&matchlist); - if (val > newcid) newcid = val; - - val = arrayu64_max(&emptylist); - if (val > newcid) newcid = val; - - if (!newcid) - newcid = generate_conversation_id(record); - - /* Mark any CID renames */ - for (i = 0 ; i < matchlist.count ; i++) - conversations_rename_cid(state, arrayu64_nth(&matchlist, i), newcid); - - if (record->cid) conversations_rename_cid(state, record->cid, newcid); - else record->cid = newcid; - } - - if (!record->cid) goto out; - - /* for CIDs with no 'B' record (hence, no existing members) it is - * safe to rename all the entries for that CID to the higher numbered - * cid. In both master and replica cases, this will execute once the - * last message has its CID renamed */ - for (i = 0; i < emptylist.count; i++) { - conversation_id_t item = arrayu64_nth(&emptylist, i); - conversations_rename_cidentry(state, item, record->cid); - } - - r = conversation_load(state, record->cid, &conv); - if (r) goto out; - - if (!conv) conv = conversation_new(state); - - /* Create the subject header if not already set */ - if (!conv->subject) - conv->subject = xstrdupnull(msubj); - - /* - * Update the database to add records for all the message-ids - * not already mentioned. Note that add_msgid does the right - * thingtm when the cid already exists. - */ - for (i = 0 ; i < msgidlist.count ; i++) { - r = conversations_add_msgid(state, strarray_nth(&msgidlist, i), record->cid); - if (r) goto out; - } - -out: - free(msgid); - strarray_fini(&msgidlist); - arrayu64_fini(&matchlist); - arrayu64_fini(&emptylist); - arrayu64_fini(&cids); - free(c_refs); - free(c_env); - free(c_me_msgid); - buf_free(&msubject); - - if (r) - conversation_free(conv); - else if (convp) - *convp = conv; - else { - r = conversation_save(state, record->cid, conv); - conversation_free(conv); - } - - return r; -} - - -/* - Format of the CACHE_SECTION cache item is a binary encoding - tree of MIME sections. In something like rpcgen notation - (see RFC 4506): - - struct part { - uint32_t header_offset; - uint32_t header_size; - uint32_t content_offset; - uint32_t content_size; - uint16_t charset; - uint16_t encoding; - }; - - struct section { - unsigned int numparts; - struct part partsnumparts; - struct sectionnumparts-1; - }; -*/ - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -EXPORTED message_t *message_new(void) -{ - message_t *m = xzmalloc(sizeof(*m)); - - m->refcount = 1; - - return m; -} - -static void message_free(message_t *m) -{ - assert(m->refcount == 0); - - message_yield(m, M_ALL); - - free(m->filename); - m->filename = NULL; - - free(m); -} - -EXPORTED message_t *message_new_from_data(const char *base, size_t len) -{ - message_t *m = message_new(); - buf_init_ro(&m->map, base, len); - m->have = m->given = M_MAP; - return m; -} - -EXPORTED message_t *message_new_from_mailbox(struct mailbox *mailbox, unsigned int recno) -{ - message_t *m = message_new(); - m->mailbox = mailbox; - m->record.recno = recno; - m->have = m->given = M_MAILBOX; - return m; -} - -EXPORTED message_t *message_new_from_record(struct mailbox *mailbox, - const struct index_record *record) -{ - message_t *m = message_new(); - assert(record->uid > 0); - m->mailbox = mailbox; - m->record = *record; - m->have = m->given = M_MAILBOX|M_RECORD|M_UID; - return m; -} - -EXPORTED message_t *message_new_from_index(struct mailbox *mailbox, - const struct index_record *record, - uint32_t msgno, - uint32_t indexflags) -{ - message_t *m = message_new(); - assert(record->uid > 0); - m->mailbox = mailbox; - m->record = *record; - m->msgno = msgno; - m->indexflags = indexflags; - m->have = m->given = M_MAILBOX|M_RECORD|M_UID|M_INDEX; - return m; -} - -EXPORTED message_t *message_new_from_filename(const char *filename) -{ - message_t *m = message_new(); - m->filename = xstrdup(filename); - m->have = m->given = M_FILENAME; - return m; -} - -EXPORTED message_t *message_ref(message_t *m) -{ - m->refcount++; - assert(m->refcount >= 1); - return m; -} - -EXPORTED void message_unref(message_t **mp) -{ - message_t *m; - - if (!mp || !(m = *mp)) return; - assert(m->refcount >= 1); - if (--m->refcount == 0) - message_free(m); - *mp = NULL; -} - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -/* - * Open or create resources which we need but do not yet have. - */ -static int message_need(message_t *m, unsigned int need) -{ -#define is_missing(flags) ((need & ~(m->have)) & (flags)) -#define found(flags) (m->have |= (flags)) - int r = 0; - - if (!is_missing(M_ALL)) - return 0; /* easy, we already have it */ - - if (is_missing(M_MAILBOX)) { - /* We can't get this for ourselves, - * it needs to be passed in by the caller */ - return IMAP_NOTFOUND; - } - - if (is_missing(M_FILENAME)) { - const char *filename; - r = message_need(m, M_MAILBOX|M_RECORD); - if (r) return r; - filename = mailbox_record_fname(m->mailbox, &m->record); - if (!filename) return IMAP_NOTFOUND; - m->filename = xstrdup(filename); - found(M_FILENAME); - } - - if (is_missing(M_RECORD|M_UID)) { - r = message_need(m, M_MAILBOX); - if (r) return r; - r = mailbox_read_index_record(m->mailbox, m->record.recno, &m->record); - if (r) return r; - found(M_RECORD|M_UID); - } - - if (is_missing(M_MAP)) { - r = message_need(m, M_FILENAME); - if (r) return r; - r = message_map_file(m, m->filename); - if (r) return r; - found(M_MAP); - } - - if (is_missing(M_CACHE)) { - r = message_need(m, M_MAILBOX|M_RECORD); - if (r) return r; - r = mailbox_cacherecord(m->mailbox, &m->record); - if (r) return r; - found(M_CACHE); - } - - if (is_missing(M_CACHEBODY)) { - if (message_need(m, M_CACHE) == 0) { - r = message_parse_cbodystructure(m); - if (r) return r; - found(M_CACHEBODY); - } - else - return message_need(m, M_FULLBODY); - } - - if (is_missing(M_FULLBODY)) { - r = message_need(m, M_MAP); - if (r) return r; - m->body = (struct body *)xzmalloc(sizeof(struct body)); - r = message_parse_mapped(m->map.s, m->map.len, m->body); - if (r) return r; - found(M_CACHEBODY|M_FULLBODY); - } - - /* Check that we got everything we asked for and could get */ - assert(!is_missing(M_ALL)); - - return 0; -#undef found -#undef is_missing -} - -/* - * Yield open resources. - */ -static void message_yield(message_t *m, unsigned int yield) -{ - /* Can only yield those resources we have. */ - yield &= m->have; - - /* Do not yield resources we were given at initialisation - * time, they cannot be rebuilt again later. */ - yield &= ~m->given; - - /* nothing to free for these - they're not constructed - * or have no dynamically allocated memory */ - yield &= ~(M_MAILBOX|M_RECORD|M_FILENAME|M_UID|M_CACHE); - - if ((yield & M_MAP)) { - buf_free(&m->map); - m->have &= ~M_MAP; - } - - if ((yield & M_BODY)) { - message_free_body(m->body); - free(m->body); - m->body = NULL; - m->have &= ~M_BODY; - } - - /* Check we yielded everything we could */ - assert((yield & m->have) == 0); -} - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -/* - * Parse various information out of the cyrus.cache. - */ - -/* - * Skip either a single NIL or a balanced possibly-nested list of - * nstrings. Useful for ignoring various constructs from the - * BODYSTRUCTURE cache. - */ -static int skip_nil_or_nstring_list(struct protstream *prot) -{ - int r = IMAP_MAILBOX_BADFORMAT; - int c; - struct buf word = BUF_INITIALIZER; - - c = prot_getc(prot); - if (c == EOF) - goto out; /* ran out of data */ - if (c == '(') { - /* possibly-nested list of atoms */ - int treedepth = 1; - do { - c = prot_getc(prot); - if (c == ' ') - c = prot_getc(prot); - if (c != ')' && c != '(') { - prot_ungetc(c, prot); - c = getnstring(prot, NULL, &word); -#if DEBUG - if (word.len) - fprintf(stderr, "%sskipping string \"%s\" at %d\n", - indent(depth), word.s, treedepth); -#endif - } - if (c == '(') - treedepth++; - else if (c == ')') - treedepth--; - else if (c == ' ') - prot_ungetc(c, prot); - else - goto out; - } while (treedepth); - c = prot_getc(prot); - if (c != ' ') goto out; - r = 0; - } - else { - prot_ungetc(c, prot); - c = getnstring(prot, NULL, &word); - if (c == ' ' && !word.len) { - /* 'NIL' */ -#if DEBUG - fprintf(stderr, "%sskipping NIL\n", indent(depth)); -#endif - r = 0; - goto out; - } - } - /* else, error */ - -out: - buf_free(&word); - return r; -} - -static int parse_mime_params(struct protstream *prot, struct param **prev) -{ - int c; - struct buf key = BUF_INITIALIZER; - struct buf val = BUF_INITIALIZER; - struct param *param; - - c = prot_getc(prot); - if (c != '(') { - /* must be NIL */ - if (c != 'N') goto err; - c = prot_getc(prot); - if (c != 'I') goto err; - c = prot_getc(prot); - if (c != 'L') goto err; - return prot_getc(prot); - } - - /* otherwise we have a list */ - do { - c = getnstring(prot, NULL, &key); - if (c != ' ') goto err; - c = getnstring(prot, NULL, &val); - if (c != ' ' && c != ')') goto err; - param = (struct param *)xzmalloc(sizeof(struct param)); - param->attribute = buf_releasenull(&key); - param->value = buf_releasenull(&val); - *prev = param; - prev = ¶m; - } while (c == ' '); - - return prot_getc(prot); - -err: - buf_free(&key); - buf_free(&val); - return EOF; -} - -static int parse_bodystructure_part(struct protstream *prot, struct body *body) -{ - int c; - int r = 0; - struct buf buf = BUF_INITIALIZER; - - memset(body, 0, sizeof(struct body)); - - c = prot_getc(prot); - if (c != '(') { -badformat: - r = IMAP_MAILBOX_BADFORMAT; - goto out; - } - - c = prot_getc(prot); - prot_ungetc(c, prot); - if (c == '(') { - while (c == '(') { - body->numparts++; - body->subpart = (struct body *)xrealloc((char *)body->subpart, - body->numparts*sizeof(struct body)); - - r = parse_bodystructure_part(prot, &body->subpartbody->numparts-1); - if (r) goto out; - - c = prot_getc(prot); - prot_ungetc(c, prot); - } - - c = prot_getc(prot); - if (c != ' ') goto badformat; - - body->type = xstrdup("MULTIPART"); - } - else { - /* parse mime-type */ - c = getnstring(prot, NULL, &buf); - if (c != ' ') goto badformat; - - body->type = buf_releasenull(&buf); - } - - /* parse mime-subtype */ - c = getnstring(prot, NULL, &buf); - if (c != ' ') goto badformat; - body->subtype = buf_releasenull(&buf); - - /* parse mime-params */ - c = parse_mime_params(prot, &body->params); - if (c != ' ') goto badformat; - - if (strcmp(body->type, "MULTIPART")) { - /* msgid */ - c = getnstring(prot, NULL, &buf); - if (c != ' ') goto badformat; - body->message_id = buf_releasenull(&buf); - - /* description */ - c = getnstring(prot, NULL, &buf); - if (c != ' ') goto badformat; - body->description = buf_releasenull(&buf); - - /* encoding */ - c = getnstring(prot, NULL, &buf); - if (c != ' ') goto badformat; - body->encoding = buf_releasenull(&buf); - - /* content-size */ - c = getword(prot, &buf); - if (c != ' ') goto badformat; - body->content_size = atoi(buf_cstring(&buf)); - - if (!strcmpsafe(body->type, "TEXT")) { - /* parse content-lines */ - c = getword(prot, &buf); - if (c != ' ') goto badformat; - body->content_lines = atoi(buf_cstring(&buf)); - } - - else if (!strcmpsafe(body->type, "MESSAGE") && - !strcmpsafe(body->subtype, "RFC822")) { - body->numparts = 1; - body->subpart = xzmalloc(sizeof(struct body)); - - /* skip envelope */ - r = skip_nil_or_nstring_list(prot); - if (r) goto out; - - /* process body */ - r = parse_bodystructure_part(prot, body->subpart); - if (r) goto out; - - /* skip trailing space (parse_bs_part doesn't eat it) */ - c = prot_getc(prot); - if (c != ' ') goto badformat; - - /* parse content-lines */ - c = getword(prot, &buf); - if (c != ' ') goto badformat; - body->content_lines = atoi(buf_cstring(&buf)); - } - - /* parse md5sum */ - c = getnstring(prot, NULL, &buf); - if (c != ' ') goto badformat; - body->md5 = buf_releasenull(&buf); - } - - /* skips disposition-and-params */ - r = skip_nil_or_nstring_list(prot); - if (r) goto out; - - /* parse languages */ /* TODO */ - r = skip_nil_or_nstring_list(prot); - if (r) goto out; - - /* location */ - c = getnstring(prot, NULL, &buf); - if (c != ')') goto badformat; /* final field */ - body->location = buf_releasenull(&buf); - - r = 0; -out: - buf_free(&buf); - return r; -} - -static int parse_bodystructure_sections(const char **cachestrp, const char *cacheend, struct body *body) -{ - struct body *this; - int nsubparts; - int part; - - if (*cachestrp + 4 > cacheend) - return IMAP_MAILBOX_BADFORMAT; - - nsubparts = CACHE_ITEM_BIT32(*cachestrp); - *cachestrp += 4; - - if (*cachestrp + 4*5*nsubparts > cacheend) - return IMAP_MAILBOX_BADFORMAT; - - if (strcmp(body->type, "MESSAGE") == 0 - && strcmp(body->subtype, "RFC822") == 0) { - if (strcmp(body->subpart->type, "MULTIPART") == 0) { - /* - * Part 0 of a message/rfc822 is the message header/text. - * Nested parts of a message/rfc822 containing a multipart - * are the sub-parts of the multipart. - */ - if (body->subpart->numparts + 1 != nsubparts) - return IMAP_MAILBOX_BADFORMAT; - - body->subpart->header_offset = CACHE_ITEM_BIT32(*cachestrp+0*4); - body->subpart->header_size = CACHE_ITEM_BIT32(*cachestrp+1*4); - body->subpart->content_offset = CACHE_ITEM_BIT32(*cachestrp+2*4); - body->subpart->content_size = CACHE_ITEM_BIT32(*cachestrp+3*4); - *cachestrp += 5*4; - - for (part = 0; part < body->subpart->numparts; part++) { - this = &body->subpart->subpartpart; - this->header_offset = CACHE_ITEM_BIT32(*cachestrp+0*4); - this->header_size = CACHE_ITEM_BIT32(*cachestrp+1*4); - this->content_offset = CACHE_ITEM_BIT32(*cachestrp+2*4); - this->content_size = CACHE_ITEM_BIT32(*cachestrp+3*4); - *cachestrp += 5*4; - } - - /* and parse subparts */ - for (part = 0; part < body->subpart->numparts; part++) { - this = &body->subpart->subpartpart; - if (parse_bodystructure_sections(cachestrp, cacheend, this)) - return IMAP_MAILBOX_BADFORMAT; - } - } - else { - /* - * Part 0 of a message/rfc822 is the message header/text. - * Part 1 of a message/rfc822 containing a non-multipart - * is the message body. - */ - - if (2 != nsubparts) - return IMAP_MAILBOX_BADFORMAT; - - /* data is the same in body, just grab the first one */ - body->subpart->header_offset = CACHE_ITEM_BIT32(*cachestrp+0*4); - body->subpart->header_size = CACHE_ITEM_BIT32(*cachestrp+1*4); - body->subpart->content_offset = CACHE_ITEM_BIT32(*cachestrp+2*4); - body->subpart->content_size = CACHE_ITEM_BIT32(*cachestrp+3*4); - *cachestrp += 10*4; - - /* and parse subpart */ - if (parse_bodystructure_sections(cachestrp, cacheend, body->subpart)) - return IMAP_MAILBOX_BADFORMAT; - } - } - else if (body->numparts) { - /* - * Cannot fetch part 0 of a multipart. - * Nested parts of a multipart are the sub-parts. - */ - if (body->numparts + 1 != nsubparts) - return IMAP_MAILBOX_BADFORMAT; - *cachestrp += 5*4; - for (part = 0; part < body->numparts; part++) { - this = &body->subpartpart; - this->header_offset = CACHE_ITEM_BIT32(*cachestrp+0*4); - this->header_size = CACHE_ITEM_BIT32(*cachestrp+1*4); - this->content_offset = CACHE_ITEM_BIT32(*cachestrp+2*4); - this->content_size = CACHE_ITEM_BIT32(*cachestrp+3*4); - *cachestrp += 5*4; - } - - for (part = 0; part < body->numparts; part++) { - this = &body->subpartpart; - if (parse_bodystructure_sections(cachestrp, cacheend, this)) - return IMAP_MAILBOX_BADFORMAT; - } - } - else { - /* - * Leaf section--no part 0 or nested parts - */ - if (nsubparts != 0) - return IMAP_MAILBOX_BADFORMAT; - } - - return 0; -} - -static int message_parse_cbodystructure(message_t *m) -{ - struct protstream *prot = NULL; - const char *cachestr = cacheitem_base(&m->record, CACHE_SECTION); - const char *cacheend = cachestr + cacheitem_size(&m->record, CACHE_SECTION); - struct body toplevel; - int r; - - /* We're reading the cache - double check we have it */ - assert(m->have & M_CACHE); - - prot = prot_readmap(cacheitem_base(&m->record, CACHE_BODYSTRUCTURE), - cacheitem_size(&m->record, CACHE_BODYSTRUCTURE)); - if (!prot) - return IMAP_MAILBOX_BADFORMAT; - prot_setisclient(prot, 1); /* don't crash parsing literals */ - - m->body = xzmalloc(sizeof(struct body)); - r = parse_bodystructure_part(prot, m->body); - if (r) syslog(LOG_ERR, "IOERROR: parsing body structure for %s %u (%.*s)", - m->mailbox->name, m->record.uid, - (int)cacheitem_size(&m->record, CACHE_BODYSTRUCTURE), - cacheitem_base(&m->record, CACHE_BODYSTRUCTURE)); - if (r) goto done; - - memset(&toplevel, 0, sizeof(struct body)); - toplevel.type = "MESSAGE"; - toplevel.subtype = "RFC822"; - toplevel.subpart = m->body; - - r = parse_bodystructure_sections(&cachestr, cacheend, &toplevel); - if (r) syslog(LOG_ERR, "IOERROR: parsing section structure for %s %u (%.*s)", - m->mailbox->name, m->record.uid, - (int)cacheitem_size(&m->record, CACHE_BODYSTRUCTURE), - cacheitem_base(&m->record, CACHE_BODYSTRUCTURE)); - -done: - prot_free(prot); - - return r; -} - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -static int message_map_file(message_t *m, const char *fname) -{ - int fd; - struct stat sbuf; - - fd = open(fname, O_RDONLY, 0666); - if (fd == -1) return errno; - - if (fstat(fd, &sbuf) == -1) { - syslog(LOG_ERR, "IOERROR: fstat on %s: %m", fname); - fatal("can't fstat message file", EC_OSFILE); - } - if (!S_ISREG(sbuf.st_mode)) { - close(fd); - return EINVAL; - } - buf_free(&m->map); - buf_init_mmap(&m->map, /*onceonly*/1, fd, fname, sbuf.st_size, - m->mailbox ? m->mailbox->name : NULL); - close(fd); - - return 0; -} - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -static void body_get_leaf_types(struct body *body, strarray_t *types) -{ - int i; - - if (strcmpsafe(body->type, "MULTIPART") && - strcmpsafe(body->type, "MESSAGE")) { - strarray_append(types, body->type); - strarray_append(types, body->subtype); - } - - for (i = 0; i < body->numparts; i++) { - body_get_leaf_types(&body->subparti, types); - } -} - -static int body_foreach_text_section(struct body *body, - struct message *message, - int (*proc)(int isbody, int charset, int encoding, - const char *subtype, struct buf *data, void *rock), - void *rock) -{ - struct buf data = BUF_INITIALIZER; - int i; - int r; - - if (body->header_size) { - buf_init_ro(&data, message->map.s + body->header_offset, body->header_size); - r = proc(/*isbody*/0, 0, 0, NULL, &data, rock); - buf_free(&data); - - if (r) return r; - } - - if (!strcmpsafe(body->type, "TEXT")) { - int encoding, charset; - message_parse_charset(body, &encoding, &charset); - - buf_init_ro(&data, message->map.s + body->content_offset, body->content_size); - r = proc(/*isbody*/1, charset, encoding, - body->subtype, &data, rock); - buf_free(&data); - - if (r) return r; - } - - for (i = 0; i < body->numparts; i++) { - r = body_foreach_text_section(&body->subparti, message, proc, rock); - if (r) return r; - } - - return 0; -} - - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -/* - * Iterate 'proc' over all the MIME header sections and body sections of - * type TEXT, in the message 'm', preorder. The 'proc' is called with - * 'partno' equal to zero for header sections, non-zero for body - * sections. If 'proc' returns non-zero, the iteration finishes early - * and the return value of 'proc' is returned. Otherwise returns 0. - */ -EXPORTED int message_foreach_text_section(message_t *m, - int (*proc)(int isbody, int charset, int encoding, - const char *subtype, struct buf *data, void *rock), - void *rock) -{ - int r = message_need(m, M_CACHEBODY|M_MAP); - if (r) return r; - return body_foreach_text_section(m->body, m, proc, rock); -} - -/* - * Get the MIME content types of all leaf sections, i.e. sections whose - * type is not multipart or message. Strings are added to the array in - * pairs, type first then subtype. - */ -EXPORTED int message_get_leaf_types(message_t *m, strarray_t *types) -{ - int r = message_need(m, M_CACHEBODY); - if (r) return r; - body_get_leaf_types(m->body, types); - return 0; -} - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -EXPORTED int message_get_bcc(message_t *m, struct buf *buf) -{ - return message_get_field(m, "bcc", MESSAGE_RAW, buf); -} - -EXPORTED int message_get_cc(message_t *m, struct buf *buf) -{ - return message_get_field(m, "cc", MESSAGE_RAW, buf); -} - -EXPORTED int message_get_to(message_t *m, struct buf *buf) -{ - return message_get_field(m, "to", MESSAGE_RAW, buf); -} - -EXPORTED int message_get_from(message_t *m, struct buf *buf) -{ - return message_get_field(m, "from", MESSAGE_RAW, buf); -} - -EXPORTED int message_get_listid(message_t *m, struct buf *buf) -{ - return message_get_field(m, "list-id", MESSAGE_RAW, buf); -} - -EXPORTED int message_get_messageid(message_t *m, struct buf *buf) -{ - return message_get_field(m, "message-id", MESSAGE_RAW, buf); -} - -EXPORTED int message_get_subject(message_t *m, struct buf *buf) -{ - return message_get_field(m, "subject", MESSAGE_RAW, buf); -} - -EXPORTED int message_get_mailinglist(message_t *m, struct buf *buf) -{ - return message_get_field(m, "mailing-list", MESSAGE_RAW, buf); -} - -EXPORTED int message_get_size(message_t *m, uint32_t *sizep) -{ - int r = message_need(m, M_RECORD); - if (r) return r; - *sizep = m->record.size; - return 0; -} - -EXPORTED int message_get_uid(message_t *m, uint32_t *uidp) -{ - int r = message_need(m, M_RECORD); - if (r) return r; - *uidp = m->record.uid; - return 0; -} - -EXPORTED int message_get_cid(message_t *m, conversation_id_t *cidp) -{ - int r = message_need(m, M_RECORD); - if (r) return r; - *cidp = m->record.cid; - return 0; -} - -EXPORTED int message_get_modseq(message_t *m, modseq_t *modseqp) -{ - int r = message_need(m, M_RECORD); - if (r) return r; - *modseqp = m->record.modseq; - return 0; -} - -EXPORTED int message_get_msgno(message_t *m, uint32_t *msgnop) -{ - int r = message_need(m, M_INDEX); - if (r) return r; - *msgnop = m->msgno; - return 0; -} - -EXPORTED int message_get_userflags(message_t *m, uint32_t *flagsp) -{ - int r = message_need(m, M_RECORD); - int i; - if (r) return r; - for (i = 0; i < MAX_USER_FLAGS/32; i++) - flagspi = m->record.user_flagsi; - return 0; -} - -EXPORTED int message_get_systemflags(message_t *m, uint32_t *flagsp) -{ - int r = message_need(m, M_RECORD); - if (r) return r; - *flagsp = m->record.system_flags; - return 0; -} - -EXPORTED int message_get_indexflags(message_t *m, uint32_t *flagsp) -{ - int r = message_need(m, M_INDEX); - if (r) return r; - *flagsp = m->indexflags; - return 0; -} - -EXPORTED int message_get_sentdate(message_t *m, time_t *datep) -{ - int r = message_need(m, M_RECORD); - if (r) return r; - *datep = m->record.sentdate; - return 0; -} - -EXPORTED int message_get_internaldate(message_t *m, time_t *datep) -{ - int r = message_need(m, M_RECORD); - if (r) return r; - *datep = m->record.internaldate; - return 0; -} - -EXPORTED int message_get_fname(message_t *m, const char **fnamep) -{ - int r = message_need(m, M_FILENAME); - if (r) return r; - *fnamep = m->filename; - return 0; -} - -static void extract_one(struct buf *buf, - const char *name, - int format, - int has_name, - struct buf *raw) -{ - char *p = NULL; - - if (has_name && !(format & MESSAGE_FIELDNAME)) { - /* remove the fieldname and colon */ - int pos = buf_findchar(raw, 0, ':'); - assert(pos > 0); - buf_remove(raw, 0, pos+1); - } - else if (!has_name && (format & MESSAGE_FIELDNAME)) { - /* insert a fieldname and colon */ - buf_insertcstr(raw, 0, ":"); - buf_insertcstr(raw, 0, name); - } - - if (!(format & MESSAGE_APPEND)) - buf_reset(buf); - - switch (format & _MESSAGE_FORMAT_MASK) { - case MESSAGE_RAW: - /* Logically, we're appending to the resulting buffer. - * However if the buf is empty we can save a memory copy - * by setting it up as a CoW buffer. This means that - * the caller will need to call buf_cstring() if they - * need a C string. */ - if (!raw->alloc) - buf_cowappendmap(buf, raw->s, raw->len); - else - buf_append(buf, raw); - break; - case MESSAGE_DECODED: - p = charset_parse_mimeheader(buf_cstring(raw)); - buf_appendcstr(buf, p); - break; - case MESSAGE_SNIPPET: - p = charset_decode_mimeheader(buf_cstring(raw), CHARSET_SNIPPET); - buf_appendcstr(buf, p); - break; - case MESSAGE_SEARCH: - /* TODO: need a variant of decode_mimeheader() which - * takes two struct buf* and a search flag */ - p = charset_decode_mimeheader(buf_cstring(raw), charset_flags); - buf_appendcstr(buf, p); - break; - } - - free(p); -} - -EXPORTED int message_get_field(message_t *m, const char *hdr, int flags, struct buf *buf) -{ - strarray_t want = STRARRAY_INITIALIZER; - struct buf raw = BUF_INITIALIZER; - int hasname = 1; - - /* the 5 standalone cache fields */ - if (!strcasecmp(hdr, "from")) { - int r = message_need(m, M_CACHE); - if (r) return r; - buf_setmap(&raw, cacheitem_base(&m->record, CACHE_FROM), - cacheitem_size(&m->record, CACHE_FROM)); - if (raw.len == 3 && raw.s0 == 'N' && raw.s1 == 'I' && raw.s2 == 'L') - buf_reset(&raw); - hasname = 0; - } - else if (!strcasecmp(hdr, "to")) { - int r = message_need(m, M_CACHE); - if (r) return r; - buf_setmap(&raw, cacheitem_base(&m->record, CACHE_TO), - cacheitem_size(&m->record, CACHE_TO)); - if (raw.len == 3 && raw.s0 == 'N' && raw.s1 == 'I' && raw.s2 == 'L') - buf_reset(&raw); - hasname = 0; - } - else if (!strcasecmp(hdr, "cc")) { - int r = message_need(m, M_CACHE); - if (r) return r; - buf_setmap(&raw, cacheitem_base(&m->record, CACHE_CC), - cacheitem_size(&m->record, CACHE_CC)); - if (raw.len == 3 && raw.s0 == 'N' && raw.s1 == 'I' && raw.s2 == 'L') - buf_reset(&raw); - hasname = 0; - } - else if (!strcasecmp(hdr, "bcc")) { - int r = message_need(m, M_CACHE); - if (r) return r; - buf_setmap(&raw, cacheitem_base(&m->record, CACHE_BCC), - cacheitem_size(&m->record, CACHE_BCC)); - if (raw.len == 3 && raw.s0 == 'N' && raw.s1 == 'I' && raw.s2 == 'L') - buf_reset(&raw); - hasname = 0; - } - else if (!strcasecmp(hdr, "subject")) { - int r = message_need(m, M_CACHE); - if (r) return r; - buf_setmap(&raw, cacheitem_base(&m->record, CACHE_SUBJECT), - cacheitem_size(&m->record, CACHE_SUBJECT)); - if (raw.len == 3 && raw.s0 == 'N' && raw.s1 == 'I' && raw.s2 == 'L') - buf_reset(&raw); - hasname = 0; - } - - /* message-id is from the envelope */ - else if (!strcasecmp(hdr, "message-id")) { - char *envtokensNUMENVTOKENS; - char *c_env; - int r = message_need(m, M_CACHE); - if (r) return r; - c_env = xstrndup(cacheitem_base(&m->record, CACHE_ENVELOPE) + 1, - cacheitem_size(&m->record, CACHE_ENVELOPE) - 2); - parse_cached_envelope(c_env, envtokens, NUMENVTOKENS); - if (envtokensENV_MSGID) - buf_appendcstr(&raw, envtokensENV_MSGID); - free(c_env); - if (raw.len == 3 && raw.s0 == 'N' && raw.s1 == 'I' && raw.s2 == 'L') - buf_reset(&raw); - hasname = 0; - } - - else if (mailbox_cached_header(hdr) != BIT32_MAX) { - /* it's in the cache */ - char *headers = NULL; - int r = message_need(m, M_CACHE); - if (r) return r; - headers = xstrndup(cacheitem_base(&m->record, CACHE_HEADERS), - cacheitem_size(&m->record, CACHE_HEADERS)); - strarray_append(&want, hdr); - message_pruneheader(headers, &want, NULL); - buf_appendcstr(&raw, headers); - free(headers); - hasname = 1; - } - else { - char *headers = NULL; - int r = message_need(m, M_MAP|M_CACHEBODY); - if (r) return r; - headers = xstrndup(m->map.s + m->body->header_offset, m->body->header_size); - strarray_append(&want, hdr); - message_pruneheader(headers, &want, NULL); - buf_appendcstr(&raw, headers); - free(headers); - hasname = 1; - } - - if (raw.len) - extract_one(buf, hdr, flags, hasname, &raw); - - buf_free(&raw); - strarray_fini(&want); - - return 0; -}
View file
cyrus-imapd-2.5.tar.gz/imap/message.h
Changed
@@ -55,7 +55,6 @@ #include "prot.h" #include "mailbox.h" -#include "strarray.h" #include "util.h" /* @@ -104,7 +103,6 @@ struct address *bcc; char *in_reply_to; char *message_id; - char *x_me_message_id; char *references; char *received_date; @@ -131,7 +129,9 @@ extern int message_copy_strict P((struct protstream *from, FILE *to, unsigned size, int allow_null)); -extern int message_parse(const char *fname, struct index_record *record); +extern int message_parse2(const char *fname, struct index_record *record, + struct body **bodyp); +#define message_parse(fname, record) message_parse2((fname), (record), NULL) struct message_content { const char *base; /* memory mapped file */ @@ -154,8 +154,6 @@ extern int message_parse_file P((FILE *infile, const char **msg_base, size_t *msg_len, struct body **body)); -extern void message_pruneheader(char *buf, const strarray_t *headers, - const strarray_t *headers_not); extern void message_fetch_part P((struct message_content *msg, const char **content_types, struct bodypart ***parts)); @@ -178,109 +176,4 @@ extern void message_read_bodystructure(struct index_record *record, struct body **body); -extern int message_update_conversations(struct conversations_state *, struct index_record *, conversation_t **); - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ -/* New message API */ - -typedef struct message message_t; -struct mailbox; - -/* Flags for use as the 'format' argument to - * message_get_field() and part_get_field(). */ -enum message_format -{ - /* Original raw octets from the on-the-wire RFC5322 format, - * including folding and RFC2047 encoding of non-ASCII characters. - * The result may point into a mapping and not be NUL-terminated, - * use buf_cstring() if necessary. */ - MESSAGE_RAW= 1, - /* Unfolded and RFC2047 decoded */ - MESSAGE_DECODED, - /* Unfolded, RFC2047 decoded, and HTML-escaped */ - MESSAGE_SNIPPET, - /* Unfolded, RFC2047 decoded, and search-normalised */ - MESSAGE_SEARCH, - -#define _MESSAGE_FORMAT_MASK (0x7) - - /* This flag can be OR'd into the format argument to request that - * the field name and a colon ':' are left in the result. Normally - * only the field value is returned. This is useful when calling - * multiple times with MESSAGE_APPEND, to accumulate multiple headers - * in the buffer. */ - MESSAGE_FIELDNAME= (1<<5), - - /* This flag can be OR'd into the format argument to request that - * all the fields of the given name are returned. Normally only - * the first is returned, which is faster. */ - MESSAGE_MULTIPLE= (1<<6), - - /* This flag can be OR'd into the format argument to request that - * results be appended to the buffer; normally the buffer is reset - * first. */ - MESSAGE_APPEND= (1<<7) -}; - -enum message_indexflags -{ - MESSAGE_SEEN= (1<<0), - MESSAGE_RECENT= (1<<1), -}; - -extern message_t *message_new(void); -extern message_t *message_new_from_data(const char *base, size_t len); -extern message_t *message_new_from_mailbox(struct mailbox *mailbox, - unsigned int recno); -extern message_t *message_new_from_record(struct mailbox *, - const struct index_record *); -extern message_t *message_new_from_index(struct mailbox *, - const struct index_record *, - uint32_t msgno, - uint32_t indexflags); -extern message_t *message_new_from_filename(const char *filename); - -extern message_t *message_ref(message_t *m); -extern void message_unref(message_t **m); - -extern int message_get_field(message_t *m, const char *name, - int format, struct buf *buf); -extern int message_get_header(message_t *m, int format, struct buf *buf); -extern int message_get_body(message_t *m, int format, struct buf *buf); -extern int message_get_type(message_t *m, const char **strp); -extern int message_get_subtype(message_t *m, const char **strp); -extern int message_get_charset(message_t *m, int *csp); -extern int message_get_encoding(message_t *m, int *encp); -extern int message_get_num_parts(message_t *m, unsigned int *np); -extern int message_get_messageid(message_t *m, struct buf *buf); -extern int message_get_listid(message_t *m, struct buf *buf); -extern int message_get_mailinglist(message_t *m, struct buf *buf); -extern int message_get_from(message_t *m, struct buf *buf); -extern int message_get_to(message_t *m, struct buf *buf); -extern int message_get_cc(message_t *m, struct buf *buf); -extern int message_get_bcc(message_t *m, struct buf *buf); -extern int message_get_inreplyto(message_t *m, struct buf *buf); -extern int message_get_references(message_t *m, struct buf *buf); -extern int message_get_subject(message_t *m, struct buf *buf); -extern int message_get_date(message_t *m, time_t *tp); -extern int message_get_mailbox(message_t *m, struct mailbox **); -extern int message_get_uid(message_t *m, uint32_t *uidp); -extern int message_get_cid(message_t *m, conversation_id_t *cidp); -extern int message_get_internaldate(message_t *m, time_t *); -extern int message_get_sentdate(message_t *m, time_t *); -extern int message_get_modseq(message_t *m, modseq_t *modseqp); -extern int message_get_systemflags(message_t *m, uint32_t *); -extern int message_get_userflags(message_t *m, uint32_t *flagsp); -extern int message_get_indexflags(message_t *m, uint32_t *); -extern int message_get_size(message_t *m, uint32_t *sizep); -extern int message_get_msgno(message_t *m, uint32_t *msgnop); -extern int message_get_fname(message_t *m, const char **fnamep); -extern int message_foreach_text_section(message_t *m, - int (*proc)(int isbody, int charset, int encoding, - const char *subtype, struct buf *data, void *rock), - void *rock); -extern int message_get_leaf_types(message_t *m, strarray_t *types); - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - #endif /* INCLUDED_MESSAGE_H */
View file
cyrus-imapd-2.5.tar.gz/imap/message_priv.h
Deleted
@@ -1,96 +0,0 @@ -/* message_priv.h -- private details of the message_t object - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * $Id: message.h,v 1.13 2010/01/06 17:01:37 murch Exp $ - */ - -#ifndef __CYRUS_MESSAGE_PRIV_H__ -#define __CYRUS_MESSAGE_PRIV_H__ - -#include <stdio.h> - -#include "message.h" -#include "mailbox.h" -#include "charset.h" -#include "util.h" - -/* - * Flags for the 'have' and 'given' bitmask fields. 'Given' is the - * resources we were initialised with by the caller, which are presumed - * to belong to the caller and will not be freed. 'Have' is the - * resources we have, including both those given us and those we created - * or opened ourselves. Resources are created or opened on demand, and - * can be shut down again to minimise resource usage, so we need to - * track the status of all these resources. - */ -#define M_MAILBOX (1<<0) /* an open mailbox* */ -#define M_FILENAME (1<<1) /* filename of a message on disk */ -#define M_RECORD (1<<2) /* a valid index_record */ -#define M_UID (1<<3) /* valid UID in index_record */ -#define M_MAP (1<<4) /* mmap()ed raw message data */ -#define M_CACHE (1<<5) /* mmap()ed cyrus.cache */ -#define M_CACHEBODY (1<<6) /* MIME header details from fields, or - * BODYSTRUCTURE from cyrus.cache */ -#define M_FULLBODY (1<<7) /* BODY parsed from the raw message */ -#define M_CHEADER (1<<8) /* header from cyrus.cache */ -#define M_CENVELOPE (1<<9) /* envelope from cyrus.cache */ -#define M_INDEX (1<<10) /* per-index bits: msgno & indexflags */ -#define M_ALL (~0U) /* everything */ - -#define M_BODY (M_CACHEBODY|M_FULLBODY) /* for yield masking */ - -struct message -{ - int refcount; - unsigned short have; - unsigned short given; - char *filename; - struct mailbox *mailbox; - unsigned int msgno; - uint32_t indexflags; - struct buf map; - struct body *body; - char **envelope; - struct index_record record; -}; - - -#endif /* __CYRUS_MESSAGE_PRIV_H__ */
View file
cyrus-imapd-2.5.tar.gz/imap/message_test.c
Deleted
@@ -1,321 +0,0 @@ -/* - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <stdlib.h> -#include <stdio.h> -#include <syslog.h> -#include <string.h> -#include <sys/stat.h> - -/* cyrus includes */ -#include "assert.h" -#include "bsearch.h" -#include "exitcodes.h" -#include "global.h" -#include "imap_err.h" -#include "index.h" -#include "search_engines.h" -#include "conversations.h" -#include "mailbox.h" -#include "mboxlist.h" -#include "message.h" -#include "sysexits.h" -#include "util.h" -#include "xmalloc.h" - -#if !HAVE___ATTRIBUTE__ -#define __attribute__(x) -#endif - -static int usage(const char *name); - -int verbose = 0; -enum { PART_TREE, TEXT_SECTIONS, TEXT_RECEIVER } dump_mode = PART_TREE; - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -static void dump_octets(FILE *fp, const char *base, unsigned int len) -{ - unsigned int i; - - while (len > 0) { - fputs(" ", fp); - for (i = 0 ; i < 16 && i < len ; i++) - fprintf(fp, "%02x ", ((unsigned char *)base)i); - for (; i < 16 ; i++) - fputs(" ", fp); - fputs(" ", fp); - for (i = 0 ; i < 16 && i < len ; i++) - fputc((isprint(basei) && !isspace(basei) ? basei : '.'), fp); - fputc('\n', fp); - - i = (len > 16 ? 16 : len); - len -= i; - base += i; - } -} - -static void dump_buf(FILE *fp, const struct buf *data) -{ -#define MAX_TEXT 512 - if (verbose || data->len <= MAX_TEXT) { - dump_octets(fp, data->s, data->len); - } - else { - dump_octets(fp, data->s, MAX_TEXT/2); - fputs(" ...\n", fp); - dump_octets(fp, data->s + data->len - MAX_TEXT/2, MAX_TEXT/2); - } -#undef MAX_TEXT -} - -static int dump_one_section(int partno, int charset, int encoding, - const char *subtype, struct buf *data, - void *rock __attribute__((unused))) -{ -#define MAX_TEXT 512 - printf("SECTION partno=%d length=%llu subtype=%s charset=%s encoding=%s\n", - partno, (unsigned long long)data->len, subtype, charset_name(charset), encoding_name(encoding)); - dump_buf(stdout, data); - return 0; -#undef MAX_TEXT -} - -static int dump_text_sections(message_t *message) -{ - return message_foreach_text_section(message, dump_one_section, NULL); -} - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -static int dump_message(message_t *message) -{ - return dump_text_sections(message); -} - -int main(int argc, char **argv) -{ - int c; - const char *alt_config = NULL; - const char *filename = NULL; - const char *mboxname = NULL; - int recno = 1; - int record_flag = 0; - int r = 0; - - if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { - fatal("must run as the Cyrus user", EC_USAGE); - } - - while ((c = getopt(argc, argv, "Rf:m:pr:stvC:")) != EOF) { - switch (c) { - - case 'f': - filename = optarg; - break; - - case 'm': - mboxname = optarg; - break; - - case 'p': - dump_mode = PART_TREE; - break; - - case 'r': - recno = atoi(optarg); - if (recno <= 0) - usage(argv0); - break; - - case 's': - dump_mode = TEXT_SECTIONS; - break; - - case 't': - dump_mode = TEXT_RECEIVER; - break; - - case 'v': - verbose++; - break; - - case 'C': /* alt config file */ - alt_config = optarg; - break; - - case 'R': - record_flag = 1; - break; - - default: - usage(argv0); - break; - } - } - - if (optind != argc) - usage(argv0); - if (mboxname && filename) - usage(argv0); - - cyrus_init(alt_config, "message_test", 0, CONFIG_NEED_PARTITION_DATA); - - mboxlist_init(0); - mboxlist_open(NULL); - - if (mboxname && record_flag) { - struct mailbox *mailbox = NULL; - struct index_record record; - message_t *message = NULL; - - r = mailbox_open_irl(mboxname, &mailbox); - if (r) { - fprintf(stderr, "Failed to open mailbox %s: %s\n", - mboxname, error_message(r)); - return 1; - } - - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) { - fprintf(stderr, "Failed to read index record %u of %s: %s\n", - recno, mboxname, error_message(r)); - return 1; - } - - message = message_new_from_record(mailbox, &record); - r = dump_message(message); - if (r) { - fprintf(stderr, "Error dumping message: %s\n", - error_message(r)); - return 1; - } - - message_unref(&message); - mailbox_close(&mailbox); - } - else if (mboxname) { - struct mailbox *mailbox = NULL; - message_t *message = NULL; - - r = mailbox_open_irl(mboxname, &mailbox); - if (r) { - fprintf(stderr, "Failed to open mailbox %s: %s\n", - mboxname, error_message(r)); - return 1; - } - - message = message_new_from_mailbox(mailbox, recno); - r = dump_message(message); - if (r) { - fprintf(stderr, "Error dumping message: %s\n", - error_message(r)); - return 1; - } - - message_unref(&message); - mailbox_close(&mailbox); - } - else if (filename) { - message_t *message = NULL; - - message = message_new_from_filename(filename); - r = dump_message(message); - if (r) { - fprintf(stderr, "Error dumping message: %s\n", - error_message(r)); - return 1; - } - - message_unref(&message); - } - else { - message_t *message = NULL; - int c; - struct buf buf = BUF_INITIALIZER; - - while ((c = fgetc(stdin)) != EOF) - buf_putc(&buf, c); - message = message_new_from_data(buf.s, buf.len); - dump_message(message); - if (r) { - fprintf(stderr, "Error dumping message: %s\n", - error_message(r)); - return 1; - } - - message_unref(&message); - buf_free(&buf); - } - - mboxlist_close(); - mboxlist_done(); - - cyrus_done(); - - return r; -} - -static int usage(const char *name) -{ - fprintf(stderr, "usage: %s format-options -m mailbox -r recno -R\n", name); - fprintf(stderr, " %s format-options -f filename\n", name); - fprintf(stderr, " %s format-options < message\n", name); - fprintf(stderr, "format-options :=\n"); - fprintf(stderr, "-p dump message part tree\n"); - fprintf(stderr, "-s dump text sections\n"); - fprintf(stderr, "-t dump output from search text receiver\n"); - exit(EC_USAGE); -} - -void fatal(const char* s, int code) -{ - fprintf(stderr, "message_test: %s\n", s); - cyrus_done(); - exit(code); -} - -
View file
cyrus-imapd-2.5.tar.gz/imap/nntpd.c
Changed
@@ -1885,9 +1885,8 @@ static void cmd_article(int part, char *msgid, unsigned long uid) { int msgno, by_msgid = (msgid != NULL); - const char *fname; + char *fname; FILE *msgfile; - message_t *msg; msgno = index_finduid(group_state, uid); if (!msgno || index_getuid(group_state, msgno) != uid) { @@ -1895,17 +1894,7 @@ return; } - msg = index_get_message(group_state, msgno); - - if (!msg) { - prot_printf(nntp_out, "502 Could not read index record\r\n"); - return; - } - - if (message_get_fname(msg, &fname)) { - prot_printf(nntp_out, "502 Could not read index record\r\n"); - return; - } + fname = mailbox_message_fname(group_state->mailbox, uid); msgfile = fopen(fname, "r"); if (!msgfile) {
View file
cyrus-imapd-2.5.tar.gz/imap/notify.c
Changed
@@ -77,94 +77,19 @@ return 0; } -static void notify_dlist(const char *sockpath, const char *method, - const char *class, const char *priority, - const char *user, const char *mailbox, - int nopt, const char **options, - const char *message, const char *fname) -{ - struct sockaddr_un sun_data; - struct protstream *in = NULL, *out = NULL; - struct dlist *dl = dlist_newkvlist(NULL, "NOTIFY"); - struct dlist *res = NULL; - struct dlist *il; - int c; - int soc = -1; - int i; - - dlist_setatom(dl, "METHOD", method); - dlist_setatom(dl, "CLASS", class); - dlist_setatom(dl, "PRIORITY", priority); - dlist_setatom(dl, "USER", user); - dlist_setatom(dl, "MAILBOX", mailbox); - il = dlist_newlist(dl, "OPTIONS"); - for (i = 0; i < nopt; i++) - dlist_setatom(il, NULL, optionsi); - dlist_setatom(dl, "MESSAGE", message); - dlist_setatom(dl, "FILEPATH", fname); - - memset((char *)&sun_data, 0, sizeof(sun_data)); - sun_data.sun_family = AF_UNIX; - strlcpy(sun_data.sun_path, sockpath, sizeof(sun_data.sun_path)); - - soc = socket(PF_UNIX, SOCK_STREAM, 0); - if (soc < 0) { - syslog(LOG_ERR, "unable to create notify socket(): %m"); - goto out; - } - - if (connect(soc, (struct sockaddr *)&sun_data, sizeof(sun_data)) < 0) { - syslog(LOG_ERR, "failed to connect to %s: %m", sockpath); - goto out; - } - - in = prot_new(soc, 0); - out = prot_new(soc, 1); - /* Force use of LITERAL+ */ - prot_setisclient(in, 1); - prot_setisclient(out, 1); - - dlist_print(dl, 1, out); - prot_printf(out, "\r\n"); - prot_flush(out); - - c = dlist_parse(&res, 1, in); - if (c == '\r') c = prot_getc(in); - /* XXX - do something with the response? Like have NOTIFY answer */ - if (c == '\n' && res && res->name) { - syslog(LOG_NOTICE, "NOTIFY: response %s to method %s", res->name, method); - } - else { - syslog(LOG_ERR, "NOTIFY: error sending %s to %s", method, sockpath); - } - -out: - if (in) prot_free(in); - if (out) prot_free(out); - if (soc >= 0) close(soc); - dlist_free(&dl); - dlist_free(&res); -} - EXPORTED void notify(const char *method, const char *class, const char *priority, const char *user, const char *mailbox, int nopt, const char **options, - const char *message, const char *fname) + const char *message) { - const char *notify_sock = config_getstring(IMAPOPT_NOTIFYSOCKET); + const char *notify_sock; int soc = -1; struct sockaddr_un sun_data; char bufNOTIFY_MAXSIZE = "", noptstr20; int buflen = 0; int i, r = 0; - if (!strncmp(notify_sock, "dlist:", 6)) { - return notify_dlist(notify_sock+6, method, class, priority, - user, mailbox, nopt, options, - message, fname); - } - soc = socket(AF_UNIX, SOCK_DGRAM, 0); if (soc == -1) { syslog(LOG_ERR, "unable to create notify socket(): %m"); @@ -173,7 +98,8 @@ memset((char *)&sun_data, 0, sizeof(sun_data)); sun_data.sun_family = AF_UNIX; - if (notify_sock) { + notify_sock = config_getstring(IMAPOPT_NOTIFYSOCKET); + if (notify_sock) { strlcpy(sun_data.sun_path, notify_sock, sizeof(sun_data.sun_path)); } else { @@ -203,7 +129,6 @@ } if (!r) r = add_arg(buf, sizeof(buf), message, &buflen); - if (!r && fname) r = add_arg(buf, sizeof(buf), fname, &buflen); if (r) { syslog(LOG_ERR, "notify datagram too large, %s, %s", @@ -225,4 +150,5 @@ out: xclose(soc); + return; }
View file
cyrus-imapd-2.5.tar.gz/imap/notify.h
Changed
@@ -47,6 +47,6 @@ const char *class, const char *priority, const char *user, const char *mailbox, int nopt, const char **options, - const char *message, const char *fname); + const char *message); #endif /* NOTIFY_H */
View file
cyrus-imapd-2.5.tar.gz/imap/pop3d.c
Changed
@@ -1762,7 +1762,7 @@ int r, log_level = LOG_ERR; const char *statusline = NULL; mbentry_t *mbentry = NULL; - struct statusdata sdata = STATUSDATA_INIT; + struct statusdata sdata; struct proc_limits limits; struct mboxevent *mboxevent; @@ -2005,18 +2005,10 @@ { FILE *msgfile; char buf4096; - const char *fname; + char *fname; int thisline = -2; - struct index_record record; - - /* XXX - map file */ - - if (mailbox_read_index_record(popd_mailbox, popd_mapmsgno-1.recno, &record)) { - prot_printf(popd_out, "-ERR SYS/PERM Could not read index record\r\n"); - return IMAP_IOERROR; - } - fname = mailbox_record_fname(popd_mailbox, &record); + fname = mailbox_message_fname(popd_mailbox, popd_mapmsgno-1.uid); msgfile = fopen(fname, "r"); if (!msgfile) { prot_printf(popd_out, "-ERR SYS/PERM Could not read message file\r\n");
View file
cyrus-imapd-2.5.tar.gz/imap/quota.c
Changed
@@ -669,8 +669,8 @@ static void reportquota_resource(struct quota * quota, const char *root, int res) { if (quota->limitsres > 0) { - printf(" %7lld %8lld", quota->limitsres, - (quota_t)((quota_t)((quota->usedsres / quota_unitsres) + printf(" %7" PRIdMAX " %8" PRIdMAX, (intmax_t)quota->limitsres, + (intmax_t)((((intmax_t)quota->usedsres / quota_unitsres) * 100) / quota->limitsres)); } else if (quota->limitsres == 0) { @@ -679,8 +679,8 @@ else { printf(" "); } - printf(" %8lld %20s %s\n", - (quota_t)(quota->usedsres / quota_unitsres), + printf(" %8" PRIdMAX " %20s %s\n", + (intmax_t)((intmax_t)quota->usedsres / quota_unitsres), quota_namesres, root); }
View file
cyrus-imapd-2.5.tar.gz/imap/quota.h
Changed
@@ -78,7 +78,7 @@ /* Information in quota entry */ quota_t usedsQUOTA_NUMRESOURCES; - quota_t limitsQUOTA_NUMRESOURCES; /* in QUOTA_UNITS */ + int limitsQUOTA_NUMRESOURCES; /* in QUOTA_UNITS */ /* information for scanning */ char *scanmbox; @@ -89,7 +89,7 @@ #define QUOTA_UNLIMITED (-1) extern const char * const quota_namesQUOTA_NUMRESOURCES; -extern const quota_t quota_unitsQUOTA_NUMRESOURCES; +extern const int quota_unitsQUOTA_NUMRESOURCES; int quota_name_to_resource(const char *str); typedef int quotaproc_t(struct quota *quota, void *rock); @@ -140,5 +140,5 @@ void quotadb_done(void); int quota_is_overquota(const struct quota *quota, enum quota_resource res, - quota_t newquotasQUOTA_NUMRESOURCES); + int newquotasQUOTA_NUMRESOURCES); #endif /* INCLUDED_QUOTA_H */
View file
cyrus-imapd-2.5.tar.gz/imap/quota_db.c
Changed
@@ -90,7 +90,7 @@ "X-NUM-FOLDERS" /* QUOTA_NUMFOLDERS */ }; -EXPORTED const quota_t quota_unitsQUOTA_NUMRESOURCES = { +EXPORTED const int quota_unitsQUOTA_NUMRESOURCES = { 1024, /* QUOTA_STORAGE -- RFC2087 */ 1, /* QUOTA_MESSAGE -- RFC2087 */ 1024, /* QUOTA_ANNOTSTORAGE */ @@ -194,7 +194,7 @@ goto out; /* need at least 2 more fields */ if (sscanf(fields->datai++, QUOTA_T_FMT, "a->usedsres) != 1) goto out; - if (sscanf(fields->datai++, QUOTA_T_FMT, "a->limitsres) != 1) + if (sscanf(fields->datai++, "%d", "a->limitsres) != 1) goto out; /* skip over temporary extra used data from failed quota -f runs */ if (i < fields->count && @@ -665,7 +665,7 @@ } EXPORTED int quota_is_overquota(const struct quota *quota, enum quota_resource res, - quota_t newquotasQUOTA_NUMRESOURCES) + int newquotasQUOTA_NUMRESOURCES) { int limit = newquotas ? newquotasres : quota->limitsres;
View file
cyrus-imapd-2.5.tar.gz/imap/rfc822_header.c
Changed
@@ -35,7 +35,7 @@ struct rfc822_header_desc { const char *name; enum rfc822_header value; }; #include <string.h> -#define TOTAL_KEYWORDS 22 +#define TOTAL_KEYWORDS 21 #define MIN_WORD_LENGTH 2 #define MAX_WORD_LENGTH 25 #define MIN_HASH_VALUE 2 @@ -181,7 +181,7 @@ {"", 0}, {"Reply-To", RFC822_REPLY_TO}, {"Content-Description", RFC822_CONTENT_DESCRIPTION}, - {"X-ME-Message-ID", RFC822_X_ME_MESSAGE_ID}, + {"", 0}, {"Content-Location", RFC822_CONTENT_LOCATION}, {"", 0}, {"", 0}, {"", 0}, {"Content-Transfer-Encoding", RFC822_CONTENT_TRANSFER_ENCODING}, @@ -241,7 +241,6 @@ "Sender", /* RFC822_SENDER */ "To", /* RFC822_TO */ "X-Deliveredinternaldate", /* RFC822_X_DELIVEREDINTERNALDATE */ - "X-ME-Message-ID", /* RFC822_X_ME_MESSAGE_ID */ }; return (v >= 0 && v < (int)(sizeof(strs)/sizeof(strs0)) ? strsv : NULL); }
View file
cyrus-imapd-2.5.tar.gz/imap/rfc822_header.h
Changed
@@ -28,7 +28,6 @@ RFC822_SENDER=18, RFC822_TO=19, RFC822_X_DELIVEREDINTERNALDATE=20, - RFC822_X_ME_MESSAGE_ID=21, }; extern enum rfc822_header rfc822_header_from_string(const char *s); extern enum rfc822_header rfc822_header_from_string_len(const char *s, size_t len);
View file
cyrus-imapd-2.5.tar.gz/imap/rfc822_header.st
Changed
@@ -65,5 +65,4 @@ ent RFC822_SENDER "Sender" ent RFC822_TO "To" ent RFC822_X_DELIVEREDINTERNALDATE "X-Deliveredinternaldate" -ent RFC822_X_ME_MESSAGE_ID "X-ME-Message-ID"
View file
cyrus-imapd-2.5.tar.gz/imap/search_engines.c
Changed
@@ -43,6 +43,8 @@ #include <config.h> #include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> #include <stdlib.h> #include <syslog.h> #include <string.h> @@ -50,282 +52,285 @@ #include <unistd.h> #endif -#include "imap_err.h" #include "index.h" -#include "message.h" #include "global.h" -#include "search_engines.h" -#include "ptrarray.h" +#include "xmalloc.h" -#ifdef USE_SQUAT -extern const struct search_engine squat_search_engine; -#endif -#ifdef USE_SPHINX -extern const struct search_engine sphinx_search_engine; -#endif -#ifdef USE_XAPIAN -extern const struct search_engine xapian_search_engine; -#endif +#include "squat.h" -static const struct search_engine default_search_engine = { - "default", - 0, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL -}; - -static const struct search_engine *engine(void) -{ - switch (config_getenum(IMAPOPT_SEARCH_ENGINE)) { -#ifdef USE_XAPIAN - case IMAP_ENUM_SEARCH_ENGINE_XAPIAN: - return &xapian_search_engine; -#endif -#ifdef USE_SPHINX - case IMAP_ENUM_SEARCH_ENGINE_SPHINX: - return &sphinx_search_engine; -#endif -#ifdef USE_SQUAT - case IMAP_ENUM_SEARCH_ENGINE_SQUAT: - return &squat_search_engine; -#endif - default: - return &default_search_engine; - } -} +typedef struct { + unsigned char *vector; + struct index_state *state; + const char *part_types; + int found_validity; +} SquatSearchResult; -EXPORTED const char *search_part_as_string(int part) +static int vector_len(struct index_state *state) { - static const char *namesSEARCH_NUM_PARTS = { - /* ANY */NULL, "FROM", "TO", "CC", - "BCC", "SUBJECT", "LISTID", "TYPE", - "HEADERS", "BODY" - }; - - return (part < 0 || part >= SEARCH_NUM_PARTS ? NULL : namespart); + return (state->exists >> 3) + 1; } +/* The document name is of the form -EXPORTED search_builder_t *search_begin_search(struct mailbox *mailbox, int opts) -{ - const struct search_engine *se = engine(); - return (se->begin_search ? - se->begin_search(mailbox, opts) : NULL); -} + pnnn.vvv -EXPORTED void search_end_search(search_builder_t *bx) -{ - const struct search_engine *se = engine(); - if (se->end_search) se->end_search(bx); -} + Where p is a part_type character (denoting which segment of the message + is represented by the document), nnn is the UID of the message, and vvv + is the UID validity value. -EXPORTED search_text_receiver_t *search_begin_update(int verbose) + This function parses the document name and returns the message + UID only if the name has the right part type and it corresponds + to a real message UID. +*/ +static int parse_doc_name(SquatSearchResult *r, const char *doc_name) { - const struct search_engine *se = engine(); - /* We don't fallback to the default search engine here - * because the default behaviour is not to index anything */ - return (se->begin_update ? se->begin_update(verbose) : NULL); -} + int ch = doc_name0; + const char *t = r->part_types; + int doc_UID, index; -static int search_batch_size(void) -{ - const struct search_engine *se = engine(); - return (se->flags & SEARCH_FLAG_CAN_BATCH ? - config_getint(IMAPOPT_SEARCH_BATCHSIZE) : INT_MAX); -} - -/* - * Flush a batch of messages to the search engine's indexer code. We - * drop the index lock during the presumably CPU and IO heavy parts of - * the procedure and re-acquire it afterward, to avoid delaying other - * processes like imapds. The reacquisition may of course fail. - * Returns an IMAP error code or 0 on success. - */ -static int flush_batch(search_text_receiver_t *rx, - struct mailbox *mailbox, - ptrarray_t *batch) -{ - int i; - int r = 0; - - /* give someone else a chance */ - mailbox_unlock_index(mailbox, NULL); - - /* prefetch files */ - for (i = 0 ; i < batch->count ; i++) { - message_t *msg = ptrarray_nth(batch, i); - const char *fname; - - r = message_get_fname(msg, &fname); - if (r) return r; - r = warmup_file(fname, 0, 0); - if (r) return r; /* means we failed to open a file, - so we'll fail later anyway */ + if (ch == 'v' && strncmp(doc_name, "validity.", 9) == 0) { + if ((unsigned) atoi(doc_name + 9) == r->state->mailbox->i.uidvalidity) { + r->found_validity = 1; + } + return -1; } - for (i = 0 ; i < batch->count ; i++) { - message_t *msg = ptrarray_nth(batch, i); - if (!r) - r = index_getsearchtext(msg, rx, 0); - message_unref(&msg); + /* make sure that the document part type is one of the ones we're + accepting */ + while (*t != 0 && *t != ch) { + t++; + } + if (*t == 0) { + return -1; } - ptrarray_truncate(batch, 0); - - if (r) return r; - if (rx->flush) { - r = rx->flush(rx); - if (r) return r; + doc_UID = atoi(++doc_name); + while ((*doc_name >= '0' && *doc_name <= '9') || *doc_name == '-') { + ++doc_name; + } + if (*doc_name != 0) { + return -1; } - return r; + index = index_finduid(r->state, doc_UID); + + return index; } -EXPORTED int search_update_mailbox(search_text_receiver_t *rx, - struct mailbox *mailbox, - int flags) +static int drop_indexed_docs(void* closure, const SquatListDoc *doc) { - uint32_t recno; - int r = 0; /* Using IMAP_* not SQUAT_* return codes here */ - int r2; - int first = 1; - int was_partial = 0; - int batch_size = search_batch_size(); - ptrarray_t batch = PTRARRAY_INITIALIZER; - struct index_record record; - int incremental = (flags & SEARCH_UPDATE_INCREMENTAL); - - r = rx->begin_mailbox(rx, mailbox, flags); - if (r) goto done; - - first = mailbox_finduid(mailbox, rx->first_unindexed_uid(rx)); - if (!first) first = 1; /* empty mailbox */ - - for (recno = first; recno <= mailbox->i.num_records; recno++) { - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) continue; - - if (record.system_flags & FLAG_EXPUNGED) - continue; - - if (rx->is_indexed(rx, record.uid)) - continue; - - if (incremental && batch.count >= batch_size) { - syslog(LOG_INFO, "search_update_mailbox batching %u messages to %s", - batch.count, mailbox->name); - was_partial = 1; - break; - } + SquatSearchResult* r = (SquatSearchResult*)closure; + int doc_uid = parse_doc_name(r, doc->doc_name); - ptrarray_append(&batch, message_new_from_record(mailbox, &record)); + if (doc_uid >= 0) { + unsigned char* vect = r->vector; + vectdoc_uid >> 3 &= ~(1 << (doc_uid & 0x7)); } - - if (batch.count) - r = flush_batch(rx, mailbox, &batch); - - done: - ptrarray_fini(&batch); - r2 = rx->end_mailbox(rx, mailbox); - if (r) return r; - if (r2) return r2; - if (was_partial) return IMAP_AGAIN; - return 0; + return SQUAT_CALLBACK_CONTINUE; } -EXPORTED int search_end_update(search_text_receiver_t *rx) +static int fill_with_hits(void* closure, char const* doc) { - const struct search_engine *se = engine(); - /* We don't fallback to the default search engine here - * because the default behaviour is not to index anything */ - return (se->end_update ? se->end_update(rx) : 0); -} + SquatSearchResult* r = (SquatSearchResult*)closure; + int doc_uid = parse_doc_name(r, doc); -EXPORTED search_text_receiver_t *search_begin_snippets(void *internalised, - int verbose, - search_snippet_cb_t proc, - void *rock) -{ - const struct search_engine *se = engine(); - return (se->begin_snippets ? se->begin_snippets(internalised, - verbose, proc, rock) : NULL); + if (doc_uid >= 0) { + unsigned char* vect = r->vector; + vectdoc_uid >> 3 |= 1 << (doc_uid & 0x7); + } + return SQUAT_CALLBACK_CONTINUE; } -EXPORTED int search_end_snippets(search_text_receiver_t *rx) +static int search_strlist(SquatSearchIndex* index, struct index_state *state, + unsigned char* output, unsigned char* tmp, + struct strlist* strs, char const* part_types) { - const struct search_engine *se = engine(); - return (se->end_snippets ? se->end_snippets(rx) : 0); -} + SquatSearchResult r; + int i; + int len = vector_len(state); + + r.part_types = part_types; + r.vector = tmp; + r.state = state; + while (strs != NULL) { + char const* s = strs->s; + + memset(tmp, 0, len); + if (squat_search_execute(index, s, strlen(s), fill_with_hits, &r) + != SQUAT_OK) { + if (squat_get_last_error() == SQUAT_ERR_SEARCH_STRING_TOO_SHORT) + return 1; /* The rest of the search is still viable */ + syslog(LOG_DEBUG, "SQUAT string list search failed on string %s " + "with part types %s", s, part_types); + return 0; + } + for (i = 0; i < len; i++) { + outputi &= tmpi; + } -EXPORTED char *search_describe_internalised(void *internalised) -{ - const struct search_engine *se = engine(); - return (se->describe_internalised ? - se->describe_internalised(internalised) : 0); + strs = strs->next; + } + return 1; } -EXPORTED void search_free_internalised(void *internalised) +static unsigned char* search_squat_do_query(SquatSearchIndex* index, + struct index_state *state, + const struct searchargs* args) { - const struct search_engine *se = engine(); - if (se->free_internalised) se->free_internalised(internalised); -} + int vlen = vector_len(state); + unsigned char* vect = xmalloc(vlen); + unsigned char* t_vect = xmalloc(vlen); + struct searchsub* sub; + int found_something = 1; + + memset(vect, 255, vlen); + + if (!(search_strlist(index, state, vect, t_vect, args->to, "t") + && search_strlist(index, state, vect, t_vect, args->from, "f") + && search_strlist(index, state, vect, t_vect, args->cc, "c") + && search_strlist(index, state, vect, t_vect, args->bcc, "b") + && search_strlist(index, state, vect, t_vect, args->subject, "s") + && search_strlist(index, state, vect, t_vect, args->header_name, "h") + && search_strlist(index, state, vect, t_vect, args->header, "h") + && search_strlist(index, state, vect, t_vect, args->body, "m") + && search_strlist(index, state, vect, t_vect, args->text, "mh"))) { + found_something = 0; + goto cleanup; + } -EXPORTED int search_start_daemon(int verbose) -{ - const struct search_engine *se = engine(); - return (se->start_daemon ? se->start_daemon(verbose) : 0); -} + sub = args->sublist; + while (sub != NULL) { + if (args->sublist->sub2 == NULL) { + /* do nothing; because our search is conservative (may include false + positives) we can't compute the NOT (since the result might include + false negatives, which we do not allow) */ + /* Note that it's OK to do nothing. We'll just be returning more + false positives. */ + } else { + unsigned char* sub1_vect = + search_squat_do_query(index, state, args->sublist->sub1); + unsigned char* sub2_vect; + int i; + + if (sub1_vect == NULL) { + found_something = 0; + goto cleanup; + } + + sub2_vect = search_squat_do_query(index, state, args->sublist->sub2); + + if (sub2_vect == NULL) { + found_something = 0; + free(sub1_vect); + goto cleanup; + } + + for (i = 0; i < vlen; i++) { + vecti &= sub1_vecti | sub2_vecti; + } + + free(sub1_vect); + free(sub2_vect); + } -EXPORTED int search_stop_daemon(int verbose) -{ - const struct search_engine *se = engine(); - return (se->stop_daemon ? se->stop_daemon(verbose) : 0); -} + sub = sub->next; + } -EXPORTED int search_list_files(const char *userid, - strarray_t *files) -{ - const struct search_engine *se = engine(); - return (se->list_files ? se->list_files(userid, files) : 0); -} +cleanup: + free(t_vect); + if (!found_something) { + free(vect); + return NULL; + } -EXPORTED int search_compact(const char *userid, - const char *tempdir, - const strarray_t *srctiers, - const char *desttier, - int flags) -{ - const struct search_engine *se = engine(); - return (se->compact ? se->compact(userid, tempdir, srctiers, desttier, flags) : 0); + return vect; } -EXPORTED int search_deluser(const char *userid) +static int search_squat(unsigned* msg_list, struct index_state *state, + const struct searchargs *searchargs) { - const struct search_engine *se = engine(); - return (se->deluser ? se->deluser(userid) : 0); + char *fname; + int fd; + SquatSearchIndex* index; + unsigned char* msg_vector; + int result; + + fname = mailbox_meta_fname(state->mailbox, META_SQUAT); + if ((fd = open(fname, O_RDONLY)) < 0) { + syslog(LOG_DEBUG, "SQUAT failed to open index file"); + return -1; /* probably not found. Just bail */ + } + if ((index = squat_search_open(fd)) == NULL) { + syslog(LOG_DEBUG, "SQUAT failed to open index"); + close(fd); + return -1; + } + if ((msg_vector = search_squat_do_query(index, state, searchargs)) + == NULL) { + result = -1; + } else { + unsigned i; + unsigned vlen = vector_len(state); + unsigned char* unindexed_vector = xmalloc(vlen); + SquatSearchResult r; + + memset(unindexed_vector, 255, vlen); + r.vector = unindexed_vector; + r.state = state; + r.part_types = "tfcbsmh"; + r.found_validity = 0; + if (squat_search_list_docs(index, drop_indexed_docs, &r) != SQUAT_OK) { + syslog(LOG_DEBUG, "SQUAT failed to get list of indexed documents"); + result = -1; + } else if (!r.found_validity) { + syslog(LOG_DEBUG, "SQUAT didn't find validity record"); + result = -1; + } else { + /* Add in any unindexed messages. They must be searched manually. */ + for (i = 0; i < vlen; i++) { + msg_vectori |= unindexed_vectori; + } + + result = 0; + for (i = 1; i <= state->exists; i++) { + if ((msg_vectori >> 3 & (1 << (i & 7))) != 0) { + msg_listresult = i; + result++; + } + } + } + free(msg_vector); + free(unindexed_vector); + } + squat_search_close(index); + close(fd); + return result; } -const char *search_op_as_string(int op) +HIDDEN int search_prefilter_messages(unsigned *msgno_list, + struct index_state *state, + const struct searchargs *searchargs) { - static char buf33; - - switch (op) { - case SEARCH_OP_AND: return "AND"; - case SEARCH_OP_OR: return "OR"; - case SEARCH_OP_NOT: return "NOT"; - default: - snprintf(buf, sizeof(buf), "(%d)", op); - return buf; + unsigned i; + int count; + + if (SQUAT_ENGINE) { + count = search_squat(msgno_list, state, searchargs); + if (count >= 0) { + syslog(LOG_DEBUG, "SQUAT returned %d messages", count); + return count; + } else { + /* otherwise, we failed for some reason, so do the default */ + syslog(LOG_DEBUG, "SQUAT failed"); + } } + + /* Just put in all possible messages. This falls back to Cyrus' default + * search. */ + + for (i = 0; i < state->exists; i++) { + msgno_listi = i + 1; + } + + return state->exists; }
View file
cyrus-imapd-2.5.tar.gz/imap/search_engines.h
Changed
@@ -43,150 +43,14 @@ #ifndef INCLUDED_SEARCH_ENGINES_H #define INCLUDED_SEARCH_ENGINES_H -#include "mailbox.h" -#include "util.h" -#include "strarray.h" +#include "index.h" -typedef int (*search_hit_cb_t)(const char *mboxname, uint32_t uidvalidity, - uint32_t uid, void *rock); -typedef int (*search_snippet_cb_t)(struct mailbox *, uint32_t uid, - /* SEARCH_PART_* constants */int part, - const char *snippet, void *rock); - -typedef struct search_builder search_builder_t; -struct search_builder { -/* These values are carefully chosen a) not to clash with the - * SEARCH_PART_* constants, and b) to reflect operator precedence */ -#define SEARCH_OP_AND 101 -#define SEARCH_OP_OR 102 -#define SEARCH_OP_NOT 103 - void (*begin_boolean)(search_builder_t *, int op); - void (*end_boolean)(search_builder_t *, int op); - void (*match)(search_builder_t *, int part, const char *str); - void *(*get_internalised)(search_builder_t *); - int (*run)(search_builder_t *, search_hit_cb_t proc, void *rock); -}; - -/* These constants are passed into the search_text_receiver_t.begin_part callback to - tell it which part of the message is being sent down */ -#define SEARCH_PART_NONE (-1) -#define SEARCH_PART_ANY 0 -#define SEARCH_PART_FROM 1 -#define SEARCH_PART_TO 2 -#define SEARCH_PART_CC 3 -#define SEARCH_PART_BCC 4 -#define SEARCH_PART_SUBJECT 5 -#define SEARCH_PART_LISTID 6 /* List-Id or Mailing-List fields */ -#define SEARCH_PART_TYPE 7 /* MIME Content-Type except multipart */ -#define SEARCH_PART_HEADERS 8 /* headers OTHER than the above headers */ -#define SEARCH_PART_BODY 9 -#define SEARCH_NUM_PARTS 10 - -extern const char *search_part_as_string(int part); - -/* The functions in search_text_receiver_t get called at least once for each part of every message. - The invocations form a sequence: - begin_message(<uid>) - receiver->begin_part(<part1>) - receiver->append_text(<text>) (1 or more times) - receiver->end_part(<part1>) - ... - receiver->begin_part(<partN>) - receiver->append_text(<text>) (1 or more times) - receiver->end_part(<partN>) - receiver->end_message(<uid>) - - The parts need not arrive in any particular order, but each part - can only participate in one begin_part ... append_text ... end_part - sequence, and the sequences for different parts cannot be interleaved. -*/ -typedef struct search_text_receiver search_text_receiver_t; -struct search_text_receiver { - int (*begin_mailbox)(search_text_receiver_t *, - struct mailbox *, int incremental); - uint32_t (*first_unindexed_uid)(search_text_receiver_t *); - int (*is_indexed)(search_text_receiver_t *, uint32_t uid); - void (*begin_message)(search_text_receiver_t *, uint32_t uid); - void (*begin_part)(search_text_receiver_t *, int part); - void (*append_text)(search_text_receiver_t *, const struct buf *); - void (*end_part)(search_text_receiver_t *, int part); - int (*end_message)(search_text_receiver_t *); - int (*end_mailbox)(search_text_receiver_t *, - struct mailbox *); - int (*flush)(search_text_receiver_t *); -}; - -#define SEARCH_FLAG_CAN_BATCH (1<<0) -struct search_engine { - const char *name; - unsigned int flags; -#define _SEARCH_VERBOSE_MASK (0x7) -#define SEARCH_VERBOSE(v) ((v)&_SEARCH_VERBOSE_MASK) -#define SEARCH_MULTIPLE (1<<3) /* return results from - * multiple folders */ -#define SEARCH_UNINDEXED (1<<4) /* return unindexed messages - * as hits (doesn't work - * with MULTIPLE) */ -#define SEARCH_COMPACT_COPYONE (1<<5) /* if only one source, just copy */ -#define SEARCH_COMPACT_FILTER (1<<6) /* filter resulting DB for - * expunged records */ -#define SEARCH_COMPACT_AUDIT (1<<7) /* check DB for missing records */ -#define SEARCH_COMPACT_REINDEX (1<<8) /* re-index all matching messages */ - search_builder_t *(*begin_search)(struct mailbox *, int opts); - void (*end_search)(search_builder_t *); - search_text_receiver_t *(*begin_update)(int verbose); - int (*end_update)(search_text_receiver_t *); - search_text_receiver_t *(*begin_snippets)(void *internalised, - int verbose, - search_snippet_cb_t, - void *rock); - int (*end_snippets)(search_text_receiver_t *); - char *(*describe_internalised)(void *); - void (*free_internalised)(void *); - int (*start_daemon)(int verbose); - int (*stop_daemon)(int verbose); - int (*list_files)(const char *userid, strarray_t *); - int (*compact)(const char *userid, const char *tempdir, - const strarray_t *srctiers, const char *desttier, - int flags); - int (*deluser)(const char *userid); -}; - -/* - * Search for messages which could match the query built with the - * search_builder_t. Calls 'proc' once for each hit found. If 'single' - * is true, only hits in 'mailbox' are reported; otherwise hits in any - * folder in the same conversation scope (i.e. the same user) as - * reported. +/* Fill the msg_list with a list of message IDs which could match the + * searchargs. + * Return the number of message IDs inserted. */ -extern search_builder_t *search_begin_search(struct mailbox *, int opts); -extern void search_end_search(search_builder_t *); - -#define SEARCH_UPDATE_INCREMENTAL (1<<0) -#define SEARCH_UPDATE_NONBLOCKING (1<<1) -search_text_receiver_t *search_begin_update(int verbose); -int search_update_mailbox(search_text_receiver_t *rx, - struct mailbox *mailbox, - int flags); -int search_end_update(search_text_receiver_t *rx); -search_text_receiver_t *search_begin_snippets(void *internalised, - int verbose, - search_snippet_cb_t proc, - void *rock); -int search_end_snippets(search_text_receiver_t *rx); -/* Returns a new string which describes the internalised query, and must - * be free()d by the caller. Only useful for whitebox testing. */ -char *search_describe_internalised(void *internalised); -void search_free_internalised(void *internalised); -int search_start_daemon(int verbose); -int search_stop_daemon(int verbose); -int search_list_files(const char *userid, strarray_t *); -int search_compact(const char *userid, const char *tempdir, - const strarray_t *srctiers, const char *desttier, int verbose); -int search_deluser(const char *userid); - - -/* for debugging */ -extern const char *search_op_as_string(int op); +extern int search_prefilter_messages(unsigned* msg_list, + struct index_state *state, + const struct searchargs *searchargs); #endif
View file
cyrus-imapd-2.5.tar.gz/imap/search_expr.c
Deleted
@@ -1,2400 +0,0 @@ -/* search_expr.c -- query tree handling for SEARCH - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> - -#include <sys/types.h> -#include <stdlib.h> -#include <syslog.h> -#include <string.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -#include "assert.h" -#include "imap_err.h" -#include "search_expr.h" -#include "index.h" -#include "message.h" -#include "charset.h" -#include "annotate.h" -#include "global.h" -#include "lsort.h" -#include "xstrlcpy.h" -#include "xmalloc.h" - -#define DEBUG 0 - -#if DEBUG -static search_expr_t **the_rootp; -static search_expr_t *the_focus; -#endif -static unsigned nnodes = 0; - -static void split(search_expr_t *e, - void (*cb)(const char *, search_expr_t *, search_expr_t *, void *), - void *rock); - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -static search_expr_t *append(search_expr_t *parent, search_expr_t *child) -{ - search_expr_t **tailp; - - for (tailp = &parent->children ; *tailp ; tailp = &(*tailp)->next) - ; - *tailp = child; - child->next = NULL; - child->parent = parent; - - return child; -} - -static search_expr_t *detachp(search_expr_t **prevp) -{ - search_expr_t *child = *prevp; - - if (child) { - *prevp = child->next; - child->next = NULL; - child->parent = NULL; - } - - return child; -} - -static search_expr_t *detach(search_expr_t *parent, search_expr_t *child) -{ - search_expr_t **prevp; - - for (prevp = &parent->children ; *prevp && *prevp != child; prevp = &(*prevp)->next) - ; - return detachp(prevp); -} - -/* - * Detach the node '*prevp' from the tree, and reparent its children to - * '*prevp' parent, preseving '*prevp's location and its children's - * order. - * - * Apparently this operation is called "splat" but I think that's - * a damn silly name. - */ -static search_expr_t *elide(search_expr_t **prevp) -{ - search_expr_t *e = *prevp; - search_expr_t *child; - - *prevp = e->children; - - for (child = e->children ; child ; child = child->next) { - child->parent = e->parent; - prevp = &child->next; - } - *prevp = e->next; - - e->next = NULL; - e->children = NULL; - e->parent = NULL; - - return e; -} - -static search_expr_t *interpolate(search_expr_t **prevp, enum search_op op) -{ - search_expr_t *e = search_expr_new(NULL, op); - - e->parent = (*prevp)->parent; - e->children = (*prevp); - e->next = (*prevp)->next; - (*prevp)->next = NULL; - (*prevp)->parent = e; - *prevp = e; - - return e; -} - -/* - * Create a new node in a search expression tree, with the given - * operation. If 'parent' is not NULL, the new node is attached as the - * last child of 'parent'. Returns a new node, never returns NULL. - */ -EXPORTED search_expr_t *search_expr_new(search_expr_t *parent, enum search_op op) -{ - search_expr_t *e = xzmalloc(sizeof(search_expr_t)); - e->op = op; - if (parent) append(parent, e); - nnodes++; - return e; -} - -static int complexity_check(int r) -{ - unsigned max = (unsigned)config_getint(IMAPOPT_SEARCH_NORMALISATION_MAX); - return (max && nnodes >= max ? -1 : r); -} - -/* - * Append the given search expression tree 'e' to the end of the - * node 'parent'. 'e' must not already have a parent. - */ -EXPORTED void search_expr_append(search_expr_t *parent, search_expr_t *e) -{ - assert(e->parent == NULL); - append(parent, e); -} - -/* - * Recursively free a search expression tree including the given node - * and all descendant nodes. - */ -EXPORTED void search_expr_free(search_expr_t *e) -{ - if (!e) return; - while (e->children) - search_expr_free(detach(e, e->children)); - if (e->attr) { - if (e->attr->internalise) e->attr->internalise(NULL, NULL, &e->internalised); - if (e->attr->free) e->attr->free(&e->value); - } - free(e); -} - -/* - * Create and return a new search expression tree which is an - * exact duplicate of the given tree. - */ -EXPORTED search_expr_t *search_expr_duplicate(const search_expr_t *e) -{ - search_expr_t *newe; - search_expr_t *child; - - newe = search_expr_new(NULL, e->op); - newe->attr = e->attr; - if (newe->attr && newe->attr->duplicate) - newe->attr->duplicate(&newe->value, &e->value); - else - newe->value = e->value; - - for (child = e->children ; child ; child = child->next) - append(newe, search_expr_duplicate(child)); - - return newe; -} - -/* - * Apply the given callback to every node in the search expression tree, - * in pre-order (i.e. parent before children), as long as the callback - * returns zero. Returns the first non-zero return from the callback - * (which is typically an error code). - */ -EXPORTED int search_expr_apply(search_expr_t *e, - int (*cb)(search_expr_t *, void *), - void *rock) -{ - search_expr_t *child; - int r; - - r = cb(e, rock); - if (r) return r; - - for (child = e->children ; child ; child = child->next) { - r = search_expr_apply(child, cb, rock); - if (r) break; - } - - return r; -} - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -static const char *op_strings = { - "unknown", "true", "false", - "lt", "le", "gt", "ge", "match", - "fuzzymatch", "and", "or", "not" -}; - -static const char *op_as_string(unsigned int op) -{ - return (op < VECTOR_SIZE(op_strings) ? op_stringsop : "WTF?"); -} - -static void serialise(const search_expr_t *e, struct buf *buf) -{ - const search_expr_t *child; - -#if DEBUG - if (e == the_focus) buf_putc(buf, '<'); -#endif - buf_putc(buf, '('); - buf_appendcstr(buf, op_as_string(e->op)); - if (e->attr) { - buf_putc(buf, ' '); - buf_appendcstr(buf, e->attr->name); - buf_putc(buf, ' '); - if (e->attr->serialise) e->attr->serialise(buf, &e->value); - } - for (child = e->children ; child ; child = child->next) { - buf_putc(buf, ' '); - serialise(child, buf); - } - buf_putc(buf, ')'); -#if DEBUG - if (e == the_focus) buf_putc(buf, '>'); -#endif -} - -/* - * Given an expression tree, return a string which uniquely describes - * the tree. The string is designed to be used as a cache key and for - * unit tests, not for human readability. - * - * Returns a new string which must be free()d by the caller. - */ -EXPORTED char *search_expr_serialise(const search_expr_t *e) -{ - struct buf buf = BUF_INITIALIZER; - serialise(e, &buf); - return buf_release(&buf); -} - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -static int getseword(struct protstream *prot, char *buf, int maxlen) -{ - int c = EOF; - int quoted = 0; - - c = prot_getc(prot); - if (c == '"') - quoted = 1; - else - prot_ungetc(c, prot); - - while (maxlen > 1 && - (c = prot_getc(prot)) != EOF && - (quoted ? - (c != '"') : - (c != ' ' && c != ')'))) { - *buf++ = c; - maxlen--; - } - *buf = '\0'; - if (quoted && c != EOF) - c = prot_getc(prot); - return c; -} - -static search_expr_t *unserialise(search_expr_t *parent, - struct protstream *prot) -{ - int c; - search_expr_t *e = NULL; - unsigned int op; - char tmp128; - - c = prot_getc(prot); - if (c != '(') - goto bad; - - c = getseword(prot, tmp, sizeof(tmp)); - if (c != ' ' && c != ')') - goto bad; - - for (op = 0 ; op < VECTOR_SIZE(op_strings) ; op++) { - if (!strcmp(tmp, op_stringsop)) - break; - } - if (op == VECTOR_SIZE(op_strings)) - goto bad; - - e = search_expr_new(parent, op); - if (c == ')') - return e; /* SEOP_TRUE, SEOP_FALSE */ - - switch (op) { - case SEOP_AND: - case SEOP_OR: - case SEOP_NOT: - /* parse children */ - for (;;) { - if (unserialise(e, prot) == NULL) - goto bad; - c = prot_getc(prot); - if (c == ')') - break; - if (c != ' ') - goto bad; - } - break; - case SEOP_LT: - case SEOP_LE: - case SEOP_GT: - case SEOP_GE: - case SEOP_MATCH: - case SEOP_FUZZYMATCH: - /* parse attribute */ - c = getseword(prot, tmp, sizeof(tmp)); - if (c != ' ') - goto bad; - e->attr = search_attr_find(tmp); - if (e->attr == NULL) - goto bad; - /* parse value */ - if (e->attr->unserialise) - c = e->attr->unserialise(prot, &e->value); - if (c != ')') - goto bad; - break; - default: - c = prot_getc(prot); - if (c != ')') - goto bad; - break; - } - - return e; - -bad: - if (e) { - e->op = SEOP_UNKNOWN; - if (parent == NULL) - search_expr_free(e); - } - return NULL; -} - -/* - * Given a string generated by search_expr_serialise(), - * parse it and return a new expression tree, or NULL if - * there were any errors. Used mainly for unit tests. - */ -EXPORTED search_expr_t *search_expr_unserialise(const char *s) -{ - struct protstream *prot; - search_expr_t *root = NULL; - - if (!s || !*s) return NULL; - prot = prot_readmap(s, strlen(s)); - root = unserialise(NULL, prot); - -#if DEBUG - if (!root) { -#define MAX_CONTEXT 48 - int off = ((const char *)prot->ptr - s); - int len = strlen(s); - int context_begin = off - MIN(off, MAX_CONTEXT); - int context_end = off + MIN((len-off), MAX_CONTEXT); - int i; - fputc('\n', stderr); - fprintf(stderr, "ERROR: failed to unserialise string at or near:\n"); - if (context_begin) fputs("...", stderr); - fwrite(s+context_begin, 1, context_end-context_begin, stderr); - fputc('\n', stderr); - if (context_begin) fputs("---", stderr); - for (i = off - context_begin - 1 ; i > 0 ; i--) - fputc('-', stderr); - fputc('^', stderr); - fputc('\n', stderr); - } -#endif - - prot_free(prot); - return root; -} - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -enum { - DNF_OR, DNF_AND, DNF_NOT, DNF_CMP -}; - -/* expected depth, in a full tree. 0 is rootmost, 3 is leafmost */ -static int dnf_depth(const search_expr_t *e) -{ - switch (e->op) { - case SEOP_TRUE: - case SEOP_FALSE: - case SEOP_LT: - case SEOP_LE: - case SEOP_GT: - case SEOP_GE: - case SEOP_MATCH: - case SEOP_FUZZYMATCH: - return DNF_CMP; - case SEOP_AND: - return DNF_AND; - case SEOP_OR: - return DNF_OR; - case SEOP_NOT: - return DNF_NOT; - default: assert(0); return -1; - } - return -1; -} - -static int has_enough_children(const search_expr_t *e) -{ - const search_expr_t *child; - int min; - int n = 0; - - switch (e->op) { - case SEOP_OR: - case SEOP_AND: - min = 2; - break; - case SEOP_NOT: - min = 1; - break; - default: - return 1; - } - - for (child = e->children ; child ; child = child->next) - if (++n >= min) return 1; - return 0; -} - -static int apply_demorgan(search_expr_t **ep, search_expr_t **prevp) -{ - search_expr_t *child = *prevp; - search_expr_t **grandp; - - /* NOT nodes have exactly one child */ - assert(*prevp != NULL); - assert((*prevp)->next == NULL); - - child->op = (child->op == SEOP_AND ? SEOP_OR : SEOP_AND); - for (grandp = &child->children ; *grandp ; grandp = &(*grandp)->next) - interpolate(grandp, SEOP_NOT); - search_expr_free(elide(ep)); - - return complexity_check(1); -} - -static int apply_distribution(search_expr_t **ep, search_expr_t **prevp) -{ - search_expr_t *newor; - search_expr_t *or; - search_expr_t *and; - search_expr_t *orchild; - search_expr_t *newand; - int r = 1; - - newor = interpolate(ep, SEOP_OR); - and = detachp(&newor->children); - or = detachp(prevp); - - while (complexity_check(r) >= 0) { - orchild = detachp(&or->children); - if (orchild == NULL) break; - newand = search_expr_duplicate(and); - append(newand, orchild); - append(newor, newand); - } - - search_expr_free(and); - search_expr_free(or); - - return complexity_check(r); -} - -static int invert(search_expr_t **ep, search_expr_t **prevp) -{ - if ((*ep)->op == SEOP_NOT) - return apply_demorgan(ep, prevp); - else - return apply_distribution(ep, prevp); -} - -/* combine compatible boolean parent and child nodes */ -static void combine(search_expr_t **ep, search_expr_t **prevp) -{ - switch ((*ep)->op) { - case SEOP_NOT: - search_expr_free(elide(prevp)); - search_expr_free(elide(ep)); - break; - case SEOP_AND: - case SEOP_OR: - search_expr_free(elide(prevp)); - break; - default: - break; - } -} - -/* - * Top-level normalisation step. Returns 1 if it changed the subtree, 0 - * if it didn't, and -1 on error (such as exceeding a complexity limit). - */ -static int normalise(search_expr_t **ep) -{ - search_expr_t **prevp; - int depth; - int changed = -1; - int r; - -restart: - changed++; - -#if DEBUG - the_focus = *ep; - { - char *s = search_expr_serialise(*the_rootp); - fprintf(stderr, "normalise: tree=%s\n", s); - free(s); - } -#endif - - if (!has_enough_children(*ep)) { - /* eliminate trivial nodes: AND and ORs with - * a single child, NOTs with none */ - search_expr_free(elide(ep)); - goto restart; - } - - depth = dnf_depth(*ep); - for (prevp = &(*ep)->children ; *prevp ; prevp = &(*prevp)->next) - { - int child_depth = dnf_depth(*prevp); - if (child_depth == depth) { - combine(ep, prevp); - goto restart; - } - if (child_depth < depth) { - r = invert(ep, prevp); - if (r < 0) return -1; - goto restart; - } - r = normalise(prevp); - if (r < 0) return -1; - if (r > 0) goto restart; - } - - return complexity_check(changed); -} - -static void *getnext(void *p) -{ - return ((search_expr_t *)p)->next; -} - -static void setnext(void *p, void *next) -{ - ((search_expr_t *)p)->next = next; -} - -static int compare(void *p1, void *p2, void *calldata) -{ - const search_expr_t *e1 = p1; - const search_expr_t *e2 = p2; - int r; - - r = dnf_depth(e2) - dnf_depth(e1); - - if (!r) - r = (e1->attr ? e1->attr->cost : 0) - - (e2->attr ? e2->attr->cost : 0); - - if (!r) - r = strcasecmp(e1->attr ? e1->attr->name : "zzz", - e2->attr ? e2->attr->name : "zzz"); - - if (!r) - r = (int)e1->op - (int)e2->op; - - if (!r) { - struct buf b1 = BUF_INITIALIZER; - struct buf b2 = BUF_INITIALIZER; - if (e1->attr && e1->attr->serialise) - e1->attr->serialise(&b1, &e1->value); - if (e2->attr && e2->attr->serialise) - e2->attr->serialise(&b2, &e2->value); - r = strcmp(buf_cstring(&b1), buf_cstring(&b2)); - buf_free(&b1); - buf_free(&b2); - } - - if (!r) { - if (e1->children || e2->children) - r = compare((void *)(e1->children ? e1->children : e1), - (void *)(e2->children ? e2->children : e2), - calldata); - } - - return r; -} - -static void sort_children(search_expr_t *e) -{ - search_expr_t *child; - - for (child = e->children ; child ; child = child->next) - sort_children(child); - - e->children = lsort(e->children, getnext, setnext, compare, NULL); -} - -/* - * Reorganise a search expression tree into Disjunctive Normal Form. - * This form is useful for picking out cacheable and runnable sub-queries. - * - * An expression in DNF has a number of constraints: - * - * - it contains at most one OR node - * - if present the OR node is the root - * - NOT nodes if present have only comparisons as children - * - it contains at most 4 levels of nodes - * - nodes have a strict order of types, down from the root - * they are: OR, AND, NOT, comparisons. - * - * DNF is useful for running queries. Each of the children of the - * root OR node can be run as a separate sub-query, and cached - * independently because their results are just accumulated together - * without any further processing. Each of those children is a single - * conjuctive clause which can implemented using an index lookup (or a - * scan of all messages) followed by a filtering step. Finally, each of - * those conjunctive clauses can be analysed to discover which folders - * will need to be opened: no folders, a single specific folder, - * all folders, or all folders except some specific folders. - * - * We also enforce a fixed order on child nodes of any node, so - * that all logically equivalent trees are the same shape. This - * helps when constructing a cache key from a tree. The sorting - * criteria are: - * - * - NOT nodes after un-negated comparison nodes, then - * - comparison nodes sorted lexically on attribute, then - * - comparison nodes sorted lexically on stringified value - * - * Note that IMAP search syntax, when translated most directly into an - * expression tree, defines trees whose outermost node is always an AND. - * Those trees are not in any kind of normal form but more closely - * resemble Conjunctive Normal Form than DNF. Any IMAP search program - * containing an OR criterion will require significant juggling to - * achieve DNF. - * - * Takes the root of the tree in *'ep' and returns a possibly reshaped - * tree whose root is stored in *'ep'. - * - * Returns 1 if the subtree was changed, 0 if it wasn't, and -1 on error - * (such as exceeding a complexity limit). - */ -EXPORTED int search_expr_normalise(search_expr_t **ep) -{ - int r; - -#if DEBUG - the_rootp = ep; -#endif - r = normalise(ep); - sort_children(*ep); -#if DEBUG - the_rootp = NULL; - the_focus = NULL; -#endif - return r; -} - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -static int internalise(search_expr_t *e, void *rock) -{ - struct index_state *state = rock; - if (e->attr && e->attr->internalise) - e->attr->internalise(state, &e->value, &e->internalised); - return 0; -} - -/* - * Prepare the given expression for use with the given mailbox. - */ -EXPORTED void search_expr_internalise(struct index_state *state, search_expr_t *e) -{ - search_expr_apply(e, internalise, state); -} - -/* - * Evaluate the given search expression for the given message, - * Returns nonzero if the expression is true, 0 otherwise. - */ -EXPORTED int search_expr_evaluate(message_t *m, const search_expr_t *e) -{ - search_expr_t *child; - - switch (e->op) { - case SEOP_UNKNOWN: assert(0); return 1; - case SEOP_TRUE: return 1; - case SEOP_FALSE: return 0; - case SEOP_LT: - assert(e->attr); - assert(e->attr->cmp); - return (e->attr->cmp(m, &e->value, e->internalised, e->attr->data1) < 0); - case SEOP_LE: - assert(e->attr); - assert(e->attr->cmp); - return (e->attr->cmp(m, &e->value, e->internalised, e->attr->data1) <= 0); - case SEOP_GT: - assert(e->attr); - assert(e->attr->cmp); - return (e->attr->cmp(m, &e->value, e->internalised, e->attr->data1) > 0); - case SEOP_GE: - assert(e->attr); - assert(e->attr->cmp); - return (e->attr->cmp(m, &e->value, e->internalised, e->attr->data1) >= 0); - case SEOP_FUZZYMATCH: - /* FUZZYMATCH should never be evaluated, as such nodes are - * picked out of the expression during query optimisation and - * used to drive search engine lookups. But the nearest - * approximation would be MATCH. */ - case SEOP_MATCH: - assert(e->attr); - assert(e->attr->match); - return e->attr->match(m, &e->value, e->internalised, e->attr->data1); - case SEOP_AND: - for (child = e->children ; child ; child = child->next) - if (!search_expr_evaluate(m, child)) - return 0; - return 1; - case SEOP_OR: - for (child = e->children ; child ; child = child->next) - if (search_expr_evaluate(m, child)) - return 1; - return 0; - case SEOP_NOT: - assert(e->children); - return !search_expr_evaluate(m, e->children); - } - return 0; -} - -/* ====================================================================== */ - -static int uses_attr(search_expr_t *e, void *rock) -{ - const search_attr_t *attr = rock; - return (e->attr == attr); -} - -/* - * Returns non-zero if any comparison node in the given search - * expression tree uses the attribute with the given name. - */ -EXPORTED int search_expr_uses_attr(const search_expr_t *e, const char *name) -{ - const search_attr_t *attr = search_attr_find(name); - - if (!attr) return 0; - return search_expr_apply((search_expr_t *)e, uses_attr, (void *)attr); -} - -/* ====================================================================== */ - - /* NOTE: older than 'N' days will be a mutable search of course, - * but that fact isn't available down here - we only know the - * date range itself, and that isn't mutable. So if you need - * immutable results, you'll need to maintain a fixed date range - * up in the higher level */ - -static int is_mutable(search_expr_t *e, void *rock __attribute__((unused))) -{ - return (e->attr && (e->attr->flags & SEA_MUTABLE)); -} - -/* - * Return non-zero if the search expression is mutable, i.e. it might - * return a different set of messages if run again, assuming that - * - * a) no folders covered by the search have received new messages, and - * b) uidvalidities of folders covered by the search have not changed. - * - * Basically, mutable searches are on attributes of a message which are - * not derived solely from the message text itself and can be changed after - * the message is inserted. For example: system flags are mutable, the - * From: header field is not. - */ -EXPORTED int search_expr_is_mutable(const search_expr_t *e) -{ - return search_expr_apply((search_expr_t *)e, is_mutable, NULL); -} - -/* ====================================================================== */ - -static int get_countability(search_expr_t *e, void *rock) -{ - unsigned int *maskp = rock; - - if (e->op == SEOP_TRUE) - *maskp |= SEC_EXISTS; - else if (e->op == SEOP_FALSE) - *maskp |= SEC_EXISTS|SEC_NOT; - else if (e->op == SEOP_NOT) - *maskp |= SEC_NOT; - else if (e->attr) { - if (e->attr->get_countability) - *maskp |= e->attr->get_countability(&e->value); - else - *maskp |= SEC_UNCOUNTED; - } - - return 0; -} - -/* - * Analyse the search expression to discover how countable the results are - * going to be. By "countable" we mean "predictable from stored state, - * without searching every message". Currently that means - * - * in message mode: - * - total number of messages - * - number unseen messages - * - number seen messages (by inference) - * - number recent messages - * - number unrecent messages (by inference) - * in conversation mode: - * - total number of conversations - * - number of conversations with unseen messages - * - number of conversations with no unseen messages (by inference) - * - * Returns a mask of SEC_* constants (e.g. SEC_SEEN) describing which - * countable attributes are specified by the expression. The special value - * SEC_UNCOUNTED means that at least one uncounted attribute was found. - * Mask values with more than one bit set are effectively uncountable. - * - * Note: the heuristics used here are intended for normalised search - * expressions, and may not work correctly otherwise. In particular, - * SEC_NOT doesn't do quite what you expect. - */ - -EXPORTED unsigned int search_expr_get_countability(const search_expr_t *e) -{ - unsigned int mask = 0; - - if (!e) - return 0; - - search_expr_apply((search_expr_t *)e, get_countability, &mask); - return mask; -} - -/* ====================================================================== */ - -/* - * Neutralise a node: make it always return success. Useful for - * changing the logic of an expression without reshaping it. - */ -EXPORTED void search_expr_neutralise(search_expr_t *e) -{ - /* Leave the children and attribute in place. This might be - * wrong, but we are only called for MATCH nodes at the moment - * and they seem to be able to tolerate such weirdness. */ - e->op = SEOP_TRUE; -} - -/* ====================================================================== */ - -static int is_folder_node(const search_expr_t *e) -{ - return (e->op == SEOP_MATCH && - e->attr && - !strcasecmp(e->attr->name, "folder")); -} - -static int is_indexed_node(const search_expr_t *e) -{ - if (e->op == SEOP_NOT) - return is_indexed_node(e->children); - return (e->op == SEOP_FUZZYMATCH && - e->attr && - e->attr->part != SEARCH_PART_NONE); -} - -static int is_folder_or_indexed(search_expr_t *e, void *rock __attribute__((unused))) -{ - return (is_folder_node(e) || is_indexed_node(e)); -} - -/* - * Split a search expression into one or more parts, each of which - * satisfies the earliest of these conditions: - * - * - contains at least one indexed match node - * (the callback's 'indexed' is non-NULL), or - * - * - is limited to exactly one folder by a positive folder match node - * (the callback's 'mboxname' is non-NULL), or - * - * - applies to all folders and is not indexed - * (both the callback's 'mboxname' and 'indexed' are NULL) - * - * Destroys the original expression as a side effect. - * - * The callback function 'cb' is called one or more times with up to two - * expression trees which have just been detached from the original expression - * tree. Both of these trees will be in DNF and will be at most a - * conjuctive node, i.e. no disjunctions. - * - * The 'indexed' tree, if not NULL, contains all the indexed search terms. - * The 'scan' tree will never be NULL, although it may be a trivial tree - * comprising a single (true) node. It contains an expression that must be - * matched by every message reported by the index or the folder scan. - * - * The callback is responsible for freeing the expression using - * search_expr_free(). The callback may be called multiple times with - * the same folder/index combination, in which case the expressions should - * be considered logically ORed together. - * - * Does not require the input expression to be normalised, and may - * normalise it during processing. Expressions passed to the callback - * are always normalised. - */ -EXPORTED void search_expr_split_by_folder_and_index(search_expr_t *e, - void (*cb)(const char *, search_expr_t *, search_expr_t *, void *), - void *rock) -{ - search_expr_t *copy = NULL; - - if (!search_expr_apply(e, is_folder_or_indexed, NULL)) { - /* The expression contains neither a folder match node nor an - * indexable node, which means we can short circuit the whole - * normalisation and splitting process. This optimisation helps - * us cope with the FM web frontends generating queries with - * - * or or or or to "word" cc "word" bcc "word" from "word" subject "word" - * - * for every "word" the user types into the Search box, by - * avoiding the complexity explosion due to normalising all - * those OR nodes. - * - * But - we still sort it. */ - sort_children(e); - cb(NULL, NULL, e, rock); - return; - } - - copy = search_expr_duplicate(e); - nnodes = 0; - if (search_expr_normalise(©) < 0) - { - /* We blew the complexity limit because the expression has too - * many ORs. Rats. Give up and scan folders with the original - * expression */ - search_expr_free(copy); - cb(NULL, NULL, e, rock); - return; - } - - search_expr_free(e); - split(copy, cb, rock); -} - -static void split(search_expr_t *e, - void (*cb)(const char *, search_expr_t *, search_expr_t *, void *), - void *rock) -{ - search_expr_t *child; - - if (e->op == SEOP_OR) { - /* top level node */ - while ((child = detachp(&e->children)) != NULL) - split(child, cb, rock); - search_expr_free(e); - } - else if (e->op == SEOP_AND) { - search_expr_t **prevp; - search_expr_t **folder_prevp = NULL; - int nfolders = 0; - int nindexes = 0; - - for (prevp = &e->children ; *prevp ; prevp = &(*prevp)->next) { - if (is_indexed_node(*prevp)) { - nindexes++; - } - if (is_folder_node(*prevp)) { - nfolders++; - folder_prevp = prevp; - } - } - if (nindexes) { - /* - * The presence of indexable fields overrides all other - * considerations; we assume it's easier to consult the - * index and then filter out folders later. Note that this - * assumption is broken for SQUAT which is per-folder. - * - * We remove the indexed matches from the conjunction and - * build a new conjunction containing only those matches. - * Note that this assumes the search engine does not give - * false positives, which is also broken for SQUAT. - */ - search_expr_t *indexed = search_expr_new(NULL, SEOP_AND); - - for (prevp = &e->children ; *prevp ; ) { - if (is_indexed_node(*prevp)) - append(indexed, detachp(prevp)); - else - prevp = &(*prevp)->next; - } - search_expr_normalise(&indexed); /* in case of a trivial AND */ - if (!e->children) { - search_expr_free(e); - e = search_expr_new(NULL, SEOP_TRUE); - } - else - search_expr_normalise(&e); - cb(NULL, indexed, e, rock); - } - else if (!nfolders) { - /* No positive folder match: whole expression applies - * to all folders */ - cb(NULL, NULL, e, rock); - } - else if (nfolders == 1) { - /* Exactly one positive folder match: remainder of expression - * applies to this specific folder */ - child = detachp(folder_prevp); - /* normalise the remaining subtree - the top node might be - * trivial now that we've detached the folder match */ - search_expr_normalise(&e); - cb(child->value.s, NULL, e, rock); - search_expr_free(child); - } - else { - /* Two or more positive folder matches; this expression - * will never match any messages because messages belong - * to exactly one folder, so just delete it. - * TODO need to uniquify the (match folder) nodes - * before this will work properly */ - search_expr_free(e); - } - } - else if (is_folder_node(e)) { - cb(e->value.s, NULL, search_expr_new(NULL, SEOP_TRUE), rock); - search_expr_free(e); - } - else if (is_indexed_node(e)) { - cb(NULL, e, search_expr_new(NULL, SEOP_TRUE), rock); - } - else { - cb(NULL, NULL, e, rock); - } -} - -/* ====================================================================== */ - -static int search_string_match(message_t *m, const union search_value *v, - void *internalised, void *data1) -{ - int r; - struct buf buf = BUF_INITIALIZER; - int (*getter)(message_t *, struct buf *) = (int(*)(message_t *, struct buf *))data1; - comp_pat *pat = (comp_pat *)internalised; - - r = getter(m, &buf); - if (!r && buf.len) - r = charset_searchstring(v->s, pat, buf.s, buf.len, charset_flags); - else - r = 0; - buf_free(&buf); - - return r; -} - -static void search_string_serialise(struct buf *b, const union search_value *v) -{ - buf_printf(b, "\"%s\"", v->s); -} - -static int search_string_unserialise(struct protstream *prot, union search_value *v) -{ - int c; - char tmp1024; - - c = getseword(prot, tmp, sizeof(tmp)); - v->s = xstrdup(tmp); - return c; -} - -static void search_string_internalise(struct index_state *state __attribute__((unused)), - const union search_value *v, void **internalisedp) -{ - if (*internalisedp) { - charset_freepat(*internalisedp); - *internalisedp = NULL; - } - if (v) { - *internalisedp = charset_compilepat(v->s); - } -} - -static void search_string_duplicate(union search_value *new, - const union search_value *old) -{ - new->s = xstrdup(old->s); -} - -static void search_string_free(union search_value *v) -{ - free(v->s); - v->s = NULL; -} - -/* ====================================================================== */ - -static int search_listid_match(message_t *m, const union search_value *v, - void *internalised, - void *data1 __attribute__((unused))) -{ - int r; - struct buf buf = BUF_INITIALIZER; - comp_pat *pat = (comp_pat *)internalised; - - r = message_get_listid(m, &buf); - if (!r) { - r = charset_searchstring(v->s, pat, buf.s, buf.len, charset_flags); - if (r) goto out; // success - } - - r = message_get_mailinglist(m, &buf); - if (!r) { - r = charset_searchstring(v->s, pat, buf.s, buf.len, charset_flags); - if (r) goto out; // success - } - - r = 0; // failure - -out: - buf_free(&buf); - return r; -} - -/* ====================================================================== */ - -static int search_contenttype_match(message_t *m, const union search_value *v, - void *internalised, - void *data1 __attribute__((unused))) -{ - int r; - comp_pat *pat = (comp_pat *)internalised; - strarray_t types = STRARRAY_INITIALIZER; - int i; - char combined128; - - if (!message_get_leaf_types(m, &types)) { - for (i = 0 ; i < types.count ; i+= 2) { - const char *type = types.datai; - const char *subtype = types.datai+1; - - /* match against type */ - r = charset_searchstring(v->s, pat, type, strlen(type), charset_flags); - if (r) goto out; // success - - /* match against subtype */ - r = charset_searchstring(v->s, pat, subtype, strlen(subtype), charset_flags); - if (r) goto out; // success - - /* match against combined type_subtype */ - snprintf(combined, sizeof(combined), "%s_%s", type, subtype); - r = charset_searchstring(v->s, pat, combined, strlen(combined), charset_flags); - if (r) goto out; // success - } - } - - r = 0; // failure - -out: - strarray_fini(&types); - return r; -} - -/* ====================================================================== */ - -static int search_header_match(message_t *m, const union search_value *v, - void *internalised, void *data1) -{ - int r; - struct buf buf = BUF_INITIALIZER; - const char *field = (const char *)data1; - comp_pat *pat = (comp_pat *)internalised; - - r = message_get_field(m, field, - MESSAGE_DECODED|MESSAGE_APPEND|MESSAGE_MULTIPLE, - &buf); - if (!r) { - if (*v->s) { - r = charset_searchstring(v->s, pat, buf.s, buf.len, charset_flags); - } - else { - /* RFC3501: If the string to search is zero-length, this matches - * all messages that have a header line with the specified - * field-name regardless of the contents. */ - r = buf.len ? 1 : 0; - } - } - else - r = 0; - buf_free(&buf); - - return r; -} - -/* ====================================================================== */ - -static void internalise_sequence(const union search_value *v, - void **internalisedp, unsigned maxval) -{ - if (*internalisedp) { - seqset_free(*internalisedp); - *internalisedp = NULL; - } - if (v) { - *internalisedp = seqset_parse(v->s, NULL, maxval); - } -} - -static void search_msgno_internalise(struct index_state *state, - const union search_value *v, void **internalisedp) -{ - internalise_sequence(v, internalisedp, (state ? state->exists : 0)); -} - -static void search_uid_internalise(struct index_state *state, - const union search_value *v, void **internalisedp) -{ - internalise_sequence(v, internalisedp, (state ? state->last_uid : 0)); -} - -static int search_seq_match(message_t *m, - const union search_value *v __attribute__((unused)), - void *internalised, - void *data1) -{ - struct seqset *seq = internalised; - int r; - uint32_t u; - int (*getter)(message_t *, uint32_t *) = (int(*)(message_t *, uint32_t *))data1; - - r = getter(m, &u); - if (!r) - r = seqset_ismember(seq, u); - else - r = 0; - - return r; -} - -static void search_seq_serialise(struct buf *b, const union search_value *v) -{ - buf_appendcstr(b, v->s); -} - -/* ====================================================================== */ - -static int search_flags_match(message_t *m, const union search_value *v, - void *internalised __attribute__((unused)), - void *data1) -{ - int r; - uint32_t u; - int (*getter)(message_t *, uint32_t *) = (int(*)(message_t *, uint32_t *))data1; - - r = getter(m, &u); - if (!r) - r = !!(v->u & u); - else - r = 0; - - return r; -} - -static void search_systemflags_serialise(struct buf *b, const union search_value *v) -{ - if ((v->u & FLAG_ANSWERED)) - buf_appendcstr(b, "\\Answered"); - if ((v->u & FLAG_FLAGGED)) - buf_appendcstr(b, "\\Flagged"); - if ((v->u & FLAG_DELETED)) - buf_appendcstr(b, "\\Deleted"); - if ((v->u & FLAG_DRAFT)) - buf_appendcstr(b, "\\Draft"); - if ((v->u & FLAG_SEEN)) - buf_appendcstr(b, "\\Seen"); -} - -static int search_systemflags_unserialise(struct protstream *prot, union search_value *v) -{ - int c; - char tmp64; - - c = getseword(prot, tmp, sizeof(tmp)); - - if (!strcasecmp(tmp, "\\Answered")) - v->u = FLAG_ANSWERED; - else if (!strcasecmp(tmp, "\\Flagged")) - v->u = FLAG_FLAGGED; - else if (!strcasecmp(tmp, "\\Deleted")) - v->u = FLAG_DELETED; - else if (!strcasecmp(tmp, "\\Draft")) - v->u = FLAG_DRAFT; - else if (!strcasecmp(tmp, "\\Seen")) - v->u = FLAG_SEEN; - else - return EOF; - return c; -} - -static void search_indexflags_serialise(struct buf *b, const union search_value *v) -{ - if ((v->u & MESSAGE_SEEN)) - buf_appendcstr(b, "\\Seen"); - if ((v->u & MESSAGE_RECENT)) - buf_appendcstr(b, "\\Recent"); -} - -static int search_indexflags_unserialise(struct protstream *prot, union search_value *v) -{ - int c; - char tmp64; - - c = getseword(prot, tmp, sizeof(tmp)); - - if (!strcasecmp(tmp, "\\Seen")) - v->u = MESSAGE_SEEN; - else if (!strcasecmp(tmp, "\\Recent")) - v->u = MESSAGE_RECENT; - else - return EOF; - return c; -} - -unsigned int search_indexflags_get_countability(const union search_value *v) -{ - switch (v->u) { - case MESSAGE_SEEN: return SEC_SEEN; - case MESSAGE_RECENT: return SEC_RECENT; - default: return SEC_UNCOUNTED; - } -} - -/* ====================================================================== */ - -static void search_keyword_internalise(struct index_state *state, - const union search_value *v, - void **internalisedp) -{ - int r; - int num = 0; - - if (state) { - r = mailbox_user_flag(state->mailbox, v->s, &num, /*create*/0); - if (!r) - num++; - else - num = 0; - } - *internalisedp = (void*)(unsigned long)num; -} - -static int search_keyword_match(message_t *m, - const union search_value *v __attribute__((unused)), - void *internalised, - void *data1 __attribute__((unused))) -{ - int r; - int num = (int)(unsigned long)internalised; - uint32_t flagsMAX_USER_FLAGS/32; - - if (!num) - return 0; /* not a valid flag for this mailbox */ - num--; - - r = message_get_userflags(m, flags); - if (!r) - r = !!(flagsnum/32 & (1<<(num % 32))); - else - r = 0; - - return r; -} - -/* ====================================================================== */ - -static int search_uint64_cmp(message_t *m, const union search_value *v, - void *internalised __attribute__((unused)), - void *data1) -{ - int r; - uint64_t u; - int (*getter)(message_t *, uint64_t *) = (int(*)(message_t *, uint64_t *))data1; - - r = getter(m, &u); - if (!r) { - if (u < v->u) - r = -1; - else if (u == v->u) - r = 0; - else - r = 1; - } - else - r = 0; - return r; -} - -static int search_uint64_match(message_t *m, const union search_value *v, - void *internalised __attribute__((unused)), - void *data1) -{ - int r; - uint64_t u; - int (*getter)(message_t *, uint64_t *) = (int(*)(message_t *, uint64_t *))data1; - - r = getter(m, &u); - if (!r) - r = (v->u == u); - else - r = 0; - - return r; -} - -static void search_uint64_serialise(struct buf *b, const union search_value *v) -{ - buf_printf(b, "%llu", (unsigned long long)v->u); -} - -static int search_uint64_unserialise(struct protstream *prot, union search_value *v) -{ - int c; - char tmp32; - - c = getseword(prot, tmp, sizeof(tmp)); - v->u = strtoull(tmp, NULL, 10); - return c; -} - -/* ====================================================================== */ - -static void search_cid_serialise(struct buf *b, const union search_value *v) -{ - buf_appendcstr(b, conversation_id_encode(v->u)); -} - -static int search_cid_unserialise(struct protstream *prot, union search_value *v) -{ - int c; - conversation_id_t cid; - char tmp32; - - c = getseword(prot, tmp, sizeof(tmp)); - if (!conversation_id_decode(&cid, tmp)) - return EOF; - v->u = cid; - return c; -} - -/* ====================================================================== */ - -static void search_folder_internalise(struct index_state *state, - const union search_value *v, - void **internalisedp) -{ - if (state) - *internalisedp = (void *)(unsigned long)(!strcmp(state->mailbox->name, v->s)); -} - -static int search_folder_match(message_t *m __attribute__((unused)), - const union search_value *v __attribute__((unused)), - void *internalised, void *data1 __attribute__((unused))) -{ - return (int)(unsigned long)internalised; -} - -unsigned int search_folder_get_countability(const union search_value *v - __attribute__((unused))) -{ - return 0; -} - -/* ====================================================================== */ - -static void search_annotation_internalise(struct index_state *state, - const union search_value *v __attribute__((unused)), - void **internalisedp) -{ - if (state) - *internalisedp = state->mailbox; -} - -struct search_annot_rock { - int result; - const struct buf *match; -}; - -static int _search_annot_match(const struct buf *match, - const struct buf *value) -{ - /* These cases are not explicitly defined in RFC5257 */ - - /* NIL matches NIL and nothing else */ - if (match->s == NULL) - return (value->s == NULL); - if (value->s == NULL) - return 0; - - /* empty matches empty and nothing else */ - if (match->len == 0) - return (value->len == 0); - if (value->len == 0) - return 0; - - /* RFC5257 seems to define a simple CONTAINS style search */ - return !!memmem(value->s, value->len, - match->s, match->len); -} - -static void _search_annot_callback(const char *mboxname __attribute__((unused)), - uint32_t uid __attribute__((unused)), - const char *entry __attribute__((unused)), - struct attvaluelist *attvalues, void *rock) -{ - struct search_annot_rock *sarock = rock; - struct attvaluelist *l; - - for (l = attvalues ; l ; l = l->next) { - if (_search_annot_match(sarock->match, &l->value)) - sarock->result = 1; - } -} - -static int search_annotation_match(message_t *m, const union search_value *v, - void *internalised, void *data1 __attribute__((unused))) -{ - struct mailbox *mailbox = (struct mailbox *)internalised; - struct searchannot *sa = v->annot; - strarray_t entries = STRARRAY_INITIALIZER; - strarray_t attribs = STRARRAY_INITIALIZER; - annotate_state_t *astate = NULL; - struct search_annot_rock rock; - uint32_t uid; - int r; - - strarray_append(&entries, sa->entry); - strarray_append(&attribs, sa->attrib); - - message_get_uid(m, &uid); - - r = mailbox_get_annotate_state(mailbox, uid, &astate); - if (r) goto out; - annotate_state_set_auth(astate, sa->isadmin, - sa->userid, sa->auth_state); - - memset(&rock, 0, sizeof(rock)); - rock.match = &sa->value; - - r = annotate_state_fetch(astate, - &entries, &attribs, - _search_annot_callback, &rock, - 0); - if (r >= 0) - r = rock.result; - -out: - strarray_fini(&entries); - strarray_fini(&attribs); - return r; -} - -static void search_annotation_serialise(struct buf *b, const union search_value *v) -{ - buf_printf(b, "(entry \"%s\" attrib \"%s\" value \"%s\")", - v->annot->entry, v->annot->attrib, buf_cstring(&v->annot->value)); -} - -/* Note: this won't be usable for execution as it lacks - * namespace etc pointers. Nor can it handle binary values. */ -static int search_annotation_unserialise(struct protstream *prot, union search_value *v) -{ - int c; - char tmp64; - char entry1024; - char attrib1024; - char value1024; - - c = prot_getc(prot); - if (c != '(') return EOF; - - c = getseword(prot, tmp, sizeof(tmp)); - if (c != ' ') return EOF; - if (strcmp(tmp, "entry")) return EOF; - c = getseword(prot, entry, sizeof(entry)); - if (c != ' ') return EOF; - - c = getseword(prot, tmp, sizeof(tmp)); - if (c != ' ') return EOF; - if (strcmp(tmp, "attrib")) return EOF; - c = getseword(prot, attrib, sizeof(attrib)); - if (c != ' ') return EOF; - - c = getseword(prot, tmp, sizeof(tmp)); - if (c != ' ') return EOF; - if (strcmp(tmp, "value")) return EOF; - c = getseword(prot, value, sizeof(value)); - if (c != ')') return EOF; - - v->annot = (struct searchannot *)xzmalloc(sizeof(struct searchannot)); - v->annot->entry = xstrdup(entry); - v->annot->attrib = xstrdup(attrib); - buf_appendcstr(&v->annot->value, value); - buf_cstring(&v->annot->value); - - c = prot_getc(prot); - return c; -} - -static void search_annotation_duplicate(union search_value *new, - const union search_value *old) -{ - new->annot = (struct searchannot *)xmemdup(old->annot, sizeof(*old->annot)); - new->annot->entry = xstrdup(new->annot->entry); - new->annot->attrib = xstrdup(new->annot->attrib); - buf_init(&new->annot->value); - buf_append(&new->annot->value, &old->annot->value); -} - -static void search_annotation_free(union search_value *v) -{ - if (v->annot) { - free(v->annot->entry); - free(v->annot->attrib); - buf_free(&v->annot->value); - free(v->annot); - v->annot = NULL; - } -} - -/* ====================================================================== */ - -struct conv_rock { - struct conversations_state *cstate; - int cstate_is_ours; - int num; /* -1=invalid, 0=\Seen, 1+=index into cstate->counted_flags+1 */ -}; - -static void conv_rock_new(struct mailbox *mailbox, - struct conv_rock **rockp); -static void conv_rock_free(struct conv_rock **rockp); - -static void search_convflags_internalise(struct index_state *state, - const union search_value *v, - void **internalisedp) -{ - struct conv_rock **rockp = (struct conv_rock **)internalisedp; - struct conv_rock *rock; - - conv_rock_free(rockp); - - if (state) { - conv_rock_new(state->mailbox, rockp); - rock = *rockp; - if (rock->cstate) { - if (!strcasecmp(v->s, "\\Seen")) - rock->num = 0; - else { - rock->num = strarray_find_case(rock->cstate->counted_flags, v->s, 0); - /* rock->num might be -1 invalid */ - if (rock->num >= 0) - rock->num++; - } - } - } -} - -static int search_convflags_match(message_t *m, - const union search_value *v __attribute__((unused)), - void *internalised, - void *data1 __attribute__((unused))) -{ - struct conv_rock *rock = (struct conv_rock *)internalised; - conversation_id_t cid = NULLCONVERSATION; - conversation_t *conv = NULL; - int r = 0; /* invalid flag name */ - - if (!rock->cstate) return 0; - - message_get_cid(m, &cid); - if (conversation_load(rock->cstate, cid, &conv)) return 0; - if (!conv) return 0; - - if (rock->num == 0) - r = !conv->unseen; - else if (rock->num > 0) - r = !!conv->countsrock->num-1; - - conversation_free(conv); - return r; -} - -unsigned int search_convflags_get_countability(const union search_value *v) -{ - if (!strcasecmp(v->s, "\\Seen")) - return SEC_CONVSEEN; - return SEC_UNCOUNTED; -} - -static void search_convmodseq_internalise(struct index_state *state, - const union search_value *v __attribute__((unused)), - void **internalisedp) -{ - struct conv_rock **rockp = (struct conv_rock **)internalisedp; - - conv_rock_free(rockp); - - if (state) { - conv_rock_new(state->mailbox, rockp); - } -} - -static int search_convmodseq_match(message_t *m, const union search_value *v, - void *internalised, void *data1 __attribute__((unused))) -{ - struct conv_rock *rock = (struct conv_rock *)internalised; - conversation_id_t cid = NULLCONVERSATION; - conversation_t *conv = NULL; - int r; - - if (!rock->cstate) return 0; - - message_get_cid(m, &cid); - if (conversation_load(rock->cstate, cid, &conv)) return 0; - if (!conv) return 0; - - r = (v->u == conv->modseq); - - conversation_free(conv); - return r; -} - -static void conv_rock_new(struct mailbox *mailbox, - struct conv_rock **rockp) -{ - struct conv_rock *rock = xzmalloc(sizeof(*rock)); - - rock->cstate = conversations_get_mbox(mailbox->name); - if (!rock->cstate) { - if (conversations_open_mbox(mailbox->name, &rock->cstate)) - rock->num = -1; /* invalid */ - else - rock->cstate_is_ours = 1; - } - - *rockp = rock; -} - -static void conv_rock_free(struct conv_rock **rockp) -{ - struct conv_rock *rock = *rockp; - if (rock) { - if (rock->cstate_is_ours) - conversations_abort(&rock->cstate); - free(rock); - *rockp = NULL; - } -} - - -/* ====================================================================== */ - -static int search_uint32_cmp(message_t *m, const union search_value *v, - void *internalised __attribute__((unused)), - void *data1) -{ - int r; - uint32_t u; - int (*getter)(message_t *, uint32_t *) = (int(*)(message_t *, uint32_t *))data1; - - r = getter(m, &u); - if (!r) { - if (u < v->u) - r = -1; - else if (u == v->u) - r = 0; - else - r = 1; - } - else - r = 0; - return r; -} - -static int search_uint32_match(message_t *m, const union search_value *v, - void *internalised __attribute__((unused)), - void *data1) -{ - int r; - uint32_t u; - int (*getter)(message_t *, uint32_t *) = (int(*)(message_t *, uint32_t *))data1; - - r = getter(m, &u); - if (!r) - r = (v->u == u); - else - r = 0; - return r; -} - -static void search_uint32_serialise(struct buf *b, const union search_value *v) -{ - buf_printf(b, "%u", (uint32_t)v->u); -} - -static int search_uint32_unserialise(struct protstream *prot, union search_value *v) -{ - int c; - char tmp32; - - c = getseword(prot, tmp, sizeof(tmp)); - v->u = strtoul(tmp, NULL, 10); - return c; -} - -/* ====================================================================== */ - -/* - * Search part of a message for a substring. - */ - -struct searchmsg_rock -{ - const char *substr; - comp_pat *pat; - int skipheader; - int result; -}; - -static int searchmsg_cb(int partno, int charset, int encoding, - const char *subtype __attribute((unused)), - struct buf *data, void *rock) -{ - struct searchmsg_rock *sr = (struct searchmsg_rock *)rock; - - if (!partno) { - /* header-like */ - if (sr->skipheader) { - sr->skipheader = 0; /* Only skip top-level message header */ - return 0; - } - sr->result = charset_search_mimeheader(sr->substr, sr->pat, - buf_cstring(data), charset_flags); - } - else { - /* body-like */ - if (charset < 0 || charset == 0xffff) - return 0; - sr->result = charset_searchfile(sr->substr, sr->pat, - data->s, data->len, - charset, encoding, charset_flags); - } - if (sr->result) return 1; /* found it, exit early */ - return 0; -} - -static int search_text_match(message_t *m, const union search_value *v, - void *internalised, void *data1) -{ - struct searchmsg_rock sr; - - sr.substr = v->s; - sr.pat = (comp_pat *)internalised; - sr.skipheader = (int)(unsigned long)data1; - sr.result = 0; - message_foreach_text_section(m, searchmsg_cb, &sr); - return sr.result; -} - -/* ====================================================================== */ - -static hash_table attrs_by_name = HASH_TABLE_INITIALIZER; - -enum search_cost { - SEARCH_COST_NONE = 0, - SEARCH_COST_INDEX, - SEARCH_COST_CONV, - SEARCH_COST_ANNOT, - SEARCH_COST_CACHE, - SEARCH_COST_BODY -}; - -/* - * Call search_attr_init() before doing any work with search - * expressions. - */ -EXPORTED void search_attr_init(void) -{ - unsigned int i; - - static const search_attr_t attrs = { - { - "bcc", - SEA_FUZZABLE, - SEARCH_PART_BCC, - SEARCH_COST_CACHE, - search_string_internalise, - /*cmp*/NULL, - search_string_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - (void *)message_get_bcc - },{ - "cc", - SEA_FUZZABLE, - SEARCH_PART_CC, - SEARCH_COST_CACHE, - search_string_internalise, - /*cmp*/NULL, - search_string_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - (void *)message_get_cc - },{ - "from", - SEA_FUZZABLE, - SEARCH_PART_FROM, - SEARCH_COST_CACHE, - search_string_internalise, - /*cmp*/NULL, - search_string_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - (void *)message_get_from - },{ - "message-id", - /*flags*/0, - SEARCH_PART_NONE, - SEARCH_COST_CACHE, - search_string_internalise, - /*cmp*/NULL, - search_string_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - (void *)message_get_messageid - },{ - "listid", - SEA_FUZZABLE, - SEARCH_PART_LISTID, - SEARCH_COST_CACHE, - search_string_internalise, - /*cmp*/NULL, - search_listid_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - NULL - },{ - "contenttype", - SEA_FUZZABLE, - SEARCH_PART_TYPE, - SEARCH_COST_CACHE, - search_string_internalise, - /*cmp*/NULL, - search_contenttype_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - NULL - },{ - "subject", - SEA_FUZZABLE, - SEARCH_PART_SUBJECT, - SEARCH_COST_CACHE, - search_string_internalise, - /*cmp*/NULL, - search_string_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - (void *)message_get_subject - },{ - "to", - SEA_FUZZABLE, - SEARCH_PART_TO, - SEARCH_COST_CACHE, - search_string_internalise, - /*cmp*/NULL, - search_string_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - (void *)message_get_to - },{ - "msgno", - SEA_MUTABLE, - SEARCH_PART_NONE, - SEARCH_COST_INDEX, - search_msgno_internalise, - /*cmp*/NULL, - search_seq_match, - search_seq_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - (void *)message_get_msgno - },{ - "uid", - /*flags*/0, - SEARCH_PART_NONE, - SEARCH_COST_INDEX, - search_uid_internalise, - /*cmp*/NULL, - search_seq_match, - search_seq_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - (void *)message_get_uid - },{ - "systemflags", - SEA_MUTABLE, - SEARCH_PART_NONE, - SEARCH_COST_INDEX, - /*internalise*/NULL, - /*cmp*/NULL, - search_flags_match, - search_systemflags_serialise, - search_systemflags_unserialise, - /*get_countability*/NULL, - /*duplicate*/NULL, - /*free*/NULL, - (void *)message_get_systemflags - },{ - "indexflags", - SEA_MUTABLE, - SEARCH_PART_NONE, - SEARCH_COST_INDEX, - /*internalise*/NULL, - /*cmp*/NULL, - search_flags_match, - search_indexflags_serialise, - search_indexflags_unserialise, - search_indexflags_get_countability, - /*duplicate*/NULL, - /*free*/NULL, - (void *)message_get_indexflags - },{ - "keyword", - SEA_MUTABLE, - SEARCH_PART_NONE, - SEARCH_COST_INDEX, - search_keyword_internalise, - /*cmp*/NULL, - search_keyword_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - NULL - },{ - "convflags", - SEA_MUTABLE, - SEARCH_PART_NONE, - SEARCH_COST_CONV, - search_convflags_internalise, - /*cmp*/NULL, - search_convflags_match, - search_string_serialise, - search_string_unserialise, - search_convflags_get_countability, - search_string_duplicate, - search_string_free, - NULL - },{ - "convmodseq", - SEA_MUTABLE, - SEARCH_PART_NONE, - SEARCH_COST_CONV, - search_convmodseq_internalise, - /*cmp*/NULL, - search_convmodseq_match, - search_uint64_serialise, - search_uint64_unserialise, - /*get_countability*/NULL, - /*duplicate*/NULL, - /*free*/NULL, - NULL - },{ - "modseq", - SEA_MUTABLE, - SEARCH_PART_NONE, - SEARCH_COST_INDEX, - /*internalise*/NULL, - search_uint64_cmp, - search_uint64_match, - search_uint64_serialise, - search_uint64_unserialise, - /*get_countability*/NULL, - /*duplicate*/NULL, - /*free*/NULL, - (void *)message_get_modseq - },{ - "cid", - SEA_MUTABLE, - SEARCH_PART_NONE, - SEARCH_COST_INDEX, - /*internalise*/NULL, - search_uint64_cmp, - search_uint64_match, - search_cid_serialise, - search_cid_unserialise, - /*get_countability*/NULL, - /*duplicate*/NULL, - /*free*/NULL, - (void *)message_get_cid - },{ - "folder", - /*flags*/0, - SEARCH_PART_NONE, - SEARCH_COST_NONE, - search_folder_internalise, - /*cmp*/NULL, - search_folder_match, - search_string_serialise, - search_string_unserialise, - search_folder_get_countability, - search_string_duplicate, - search_string_free, - (void *)NULL - },{ - "annotation", - SEA_MUTABLE, - SEARCH_PART_NONE, - SEARCH_COST_ANNOT, - search_annotation_internalise, - /*cmp*/NULL, - search_annotation_match, - search_annotation_serialise, - search_annotation_unserialise, - /*get_countability*/NULL, - search_annotation_duplicate, - search_annotation_free, - (void *)NULL - },{ - "size", - /*flags*/0, - SEARCH_PART_NONE, - SEARCH_COST_INDEX, - /*internalise*/NULL, - search_uint32_cmp, - search_uint32_match, - search_uint32_serialise, - search_uint32_unserialise, - /*get_countability*/NULL, - /*duplicate*/NULL, - /*free*/NULL, - (void *)message_get_size - },{ - "internaldate", - /*flags*/0, - SEARCH_PART_NONE, - SEARCH_COST_INDEX, - /*internalise*/NULL, - search_uint32_cmp, - search_uint32_match, - search_uint32_serialise, - search_uint32_unserialise, - /*get_countability*/NULL, - /*duplicate*/NULL, - /*free*/NULL, - (void *)message_get_internaldate - },{ - "sentdate", - /*flags*/0, - SEARCH_PART_NONE, - SEARCH_COST_INDEX, - /*internalise*/NULL, - search_uint32_cmp, - search_uint32_match, - search_uint32_serialise, - search_uint32_unserialise, - /*get_countability*/NULL, - /*duplicate*/NULL, - /*free*/NULL, - (void *)message_get_sentdate - },{ - "body", - SEA_FUZZABLE, - SEARCH_PART_BODY, - SEARCH_COST_BODY, - search_string_internalise, - /*cmp*/NULL, - search_text_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - (void *)1 /* skipheader flag */ - },{ - "text", - SEA_FUZZABLE, - SEARCH_PART_ANY, - SEARCH_COST_BODY, - search_string_internalise, - /*cmp*/NULL, - search_text_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - (void *)0 /* skipheader flag */ - } - }; - - construct_hash_table(&attrs_by_name, VECTOR_SIZE(attrs), 0); - for (i = 0 ; i < VECTOR_SIZE(attrs) ; i++) - hash_insert(attrsi.name, (void *)&attrsi, &attrs_by_name); -} - -/* - * Find and return a search attribute by name. Used when building - * comparison nodes in a search expression tree. Name comparison is - * case insensitive. Returns a pointer to static data or NULL if there - * is no attribute of the given name. - */ -EXPORTED const search_attr_t *search_attr_find(const char *name) -{ - char tmp128; - - strlcpy(tmp, name, sizeof(tmp)); - lcase(tmp); - return hash_lookup(tmp, &attrs_by_name); -} - -/* - * Find and return a search attribute for the named header field. Used - * when building comparison nodes for the HEADER search criterion in a - * search expression tree. Field name comparison is case insensitive. - * Returns a pointer to internally managed data or NULL if there is no - * attribute of the given name. - */ -EXPORTED const search_attr_t *search_attr_find_field(const char *field) -{ - search_attr_t *attr; - char *key = NULL; - static const search_attr_t proto = { - "name", - SEA_FUZZABLE, - SEARCH_PART_NONE, - SEARCH_COST_NONE, - search_string_internalise, - /*cmp*/NULL, - search_header_match, - search_string_serialise, - search_string_unserialise, - /*get_countability*/NULL, - search_string_duplicate, - search_string_free, - NULL - }; - - /* some header fields can be reduced to search terms */ - if (!strcasecmp(field, "bcc") || - !strcasecmp(field, "cc") || - !strcasecmp(field, "to") || - !strcasecmp(field, "from") || - !strcasecmp(field, "subject") || - !strcasecmp(field, "message-id")) - return search_attr_find(field); - - key = lcase(strconcat("header:", field, (char *)NULL)); - attr = (search_attr_t *)hash_lookup(key, &attrs_by_name); - - if (!attr) { - attr = (search_attr_t *)xzmalloc(sizeof(search_attr_t)); - *attr = proto; - attr->name = key; - attr->part = (config_getswitch(IMAPOPT_SEARCH_INDEX_HEADERS) - ? SEARCH_PART_HEADERS : -1); - attr->data1 = strchr(key, ':')+1; - hash_insert(attr->name, (void *)attr, &attrs_by_name); - key = NULL; /* attr takes this over */ - } - - free(key); - return attr; -} - -/* - * Return non-zero if the given attribute may be used with a - * SEOP_FUZZYMATCH operation. - */ -EXPORTED int search_attr_is_fuzzable(const search_attr_t *attr) -{ - return (attr->part != SEARCH_PART_NONE && - (attr->flags & SEA_FUZZABLE)); -} -
View file
cyrus-imapd-2.5.tar.gz/imap/search_expr.h
Deleted
@@ -1,154 +0,0 @@ -/* search_expr.h -- search query tree handling for SEARCH - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * $Id: search_engines.h,v 1.4 2010/01/06 17:01:40 murch Exp $ - */ - -#ifndef __CYRUS_SEARCH_EXPR_H__ -#define __CYRUS_SEARCH_EXPR_H__ - -#include "message.h" -#include "util.h" - -struct protstream; -struct index_state; - -enum search_op { - SEOP_UNKNOWN, - SEOP_TRUE, - SEOP_FALSE, - - /* operators for ordinal types: dates, sizes */ - SEOP_LT, - SEOP_LE, - SEOP_GT, - SEOP_GE, - - /* operators for nonordinal types: strings, uid sequences */ - SEOP_MATCH, - SEOP_FUZZYMATCH, - - SEOP_AND, - SEOP_OR, - SEOP_NOT, -}; - -union search_value { - uint64_t u; - char *s; - struct searchannot *annot; -}; - -/* search_attr.flags */ -enum { - SEA_MUTABLE = (1<<0), - SEA_FUZZABLE = (1<<1), -}; - -typedef struct search_attr search_attr_t; -struct search_attr { - const char *name; - int flags; - int part; - int cost; - void (*internalise)(struct index_state *, const union search_value *, - void **internalisedp); - int (*cmp)(message_t *, const union search_value *, void *internalised, void *data1); - int (*match)(message_t *, const union search_value *, void *internalised, void *data1); - void (*serialise)(struct buf *, const union search_value *); - int (*unserialise)(struct protstream*, union search_value *); - unsigned int (*get_countability)(const union search_value *); - void (*duplicate)(union search_value *, const union search_value *); - void (*free)(union search_value *); - void *data1; /* extra data for the functions above */ -}; - -typedef struct search_expr search_expr_t; -struct search_expr { - enum search_op op; - search_expr_t *parent; - search_expr_t *next; - search_expr_t *children; - const search_attr_t *attr; - union search_value value; - void *internalised; -}; - -/* flags for search_expr_get_countability */ -enum { - SEC_EXISTS = (1<<0), - SEC_RECENT = (1<<1), - SEC_SEEN = (1<<2), - SEC_CONVSEEN = (1<<3), - SEC_NOT = (1<<29), - SEC_UNCOUNTED = (1<<30), -}; - -extern search_expr_t *search_expr_new(search_expr_t *parent, - enum search_op); -extern void search_expr_append(search_expr_t *parent, search_expr_t *child); -extern void search_expr_free(search_expr_t *); -extern search_expr_t *search_expr_duplicate(const search_expr_t *e); -extern int search_expr_apply(search_expr_t *e, - int (*cb)(search_expr_t *e, void *rock), - void *rock); -extern char *search_expr_serialise(const search_expr_t *); -extern search_expr_t *search_expr_unserialise(const char *s); -extern int search_expr_normalise(search_expr_t **); -extern void search_expr_internalise(struct index_state *, search_expr_t *); -extern int search_expr_evaluate(message_t *m, const search_expr_t *); -extern int search_expr_uses_attr(const search_expr_t *, const char *); -extern int search_expr_is_mutable(const search_expr_t *); -extern unsigned int search_expr_get_countability(const search_expr_t *); -extern void search_expr_neutralise(search_expr_t *); -extern void search_expr_split_by_folder_and_index(search_expr_t *e, - void (*cb)(const char *mboxname, - search_expr_t *indexed, - search_expr_t *scan, - void *rock), - void *rock); - -extern void search_attr_init(void); -extern const search_attr_t *search_attr_find(const char *); -extern const search_attr_t *search_attr_find_field(const char *field); -extern int search_attr_is_fuzzable(const search_attr_t *); - -#endif
View file
cyrus-imapd-2.5.tar.gz/imap/search_query.c
Deleted
@@ -1,869 +0,0 @@ -/* search_result.c -- search result data structure - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> - -#include <sys/types.h> -#include <stdlib.h> -#include <syslog.h> -#include <string.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -#include "assert.h" -#include "imap_err.h" -#include "search_expr.h" -#include "search_query.h" -#include "imapd.h" -#include "message.h" -#include "annotate.h" -#include "global.h" -#include "bsearch.h" -#include "xstrlcpy.h" -#include "xmalloc.h" - -/* ====================================================================== */ - -EXPORTED search_query_t *search_query_new(struct index_state *state, - struct searchargs *searchargs) -{ - search_query_t *query; - - query = xzmalloc(sizeof(*query)); - query->state = state; - query->searchargs = searchargs; - construct_hash_table(&query->subs_by_folder, 128, 0); - construct_hash_table(&query->subs_by_indexed, 128, 0); - ptrarray_init(&query->merged_msgdata); - construct_hash_table(&query->folders_by_name, 128, 0); - ptrarray_init(&query->folders_by_id); - - return query; -} - -static void folder_free(void *data) -{ - search_folder_t *folder = data; - - free(folder->mboxname); - bv_free(&folder->uids); - bv_free(&folder->unchecked_uids); - free(folder); -} - -static void subquery_free(void *data) -{ - search_subquery_t *sub = data; - - free(sub->mboxname); - search_expr_free(sub->indexed); - search_expr_free(sub->expr); - free(sub); -} - -EXPORTED void search_query_free(search_query_t *query) -{ - int i; - - if (!query) return; - free_hash_table(&query->subs_by_folder, subquery_free); - free_hash_table(&query->subs_by_indexed, subquery_free); - search_expr_free(query->global_sub.expr); - ptrarray_fini(&query->folders_by_id); - free_hash_table(&query->folders_by_name, folder_free); - ptrarray_fini(&query->merged_msgdata); - - /* free pending MsgData arrays */ - for (i = 0 ; i < query->saved_msgdata.count ; i++) { - struct search_saved_msgdata *saved = ptrarray_nth(&query->saved_msgdata, i); - index_msgdata_free(saved->msgdata, saved->n); - free(saved); - } - ptrarray_fini(&query->saved_msgdata); - - free(query); -} - -/* ====================================================================== */ - - -/* - * Find the named folder folder. Returns NULL if there are no - * search results for that folder. - */ -EXPORTED search_folder_t *search_query_find_folder(search_query_t *query, - const char *mboxname) -{ - return (search_folder_t *)hash_lookup(mboxname, &query->folders_by_name); -} - -/* - * Switch the folder over to reporting MSNs rather than UIDs. - */ -EXPORTED void search_folder_use_msn(search_folder_t *folder, struct index_state *state) -{ - int uid; - unsigned msgno; - bitvector_t msns = BV_INITIALIZER; - - search_folder_foreach(folder, uid) { - msgno = index_finduid(state, uid); - if (index_getuid(state, msgno) == (unsigned)uid) - bv_set(&msns, msgno); - } - bv_free(&folder->uids); - folder->uids = msns; -} - -/* - * Return the results for the given folder as a sequence of UIDs (or - * MSNs if search_folder_use_msn() has been called). The caller is - * responsible for freeing the result using seqset_free() - */ -EXPORTED struct seqset *search_folder_get_seqset(const search_folder_t *folder) -{ - struct seqset *seq = seqset_init(0, SEQ_SPARSE); - int uid; - - for (uid = bv_next_set(&folder->uids, 0) ; - uid != -1 ; - uid = bv_next_set(&folder->uids, uid+1)) - seqset_add(seq, uid, 1); - - return seq; -} - -/* - * Return the results for a given folder as an array of UIDs (or MSNs if - * search_folder_use_msn() has been called). Returns the number of - * results or zero, and the newly allocated array in *'arrayp'. The - * caller is responsible for freeing the result using free(). - */ -EXPORTED int search_folder_get_array(const search_folder_t *folder, unsigned int **arrayp) -{ - int n = search_folder_get_count(folder); - unsigned int *p; - int uid; - - if (n) { - - p = *arrayp = xzmalloc(sizeof(unsigned int) * n); - for (uid = bv_next_set(&folder->uids, 0) ; - uid != -1 ; - uid = bv_next_set(&folder->uids, uid+1)) - *p++ = (unsigned)uid; - } - - return n; -} - -/* - * Return the minimum UID (or MSN if search_folder_use_msn() has been - * called). - */ -EXPORTED uint32_t search_folder_get_min(const search_folder_t *folder) -{ - return bv_first_set(&folder->uids); -} - -/* - * Return the maximum UID (or MSN if search_folder_use_msn() has been - * called). - */ -EXPORTED uint32_t search_folder_get_max(const search_folder_t *folder) -{ - return bv_last_set(&folder->uids); -} - -/* - * Returns the count of UIDs or MSNs. - */ -EXPORTED unsigned int search_folder_get_count(const search_folder_t *folder) -{ - return bv_count(&folder->uids); -} - -EXPORTED uint64_t search_folder_get_highest_modseq(const search_folder_t *folder) -{ - return folder->highest_modseq; -} - -/* ====================================================================== */ - -static search_folder_t *query_get_folder(search_query_t *query, const char *mboxname) -{ - search_folder_t *folder; - - folder = (search_folder_t *)hash_lookup(mboxname, &query->folders_by_name); - if (!folder) { - folder = xzmalloc(sizeof(*folder)); - folder->mboxname = xstrdup(mboxname); - folder->id = -1; - hash_insert(folder->mboxname, folder, &query->folders_by_name); - } - return folder; -} - -static search_folder_t *query_get_valid_folder(search_query_t *query, - const char *mboxname, - uint32_t uidvalidity) -{ - search_folder_t *folder; - - if (mboxname_isdeletedmailbox(mboxname, 0)) - return NULL; - - folder = query_get_folder(query, mboxname); - - if (uidvalidity < folder->uidvalidity) { - /* these are uids are too old, forget them */ - return NULL; - } - if (uidvalidity > folder->uidvalidity) { - /* these uids are newer than what we have, - * forget the old ones; or none at all and - * remember the uidvalidity for later */ - bv_clearall(&folder->uids); - bv_clearall(&folder->unchecked_uids); - folder->uidvalidity = uidvalidity; - } - - return folder; -} - -static void folder_add_uid(search_folder_t *folder, uint32_t uid) -{ - bv_set(&folder->uids, uid); -} - -static void folder_add_modseq(search_folder_t *folder, uint64_t modseq) -{ - if (modseq > folder->highest_modseq) - folder->highest_modseq = modseq; -} - -static int query_begin_index(search_query_t *query, - const char *mboxname, - struct index_state **statep) -{ - int r = 0; - int needs_refresh = 0; - - /* open an index_state */ - if (!strcmp(index_mboxname(query->state), mboxname)) { - *statep = query->state; - needs_refresh = 1; - } - else { - struct index_init init; - - memset(&init, 0, sizeof(struct index_init)); - init.userid = query->searchargs->userid; - init.authstate = query->searchargs->authstate; - init.out = query->state->out; - - r = index_open(mboxname, &init, statep); - if (r == IMAP_PERMISSION_DENIED) r = IMAP_MAILBOX_NONEXISTENT; - if (r == IMAP_MAILBOX_BADTYPE) r = IMAP_MAILBOX_NONEXISTENT; - if (r) goto out; - - index_checkflags(*statep, 0, 0); - } - - if (query->need_expunge) { - /* make sure \Deleted messages are expunged. Will also lock the - * mailbox state and read any new information */ - r = index_expunge(*statep, NULL, 1); - if (r) goto out; - } - else if (needs_refresh) { - /* Expunge considered unhelpful - just refresh */ - r = index_refresh(*statep); - if (r) goto out; - } - - r = cmd_cancelled(); - -out: - return r; -} - -static void query_end_index(search_query_t *query, - struct index_state **statep) -{ - if (*statep != query->state) - index_close(statep); - else - *statep = NULL; -} - -/* ====================================================================== */ - -static void add_folder(const char *key __attribute__((unused)), - void *data, void *rock) -{ - search_folder_t *folder = data; - ptrarray_t *array = rock; - - ptrarray_append(array, folder); -} - -static int compare_folders(const void **v1, const void **v2) -{ - const search_folder_t *f1 = (const search_folder_t *)*v1; - const search_folder_t *f2 = (const search_folder_t *)*v2; - - return bsearch_compare_mbox(f1->mboxname, f2->mboxname); -} - -/* - * Assign a contiguous 0-based sequence of folder ids to the folders - * that have any remaining uids in the search results, in folder name - * order. The order isn't necessary but helps make the results - * consistent which makes testing easier. - */ -static void query_assign_folder_ids(search_query_t *query) -{ - ptrarray_t folders = PTRARRAY_INITIALIZER; - int i; - - /* TODO: need a hash_values() function */ - hash_enumerate(&query->folders_by_name, add_folder, &folders); - - ptrarray_sort(&folders, compare_folders); - - for (i = 0 ; i < folders.count ; i++) { - search_folder_t *folder = ptrarray_nth(&folders, i); - - if (search_folder_get_count(folder) && folder->id < 0) { - folder->id = query->folders_by_id.count; - ptrarray_append(&query->folders_by_id, folder); - } - } - - ptrarray_fini(&folders); -} - -/* ====================================================================== */ - -static void query_load_msgdata(search_query_t *query, - search_folder_t *folder, - struct index_state *state, - unsigned *msgno_list, unsigned nmsgs) -{ - unsigned i; - MsgData **msgdata; - struct search_saved_msgdata *saved; - - msgdata = index_msgdata_load(state, msgno_list, nmsgs, query->sortcrit, 0, NULL); - - /* add the new messages to the global list */ - for (i = 0 ; i < nmsgs ; i++) { - ptrarray_append(&query->merged_msgdata, msgdatai); - msgdatai->folder = folder; - } - - /* save the MsgData array for later deletion */ - saved = xzmalloc(sizeof(*saved)); - saved->msgdata = msgdata; - saved->n = nmsgs; - ptrarray_append(&query->saved_msgdata, saved); -} - -struct subquery_rock { - search_query_t *query; - search_subquery_t *sub; -}; - -/* - * After an indexed subquery is run, we have accumulated a number of - * unchecked UID hits in folders. Here we check those UIDs for a) not - * being deleted since indexing and b) matching any unindexed scan - * expression. We also take advantage of having an open index_state to - * load some MsgData objects and save them to query->merged_msgdata. - */ -static void subquery_post_indexed(const char *key, void *data, void *rock) -{ - const char *mboxname = key; - search_folder_t *folder = data; - struct subquery_rock *qr = rock; - search_query_t *query = qr->query; - search_subquery_t *sub = qr->sub; - struct index_state *state = NULL; - unsigned msgno; - unsigned nmsgs = 0; - unsigned *msgno_list = NULL; - int r = 0; - - if (query->error) return; - if (!folder->unchecked_dirty) return; - - if (sub->expr && query->verbose) { - char *s = search_expr_serialise(sub->expr); - syslog(LOG_INFO, "Folder %s: applying scan expression: %s", - folder->mboxname, s); - free(s); - } - if (query->sortcrit && query->verbose) { - char *s = sortcrit_as_string(query->sortcrit); - syslog(LOG_INFO, "Folder %s: loading MsgData for sort criteria %s", - folder->mboxname, s); - free(s); - } - - r = query_begin_index(query, mboxname, &state); - if (r == IMAP_MAILBOX_NONEXISTENT) { - /* Silently swallow mailboxes which have been deleted, renamed, - * or had their ACL changed to prevent us reading them, after - * the index was constructed IRIS-2469. */ - r = 0; - goto out; - } - if (r) goto out; - - if (!state->exists) goto out; - - search_expr_internalise(state, sub->expr); - - if (query->sortcrit) - msgno_list = (unsigned *) xmalloc(state->exists * sizeof(unsigned)); - - /* One pass through the folder's message list */ - for (msgno = 1 ; msgno <= state->exists ; msgno++) { - struct index_map *im = &state->mapmsgno-1; - - r = cmd_cancelled(); - if (r) goto out; - - /* we only want to look at unchecked UIDs */ - if (!bv_isset(&folder->unchecked_uids, im->uid)) - continue; - - /* moot if already in the uids set */ - if (bv_isset(&folder->uids, im->uid)) - continue; - - /* can happen if we didn't "tellchanges" yet */ - if (im->system_flags & FLAG_EXPUNGED) - continue; - - /* run the search program */ - if (!index_search_evaluate(state, sub->expr, msgno)) - continue; - - /* we have a new UID that needs to be merged in */ - - folder_add_uid(folder, im->uid); - folder_add_modseq(folder, im->modseq); - if (query->sortcrit) - msgno_listnmsgs++ = msgno; - } - - /* msgno_list contains only the MSNs for newly - * checked messages */ - if (query->sortcrit && nmsgs) - query_load_msgdata(query, folder, state, msgno_list, nmsgs); - - folder->unchecked_dirty = 0; - r = 0; - -out: - query_end_index(query, &state); - free(msgno_list); - if (r) query->error = r; -} - -void build_query(search_builder_t *bx, search_expr_t *e) -{ - search_expr_t *child; - int bop = -1; - - switch (e->op) { - - case SEOP_NOT: - bop = SEARCH_OP_NOT; - break; - - case SEOP_AND: - bop = SEARCH_OP_AND; - break; - - case SEOP_OR: - bop = SEARCH_OP_OR; - break; - - case SEOP_FUZZYMATCH: - if (e->attr && e->attr->part >= 0) { - bx->match(bx, e->attr->part, e->value.s); - } - return; - - default: - return; - } - - if (e->children) { - assert(bop != -1); - bx->begin_boolean(bx, bop); - for (child = e->children ; child ; child = child->next) - build_query(bx, child); - bx->end_boolean(bx, bop); - } -} - -static int add_unchecked_uid(const char *mboxname, uint32_t uidvalidity, - uint32_t uid, void *rock) -{ - search_query_t *query = rock; - search_folder_t *folder = query_get_valid_folder(query, mboxname, uidvalidity); - if (folder) { - bv_set(&folder->unchecked_uids, uid); - folder->unchecked_dirty = 1; - } - return 0; -} - -static void subquery_run_indexed(const char *key __attribute__((unused)), - void *data, void *rock) -{ -// const char *mboxname = key; - search_subquery_t *sub = data; - search_query_t *query = rock; - search_builder_t *bx; - struct subquery_rock qr; - int r; - - if (query->error) return; - - if (query->verbose) { - char *s = search_expr_serialise(sub->indexed); - syslog(LOG_INFO, "Running indexed subquery: %s", s); - free(s); - } - - bx = search_begin_search(query->state->mailbox, - (query->multiple ? SEARCH_MULTIPLE : 0)| - SEARCH_VERBOSE(query->verbose)); - if (!bx) { - r = IMAP_INTERNAL; - goto out; - } - build_query(bx, sub->indexed); - r = bx->run(bx, add_unchecked_uid, query); - search_end_search(bx); - if (r) goto out; - - qr.query = query; - qr.sub = sub; - hash_enumerate(&query->folders_by_name, subquery_post_indexed, &qr); - -out: - if (r) query->error = r; -} - -static int subquery_run_one_folder(search_query_t *query, - const char *mboxname, - search_expr_t *e) -{ - struct index_state *state = NULL; - unsigned msgno; - search_folder_t *folder = NULL; - unsigned nmsgs = 0; - unsigned *msgno_list = NULL; - int r = 0; - - if (query->verbose) { - char *s = search_expr_serialise(e); - syslog(LOG_INFO, "Folder %s: running folder scan subquery: %s", - mboxname, s); - free(s); - } - if (query->sortcrit && query->verbose) { - char *s = sortcrit_as_string(query->sortcrit); - syslog(LOG_INFO, "Folder %s: loading MsgData for sort criteria %s", - folder->mboxname, s); - free(s); - } - - r = query_begin_index(query, mboxname, &state); - if (r == IMAP_MAILBOX_NONEXISTENT) { - /* Silently swallow mailboxes which have been deleted, renamed, - * or had their ACL changed to prevent us reading them, after - * the index was constructed IRIS-2469. */ - r = 0; - goto out; - } - if (r) goto out; - - if (!state->exists) goto out; - - search_expr_internalise(state, e); - - if (query->sortcrit) - msgno_list = (unsigned *) xmalloc(state->exists * sizeof(unsigned)); - - /* One pass through the folder's message list */ - for (msgno = 1 ; msgno <= state->exists ; msgno++) { - struct index_map *im = &state->mapmsgno-1; - - r = cmd_cancelled(); - if (r) goto out; - - /* can happen if we didn't "tellchanges" yet */ - if (im->system_flags & FLAG_EXPUNGED) - continue; - - /* run the search program */ - if (!index_search_evaluate(state, e, msgno)) - continue; - - if (!folder) { - folder = query_get_valid_folder(query, mboxname, state->uidvalidity); - if (!folder) { - r = IMAP_INTERNAL; - goto out; /* can't happen */ - } - } - - /* moot if already in the uids set */ - if (bv_isset(&folder->uids, im->uid)) - continue; - - folder_add_uid(folder, im->uid); - folder_add_modseq(folder, im->modseq); - - if (query->sortcrit) - msgno_listnmsgs++ = msgno; - } - - if (query->sortcrit && nmsgs) - query_load_msgdata(query, folder, state, msgno_list, nmsgs); - - r = 0; - -out: - query_end_index(query, &state); - free(msgno_list); - return r; -} - -static void subquery_run_folder(const char *key, void *data, void *rock) -{ - const char *mboxname = key; - search_subquery_t *sub = data; - search_query_t *query = rock; - int r; - - if (query->error) return; - if (!query->multiple && strcmp(mboxname, index_mboxname(query->state))) - return; - r = subquery_run_one_folder(query, mboxname, sub->expr); - if (r) query->error = r; -} - -static int subquery_run_global(search_query_t *query, const char *mboxname) -{ - search_subquery_t *sub; - search_expr_t *e, *exprs2; - int nexprs = 0; - int r; - - sub = (search_subquery_t *)hash_lookup(mboxname, &query->subs_by_folder); - if (sub) { - /* this folder also has a per-folder scan expression, OR it in */ - exprsnexprs++ = search_expr_duplicate(sub->expr); - } - - if (query->global_sub.expr) - exprsnexprs++ = search_expr_duplicate(query->global_sub.expr); - - switch (nexprs) { - case 0: - e = search_expr_new(NULL, SEOP_TRUE); - break; - case 1: - e = exprs0; - break; - case 2: - e = search_expr_new(NULL, SEOP_OR); - search_expr_append(e, exprs0); - search_expr_append(e, exprs1); - break; - } - - r = subquery_run_one_folder(query, mboxname, e); - search_expr_free(e); - return r; -} - -static int subquery_run_global_cb(void *rock, - const char *key, size_t keylen, - const char *val __attribute((unused)), - size_t vallen __attribute((unused))) -{ - search_query_t *query = rock; - char *mboxname = xstrndup(key, keylen); - int r; - - r = subquery_run_global(query, mboxname); - free(mboxname); - return r; -} - -static search_subquery_t *subquery_new(void) -{ - search_subquery_t *sub = xzmalloc(sizeof(*sub)); - return sub; -} - -static void query_add_subquery(const char *mboxname, - search_expr_t *indexed, - search_expr_t *e, - void *rock) -{ - search_query_t *query = rock; - search_subquery_t *sub; - - if (indexed) { - char *key = search_expr_serialise(indexed); - sub = (search_subquery_t *)hash_lookup(key, &query->subs_by_indexed); - if (!sub) { - sub = subquery_new(); - sub->indexed = indexed; - hash_insert(key, sub, &query->subs_by_indexed); - query->indexed_count++; - } - free(key); - } - else if (mboxname) { - sub = (search_subquery_t *)hash_lookup(mboxname, &query->subs_by_folder); - if (!sub) { - sub = subquery_new(); - sub->mboxname = xstrdup(mboxname); - hash_insert(sub->mboxname, sub, &query->subs_by_folder); - query->folder_count++; - } - } - else { - sub = &query->global_sub; - } - - if (sub->expr == NULL) { - /* adding the first expression: just store it */ - sub->expr = e; - } - else if (sub->expr->op != SEOP_OR) { - /* adding the second: make a new OR node */ - search_expr_t *or = search_expr_new(NULL, SEOP_OR); - search_expr_append(or, sub->expr); - search_expr_append(or, e); - sub->expr = or; - } - else { - /* append to the existing OR node */ - search_expr_append(sub->expr, e); - } -} - -EXPORTED int search_query_run(search_query_t *query) -{ - int r = 0; - - search_expr_split_by_folder_and_index(query->searchargs->root, query_add_subquery, query); - query->searchargs->root = NULL; - - if (query->indexed_count) { - /* - * Indexed searches proceed in two phases. The first runs - * all the search engine queries, and builds a set of matched - * uids per folder. The second runs per folder and applies - * any scan expression. - */ - hash_enumerate(&query->subs_by_indexed, subquery_run_indexed, query); - r = query->error; - if (r) goto out; - } - - if (query->global_sub.expr) { - /* We have a scan expression which applies to all folders. - * Walk over every folder, applying the scan expression. */ - if (query->multiple) - r = mboxlist_allusermbox(mboxname_to_userid(index_mboxname(query->state)), - subquery_run_global_cb, query, /*+deleted*/0); - else - r = subquery_run_global(query, index_mboxname(query->state)); - if (r) goto out; - } - else if (query->folder_count) { - /* We only have scan expressions limited to specific folders, - * let's iterate those folders */ - hash_enumerate(&query->subs_by_folder, subquery_run_folder, query); - r = query->error; - if (r) goto out; - } - - if (query->need_ids) - query_assign_folder_ids(query); - - if (query->sortcrit) { - /* - * Do a post-search sorting phase. - * - * Sorts MsgData objects. These really really need to be replaced with - * either message_t objects or some new smaller object which only stores - * exactly the data we need to sort with, according to sortcrit, plus a - * few things we always need like folder, uid, cid etc. But in the - * interests of getting this code working before Christmas we're going - * to use MsgData for now, and in the way that means the least amount of - * code changes. - */ - index_msgdata_sort((MsgData **)query->merged_msgdata.data, - query->merged_msgdata.count, - query->sortcrit); - } - -out: - return r; -} - -/* ====================================================================== */
View file
cyrus-imapd-2.5.tar.gz/imap/search_query.h
Deleted
@@ -1,175 +0,0 @@ -/* search_result.h -- search result data structure - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __CYRUS_SEARCH_RESULT_H__ -#define __CYRUS_SEARCH_RESULT_H__ - -#include "mailbox.h" -#include "message.h" -#include "conversations.h" -#include "util.h" -#include "bitvector.h" -#include "ptrarray.h" - -struct sortcrit; /* imapd.h */ -struct searchargs; /* imapd.h */ -typedef struct search_subquery search_subquery_t; -typedef struct search_query search_query_t; -typedef struct search_folder search_folder_t; - -struct search_folder { - char *mboxname; - uint32_t uidvalidity; - uint64_t highest_modseq; /* of returned messages, not the folder */ - int id; - bitvector_t uids; - bitvector_t unchecked_uids; - int unchecked_dirty; -}; - -struct search_subquery { - char *mboxname; /* may be NULL */ - search_expr_t *indexed; /* may be NULL */ - search_expr_t *expr; -}; - -struct search_saved_msgdata { - /* Used to remember MsgData** arrays returned by - * index_msgdata_load() for later freeing. */ - struct msgdata **msgdata; - int n; -}; - -struct search_query { - - /* - * A query may report results from multiple folders, but we need - * this one specific folder to tell us the username to limit the - * search engine scope. Also, for most IMAP search commands we - * start with a selected folder anyway so we need to avoid - * double-opening it. - */ - struct index_state *state; - /* - * Input parameters of the query. Set these after - * search_query_new() and before search_query_run(). - */ - struct searchargs *searchargs; - const struct sortcrit *sortcrit; - int multiple; - int need_ids; - int need_expunge; - int verbose; - - /* - * A query comprises multiple sub-queries logically ORed together. - * The sub-queries are organised by the first of three criteria - * which might apply: - * - * - subs_by_indexed: one or more indexed match nodes (need to scan - * all messages reported by a given search engine lookup) - * - * - subs_by_folder: a single positive folder match node (need to - * scan all messages in a given folder) - * - * - global_sub: neither indexed nor folder (need to scan all - * messages in many folders) - */ - hash_table subs_by_indexed; - unsigned int indexed_count; - hash_table subs_by_folder; - unsigned int folder_count; - search_subquery_t global_sub; - - /* Used as a temporary holder for errors, e.g. to pass an error from - * a hashtable enumeration callback back up to the caller */ - int error; - /* - * Resulting messages from a search engine query or a folder scan - * need to be organised per-folder both for the secondary scan - * (which needs to proceed per-folder to minimise the number of - * index_open() calls) and for reporting back to the IMAP client. - */ - hash_table folders_by_name; - /* - * Some callers need a unique and contiguous 0-based set of integer - * ids for folders which have any results. Note that the - * folders_by_name hash table may contain folders with no results in - * them, e.g. when the search engine returns hits for a folder which - * all subsequently prove to be deleted. - */ - ptrarray_t folders_by_id; - /* - * Array of search_saved_msgdata objects for later freeing - */ - ptrarray_t saved_msgdata; - /* - * When sorting in requested, this array contains the final merged - * sort results as an array of MsgData*. The MsgData objects might - * be "fake" ones if the results were retrieved from the cache DB, - * but the following fields are guaranteed to be usable: uid, cid, - * folder. - */ - ptrarray_t merged_msgdata; -}; - -extern search_query_t *search_query_new(struct index_state *state, - struct searchargs *); -extern int search_query_run(search_query_t *query); -extern void search_query_free(search_query_t *query); - -extern search_folder_t *search_query_find_folder(search_query_t *query, - const char *mboxname); -extern void search_folder_use_msn(search_folder_t *, struct index_state *); -extern struct seqset *search_folder_get_seqset(const search_folder_t *); -extern int search_folder_get_array(const search_folder_t *, unsigned int **); -extern uint32_t search_folder_get_min(const search_folder_t *); -extern uint32_t search_folder_get_max(const search_folder_t *); -extern unsigned int search_folder_get_count(const search_folder_t *); -#define search_folder_foreach(folder, u) \ - for ((u) = bv_next_set(&(folder)->uids, 0) ; \ - (u) != -1 ; \ - (u) = bv_next_set(&(folder)->uids, (u)+1)) -extern uint64_t search_folder_get_highest_modseq(const search_folder_t *); - - -#endif /* __CYRUS_SEARCH_RESULT_H__ */
View file
cyrus-imapd-2.5.tar.gz/imap/search_sphinx.c
Deleted
@@ -1,2389 +0,0 @@ -/* search_sphinx.c -- glue code for searching with Sphinx - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <stdlib.h> -#include <syslog.h> -#include <string.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -#include "index.h" -#include "imap_err.h" -#include "global.h" -#include "retry.h" -#include "command.h" -#include "xmalloc.h" -#include "xstrlcpy.h" -#include "xstrlcat.h" -#include "ptrarray.h" -#include "bitvector.h" -#include "mboxlist.h" -#include "xstats.h" -#include "search_engines.h" -#include "cyr_lock.h" - -#include <mysql/mysql.h> - -struct connection -{ - MYSQL *mysql; -}; -#define CONNECTION_INITIALIZER { 0 } - -struct latestdb -{ - struct db *db; - char *path; -}; -#define LATESTDB_INITIALIZER { 0, 0 } -#define LATESTDB_VERSION 1 -#define LATESTDB_FNAME "/latest.db" -#define LATESTDB_LASTID_KEY "LASTID" - -#define SPHINX_CONFIG "/sphinx.conf" -#define SEARCHD "/usr/bin/searchd" - -#define INDEXING_LOCK_SUFFIX ".indexing.lock" - -static int open_latest(struct mailbox *, struct latestdb *); -static void close_latest(struct latestdb *); -static int read_latest(struct latestdb *, struct mailbox *, uint32_t *, int); -static int write_latest(struct latestdb *, struct mailbox *, uint32_t, int); -static int doquery(struct connection *, int, const struct buf *); -static int sphinx_basedir(const struct mailbox *mailbox, char **basedirp, char **indexnamep); -static int sphinx_setup(const struct mailbox *mailbox, int verbose, int create); - -/* Name of columns */ -#define COL_CYRUSID "cyrusid" -static const char * const column_by_partSEARCH_NUM_PARTS = { - NULL, - "header_from", - "header_to", - "header_cc", - "header_bcc", - "header_subject", - "header_listid", - "header_type", - "headers", - "body" -}; - -static void close_connection(struct connection *conn) -{ - if (conn->mysql) { - xstats_inc(SPHINX_CLOSE); - mysql_close(conn->mysql); - conn->mysql = NULL; - mysql_library_end(); - } -} - -static int get_connection(struct connection *conn) -{ - MYSQL *c = NULL; - const char *socket_path = config_getstring(IMAPOPT_SPHINX_SOCKET); - - if (conn->mysql) return 0; /* already open */ - - close_connection(conn); - - xstats_inc(SPHINX_CONNECT); - c = mysql_init(NULL); - - if (!mysql_real_connect(c, - /*host*/NULL, - /*user*/"", /*password*/"", - /*database*/NULL, - /*port*/0, socket_path, - /*client_flag*/0)) { - syslog(LOG_ERR, "IOERROR: failed to connect to Sphinx: %s", - mysql_error(c)); - mysql_close(c); - mysql_library_end(); - return IMAP_IOERROR; - } - - conn->mysql = c; - return 0; -} - -static int parse_cyrusid(const char *cyrusid, - const char **mboxnamep, - unsigned int *uidvalidityp, - unsigned int *uidp) -{ - // user.cassandane.1320711192.196715 - static struct buf buf = BUF_INITIALIZER; - char *p; - - buf_reset(&buf); - buf_appendcstr(&buf, cyrusid); - - p = strrchr(buf_cstring(&buf), '.'); - if (!p) - return 0; - *p++ = '\0'; - *uidp = strtoul(p, NULL, 10); - - p = strrchr(buf.s, '.'); - if (!p) - return 0; - *p++ = '\0'; - *uidvalidityp = strtoul(p, NULL, 10); - - *mboxnamep = buf.s; - - return 1; -} - -static const struct buf *make_cyrusid(struct mailbox *mailbox, uint32_t uid) -{ - static struct buf buf = BUF_INITIALIZER; - // user.cassandane.1320711192.196715 - buf_reset(&buf); - buf_printf(&buf, "%s.%u.%u", - mailbox->name, - mailbox->i.uidvalidity, - uid); - return &buf; -} - -/* - * Escape a string for MySQL. Note that mysql_real_escape_string - * requires a live connection, and we now want to be able to build a - * query string before we have a connection. From the MySQL - * documentation: - * - * Strictly speaking, MySQL requires only that backslash and - * the quote character used to quote the string in the query - * be escaped. mysql_real_escape_string() quotes the other - * characters to make them easier to read in log files. - * - * Note that we need to escape a number of SphinxQL extended query - * syntax metacharacters like ^ and $. - */ -static void append_escaped_map(struct buf *buf, - const char *base, unsigned int len, - int quote) -{ - static const char metacharacters = "!\"$'-/<=@\\^|~"; - buf_ensure(buf, len+1); - - buf_putc(buf, quote); - for ( ; len ; len--, base++) { - int c = *(unsigned char *)base; - if (strchr(metacharacters, c)) - buf_putc(buf, '\\'); - buf_putc(buf, c); - } - buf_putc(buf, quote); - buf_cstring(buf); -} - -static void append_escaped(struct buf *to, const struct buf *from, int quote) -{ - append_escaped_map(to, from->s, from->len, quote); -} - -static void append_escaped_cstr(struct buf *to, const char *str, int quote) -{ - if (str) - append_escaped_map(to, str, strlen(str), quote); -} - -struct opnode { - int op; /* SEARCH_OP_* or SEARCH_PART_* constant */ - char *arg; - struct opnode *next; - struct opnode *children; -}; - -typedef struct sphinx_builder sphinx_builder_t; -struct sphinx_builder { - search_builder_t super; - struct mailbox *mailbox; - char *indexname; - int opts; - struct opnode *root; - ptrarray_t stack; /* points to opnode* */ -}; - -static struct opnode *opnode_new(int op, const char *arg) -{ - struct opnode *on = xzmalloc(sizeof(struct opnode)); - on->op = op; - on->arg = xstrdupnull(arg); - return on; -} - -static void opnode_delete(struct opnode *on) -{ - struct opnode *child; - struct opnode *next; - - for (child = on->children ; child ; child = next) { - next = child->next; - opnode_delete(child); - } - free(on->arg); - free(on); -} - -static void opnode_detach_child(struct opnode *parent, struct opnode *child) -{ - struct opnode **prevp; - - for (prevp = &parent->children ; *prevp ; prevp = &((*prevp)->next)) { - if (*prevp == child) { - *prevp = child->next; - child->next = NULL; - return; - } - } -} - -static void opnode_append_child(struct opnode *parent, struct opnode *child) -{ - struct opnode **tailp; - - for (tailp = &parent->children ; *tailp ; tailp = &((*tailp)->next)) - ; - *tailp = child; - child->next = NULL; -} - -static void opnode_insert_child(struct opnode *parent __attribute__((unused)), - struct opnode *after, - struct opnode *child) -{ - child->next = after->next; - after->next = child; -} - -static void begin_boolean(search_builder_t *bx, int op) -{ - sphinx_builder_t *bb = (sphinx_builder_t *)bx; - struct opnode *top = ptrarray_tail(&bb->stack); - struct opnode *on = opnode_new(op, NULL); - if (top) - opnode_append_child(top, on); - else - bb->root = on; - ptrarray_push(&bb->stack, on); - if (SEARCH_VERBOSE(bb->opts)) - syslog(LOG_INFO, "begin_boolean(op=%s)", search_op_as_string(op)); -} - -static void end_boolean(search_builder_t *bx, int op __attribute__((unused))) -{ - sphinx_builder_t *bb = (sphinx_builder_t *)bx; - if (SEARCH_VERBOSE(bb->opts)) - syslog(LOG_INFO, "end_boolean"); - ptrarray_pop(&bb->stack); -} - -static void match(search_builder_t *bx, int part, const char *str) -{ - sphinx_builder_t *bb = (sphinx_builder_t *)bx; - struct opnode *top = ptrarray_tail(&bb->stack); - struct opnode *on; - - if (!str) return; - if (SEARCH_VERBOSE(bb->opts)) - syslog(LOG_INFO, "match(part=%s, str=\"%s\")", - search_part_as_string(part), str); - - xstats_inc(SPHINX_MATCH); - - on = opnode_new(part, str); - if (top) - opnode_append_child(top, on); - else - bb->root = on; -} - -static void optimise_nodes(struct opnode *parent, struct opnode *on) -{ - struct opnode *child; - struct opnode *next; - - switch (on->op) { - case SEARCH_OP_NOT: - case SEARCH_OP_OR: - case SEARCH_OP_AND: - for (child = on->children ; child ; child = next) { - next = child->next; - optimise_nodes(on, child); - } - if (parent) { - if (!on->children) { - /* empty node - remove it */ - opnode_detach_child(parent, on); - opnode_delete(on); - } - else if (on->op != SEARCH_OP_NOT && !on->children->next) { - /* logical AND or OR with only one child - replace - * the node with its child */ - struct opnode *child = on->children; - opnode_detach_child(on, child); - opnode_insert_child(parent, on, child); - opnode_detach_child(parent, on); - opnode_delete(on); - } - } - break; - } -} - -/* Sphinx extended query syntax, not SphinxQL */ -static void generate_query(struct buf *query, - struct opnode *on, - struct opnode *parent) -{ - struct opnode *child; - struct buf arg = BUF_INITIALIZER; - int need_paren; - int sep; - - buf_init_ro_cstr(&arg, on->arg); - - switch (on->op) { - - case SEARCH_OP_NOT: - if (on->children) buf_appendcstr(query, "!"); - /* fall through - note we treat multiple children to a NOT - * node as if they were ANDed together because that's the - * closest match to IMAP semantics. */ - case SEARCH_OP_OR: - case SEARCH_OP_AND: - - need_paren = 0; - if (parent && parent->op > on->op) - need_paren = 1; - if (on->op == SEARCH_OP_NOT) - need_paren = 1; - /* Note that we might have 0, 1, or >1 children, the caller will - * not have optimised the silly cases out. */ - if (!on->children || !on->children->next) - need_paren = 0; - - sep = (on->op == SEARCH_OP_OR ? '|' : ' '); - - if (need_paren) buf_putc(query, '('); - for (child = on->children ; child ; child = child->next) { - generate_query(query, child, on); - if (child->next) buf_putc(query, sep); - } - if (need_paren) buf_putc(query, ')'); - break; - - case SEARCH_PART_ANY: - assert(on->children == NULL); - if (config_getswitch(IMAPOPT_SPHINX_TEXT_EXCLUDES_ODD_HEADERS)) { - /* This horrible hack makes TEXT searches match FROM, TO, CC, BCC - * and SUBJECT but not any other random headers, which is more - * like what users expect. */ - int i; - const char *sep = "("; - buf_appendcstr(query, "@"); - for (i = 0 ; i < SEARCH_NUM_PARTS ; i++) { - if (column_by_parti && i != SEARCH_PART_HEADERS) { - buf_appendcstr(query, sep); - buf_appendcstr(query, column_by_parti); - sep = ","; - } - } - buf_appendcstr(query, ") "); - } - append_escaped(query, &arg, '"'); - break; - - default: - /* other SEARCH_PART_* constants */ - assert(on->op >= 0 && on->op < SEARCH_NUM_PARTS); - assert(on->children == NULL); - buf_appendcstr(query, "@"); - buf_appendcstr(query, column_by_parton->op); - buf_appendcstr(query, " "); - append_escaped(query, &arg, '"'); - break; - } - - buf_free(&arg); -} - -static void *get_internalised(search_builder_t *bx) -{ - sphinx_builder_t *bb = (sphinx_builder_t *)bx; - struct opnode *on = bb->root; - bb->root = NULL; - optimise_nodes(NULL, on); - return on; -} - -static char *describe_internalised(void *internalised) -{ - struct opnode *on = (struct opnode *)internalised; - struct buf buf = BUF_INITIALIZER; - - generate_query(&buf, on, NULL); - return buf_release(&buf); -} - -static void free_internalised(void *internalised) -{ - struct opnode *on = (struct opnode *)internalised; - if (on) opnode_delete(on); -} - -static int run(search_builder_t *bx, search_hit_cb_t proc, void *rock); - -static search_builder_t *begin_search(struct mailbox *mailbox, int opts) -{ - sphinx_builder_t *bb; - int r; - - bb = xzmalloc(sizeof(sphinx_builder_t)); - bb->super.begin_boolean = begin_boolean; - bb->super.end_boolean = end_boolean; - bb->super.match = match; - bb->super.get_internalised = get_internalised; - bb->super.run = run; - - bb->mailbox = mailbox; - bb->opts = opts; - - r = sphinx_basedir(mailbox, NULL, &bb->indexname); - if (r) { - free(bb); - return NULL; - } - - if ((opts & SEARCH_MULTIPLE)) - xstats_inc(SPHINX_MULTIPLE); - else - xstats_inc(SPHINX_SINGLE); - - return &bb->super; -} - -static int run(search_builder_t *bx, search_hit_cb_t proc, void *rock) -{ - sphinx_builder_t *bb = (sphinx_builder_t *)bx; - struct connection conn = CONNECTION_INITIALIZER; - struct buf query = BUF_INITIALIZER; /* SphinxQL query */ - struct buf inner_query = BUF_INITIALIZER; /* Sphinx extended match syntax */ - MYSQL_RES *res = NULL; - MYSQL_ROW row; - uint32_t uid; - uint32_t latest = 0; - int r = 0; - - r = sphinx_setup(bb->mailbox, SEARCH_VERBOSE(bb->opts), /*create*/0); - if (r == IMAP_NOTFOUND) { - /* there's no index for this user */ - r = 0; - goto out; - } - if (r) goto out; - - r = get_connection(&conn); - if (r) goto out; - - if ((bb->opts & SEARCH_UNINDEXED)) { - /* To avoid races, we want the 'latest' uid we use to be - * an underestimate, because the caller can handle false - * positives but not false negatives. So we fetch it - * first before the main query. */ - struct latestdb ldb = LATESTDB_INITIALIZER; - r = open_latest(bb->mailbox, &ldb); - if (!r) goto out; - r = read_latest(&ldb, bb->mailbox, &latest, - SEARCH_VERBOSE(bb->opts)); - close_latest(&ldb); - if (r) goto out; - } - - optimise_nodes(NULL, bb->root); - generate_query(&inner_query, bb->root, NULL); - - buf_printf(&query, "SELECT "COL_CYRUSID" FROM %s WHERE MATCH(", bb->indexname); - append_escaped(&query, &inner_query, '\''); - buf_appendcstr(&query, ")"); - // get sphinx to sort by most recent date first - buf_appendcstr(&query, " ORDER BY "COL_CYRUSID" DESC " - " LIMIT " SPHINX_MAX_MATCHES - " OPTION max_matches=" SPHINX_MAX_MATCHES); - buf_cstring(&query); - - if (SEARCH_VERBOSE(bb->opts)) - syslog(LOG_NOTICE, "Sphinx query %s", query.s); - xstats_inc(SPHINX_QUERY); - - r = mysql_real_query(conn.mysql, query.s, query.len); - if (r) { - syslog(LOG_ERR, "IOERROR: Sphinx query %s failed: %s", - query.s, mysql_error(conn.mysql)); - r = IMAP_IOERROR; - goto out; - } - - res = mysql_use_result(conn.mysql); - while ((row = mysql_fetch_row(res))) { - const char *mboxname; - unsigned int uidvalidity; - unsigned int uid; - if (SEARCH_VERBOSE(bb->opts) > 1) - syslog(LOG_NOTICE, "Sphinx row cyrusid=%s", row0); - xstats_inc(SPHINX_ROW); - if (!parse_cyrusid(row0, &mboxname, &uidvalidity, &uid)) - // TODO: whine - continue; - if (!(bb->opts & SEARCH_MULTIPLE)) { - if (strcmp(mboxname, bb->mailbox->name)) - continue; - if (uidvalidity != bb->mailbox->i.uidvalidity) - continue; - } - xstats_inc(SPHINX_RESULT); - r = proc(mboxname, uidvalidity, uid, rock); - if (r) goto out; - } - r = 0; - - if ((bb->opts & SEARCH_UNINDEXED)) { - /* add in the unindexed uids as false positives */ - for (uid = latest+1 ; uid <= bb->mailbox->i.last_uid ; uid++) { - xstats_inc(SPHINX_UNINDEXED); - r = proc(bb->mailbox->name, bb->mailbox->i.uidvalidity, uid, rock); - if (r) goto out; - } - } - -out: - if (res) mysql_free_result(res); - close_connection(&conn); - buf_free(&query); - buf_free(&inner_query); - return r; -} - -static void end_search(search_builder_t *bx) -{ - sphinx_builder_t *bb = (sphinx_builder_t *)bx; - - ptrarray_fini(&bb->stack); - if (bb->root) opnode_delete(bb->root); - free(bb->indexname); - free(bx); -} - -/* base class for both update and snippet receivers */ -typedef struct sphinx_receiver sphinx_receiver_t; -struct sphinx_receiver -{ - search_text_receiver_t super; - int verbose; - struct mailbox *mailbox; - char *indexname; - uint32_t uid; - int part; - unsigned int parts_total; - int truncate_warning; - struct buf partsSEARCH_NUM_PARTS; - struct buf tmp; /* SphinxQL query */ -}; - -/* receiver used for updating the index */ -typedef struct sphinx_update_receiver sphinx_update_receiver_t; -struct sphinx_update_receiver -{ - sphinx_receiver_t super; - struct connection conn; - int indexing_lock_fd; - unsigned int uncommitted; - uint32_t latest; - struct latestdb latestdb; - uint32_t lastid; /* largest document ID in the 'tr' table, - * used to assign new document IDs when - * INSERTing into the table */ -}; - -/* receiver used for extracting snippets after a search */ -typedef struct sphinx_snippet_receiver sphinx_snippet_receiver_t; -struct sphinx_snippet_receiver -{ - sphinx_receiver_t super; - struct connection conn; - struct opnode *root; - search_snippet_cb_t proc; - void *rock; -}; - -/* Maximum size of a query, determined empirically, is a little bit - * under 8MB. That seems like more than enough, so let's limit the - * total amount of parts text to 4 MB. */ -#define MAX_PARTS_SIZE (4*1024*1024) - -static const char *describe_query(struct buf *desc, - const struct buf *query, - unsigned maxlen) -{ - buf_reset(desc); - buf_appendcstr(desc, "Sphinx query "); - if (maxlen && query->len > maxlen) { - buf_appendmap(desc, query->s, maxlen); - buf_appendcstr(desc, "..."); - } - else { - buf_append(desc, query); - } - return buf_cstring(desc); -} - -static int doquery(struct connection *conn, int verbose, const struct buf *query) -{ - int r; - struct buf desc = BUF_INITIALIZER; - unsigned int maxlen = verbose > 2 ? /*unlimited*/0 : 128; - - if (verbose > 1) - syslog(LOG_NOTICE, "%s", describe_query(&desc, query, maxlen)); - - r = mysql_real_query(conn->mysql, query->s, query->len); - if (r) { - syslog(LOG_ERR, "IOERROR: %s failed: %s", - describe_query(&desc, query, maxlen), - mysql_error(conn->mysql)); - r = IMAP_IOERROR; - } - - buf_free(&desc); - return r; -} - -#if 0 -/* Dump a result which has had mysql_store_result() called on it */ -static void dump_result(MYSQL_RES *res) -{ - uint64_t nrows = mysql_num_rows(res); - unsigned int nfields = mysql_num_fields(res); - unsigned int i; - unsigned int j; - MYSQL_FIELD *fields = mysql_fetch_fields(res); - MYSQL_ROW *row; - struct buf buf = BUF_INITIALIZER; - - syslog(LOG_NOTICE, "Sphinx result: %u rows", (unsigned int)nrows); - i = 0; - while ((row = mysql_fetch_row(res))) { - buf_reset(&buf); - for (j = 0 ; j < nfields ; j++) - buf_printf(&buf, " %s=\"%s\"", fieldsj.name, rowj); - syslog(LOG_NOTICE, " %u%s", ++i, buf_cstring(&buf)); - } - - buf_free(&buf); - mysql_data_seek(res, 0); /* rewind */ -} -#endif - -static const char *sphinx_config_file(void) -{ - static const char *config_file = NULL; - - if (!config_file) - config_file = strconcat(config_dir, SPHINX_CONFIG, (char *)NULL); - return config_file; -} - -static const char *sphinx_rootdir(const char *partition) -{ - char *confkey; - const char *root; - if (!partition) - partition = config_getstring(IMAPOPT_DEFAULTPARTITION); - confkey = strconcat("searchpartition-", partition, NULL); - root = config_getoverflowstring(confkey, NULL); - free(confkey); - return root; -} - -/* When building an index name we need to escape any @ or . characters - * in the username. We're slightly more general than that. */ -static void z_escape(struct buf *buf, const char *s) -{ - for ( ; *s ; s++) { - if (*s == 'Z' || !Uisalnum(*s)) - buf_printf(buf, "Z%02X", *(const unsigned char *)s); - else - buf_putc(buf, *s); - } -} - -/* Returns in *basedir and *indexname new strings which must be free()d */ -static int sphinx_basedir(const struct mailbox *mailbox, - char **basedirp, - char **indexnamep) -{ - const char *root; - char *basedir = NULL; - struct buf indexname = BUF_INITIALIZER; - struct mboxname_parts parts; - char c2, d2; - int r; - - mboxname_init_parts(&parts); - - root = sphinx_rootdir(mailbox->part); - if (!root) { - r = IMAP_PARTITION_UNKNOWN; - goto out; - } - - r = mboxname_to_parts(mailbox->name, &parts); - if (r) goto out; - if (!parts.userid) { - r = IMAP_PARTITION_UNKNOWN; - goto out; - } - - if (parts.domain) - basedir = strconcat(root, - FNAME_DOMAINDIR, - dir_hash_b(parts.domain, config_fulldirhash, d), - "/", parts.domain, - FNAME_USERDIR, - dir_hash_b(parts.userid, config_fulldirhash, c), - "/", parts.userid, - (char *)NULL); - else - basedir = strconcat(root, - FNAME_USERDIR, - dir_hash_b(parts.userid, config_fulldirhash, c), - "/", parts.userid, - (char *)NULL); - - buf_appendcstr(&indexname, "X"); - z_escape(&indexname, parts.userid); - if (parts.domain) { - z_escape(&indexname, "@"); - z_escape(&indexname, parts.domain); - } - - r = 0; - -out: - if (!r && indexnamep) - *indexnamep = buf_release(&indexname); - buf_free(&indexname); - if (!r && basedirp) - *basedirp = basedir; - else - free(basedir); - mboxname_free_parts(&parts); - return r; -} - -static const char *sphinx_syslog_prefix(void) -{ - static const char *prefix = NULL; - - if (!prefix) { - prefix = config_getstring(IMAPOPT_SYSLOG_PREFIX); - if (!prefix) - prefix = "cyrus"; - } - return prefix; -} - -static int check_directory(const char *dir, int verbose, int create) -{ - int r; - char *dummyfile = NULL; - struct stat sb; - - r = stat(dir, &sb); - if (r < 0) { - if (errno != ENOENT) { - /* something went wrong - permissions problem most likely */ - syslog(LOG_ERR, "IOERROR: unable to stat %s: %m", dir); - r = IMAP_IOERROR; - goto out; - } - /* the directory is just missing */ - if (!create) { - /* caller doesn't care that much */ - r = IMAP_NOTFOUND; - goto out; - } - if (verbose) - syslog(LOG_INFO, "Building directory %s", dir); - dummyfile = strconcat(dir, "/dummy", (char *)NULL); - cyrus_mkdir(dummyfile, 0700); - r = stat(dir, &sb); - if (r < 0) { - /* something went wrong - permissions problem most likely */ - syslog(LOG_ERR, "IOERROR: unable to stat %s: %m", dir); - r = IMAP_IOERROR; - goto out; - } - } - -out: - free(dummyfile); - return r; -} - -static int sphinx_signal(int sig, int verbose) -{ - const char *pidfile = config_getstring(IMAPOPT_SPHINX_PIDFILE); - int fd = -1; - pid_t pid; - int r; - char buf33; - - fd = open(pidfile, O_RDONLY, 0); - if (fd < 0) { - if (errno == ENOENT) { - r = IMAP_NOTFOUND; - } - else { - syslog(LOG_ERR, "IOERROR: unable to open %s for reading: %m", pidfile); - r = IMAP_IOERROR; - } - goto out; - } - - memset(buf, 0, sizeof(buf)); - r = read(fd, buf, sizeof(buf)-1); - if (r < 0) { - syslog(LOG_ERR, "IOERROR: unable to read %s: %m", pidfile); - r = IMAP_IOERROR; - goto out; - } - if (r == 0) { - syslog(LOG_ERR, "IOERROR: short file %s", pidfile); - r = IMAP_IOERROR; - goto out; - } - - pid = strtoul(buf, NULL, 10); - if (pid <= 0) { - syslog(LOG_ERR, "IOERROR: invalid contents of %s", pidfile); - r = IMAP_IOERROR; - goto out; - } - - if (verbose) - syslog(LOG_INFO, "Sending signal %d to pid %d from %s", - sig, (int)pid, pidfile); - - r = kill(pid, sig); - if (r < 0) { - if (errno == ESRCH) { - r = IMAP_NOTFOUND; - } - else { - syslog(LOG_ERR, "IOERROR: failed to send signal %d " - "to searchd pid %d: %m", - sig, (int)pid); - r = IMAP_SYS_ERROR; - } - goto out; - } - - r = 0; - -out: - if (fd >= 0) close(fd); - return r; -} - - -static int sphinx_setup(const struct mailbox *mailbox, int verbose, int create) -{ - static const char user_config = - "index $indexname\n" - "{\n" - " type = rt\n" - " path = $basedir/rt\n" - " morphology = stem_en\n" - " charset_type = utf-8\n" - " charset_table = 0..9, A..Z->a..z, _, a..z, \\\n" - /* Support for Cyrillic from - * http://sphinxsearch.com/wiki/doku.php?id=charset_tables#cyrillic */ - " U+0400->U+0435, U+0401->U+0435, U+0402->U+0452, U+0452, \\\n" - " U+0403->U+0433, U+0404->U+0454, U+0454, U+0405->U+0455, \\\n" - " U+0455, U+0406->U+0456, U+0407->U+0456, U+0457->U+0456, \\\n" - " U+0456, U+0408..U+040B->U+0458..U+045B, U+0458..U+045B, \\\n" - " U+040C->U+043A, U+040D->U+0438, U+040E->U+0443, U+040F->U+045F, \\\n" - " U+045F, U+0450->U+0435, U+0451->U+0435, U+0453->U+0433, \\\n" - " U+045C->U+043A, U+045D->U+0438, U+045E->U+0443, U+0460->U+0461, \\\n" - " U+0461, U+0462->U+0463, U+0463, U+0464->U+0465, U+0465, \\\n" - " U+0466->U+0467, U+0467, U+0468->U+0469, U+0469, U+046A->U+046B, \\\n" - " U+046B, U+046C->U+046D, U+046D, U+046E->U+046F, U+046F, \\\n" - " U+0470->U+0471, U+0471, U+0472->U+0473, U+0473, U+0474->U+0475, \\\n" - " U+0476->U+0475, U+0477->U+0475, U+0475, U+0478->U+0479, U+0479, \\\n" - " U+047A->U+047B, U+047B, U+047C->U+047D, U+047D, U+047E->U+047F, \\\n" - " U+047F, U+0480->U+0481, U+0481, U+048A->U+0438, U+048B->U+0438, \\\n" - " U+048C->U+044C, U+048D->U+044C, U+048E->U+0440, U+048F->U+0440, \\\n" - " U+0490->U+0433, U+0491->U+0433, U+0490->U+0433, U+0491->U+0433, \\\n" - " U+0492->U+0433, U+0493->U+0433, U+0494->U+0433, U+0495->U+0433, \\\n" - " U+0496->U+0436, U+0497->U+0436, U+0498->U+0437, U+0499->U+0437, \\\n" - " U+049A->U+043A, U+049B->U+043A, U+049C->U+043A, U+049D->U+043A, \\\n" - " U+049E->U+043A, U+049F->U+043A, U+04A0->U+043A, U+04A1->U+043A, \\\n" - " U+04A2->U+043D, U+04A3->U+043D, U+04A4->U+043D, U+04A5->U+043D, \\\n" - " U+04A6->U+043F, U+04A7->U+043F, U+04A8->U+04A9, U+04A9, \\\n" - " U+04AA->U+0441, U+04AB->U+0441, U+04AC->U+0442, U+04AD->U+0442, \\\n" - " U+04AE->U+0443, U+04AF->U+0443, U+04B0->U+0443, U+04B1->U+0443, \\\n" - " U+04B2->U+0445, U+04B3->U+0445, U+04B4->U+04B5, U+04B5, \\\n" - " U+04B6->U+0447, U+04B7->U+0447, U+04B8->U+0447, U+04B9->U+0447, \\\n" - " U+04BA->U+04BB, U+04BB, U+04BC->U+04BD, U+04BE->U+04BD, \\\n" - " U+04BF->U+04BD, U+04BD, U+04C0->U+04CF, U+04CF, U+04C1->U+0436, \\\n" - " U+04C2->U+0436, U+04C3->U+043A, U+04C4->U+043A, U+04C5->U+043B, \\\n" - " U+04C6->U+043B, U+04C7->U+043D, U+04C8->U+043D, U+04C9->U+043D, \\\n" - " U+04CA->U+043D, U+04CB->U+0447, U+04CC->U+0447, U+04CD->U+043C, \\\n" - " U+04CE->U+043C, U+04D0->U+0430, U+04D1->U+0430, U+04D2->U+0430, \\\n" - " U+04D3->U+0430, U+04D4->U+00E6, U+04D5->U+00E6, U+04D6->U+0435, \\\n" - " U+04D7->U+0435, U+04D8->U+04D9, U+04DA->U+04D9, U+04DB->U+04D9, \\\n" - " U+04D9, U+04DC->U+0436, U+04DD->U+0436, U+04DE->U+0437, \\\n" - " U+04DF->U+0437, U+04E0->U+04E1, U+04E1, U+04E2->U+0438, \\\n" - " U+04E3->U+0438, U+04E4->U+0438, U+04E5->U+0438, U+04E6->U+043E, \\\n" - " U+04E7->U+043E, U+04E8->U+043E, U+04E9->U+043E, U+04EA->U+043E, \\\n" - " U+04EB->U+043E, U+04EC->U+044D, U+04ED->U+044D, U+04EE->U+0443, \\\n" - " U+04EF->U+0443, U+04F0->U+0443, U+04F1->U+0443, U+04F2->U+0443, \\\n" - " U+04F3->U+0443, U+04F4->U+0447, U+04F5->U+0447, U+04F6->U+0433, \\\n" - " U+04F7->U+0433, U+04F8->U+044B, U+04F9->U+044B, U+04FA->U+0433, \\\n" - " U+04FB->U+0433, U+04FC->U+0445, U+04FD->U+0445, U+04FE->U+0445, \\\n" - " U+04FF->U+0445, U+0410..U+0418->U+0430..U+0438, U+0419->U+0438, \\\n" - " U+0430..U+0438, U+041A..U+042F->U+043A..U+044F, U+043A..U+044F,\\\n" - /* and this one was missing dammit */ - " U+0418->U+0438, U+0439->U+0438, \\\n" - /* Sumerian cuneiform which is fun for testing but seems - * to be broken in Spinx. */ - /* - " U+12000..U+1237F, \\\n" - */ - /* Support for Chinese/Japanese/Korean, from - * http://sphinxsearch.com/wiki/doku.php?id=charset_tables#cjk */ - " U+F900->U+8C48, U+F901->U+66F4, U+F902->U+8ECA, U+F903->U+8CC8, \\\n" - " U+F904->U+6ED1, U+F905->U+4E32, U+F906->U+53E5, U+F907->U+9F9C, \\\n" - " U+F908->U+9F9C, U+F909->U+5951, U+F90A->U+91D1, U+F90B->U+5587, \\\n" - " U+F90C->U+5948, U+F90D->U+61F6, U+F90E->U+7669, U+F90F->U+7F85, \\\n" - " U+F910->U+863F, U+F911->U+87BA, U+F912->U+88F8, U+F913->U+908F, \\\n" - " U+F914->U+6A02, U+F915->U+6D1B, U+F916->U+70D9, U+F917->U+73DE, \\\n" - " U+F918->U+843D, U+F919->U+916A, U+F91A->U+99F1, U+F91B->U+4E82, \\\n" - " U+F91C->U+5375, U+F91D->U+6B04, U+F91E->U+721B, U+F91F->U+862D, \\\n" - " U+F920->U+9E1E, U+F921->U+5D50, U+F922->U+6FEB, U+F923->U+85CD, \\\n" - " U+F924->U+8964, U+F925->U+62C9, U+F926->U+81D8, U+F927->U+881F, \\\n" - " U+F928->U+5ECA, U+F929->U+6717, U+F92A->U+6D6A, U+F92B->U+72FC, \\\n" - " U+F92C->U+90CE, U+F92D->U+4F86, U+F92E->U+51B7, U+F92F->U+52DE, \\\n" - " U+F930->U+64C4, U+F931->U+6AD3, U+F932->U+7210, U+F933->U+76E7, \\\n" - " U+F934->U+8001, U+F935->U+8606, U+F936->U+865C, U+F937->U+8DEF, \\\n" - " U+F938->U+9732, U+F939->U+9B6F, U+F93A->U+9DFA, U+F93B->U+788C, \\\n" - " U+F93C->U+797F, U+F93D->U+7DA0, U+F93E->U+83C9, U+F93F->U+9304, \\\n" - " U+F940->U+9E7F, U+F941->U+8AD6, U+F942->U+58DF, U+F943->U+5F04, \\\n" - " U+F944->U+7C60, U+F945->U+807E, U+F946->U+7262, U+F947->U+78CA, \\\n" - " U+F948->U+8CC2, U+F949->U+96F7, U+F94A->U+58D8, U+F94B->U+5C62, \\\n" - " U+F94C->U+6A13, U+F94D->U+6DDA, U+F94E->U+6F0F, U+F94F->U+7D2F, \\\n" - " U+F950->U+7E37, U+F951->U+964B, U+F952->U+52D2, U+F953->U+808B, \\\n" - " U+F954->U+51DC, U+F955->U+51CC, U+F956->U+7A1C, U+F957->U+7DBE, \\\n" - " U+F958->U+83F1, U+F959->U+9675, U+F95A->U+8B80, U+F95B->U+62CF, \\\n" - " U+F95C->U+6A02, U+F95D->U+8AFE, U+F95E->U+4E39, U+F95F->U+5BE7, \\\n" - " U+F960->U+6012, U+F961->U+7387, U+F962->U+7570, U+F963->U+5317, \\\n" - " U+F964->U+78FB, U+F965->U+4FBF, U+F966->U+5FA9, U+F967->U+4E0D, \\\n" - " U+F968->U+6CCC, U+F969->U+6578, U+F96A->U+7D22, U+F96B->U+53C3, \\\n" - " U+F96C->U+585E, U+F96D->U+7701, U+F96E->U+8449, U+F96F->U+8AAA, \\\n" - " U+F970->U+6BBA, U+F971->U+8FB0, U+F972->U+6C88, U+F973->U+62FE, \\\n" - " U+F974->U+82E5, U+F975->U+63A0, U+F976->U+7565, U+F977->U+4EAE, \\\n" - " U+F978->U+5169, U+F979->U+51C9, U+F97A->U+6881, U+F97B->U+7CE7, \\\n" - " U+F97C->U+826F, U+F97D->U+8AD2, U+F97E->U+91CF, U+F97F->U+52F5, \\\n" - " U+F980->U+5442, U+F981->U+5973, U+F982->U+5EEC, U+F983->U+65C5, \\\n" - " U+F984->U+6FFE, U+F985->U+792A, U+F986->U+95AD, U+F987->U+9A6A, \\\n" - " U+F988->U+9E97, U+F989->U+9ECE, U+F98A->U+529B, U+F98B->U+66C6, \\\n" - " U+F98C->U+6B77, U+F98D->U+8F62, U+F98E->U+5E74, U+F98F->U+6190, \\\n" - " U+F990->U+6200, U+F991->U+649A, U+F992->U+6F23, U+F993->U+7149, \\\n" - " U+F994->U+7489, U+F995->U+79CA, U+F996->U+7DF4, U+F997->U+806F, \\\n" - " U+F998->U+8F26, U+F999->U+84EE, U+F99A->U+9023, U+F99B->U+934A, \\\n" - " U+F99C->U+5217, U+F99D->U+52A3, U+F99E->U+54BD, U+F99F->U+70C8, \\\n" - " U+F9A0->U+88C2, U+F9A1->U+8AAA, U+F9A2->U+5EC9, U+F9A3->U+5FF5, \\\n" - " U+F9A4->U+637B, U+F9A5->U+6BAE, U+F9A6->U+7C3E, U+F9A7->U+7375, \\\n" - " U+F9A8->U+4EE4, U+F9A9->U+56F9, U+F9AA->U+5BE7, U+F9AB->U+5DBA, \\\n" - " U+F9AC->U+601C, U+F9AD->U+73B2, U+F9AE->U+7469, U+F9AF->U+7F9A, \\\n" - " U+F9B0->U+8046, U+F9B1->U+9234, U+F9B2->U+96F6, U+F9B3->U+9748, \\\n" - " U+F9B4->U+9818, U+F9B5->U+4F8B, U+F9B6->U+79AE, U+F9B7->U+91B4, \\\n" - " U+F9B8->U+96B8, U+F9B9->U+60E1, U+F9BA->U+4E86, U+F9BB->U+50DA, \\\n" - " U+F9BC->U+5BEE, U+F9BD->U+5C3F, U+F9BE->U+6599, U+F9BF->U+6A02, \\\n" - " U+F9C0->U+71CE, U+F9C1->U+7642, U+F9C2->U+84FC, U+F9C3->U+907C, \\\n" - " U+F9C4->U+9F8D, U+F9C5->U+6688, U+F9C6->U+962E, U+F9C7->U+5289, \\\n" - " U+F9C8->U+677B, U+F9C9->U+67F3, U+F9CA->U+6D41, U+F9CB->U+6E9C, \\\n" - " U+F9CC->U+7409, U+F9CD->U+7559, U+F9CE->U+786B, U+F9CF->U+7D10, \\\n" - " U+F9D0->U+985E, U+F9D1->U+516D, U+F9D2->U+622E, U+F9D3->U+9678, \\\n" - " U+F9D4->U+502B, U+F9D5->U+5D19, U+F9D6->U+6DEA, U+F9D7->U+8F2A, \\\n" - " U+F9D8->U+5F8B, U+F9D9->U+6144, U+F9DA->U+6817, U+F9DB->U+7387, \\\n" - " U+F9DC->U+9686, U+F9DD->U+5229, U+F9DE->U+540F, U+F9DF->U+5C65, \\\n" - " U+F9E0->U+6613, U+F9E1->U+674E, U+F9E2->U+68A8, U+F9E3->U+6CE5, \\\n" - " U+F9E4->U+7406, U+F9E5->U+75E2, U+F9E6->U+7F79, U+F9E7->U+88CF, \\\n" - " U+F9E8->U+88E1, U+F9E9->U+91CC, U+F9EA->U+96E2, U+F9EB->U+533F, \\\n" - " U+F9EC->U+6EBA, U+F9ED->U+541D, U+F9EE->U+71D0, U+F9EF->U+7498, \\\n" - " U+F9F0->U+85FA, U+F9F1->U+96A3, U+F9F2->U+9C57, U+F9F3->U+9E9F, \\\n" - " U+F9F4->U+6797, U+F9F5->U+6DCB, U+F9F6->U+81E8, U+F9F7->U+7ACB, \\\n" - " U+F9F8->U+7B20, U+F9F9->U+7C92, U+F9FA->U+72C0, U+F9FB->U+7099, \\\n" - " U+F9FC->U+8B58, U+F9FD->U+4EC0, U+F9FE->U+8336, U+F9FF->U+523A, \\\n" - " U+FA00->U+5207, U+FA01->U+5EA6, U+FA02->U+62D3, U+FA03->U+7CD6, \\\n" - " U+FA04->U+5B85, U+FA05->U+6D1E, U+FA06->U+66B4, U+FA07->U+8F3B, \\\n" - " U+FA08->U+884C, U+FA09->U+964D, U+FA0A->U+898B, U+FA0B->U+5ED3, \\\n" - " U+FA0C->U+5140, U+FA0D->U+55C0, U+FA10->U+585A, U+FA12->U+6674, \\\n" - " U+FA15->U+51DE, U+FA16->U+732A, U+FA17->U+76CA, U+FA18->U+793C, \\\n" - " U+FA19->U+795E, U+FA1A->U+7965, U+FA1B->U+798F, U+FA1C->U+9756, \\\n" - " U+FA1D->U+7CBE, U+FA1E->U+7FBD, U+FA20->U+8612, U+FA22->U+8AF8, \\\n" - " U+FA25->U+9038, U+FA26->U+90FD, U+FA2A->U+98EF, U+FA2B->U+98FC, \\\n" - " U+FA2C->U+9928, U+FA2D->U+9DB4, U+FA30->U+4FAE, U+FA31->U+50E7, \\\n" - " U+FA32->U+514D, U+FA33->U+52C9, U+FA34->U+52E4, U+FA35->U+5351, \\\n" - " U+FA36->U+559D, U+FA37->U+5606, U+FA38->U+5668, U+FA39->U+5840, \\\n" - " U+FA3A->U+58A8, U+FA3B->U+5C64, U+FA3C->U+5C6E, U+FA3D->U+6094, \\\n" - " U+FA3E->U+6168, U+FA3F->U+618E, U+FA40->U+61F2, U+FA41->U+654F, \\\n" - " U+FA42->U+65E2, U+FA43->U+6691, U+FA44->U+6885, U+FA45->U+6D77, \\\n" - " U+FA46->U+6E1A, U+FA47->U+6F22, U+FA48->U+716E, U+FA49->U+722B, \\\n" - " U+FA4A->U+7422, U+FA4B->U+7891, U+FA4C->U+793E, U+FA4D->U+7949, \\\n" - " U+FA4E->U+7948, U+FA4F->U+7950, U+FA50->U+7956, U+FA51->U+795D, \\\n" - " U+FA52->U+798D, U+FA53->U+798E, U+FA54->U+7A40, U+FA55->U+7A81, \\\n" - " U+FA56->U+7BC0, U+FA57->U+7DF4, U+FA58->U+7E09, U+FA59->U+7E41, \\\n" - " U+FA5A->U+7F72, U+FA5B->U+8005, U+FA5C->U+81ED, U+FA5D->U+8279, \\\n" - " U+FA5E->U+8279, U+FA5F->U+8457, U+FA60->U+8910, U+FA61->U+8996, \\\n" - " U+FA62->U+8B01, U+FA63->U+8B39, U+FA64->U+8CD3, U+FA65->U+8D08, \\\n" - " U+FA66->U+8FB6, U+FA67->U+9038, U+FA68->U+96E3, U+FA69->U+97FF, \\\n" - " U+FA6A->U+983B, U+FA70->U+4E26, U+FA71->U+51B5, U+FA72->U+5168, \\\n" - " U+FA73->U+4F80, U+FA74->U+5145, U+FA75->U+5180, U+FA76->U+52C7, \\\n" - " U+FA77->U+52FA, U+FA78->U+559D, U+FA79->U+5555, U+FA7A->U+5599, \\\n" - " U+FA7B->U+55E2, U+FA7C->U+585A, U+FA7D->U+58B3, U+FA7E->U+5944, \\\n" - " U+FA7F->U+5954, U+FA80->U+5A62, U+FA81->U+5B28, U+FA82->U+5ED2, \\\n" - " U+FA83->U+5ED9, U+FA84->U+5F69, U+FA85->U+5FAD, U+FA86->U+60D8, \\\n" - " U+FA87->U+614E, U+FA88->U+6108, U+FA89->U+618E, U+FA8A->U+6160, \\\n" - " U+FA8B->U+61F2, U+FA8C->U+6234, U+FA8D->U+63C4, U+FA8E->U+641C, \\\n" - " U+FA8F->U+6452, U+FA90->U+6556, U+FA91->U+6674, U+FA92->U+6717, \\\n" - " U+FA93->U+671B, U+FA94->U+6756, U+FA95->U+6B79, U+FA96->U+6BBA, \\\n" - " U+FA97->U+6D41, U+FA98->U+6EDB, U+FA99->U+6ECB, U+FA9A->U+6F22, \\\n" - " U+FA9B->U+701E, U+FA9C->U+716E, U+FA9D->U+77A7, U+FA9E->U+7235, \\\n" - " U+FA9F->U+72AF, U+FAA0->U+732A, U+FAA1->U+7471, U+FAA2->U+7506, \\\n" - " U+FAA3->U+753B, U+FAA4->U+761D, U+FAA5->U+761F, U+FAA6->U+76CA, \\\n" - " U+FAA7->U+76DB, U+FAA8->U+76F4, U+FAA9->U+774A, U+FAAA->U+7740, \\\n" - " U+FAAB->U+78CC, U+FAAC->U+7AB1, U+FAAD->U+7BC0, U+FAAE->U+7C7B, \\\n" - " U+FAAF->U+7D5B, U+FAB0->U+7DF4, U+FAB1->U+7F3E, U+FAB2->U+8005, \\\n" - " U+FAB3->U+8352, U+FAB4->U+83EF, U+FAB5->U+8779, U+FAB6->U+8941, \\\n" - " U+FAB7->U+8986, U+FAB8->U+8996, U+FAB9->U+8ABF, U+FABA->U+8AF8, \\\n" - " U+FABB->U+8ACB, U+FABC->U+8B01, U+FABD->U+8AFE, U+FABE->U+8AED, \\\n" - " U+FABF->U+8B39, U+FAC0->U+8B8A, U+FAC1->U+8D08, U+FAC2->U+8F38, \\\n" - " U+FAC3->U+9072, U+FAC4->U+9199, U+FAC5->U+9276, U+FAC6->U+967C, \\\n" - " U+FAC7->U+96E3, U+FAC8->U+9756, U+FAC9->U+97DB, U+FACA->U+97FF, \\\n" - " U+FACB->U+980B, U+FACC->U+983B, U+FACD->U+9B12, U+FACE->U+9F9C, \\\n" - " U+FACF->U+2284A, U+FAD0->U+22844, U+FAD1->U+233D5, U+FAD2->U+3B9D, \\\n" - " U+FAD3->U+4018, U+FAD4->U+4039, U+FAD5->U+25249, U+FAD6->U+25CD0, \\\n" - " U+FAD7->U+27ED3, U+FAD8->U+9F43, U+FAD9->U+9F8E, U+2F800->U+4E3D, \\\n" - " U+2F801->U+4E38, U+2F802->U+4E41, U+2F803->U+20122, U+2F804->U+4F60, \\\n" - " U+2F805->U+4FAE, U+2F806->U+4FBB, U+2F807->U+5002, U+2F808->U+507A, \\\n" - " U+2F809->U+5099, U+2F80A->U+50E7, U+2F80B->U+50CF, U+2F80C->U+349E, \\\n" - " U+2F80D->U+2063A, U+2F80E->U+514D, U+2F80F->U+5154, U+2F810->U+5164, \\\n" - " U+2F811->U+5177, U+2F812->U+2051C, U+2F813->U+34B9, U+2F814->U+5167, \\\n" - " U+2F815->U+518D, U+2F816->U+2054B, U+2F817->U+5197, U+2F818->U+51A4, \\\n" - " U+2F819->U+4ECC, U+2F81A->U+51AC, U+2F81B->U+51B5, U+2F81C->U+291DF, \\\n" - " U+2F81D->U+51F5, U+2F81E->U+5203, U+2F81F->U+34DF, U+2F820->U+523B, \\\n" - " U+2F821->U+5246, U+2F822->U+5272, U+2F823->U+5277, U+2F824->U+3515, \\\n" - " U+2F825->U+52C7, U+2F826->U+52C9, U+2F827->U+52E4, U+2F828->U+52FA, \\\n" - " U+2F829->U+5305, U+2F82A->U+5306, U+2F82B->U+5317, U+2F82C->U+5349, \\\n" - " U+2F82D->U+5351, U+2F82E->U+535A, U+2F82F->U+5373, U+2F830->U+537D, \\\n" - " U+2F831->U+537F, U+2F832->U+537F, U+2F833->U+537F, U+2F834->U+20A2C, \\\n" - " U+2F835->U+7070, U+2F836->U+53CA, U+2F837->U+53DF, U+2F838->U+20B63, \\\n" - " U+2F839->U+53EB, U+2F83A->U+53F1, U+2F83B->U+5406, U+2F83C->U+549E, \\\n" - " U+2F83D->U+5438, U+2F83E->U+5448, U+2F83F->U+5468, U+2F840->U+54A2, \\\n" - " U+2F841->U+54F6, U+2F842->U+5510, U+2F843->U+5553, U+2F844->U+5563, \\\n" - " U+2F845->U+5584, U+2F846->U+5584, U+2F847->U+5599, U+2F848->U+55AB, \\\n" - " U+2F849->U+55B3, U+2F84A->U+55C2, U+2F84B->U+5716, U+2F84C->U+5606, \\\n" - " U+2F84D->U+5717, U+2F84E->U+5651, U+2F84F->U+5674, U+2F850->U+5207, \\\n" - " U+2F851->U+58EE, U+2F852->U+57CE, U+2F853->U+57F4, U+2F854->U+580D, \\\n" - " U+2F855->U+578B, U+2F856->U+5832, U+2F857->U+5831, U+2F858->U+58AC, \\\n" - " U+2F859->U+214E4, U+2F85A->U+58F2, U+2F85B->U+58F7, U+2F85C->U+5906, \\\n" - " U+2F85D->U+591A, U+2F85E->U+5922, U+2F85F->U+5962, U+2F860->U+216A8, \\\n" - " U+2F861->U+216EA, U+2F862->U+59EC, U+2F863->U+5A1B, U+2F864->U+5A27, \\\n" - " U+2F865->U+59D8, U+2F866->U+5A66, U+2F867->U+36EE, U+2F868->U+36FC, \\\n" - " U+2F869->U+5B08, U+2F86A->U+5B3E, U+2F86B->U+5B3E, U+2F86C->U+219C8, \\\n" - " U+2F86D->U+5BC3, U+2F86E->U+5BD8, U+2F86F->U+5BE7, U+2F870->U+5BF3, \\\n" - " U+2F871->U+21B18, U+2F872->U+5BFF, U+2F873->U+5C06, U+2F874->U+5F53, \\\n" - " U+2F875->U+5C22, U+2F876->U+3781, U+2F877->U+5C60, U+2F878->U+5C6E, \\\n" - " U+2F879->U+5CC0, U+2F87A->U+5C8D, U+2F87B->U+21DE4, U+2F87C->U+5D43, \\\n" - " U+2F87D->U+21DE6, U+2F87E->U+5D6E, U+2F87F->U+5D6B, U+2F880->U+5D7C, \\\n" - " U+2F881->U+5DE1, U+2F882->U+5DE2, U+2F883->U+382F, U+2F884->U+5DFD, \\\n" - " U+2F885->U+5E28, U+2F886->U+5E3D, U+2F887->U+5E69, U+2F888->U+3862, \\\n" - " U+2F889->U+22183, U+2F88A->U+387C, U+2F88B->U+5EB0, U+2F88C->U+5EB3, \\\n" - " U+2F88D->U+5EB6, U+2F88E->U+5ECA, U+2F88F->U+2A392, U+2F890->U+5EFE, \\\n" - " U+2F891->U+22331, U+2F892->U+22331, U+2F893->U+8201, U+2F894->U+5F22, \\\n" - " U+2F895->U+5F22, U+2F896->U+38C7, U+2F897->U+232B8, U+2F898->U+261DA, \\\n" - " U+2F899->U+5F62, U+2F89A->U+5F6B, U+2F89B->U+38E3, U+2F89C->U+5F9A, \\\n" - " U+2F89D->U+5FCD, U+2F89E->U+5FD7, U+2F89F->U+5FF9, U+2F8A0->U+6081, \\\n" - " U+2F8A1->U+393A, U+2F8A2->U+391C, U+2F8A3->U+6094, U+2F8A4->U+226D4, \\\n" - " U+2F8A5->U+60C7, U+2F8A6->U+6148, U+2F8A7->U+614C, U+2F8A8->U+614E, \\\n" - " U+2F8A9->U+614C, U+2F8AA->U+617A, U+2F8AB->U+618E, U+2F8AC->U+61B2, \\\n" - " U+2F8AD->U+61A4, U+2F8AE->U+61AF, U+2F8AF->U+61DE, U+2F8B0->U+61F2, \\\n" - " U+2F8B1->U+61F6, U+2F8B2->U+6210, U+2F8B3->U+621B, U+2F8B4->U+625D, \\\n" - " U+2F8B5->U+62B1, U+2F8B6->U+62D4, U+2F8B7->U+6350, U+2F8B8->U+22B0C, \\\n" - " U+2F8B9->U+633D, U+2F8BA->U+62FC, U+2F8BB->U+6368, U+2F8BC->U+6383, \\\n" - " U+2F8BD->U+63E4, U+2F8BE->U+22BF1, U+2F8BF->U+6422, U+2F8C0->U+63C5, \\\n" - " U+2F8C1->U+63A9, U+2F8C2->U+3A2E, U+2F8C3->U+6469, U+2F8C4->U+647E, \\\n" - " U+2F8C5->U+649D, U+2F8C6->U+6477, U+2F8C7->U+3A6C, U+2F8C8->U+654F, \\\n" - " U+2F8C9->U+656C, U+2F8CA->U+2300A, U+2F8CB->U+65E3, U+2F8CC->U+66F8, \\\n" - " U+2F8CD->U+6649, U+2F8CE->U+3B19, U+2F8CF->U+6691, U+2F8D0->U+3B08, \\\n" - " U+2F8D1->U+3AE4, U+2F8D2->U+5192, U+2F8D3->U+5195, U+2F8D4->U+6700, \\\n" - " U+2F8D5->U+669C, U+2F8D6->U+80AD, U+2F8D7->U+43D9, U+2F8D8->U+6717, \\\n" - " U+2F8D9->U+671B, U+2F8DA->U+6721, U+2F8DB->U+675E, U+2F8DC->U+6753, \\\n" - " U+2F8DD->U+233C3, U+2F8DE->U+3B49, U+2F8DF->U+67FA, U+2F8E0->U+6785, \\\n" - " U+2F8E1->U+6852, U+2F8E2->U+6885, U+2F8E3->U+2346D, U+2F8E4->U+688E, \\\n" - " U+2F8E5->U+681F, U+2F8E6->U+6914, U+2F8E7->U+3B9D, U+2F8E8->U+6942, \\\n" - " U+2F8E9->U+69A3, U+2F8EA->U+69EA, U+2F8EB->U+6AA8, U+2F8EC->U+236A3, \\\n" - " U+2F8ED->U+6ADB, U+2F8EE->U+3C18, U+2F8EF->U+6B21, U+2F8F0->U+238A7, \\\n" - " U+2F8F1->U+6B54, U+2F8F2->U+3C4E, U+2F8F3->U+6B72, U+2F8F4->U+6B9F, \\\n" - " U+2F8F5->U+6BBA, U+2F8F6->U+6BBB, U+2F8F7->U+23A8D, U+2F8F8->U+21D0B, \\\n" - " U+2F8F9->U+23AFA, U+2F8FA->U+6C4E, U+2F8FB->U+23CBC, U+2F8FC->U+6CBF, \\\n" - " U+2F8FD->U+6CCD, U+2F8FE->U+6C67, U+2F8FF->U+6D16, U+2F900->U+6D3E, \\\n" - " U+2F901->U+6D77, U+2F902->U+6D41, U+2F903->U+6D69, U+2F904->U+6D78, \\\n" - " U+2F905->U+6D85, U+2F906->U+23D1E, U+2F907->U+6D34, U+2F908->U+6E2F, \\\n" - " U+2F909->U+6E6E, U+2F90A->U+3D33, U+2F90B->U+6ECB, U+2F90C->U+6EC7, \\\n" - " U+2F90D->U+23ED1, U+2F90E->U+6DF9, U+2F90F->U+6F6E, U+2F910->U+23F5E, \\\n" - " U+2F911->U+23F8E, U+2F912->U+6FC6, U+2F913->U+7039, U+2F914->U+701E, \\\n" - " U+2F915->U+701B, U+2F916->U+3D96, U+2F917->U+704A, U+2F918->U+707D, \\\n" - " U+2F919->U+7077, U+2F91A->U+70AD, U+2F91B->U+20525, U+2F91C->U+7145, \\\n" - " U+2F91D->U+24263, U+2F91E->U+719C, U+2F91F->U+243AB, U+2F920->U+7228, \\\n" - " U+2F921->U+7235, U+2F922->U+7250, U+2F923->U+24608, U+2F924->U+7280, \\\n" - " U+2F925->U+7295, U+2F926->U+24735, U+2F927->U+24814, U+2F928->U+737A, \\\n" - " U+2F929->U+738B, U+2F92A->U+3EAC, U+2F92B->U+73A5, U+2F92C->U+3EB8, \\\n" - " U+2F92D->U+3EB8, U+2F92E->U+7447, U+2F92F->U+745C, U+2F930->U+7471, \\\n" - " U+2F931->U+7485, U+2F932->U+74CA, U+2F933->U+3F1B, U+2F934->U+7524, \\\n" - " U+2F935->U+24C36, U+2F936->U+753E, U+2F937->U+24C92, U+2F938->U+7570, \\\n" - " U+2F939->U+2219F, U+2F93A->U+7610, U+2F93B->U+24FA1, U+2F93C->U+24FB8, \\\n" - " U+2F93D->U+25044, U+2F93E->U+3FFC, U+2F93F->U+4008, U+2F940->U+76F4, \\\n" - " U+2F941->U+250F3, U+2F942->U+250F2, U+2F943->U+25119, U+2F944->U+25133, \\\n" - " U+2F945->U+771E, U+2F946->U+771F, U+2F947->U+771F, U+2F948->U+774A, \\\n" - " U+2F949->U+4039, U+2F94A->U+778B, U+2F94B->U+4046, U+2F94C->U+4096, \\\n" - " U+2F94D->U+2541D, U+2F94E->U+784E, U+2F94F->U+788C, U+2F950->U+78CC, \\\n" - " U+2F951->U+40E3, U+2F952->U+25626, U+2F953->U+7956, U+2F954->U+2569A, \\\n" - " U+2F955->U+256C5, U+2F956->U+798F, U+2F957->U+79EB, U+2F958->U+412F, \\\n" - " U+2F959->U+7A40, U+2F95A->U+7A4A, U+2F95B->U+7A4F, U+2F95C->U+2597C, \\\n" - " U+2F95D->U+25AA7, U+2F95E->U+25AA7, U+2F95F->U+7AEE, U+2F960->U+4202, \\\n" - " U+2F961->U+25BAB, U+2F962->U+7BC6, U+2F963->U+7BC9, U+2F964->U+4227, \\\n" - " U+2F965->U+25C80, U+2F966->U+7CD2, U+2F967->U+42A0, U+2F968->U+7CE8, \\\n" - " U+2F969->U+7CE3, U+2F96A->U+7D00, U+2F96B->U+25F86, U+2F96C->U+7D63, \\\n" - " U+2F96D->U+4301, U+2F96E->U+7DC7, U+2F96F->U+7E02, U+2F970->U+7E45, \\\n" - " U+2F971->U+4334, U+2F972->U+26228, U+2F973->U+26247, U+2F974->U+4359, \\\n" - " U+2F975->U+262D9, U+2F976->U+7F7A, U+2F977->U+2633E, U+2F978->U+7F95, \\\n" - " U+2F979->U+7FFA, U+2F97A->U+8005, U+2F97B->U+264DA, U+2F97C->U+26523, \\\n" - " U+2F97D->U+8060, U+2F97E->U+265A8, U+2F97F->U+8070, U+2F980->U+2335F, \\\n" - " U+2F981->U+43D5, U+2F982->U+80B2, U+2F983->U+8103, U+2F984->U+440B, \\\n" - " U+2F985->U+813E, U+2F986->U+5AB5, U+2F987->U+267A7, U+2F988->U+267B5, \\\n" - " U+2F989->U+23393, U+2F98A->U+2339C, U+2F98B->U+8201, U+2F98C->U+8204, \\\n" - " U+2F98D->U+8F9E, U+2F98E->U+446B, U+2F98F->U+8291, U+2F990->U+828B, \\\n" - " U+2F991->U+829D, U+2F992->U+52B3, U+2F993->U+82B1, U+2F994->U+82B3, \\\n" - " U+2F995->U+82BD, U+2F996->U+82E6, U+2F997->U+26B3C, U+2F998->U+82E5, \\\n" - " U+2F999->U+831D, U+2F99A->U+8363, U+2F99B->U+83AD, U+2F99C->U+8323, \\\n" - " U+2F99D->U+83BD, U+2F99E->U+83E7, U+2F99F->U+8457, U+2F9A0->U+8353, \\\n" - " U+2F9A1->U+83CA, U+2F9A2->U+83CC, U+2F9A3->U+83DC, U+2F9A4->U+26C36, \\\n" - " U+2F9A5->U+26D6B, U+2F9A6->U+26CD5, U+2F9A7->U+452B, U+2F9A8->U+84F1, \\\n" - " U+2F9A9->U+84F3, U+2F9AA->U+8516, U+2F9AB->U+273CA, U+2F9AC->U+8564, \\\n" - " U+2F9AD->U+26F2C, U+2F9AE->U+455D, U+2F9AF->U+4561, U+2F9B0->U+26FB1, \\\n" - " U+2F9B1->U+270D2, U+2F9B2->U+456B, U+2F9B3->U+8650, U+2F9B4->U+865C, \\\n" - " U+2F9B5->U+8667, U+2F9B6->U+8669, U+2F9B7->U+86A9, U+2F9B8->U+8688, \\\n" - " U+2F9B9->U+870E, U+2F9BA->U+86E2, U+2F9BB->U+8779, U+2F9BC->U+8728, \\\n" - " U+2F9BD->U+876B, U+2F9BE->U+8786, U+2F9BF->U+45D7, U+2F9C0->U+87E1, \\\n" - " U+2F9C1->U+8801, U+2F9C2->U+45F9, U+2F9C3->U+8860, U+2F9C4->U+8863, \\\n" - " U+2F9C5->U+27667, U+2F9C6->U+88D7, U+2F9C7->U+88DE, U+2F9C8->U+4635, \\\n" - " U+2F9C9->U+88FA, U+2F9CA->U+34BB, U+2F9CB->U+278AE, U+2F9CC->U+27966, \\\n" - " U+2F9CD->U+46BE, U+2F9CE->U+46C7, U+2F9CF->U+8AA0, U+2F9D0->U+8AED, \\\n" - " U+2F9D1->U+8B8A, U+2F9D2->U+8C55, U+2F9D3->U+27CA8, U+2F9D4->U+8CAB, \\\n" - " U+2F9D5->U+8CC1, U+2F9D6->U+8D1B, U+2F9D7->U+8D77, U+2F9D8->U+27F2F, \\\n" - " U+2F9D9->U+20804, U+2F9DA->U+8DCB, U+2F9DB->U+8DBC, U+2F9DC->U+8DF0, \\\n" - " U+2F9DD->U+208DE, U+2F9DE->U+8ED4, U+2F9DF->U+8F38, U+2F9E0->U+285D2, \\\n" - " U+2F9E1->U+285ED, U+2F9E2->U+9094, U+2F9E3->U+90F1, U+2F9E4->U+9111, \\\n" - " U+2F9E5->U+2872E, U+2F9E6->U+911B, U+2F9E7->U+9238, U+2F9E8->U+92D7, \\\n" - " U+2F9E9->U+92D8, U+2F9EA->U+927C, U+2F9EB->U+93F9, U+2F9EC->U+9415, \\\n" - " U+2F9ED->U+28BFA, U+2F9EE->U+958B, U+2F9EF->U+4995, U+2F9F0->U+95B7, \\\n" - " U+2F9F1->U+28D77, U+2F9F2->U+49E6, U+2F9F3->U+96C3, U+2F9F4->U+5DB2, \\\n" - " U+2F9F5->U+9723, U+2F9F6->U+29145, U+2F9F7->U+2921A, U+2F9F8->U+4A6E, \\\n" - " U+2F9F9->U+4A76, U+2F9FA->U+97E0, U+2F9FB->U+2940A, U+2F9FC->U+4AB2, \\\n" - " U+2F9FD->U+29496, U+2F9FE->U+980B, U+2F9FF->U+980B, U+2FA00->U+9829, \\\n" - " U+2FA01->U+295B6, U+2FA02->U+98E2, U+2FA03->U+4B33, U+2FA04->U+9929, \\\n" - " U+2FA05->U+99A7, U+2FA06->U+99C2, U+2FA07->U+99FE, U+2FA08->U+4BCE, \\\n" - " U+2FA09->U+29B30, U+2FA0A->U+9B12, U+2FA0B->U+9C40, U+2FA0C->U+9CFD, \\\n" - " U+2FA0D->U+4CCE, U+2FA0E->U+4CED, U+2FA0F->U+9D67, U+2FA10->U+2A0CE, \\\n" - " U+2FA11->U+4CF8, U+2FA12->U+2A105, U+2FA13->U+2A20E, U+2FA14->U+2A291, \\\n" - " U+2FA15->U+9EBB, U+2FA16->U+4D56, U+2FA17->U+9EF9, U+2FA18->U+9EFE, \\\n" - " U+2FA19->U+9F05, U+2FA1A->U+9F0F, U+2FA1B->U+9F16, U+2FA1C->U+9F3B, \\\n" - " U+2FA1D->U+2A600, U+2F00->U+4E00, U+2F01->U+4E28, U+2F02->U+4E36, \\\n" - " U+2F03->U+4E3F, U+2F04->U+4E59, U+2F05->U+4E85, U+2F06->U+4E8C, \\\n" - " U+2F07->U+4EA0, U+2F08->U+4EBA, U+2F09->U+513F, U+2F0A->U+5165, \\\n" - " U+2F0B->U+516B, U+2F0C->U+5182, U+2F0D->U+5196, U+2F0E->U+51AB, \\\n" - " U+2F0F->U+51E0, U+2F10->U+51F5, U+2F11->U+5200, U+2F12->U+529B, \\\n" - " U+2F13->U+52F9, U+2F14->U+5315, U+2F15->U+531A, U+2F16->U+5338, \\\n" - " U+2F17->U+5341, U+2F18->U+535C, U+2F19->U+5369, U+2F1A->U+5382, \\\n" - " U+2F1B->U+53B6, U+2F1C->U+53C8, U+2F1D->U+53E3, U+2F1E->U+56D7, \\\n" - " U+2F1F->U+571F, U+2F20->U+58EB, U+2F21->U+5902, U+2F22->U+590A, \\\n" - " U+2F23->U+5915, U+2F24->U+5927, U+2F25->U+5973, U+2F26->U+5B50, \\\n" - " U+2F27->U+5B80, U+2F28->U+5BF8, U+2F29->U+5C0F, U+2F2A->U+5C22, \\\n" - " U+2F2B->U+5C38, U+2F2C->U+5C6E, U+2F2D->U+5C71, U+2F2E->U+5DDB, \\\n" - " U+2F2F->U+5DE5, U+2F30->U+5DF1, U+2F31->U+5DFE, U+2F32->U+5E72, \\\n" - " U+2F33->U+5E7A, U+2F34->U+5E7F, U+2F35->U+5EF4, U+2F36->U+5EFE, \\\n" - " U+2F37->U+5F0B, U+2F38->U+5F13, U+2F39->U+5F50, U+2F3A->U+5F61, \\\n" - " U+2F3B->U+5F73, U+2F3C->U+5FC3, U+2F3D->U+6208, U+2F3E->U+6236, \\\n" - " U+2F3F->U+624B, U+2F40->U+652F, U+2F41->U+6534, U+2F42->U+6587, \\\n" - " U+2F43->U+6597, U+2F44->U+65A4, U+2F45->U+65B9, U+2F46->U+65E0, \\\n" - " U+2F47->U+65E5, U+2F48->U+66F0, U+2F49->U+6708, U+2F4A->U+6728, \\\n" - " U+2F4B->U+6B20, U+2F4C->U+6B62, U+2F4D->U+6B79, U+2F4E->U+6BB3, \\\n" - " U+2F4F->U+6BCB, U+2F50->U+6BD4, U+2F51->U+6BDB, U+2F52->U+6C0F, \\\n" - " U+2F53->U+6C14, U+2F54->U+6C34, U+2F55->U+706B, U+2F56->U+722A, \\\n" - " U+2F57->U+7236, U+2F58->U+723B, U+2F59->U+723F, U+2F5A->U+7247, \\\n" - " U+2F5B->U+7259, U+2F5C->U+725B, U+2F5D->U+72AC, U+2F5E->U+7384, \\\n" - " U+2F5F->U+7389, U+2F60->U+74DC, U+2F61->U+74E6, U+2F62->U+7518, \\\n" - " U+2F63->U+751F, U+2F64->U+7528, U+2F65->U+7530, U+2F66->U+758B, \\\n" - " U+2F67->U+7592, U+2F68->U+7676, U+2F69->U+767D, U+2F6A->U+76AE, \\\n" - " U+2F6B->U+76BF, U+2F6C->U+76EE, U+2F6D->U+77DB, U+2F6E->U+77E2, \\\n" - " U+2F6F->U+77F3, U+2F70->U+793A, U+2F71->U+79B8, U+2F72->U+79BE, \\\n" - " U+2F73->U+7A74, U+2F74->U+7ACB, U+2F75->U+7AF9, U+2F76->U+7C73, \\\n" - " U+2F77->U+7CF8, U+2F78->U+7F36, U+2F79->U+7F51, U+2F7A->U+7F8A, \\\n" - " U+2F7B->U+7FBD, U+2F7C->U+8001, U+2F7D->U+800C, U+2F7E->U+8012, \\\n" - " U+2F7F->U+8033, U+2F80->U+807F, U+2F81->U+8089, U+2F82->U+81E3, \\\n" - " U+2F83->U+81EA, U+2F84->U+81F3, U+2F85->U+81FC, U+2F86->U+820C, \\\n" - " U+2F87->U+821B, U+2F88->U+821F, U+2F89->U+826E, U+2F8A->U+8272, \\\n" - " U+2F8B->U+8278, U+2F8C->U+864D, U+2F8D->U+866B, U+2F8E->U+8840, \\\n" - " U+2F8F->U+884C, U+2F90->U+8863, U+2F91->U+897E, U+2F92->U+898B, \\\n" - " U+2F93->U+89D2, U+2F94->U+8A00, U+2F95->U+8C37, U+2F96->U+8C46, \\\n" - " U+2F97->U+8C55, U+2F98->U+8C78, U+2F99->U+8C9D, U+2F9A->U+8D64, \\\n" - " U+2F9B->U+8D70, U+2F9C->U+8DB3, U+2F9D->U+8EAB, U+2F9E->U+8ECA, \\\n" - " U+2F9F->U+8F9B, U+2FA0->U+8FB0, U+2FA1->U+8FB5, U+2FA2->U+9091, \\\n" - " U+2FA3->U+9149, U+2FA4->U+91C6, U+2FA5->U+91CC, U+2FA6->U+91D1, \\\n" - " U+2FA7->U+9577, U+2FA8->U+9580, U+2FA9->U+961C, U+2FAA->U+96B6, \\\n" - " U+2FAB->U+96B9, U+2FAC->U+96E8, U+2FAD->U+9751, U+2FAE->U+975E, \\\n" - " U+2FAF->U+9762, U+2FB0->U+9769, U+2FB1->U+97CB, U+2FB2->U+97ED, \\\n" - " U+2FB3->U+97F3, U+2FB4->U+9801, U+2FB5->U+98A8, U+2FB6->U+98DB, \\\n" - " U+2FB7->U+98DF, U+2FB8->U+9996, U+2FB9->U+9999, U+2FBA->U+99AC, \\\n" - " U+2FBB->U+9AA8, U+2FBC->U+9AD8, U+2FBD->U+9ADF, U+2FBE->U+9B25, \\\n" - " U+2FBF->U+9B2F, U+2FC0->U+9B32, U+2FC1->U+9B3C, U+2FC2->U+9B5A, \\\n" - " U+2FC3->U+9CE5, U+2FC4->U+9E75, U+2FC5->U+9E7F, U+2FC6->U+9EA5, \\\n" - " U+2FC7->U+9EBB, U+2FC8->U+9EC3, U+2FC9->U+9ECD, U+2FCA->U+9ED1, \\\n" - " U+2FCB->U+9EF9, U+2FCC->U+9EFD, U+2FCD->U+9F0E, U+2FCE->U+9F13, \\\n" - " U+2FCF->U+9F20, U+2FD0->U+9F3B, U+2FD1->U+9F4A, U+2FD2->U+9F52, \\\n" - " U+2FD3->U+9F8D, U+2FD4->U+9F9C, U+2FD5->U+9FA0, U+3042->U+3041, \\\n" - " U+3044->U+3043, U+3046->U+3045, U+3048->U+3047, U+304A->U+3049, \\\n" - " U+304C->U+304B, U+304E->U+304D, U+3050->U+304F, U+3052->U+3051, \\\n" - " U+3054->U+3053, U+3056->U+3055, U+3058->U+3057, U+305A->U+3059, \\\n" - " U+305C->U+305B, U+305E->U+305D, U+3060->U+305F, U+3062->U+3061, \\\n" - " U+3064->U+3063, U+3065->U+3063, U+3067->U+3066, U+3069->U+3068, \\\n" - " U+3070->U+306F, U+3071->U+306F, U+3073->U+3072, U+3074->U+3072, \\\n" - " U+3076->U+3075, U+3077->U+3075, U+3079->U+3078, U+307A->U+3078, \\\n" - " U+307C->U+307B, U+307D->U+307B, U+3084->U+3083, U+3086->U+3085, \\\n" - " U+3088->U+3087, U+308F->U+308E, U+3094->U+3046, U+3095->U+304B, \\\n" - " U+3096->U+3051, U+30A2->U+30A1, U+30A4->U+30A3, U+30A6->U+30A5, \\\n" - " U+30A8->U+30A7, U+30AA->U+30A9, U+30AC->U+30AB, U+30AE->U+30AD, \\\n" - " U+30B0->U+30AF, U+30B2->U+30B1, U+30B4->U+30B3, U+30B6->U+30B5, \\\n" - " U+30B8->U+30B7, U+30BA->U+30B9, U+30BC->U+30BB, U+30BE->U+30BD, \\\n" - " U+30C0->U+30BF, U+30C2->U+30C1, U+30C5->U+30C4, U+30C7->U+30C6, \\\n" - " U+30C9->U+30C8, U+30D0->U+30CF, U+30D1->U+30CF, U+30D3->U+30D2, \\\n" - " U+30D4->U+30D2, U+30D6->U+30D5, U+30D7->U+30D5, U+30D9->U+30D8, \\\n" - " U+30DA->U+30D8, U+30DC->U+30DB, U+30DD->U+30DB, U+30E4->U+30E3, \\\n" - " U+30E6->U+30E5, U+30E8->U+30E7, U+30EF->U+30EE, U+30F4->U+30A6, \\\n" - " U+30AB->U+30F5, U+30B1->U+30F6, U+30F7->U+30EF, U+30F8->U+30F0, \\\n" - " U+30F9->U+30F1, U+30FA->U+30F2, U+30AF->U+31F0, U+30B7->U+31F1, \\\n" - " U+30B9->U+31F2, U+30C8->U+31F3, U+30CC->U+31F4, U+30CF->U+31F5, \\\n" - " U+30D2->U+31F6, U+30D5->U+31F7, U+30D8->U+31F8, U+30DB->U+31F9, \\\n" - " U+30E0->U+31FA, U+30E9->U+31FB, U+30EA->U+31FC, U+30EB->U+31FD, \\\n" - " U+30EC->U+31FE, U+30ED->U+31FF, U+FF66->U+30F2, U+FF67->U+30A1, \\\n" - " U+FF68->U+30A3, U+FF69->U+30A5, U+FF6A->U+30A7, U+FF6B->U+30A9, \\\n" - " U+FF6C->U+30E3, U+FF6D->U+30E5, U+FF6E->U+30E7, U+FF6F->U+30C3, \\\n" - " U+FF71->U+30A1, U+FF72->U+30A3, U+FF73->U+30A5, U+FF74->U+30A7, \\\n" - " U+FF75->U+30A9, U+FF76->U+30AB, U+FF77->U+30AD, U+FF78->U+30AF, \\\n" - " U+FF79->U+30B1, U+FF7A->U+30B3, U+FF7B->U+30B5, U+FF7C->U+30B7, \\\n" - " U+FF7D->U+30B9, U+FF7E->U+30BB, U+FF7F->U+30BD, U+FF80->U+30BF, \\\n" - " U+FF81->U+30C1, U+FF82->U+30C3, U+FF83->U+30C6, U+FF84->U+30C8, \\\n" - " U+FF85->U+30CA, U+FF86->U+30CB, U+FF87->U+30CC, U+FF88->U+30CD, \\\n" - " U+FF89->U+30CE, U+FF8A->U+30CF, U+FF8B->U+30D2, U+FF8C->U+30D5, \\\n" - " U+FF8D->U+30D8, U+FF8E->U+30DB, U+FF8F->U+30DE, U+FF90->U+30DF, \\\n" - " U+FF91->U+30E0, U+FF92->U+30E1, U+FF93->U+30E2, U+FF94->U+30E3, \\\n" - " U+FF95->U+30E5, U+FF96->U+30E7, U+FF97->U+30E9, U+FF98->U+30EA, \\\n" - " U+FF99->U+30EB, U+FF9A->U+30EC, U+FF9B->U+30ED, U+FF9C->U+30EF, \\\n" - " U+FF9D->U+30F3, U+FFA0->U+3164, U+FFA1->U+3131, U+FFA2->U+3132, \\\n" - " U+FFA3->U+3133, U+FFA4->U+3134, U+FFA5->U+3135, U+FFA6->U+3136, \\\n" - " U+FFA7->U+3137, U+FFA8->U+3138, U+FFA9->U+3139, U+FFAA->U+313A, \\\n" - " U+FFAB->U+313B, U+FFAC->U+313C, U+FFAD->U+313D, U+FFAE->U+313E, \\\n" - " U+FFAF->U+313F, U+FFB0->U+3140, U+FFB1->U+3141, U+FFB2->U+3142, \\\n" - " U+FFB3->U+3143, U+FFB4->U+3144, U+FFB5->U+3145, U+FFB6->U+3146, \\\n" - " U+FFB7->U+3147, U+FFB8->U+3148, U+FFB9->U+3149, U+FFBA->U+314A, \\\n" - " U+FFBB->U+314B, U+FFBC->U+314C, U+FFBD->U+314D, U+FFBE->U+314E, \\\n" - " U+FFC2->U+314F, U+FFC3->U+3150, U+FFC4->U+3151, U+FFC5->U+3152, \\\n" - " U+FFC6->U+3153, U+FFC7->U+3154, U+FFCA->U+3155, U+FFCB->U+3156, \\\n" - " U+FFCC->U+3157, U+FFCD->U+3158, U+FFCE->U+3159, U+FFCF->U+315A, \\\n" - " U+FFD2->U+315B, U+FFD3->U+315C, U+FFD4->U+315D, U+FFD5->U+315E, \\\n" - " U+FFD6->U+315F, U+FFD7->U+3160, U+FFDA->U+3161, U+FFDB->U+3162, \\\n" - " U+FFDC->U+3163, U+3131->U+1100, U+3132->U+1101, U+3133->U+11AA, \\\n" - " U+3134->U+1102, U+3135->U+11AC, U+3136->U+11AD, U+3137->U+1103, \\\n" - " U+3138->U+1104, U+3139->U+1105, U+313A->U+11B0, U+313B->U+11B1, \\\n" - " U+313C->U+11B2, U+313D->U+11B3, U+313E->U+11B4, U+313F->U+11B5, \\\n" - " U+3140->U+111A, U+3141->U+1106, U+3142->U+1107, U+3143->U+1108, \\\n" - " U+3144->U+1121, U+3145->U+1109, U+3146->U+110A, U+3147->U+110B, \\\n" - " U+3148->U+110C, U+3149->U+110D, U+314A->U+110E, U+314B->U+110F, \\\n" - " U+314C->U+1110, U+314D->U+1111, U+314E->U+1112, U+314F->U+1161, \\\n" - " U+3150->U+1162, U+3151->U+1163, U+3152->U+1164, U+3153->U+1165, \\\n" - " U+3154->U+1166, U+3155->U+1167, U+3156->U+1168, U+3157->U+1169, \\\n" - " U+3158->U+116A, U+3159->U+116B, U+315A->U+116C, U+315B->U+116D, \\\n" - " U+315C->U+116E, U+315D->U+116F, U+315E->U+1170, U+315F->U+1171, \\\n" - " U+3160->U+1172, U+3161->U+1173, U+3162->U+1174, U+3163->U+1175, \\\n" - " U+3165->U+1114, U+3166->U+1115, U+3167->U+11C7, U+3168->U+11C8, \\\n" - " U+3169->U+11CC, U+316A->U+11CE, U+316B->U+11D3, U+316C->U+11D7, \\\n" - " U+316D->U+11D9, U+316E->U+111C, U+316F->U+11DD, U+3170->U+11DF, \\\n" - " U+3171->U+111D, U+3172->U+111E, U+3173->U+1120, U+3174->U+1122, \\\n" - " U+3175->U+1123, U+3176->U+1127, U+3177->U+1129, U+3178->U+112B, \\\n" - " U+3179->U+112C, U+317A->U+112D, U+317B->U+112E, U+317C->U+112F, \\\n" - " U+317D->U+1132, U+317E->U+1136, U+317F->U+1140, U+3180->U+1147, \\\n" - " U+3181->U+114C, U+3182->U+11F1, U+3183->U+11F2, U+3184->U+1157, \\\n" - " U+3185->U+1158, U+3186->U+1159, U+3187->U+1184, U+3188->U+1185, \\\n" - " U+3189->U+1188, U+318A->U+1191, U+318B->U+1192, U+318C->U+1194, \\\n" - " U+318D->U+119E, U+318E->U+11A1, U+A490->U+A408, U+A491->U+A1B9, \\\n" - " U+4E00..U+9FBB, U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, \\\n" - " U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, \\\n" - " U+FA29, U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, \\\n" - " U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, U+3059, \\\n" - " U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, \\\n" - " U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, U+3085, U+3087, \\\n" - " U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, \\\n" - " U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, U+30BD, U+30BF, U+30C1, U+30C3, \\\n" - " U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, \\\n" - " U+30E2, U+30E3, U+30E5, U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, \\\n" - " U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, \\\n" - " U+31F9, U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, \\\n" - " U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, \\\n" - " U+A492..U+A4C6\n" - " ngram_chars = \\\n" - /* Support for Chinese/Japanese/Korean, from - * http://sphinxsearch.com/wiki/doku.php?id=charset_tables#cjk */ - " U+4E00..U+9FBB, U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, \\\n" - " U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, \\\n" - " U+FA29, U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, \\\n" - " U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, U+3059, \\\n" - " U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, \\\n" - " U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, U+3085, U+3087, \\\n" - " U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, \\\n" - " U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, U+30BD, U+30BF, U+30C1, U+30C3, \\\n" - " U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, \\\n" - " U+30E2, U+30E3, U+30E5, U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, \\\n" - " U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, \\\n" - " U+31F9, U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, \\\n" - " U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, \\\n" - " U+A492..U+A4C6\n" - " ngram_len = 1\n" - "\n" - " rt_attr_string = cyrusid\n" - " rt_field = header_from\n" - " rt_field = header_to\n" - " rt_field = header_cc\n" - " rt_field = header_bcc\n" - " rt_field = header_subject\n" - " rt_field = header_listid\n" - " rt_field = header_type\n" - " rt_field = headers\n" - " rt_field = body\n" - "}\n" - "\n"; - static const char global_config = - "searchd\n" - "{\n" - " listen = $socket:mysql41\n" - " log = syslog\n" - " pid_file = $pidfile\n" - " binlog_path = $rootdir/binlog\n" - " compat_sphinxql_magics = 0\n" - " workers = threads\n" - " max_matches = " SPHINX_MAX_MATCHES "\n" - "}\n" - /* This index exists only to allow the searchd to start before - * any real user indexes have been added */ - "index dummy\n" - "{\n" - " type = rt\n" - " path = $rootdir/dummy\n" - " rt_field = dummy\n" - "}\n"; - const char *rootdir = NULL; - char *basedir = NULL; - char *binlogdir = NULL; - char *indexname = NULL; - const char *config_file = NULL; - char *new_config_file = NULL; - int fd = -1; - struct buf bits = BUF_INITIALIZER; - struct buf user = BUF_INITIALIZER; - struct buf global = BUF_INITIALIZER; - int changed = 0; - int r; - - config_file = sphinx_config_file(); - if (verbose) { - if (mailbox) - syslog(LOG_INFO, "Sphinx setting up config file %s for mailbox %s", - config_file, mailbox->name); - else - syslog(LOG_INFO, "Sphinx setting up config file %s", - config_file); - } - - /* Read the old file */ - fd = open(config_file, O_RDONLY, 0); - if (fd >= 0) { - struct stat sb; - - r = fstat(fd, &sb); - if (r < 0) { - syslog(LOG_ERR, "IOERROR: unable to fstat %s: %m", - config_file); - r = IMAP_IOERROR; - goto out; - } - buf_ensure(&bits, sb.st_size+1); - bits.len = sb.st_size; - r = retry_read(fd, bits.s, bits.len); - buf_cstring(&bits); - if (r < 0) { - syslog(LOG_ERR, "IOERROR: unable to read %s: %m", - config_file); - r = IMAP_IOERROR; - goto out; - } - close(fd); - fd = -1; - - if (verbose) - syslog(LOG_INFO, "Sphinx read %d bytes of config file %s", - bits.len, config_file); - } - else if (errno != ENOENT) { - /* it's ok to be missing the file - we build it from scratch */ - syslog(LOG_ERR, "IOERROR: unable to open %s for reading: %m", - config_file); - r = IMAP_IOERROR; - goto out; - } - - /* adjust the file contents */ - if (buf_findline(&bits, global_config) < 0) { - - /* See if the root directory already exists */ - rootdir = sphinx_rootdir(NULL); - binlogdir = strconcat(rootdir, "/binlog", (char *)NULL); - r = check_directory(binlogdir, verbose, create); - if (r) goto out; - - buf_init_ro_cstr(&global, global_config); - buf_replace_all(&global, "$socket", config_getstring(IMAPOPT_SPHINX_SOCKET)); - buf_replace_all(&global, "$pidfile", config_getstring(IMAPOPT_SPHINX_PIDFILE)); - buf_replace_all(&global, "$rootdir", rootdir); - buf_append(&bits, &global); - changed++; - if (verbose) - syslog(LOG_INFO, "Sphinx adding section \"searchd\""); - } - - if (mailbox) { - - r = sphinx_basedir(mailbox, &basedir, &indexname); - if (r) { - syslog(LOG_ERR, "IOERROR: unable to name sphinx basedir for %s: %s", - mailbox->name, error_message(r)); - r = IMAP_IOERROR; - goto out; - } - - /* see if the base directory already exists */ - r = check_directory(basedir, verbose, create); - if (r) goto out; - - buf_init_ro_cstr(&user, user_config); - buf_replace_all(&user, "$indexname", indexname); - if (buf_findline(&bits, user.s) < 0) { - buf_replace_all(&user, "$basedir", basedir); - buf_append(&bits, &user); - changed++; - if (verbose) - syslog(LOG_INFO, "Sphinx adding section \"index %s\"", indexname); - } - } - - if (changed) { - - /* Write the new config file back */ - /* TODO: locking so multiple imapds don't all try to do this - * at the same time */ - - new_config_file = strconcat(config_file, ".new", (char *)NULL); - if (verbose) - syslog(LOG_INFO, "Sphinx writing new config file %s", new_config_file); - - fd = open(new_config_file, O_WRONLY|O_CREAT|O_TRUNC, 0600); - if (fd < 0) { - /* build the directory and retry */ - cyrus_mkdir(new_config_file, 0700); - fd = open(new_config_file, O_WRONLY|O_CREAT|O_TRUNC, 0600); - } - if (fd < 0) { - syslog(LOG_ERR, "IOERROR: unable to open %s for writing: %m", - new_config_file); - r = IMAP_IOERROR; - goto out; - } - - r = retry_write(fd, bits.s, bits.len); - if (r < 0) { - syslog(LOG_ERR, "IOERROR: unable to write %s: %m", - new_config_file); - r = IMAP_IOERROR; - goto out; - } - - close(fd); - fd = -1; - - if (verbose) - syslog(LOG_INFO, "Sphinx renaming config file %s into place", config_file); - - r = rename(new_config_file, config_file); - if (r < 0) { - syslog(LOG_ERR, "IOERROR: unable to rename %s to %s: %m", - new_config_file, config_file); - r = IMAP_IOERROR; - goto out; - } - } - - r = sphinx_signal(changed ? SIGHUP : 0, verbose); - if (r == IMAP_NOTFOUND) { - /* searchd not running, start it */ - if (verbose) - syslog(LOG_INFO, "Sphinx starting searchd"); - - r = run_command(SEARCHD, "--config", config_file, - "--syslog-prefix", sphinx_syslog_prefix(), (char *)NULL); - if (r) - syslog(LOG_ERR, "Failed to start searchd: %s", error_message(r)); - } - -out: - if (fd >= 0) close(fd); - if (new_config_file) unlink(new_config_file); - free(new_config_file); - free(basedir); - free(indexname); - free(binlogdir); - buf_free(&bits); - buf_free(&user); - buf_free(&global); - return r; -} - - -static int open_latest(struct mailbox *mailbox, struct latestdb *ldb) -{ - char *basedir = NULL; - char *path = NULL; - int r; - - r = sphinx_basedir(mailbox, &basedir, NULL); - if (r) return r; - path = strconcat(basedir, LATESTDB_FNAME, NULL); - free(basedir); - - if (!strcmpsafe(path, ldb->path)) { - free(path); - return 0; - } - - /* need to open a new DB */ - - close_latest(ldb); - - r = cyrusdb_open(config_getstring(IMAPOPT_SEARCH_INDEXED_DB), - path, CYRUSDB_CREATE, &ldb->db); - if (r) { - syslog(LOG_ERR, "DBERROR: opening %s: %s", - path, cyrusdb_strerror(r)); - r = IMAP_IOERROR; - } - - if (r) { - free(path); - } - else { - ldb->path = path; - } - return r; -} - -static void close_latest(struct latestdb *ldb) -{ - free(ldb->path); - ldb->path = NULL; - - if (ldb->db) { - cyrusdb_close(ldb->db); - ldb->db = NULL; - } -} - -/* - * Read the most recently indexed UID for the current mailboxfrom the - * 'latest' DB in the Sphinx directory. - * Returns 0 on success or an IMAP error code. - */ -static int read_latest(struct latestdb *ldb, - struct mailbox *mailbox, - uint32_t *latestp, - int verbose) -{ - struct buf key = BUF_INITIALIZER; - struct buf buf = BUF_INITIALIZER; - const char *data = NULL; - size_t datalen = 0; - int r = 0; - unsigned int version = 0; - unsigned int uid = 0; - - *latestp = 0; - if (verbose > 1) - syslog(LOG_INFO, "read_latest db=%s mailbox=%s uidvalidity=%u", - ldb->path, mailbox->name, mailbox->i.uidvalidity); - - buf_printf(&key, "%s.%u", mailbox->name, mailbox->i.uidvalidity); - - r = cyrusdb_fetch(ldb->db, - key.s, key.len, - &data, &datalen, - (struct txn **)NULL); - if (r == CYRUSDB_NOTFOUND) { - if (verbose > 1) syslog(LOG_INFO, "read_latest defaults to 0"); - r = 0; - goto out; - } - if (r) goto out; - buf_init_ro(&buf, data, datalen); - buf_cstring(&buf); - - r = sscanf(buf.s, "%u %u", &version, &uid); - if (r != 2 || version != LATESTDB_VERSION) { - r = IMAP_MAILBOX_BADFORMAT; - goto out; - } - - if (verbose > 1) syslog(LOG_INFO, "read_latest uid=%u", uid); - *latestp = uid; - r = 0; - -out: - buf_free(&key); - buf_free(&buf); - return r; -} - -static int write_latest(struct latestdb *ldb, - struct mailbox *mailbox, - uint32_t uid, - int verbose) -{ - struct buf key = BUF_INITIALIZER; - struct buf data = BUF_INITIALIZER; - struct txn *txn = NULL; - int r = 0; - - if (verbose) - syslog(LOG_INFO, "write_latest db=%s mailbox=%s uidvalidity=%u uid=%u", - ldb->path, mailbox->name, mailbox->i.uidvalidity, uid); - - buf_printf(&key, "%s.%u", mailbox->name, mailbox->i.uidvalidity); - buf_printf(&data, "%u %u", LATESTDB_VERSION, uid); - - do { - r = cyrusdb_store(ldb->db, - key.s, key.len, - data.s, data.len, - &txn); - } while (r == CYRUSDB_AGAIN); - if (!r) - r = cyrusdb_commit(ldb->db, txn); - else - cyrusdb_abort(ldb->db, txn); - - buf_free(&data); - buf_free(&key); - return r; -} - -/* - * Read the last document ID from the "latest" DB. This is a - * temporary measure until we can get autoincrement behaviour - * working for Sphinx. - * - * Returns: 0 on success or an IMAP error code. - */ -static int read_lastid(struct latestdb *ldb, - uint32_t *lastidp, - int verbose) -{ - struct buf key = BUF_INITIALIZER; - struct buf buf = BUF_INITIALIZER; - const char *data = NULL; - size_t datalen = 0; - int r = 0; - unsigned int version = 0; - unsigned int id = 0; - - *lastidp = 0; - if (verbose > 1) syslog(LOG_INFO, "read_lastid db=%s", ldb->path); - - buf_init_ro_cstr(&key, LATESTDB_LASTID_KEY); - - r = cyrusdb_fetch(ldb->db, - key.s, key.len, - &data, &datalen, - (struct txn **)NULL); - if (r == CYRUSDB_NOTFOUND) { - if (verbose > 1) syslog(LOG_INFO, "read_lastid defaults to 0"); - r = 0; - goto out; - } - if (r) goto out; - buf_init_ro(&buf, data, datalen); - buf_cstring(&buf); - - r = sscanf(buf.s, "%u %u", &version, &id); - if (r != 2 || version != LATESTDB_VERSION) { - r = IMAP_MAILBOX_BADFORMAT; - goto out; - } - - if (verbose > 1) syslog(LOG_INFO, "read_lastid id=%u", id); - *lastidp = id; - r = 0; - -out: - buf_free(&key); - buf_free(&buf); - return r; -} - -static int write_lastid(struct latestdb *ldb, - uint32_t lastid, - int verbose) -{ - struct buf key = BUF_INITIALIZER; - struct buf data = BUF_INITIALIZER; - struct txn *txn = NULL; - int r = 0; - - if (verbose > 1) - syslog(LOG_INFO, "write_lastid db=%s lastid=%u", ldb->path, lastid); - - buf_init_ro_cstr(&key, LATESTDB_LASTID_KEY); - buf_printf(&data, "%u %u", LATESTDB_VERSION, lastid); - - do { - r = cyrusdb_store(ldb->db, - key.s, key.len, - data.s, data.len, - &txn); - } while (r == CYRUSDB_AGAIN); - if (!r) - r = cyrusdb_commit(ldb->db, txn); - else - cyrusdb_abort(ldb->db, txn); - - - buf_free(&data); - buf_free(&key); - return r; -} - -static int flush(search_text_receiver_t *rx) -{ - sphinx_update_receiver_t *tr = (sphinx_update_receiver_t *)rx; - int r = 0; - - if (!tr->uncommitted) return 0; - - /* We write the lastid out first, to avoid a future instance - * allocating a duplicate Sphinx document id should we crash */ - r = write_lastid(&tr->latestdb, tr->lastid, tr->super.verbose); - if (r) return r; - - if (tr->super.verbose > 1) - syslog(LOG_NOTICE, "Sphinx committing %u updates", - tr->uncommitted); - - r = mysql_commit(tr->conn.mysql); - if (r) { - syslog(LOG_ERR, "IOERROR: Sphinx COMMIT failed for " - "mailbox %s, %u messages ending at uid %u: %s", - tr->super.mailbox->name, - tr->uncommitted, - tr->super.uid, - mysql_error(tr->conn.mysql)); - r = IMAP_IOERROR; - goto out; - } - - /* We write out the latestid for the mailbox only after successfully - * updating the index, to avoid a future instance not realising that - * there are unindexed messages should we fail to index */ - r = write_latest(&tr->latestdb, tr->super.mailbox, tr->latest, - tr->super.verbose); - if (r) goto out; - - tr->uncommitted = 0; - buf_reset(&tr->super.tmp); - -out: - return r; -} - -static void begin_message(search_text_receiver_t *rx, uint32_t uid) -{ - sphinx_receiver_t *tr = (sphinx_receiver_t *)rx; - int i; - - tr->uid = uid; - for (i = 0 ; i < SEARCH_NUM_PARTS ; i++) - buf_reset(&tr->partsi); - tr->parts_total = 0; - tr->truncate_warning = 0; -} - -static void begin_part(search_text_receiver_t *rx, int part) -{ - sphinx_receiver_t *tr = (sphinx_receiver_t *)rx; - - tr->part = part; -} - -static void append_text(search_text_receiver_t *rx, - const struct buf *text) -{ - sphinx_receiver_t *tr = (sphinx_receiver_t *)rx; - - if (tr->part) { - unsigned len = text->len; - if (tr->parts_total + len > MAX_PARTS_SIZE) { - if (!tr->truncate_warning++) - syslog(LOG_ERR, "Sphinx: truncating text from " - "message mailbox %s uid %u", - tr->mailbox->name, tr->uid); - len = MAX_PARTS_SIZE - tr->parts_total; - } - if (len) { - tr->parts_total += len; - buf_appendmap(&tr->partstr->part, text->s, len); - } - } -} - -static void end_part(search_text_receiver_t *rx, - int part __attribute__((unused))) -{ - sphinx_receiver_t *tr = (sphinx_receiver_t *)rx; - - if (tr->verbose > 1) - syslog(LOG_NOTICE, "Sphinx: %u bytes in part %s", - tr->partstr->part.len, search_part_as_string(tr->part)); - - tr->part = 0; -} - -static void log_keywords(sphinx_update_receiver_t *tr) -{ - int i; - int r; - struct buf query = BUF_INITIALIZER; - MYSQL_RES *res = NULL; - MYSQL_ROW row = NULL; - - for (i = 0 ; i < SEARCH_NUM_PARTS ; i++) { - if (!tr->super.partsi.len) continue; - buf_reset(&query); - buf_appendcstr(&query, "CALL KEYWORDS("); - append_escaped(&query, &tr->super.partsi, '\''); - buf_appendcstr(&query, ", "); - append_escaped_cstr(&query, tr->super.indexname, '\''); - buf_appendcstr(&query, ",0)"); - r = doquery(&tr->conn, tr->super.verbose, &query); - - res = mysql_use_result(tr->conn.mysql); - if (!res) continue; - - while ((row = mysql_fetch_row(res))) - syslog(LOG_INFO, "keyword %s\n", row0); - mysql_free_result(res); - } - buf_free(&query); -} - -static int end_message_update(search_text_receiver_t *rx) -{ - sphinx_update_receiver_t *tr = (sphinx_update_receiver_t *)rx; - struct buf *query = &tr->super.tmp; - int i; - int r; - - if (!tr->conn.mysql) return IMAP_INTERNAL; - - buf_reset(query); - buf_printf(query, "INSERT INTO %s (id,"COL_CYRUSID, tr->super.indexname); - for (i = 0 ; i < SEARCH_NUM_PARTS ; i++) { - if (tr->super.partsi.len) { - buf_appendcstr(query, ","); - buf_appendcstr(query, column_by_parti); - } - } - buf_appendcstr(query, ") VALUES ("); - buf_printf(query, "%u,", ++tr->lastid); - append_escaped(query, make_cyrusid(tr->super.mailbox, tr->super.uid), '\''); - for (i = 0 ; i < SEARCH_NUM_PARTS ; i++) { - if (tr->super.partsi.len) { - buf_appendcstr(query, ","); - append_escaped(query, &tr->super.partsi, '\''); - } - } - /* apparently Sphinx doesn't let you explicitly INSERT a NULL */ - buf_appendcstr(query, ")"); - - r = doquery(&tr->conn, tr->super.verbose, query); - if (r) goto out; /* TODO: propagate error to the user */ - - if (tr->super.verbose > 3) log_keywords(tr); - - ++tr->uncommitted; - tr->latest = tr->super.uid; - -out: - tr->super.uid = 0; - return r; -} - -static const char *indexing_lockpath(struct mailbox *mailbox) -{ - char *usermbox = mboxname_user_mbox(mboxname_to_userid(mailbox->name), NULL); - const char *lockpath = mboxname_lockpath_suffix(usermbox, INDEXING_LOCK_SUFFIX); - free(usermbox); - return lockpath; -} - -static void indexing_unlock(int *fdp) -{ - if (*fdp >= 0) { - close(*fdp); - *fdp = -1; - } -} - -static int indexing_lock(struct mailbox *mailbox, int *fdp) -{ - const char *lockpath = indexing_lockpath(mailbox); - int fd; - int r; - - indexing_unlock(fdp); - - fd = open(lockpath, O_WRONLY|O_CREAT, 0600); - if (fd < 0) { - if (cyrus_mkdir(lockpath, 0755) < 0) { - syslog(LOG_ERR, "IOERROR: unable to cyrus_mkdir %s: %m", lockpath); - return IMAP_IOERROR; - } - fd = open(lockpath, O_WRONLY|O_CREAT, 0600); - } - if (fd < 0) { - syslog(LOG_ERR, "IOERROR: unable to create %s: %m", lockpath); - return IMAP_IOERROR; - } - - r = lock_blocking(fd, lockpath); - if (r < 0) { - syslog(LOG_ERR, "IOERROR: unable to lock %s: %m", - lockpath); - close(fd); - return IMAP_IOERROR; - } - - *fdp = fd; - return 0; -} - -static int begin_mailbox_update(search_text_receiver_t *rx, - struct mailbox *mailbox, - int incremental __attribute__((unused))) -{ - sphinx_update_receiver_t *tr = (sphinx_update_receiver_t *)rx; - int r; - - r = sphinx_setup(mailbox, tr->super.verbose, /*create*/1); - if (r) return r; - - r = sphinx_basedir(mailbox, NULL, &tr->super.indexname); - if (r) return r; - - r = get_connection(&tr->conn); - if (r) return r; - - tr->super.mailbox = mailbox; - - r = indexing_lock(mailbox, &tr->indexing_lock_fd); - if (r) return r; - - r = open_latest(mailbox, &tr->latestdb); - if (r) return r; - - r = read_lastid(&tr->latestdb, &tr->lastid, tr->super.verbose); - if (r) return r; - - r = read_latest(&tr->latestdb, mailbox, &tr->latest, tr->super.verbose); - if (r) return r; - - return 0; -} - -static uint32_t first_unindexed_uid(search_text_receiver_t *rx) -{ - sphinx_update_receiver_t *tr = (sphinx_update_receiver_t *)rx; - - return tr->latest+1; -} - -static int is_indexed(search_text_receiver_t *rx, uint32_t uid) -{ - sphinx_update_receiver_t *tr = (sphinx_update_receiver_t *)rx; - - return (uid <= tr->latest); -} - -static int end_mailbox_update(search_text_receiver_t *rx, - struct mailbox *mailbox - __attribute__((unused))) -{ - sphinx_update_receiver_t *tr = (sphinx_update_receiver_t *)rx; - int r = 0; - - if (tr->conn.mysql) - r = flush(rx); - - tr->super.mailbox = NULL; - free(tr->super.indexname); - tr->super.indexname = NULL; - - return r; -} - -static search_text_receiver_t *begin_update(int verbose) -{ - sphinx_update_receiver_t *tr; - - tr = xzmalloc(sizeof(sphinx_update_receiver_t)); - tr->super.super.begin_mailbox = begin_mailbox_update; - tr->super.super.first_unindexed_uid = first_unindexed_uid; - tr->super.super.is_indexed = is_indexed; - tr->super.super.begin_message = begin_message; - tr->super.super.begin_part = begin_part; - tr->super.super.append_text = append_text; - tr->super.super.end_part = end_part; - tr->super.super.end_message = end_message_update; - tr->super.super.end_mailbox = end_mailbox_update; - tr->super.super.flush = flush; - - tr->indexing_lock_fd = -1; - tr->super.verbose = verbose; - - return &tr->super.super; -} - -static int free_receiver(sphinx_receiver_t *tr) -{ - int i; - int r = 0; - - for (i = 0 ; i < SEARCH_NUM_PARTS ; i++) - buf_free(&tr->partsi); - buf_free(&tr->tmp); - free(tr->indexname); - - free(tr); - - return r; -} - -static int end_update(search_text_receiver_t *rx) -{ - sphinx_update_receiver_t *tr = (sphinx_update_receiver_t *)rx; - - close_latest(&tr->latestdb); - close_connection(&tr->conn); - indexing_unlock(&tr->indexing_lock_fd); - return free_receiver(&tr->super); -} - -static int begin_mailbox_snippets(search_text_receiver_t *rx, - struct mailbox *mailbox, - int incremental __attribute__((unused))) -{ - sphinx_snippet_receiver_t *tr = (sphinx_snippet_receiver_t *)rx; - int r; - - r = get_connection(&tr->conn); - if (r) return r; - - r = sphinx_basedir(mailbox, NULL, &tr->super.indexname); - if (r) return r; - - tr->super.mailbox = mailbox; - - return 0; -} - -/* Generate Sphinx extended query syntax, not SphinxQL, customised for - * using CALL SNIPPETS. This differs from the regular query because - * CALL SNIPPETS will silently ignore @field modifiers instead of - * enforcing them. So we have to emit one CALL SNIPPETS for every - * field that may be mentioned in the original query, with a munged - * query that contains a disjunction of all the search terms that - * might apply to that part. Thanks heaps Sphinx. IRIS-2038 */ -static void generate_snippet_query(struct buf *query, - int part, - struct opnode *on) -{ - struct opnode *child; - struct buf arg = BUF_INITIALIZER; - - buf_init_ro_cstr(&arg, on->arg); - - switch (on->op) { - - case SEARCH_OP_NOT: - case SEARCH_OP_OR: - case SEARCH_OP_AND: - for (child = on->children ; child ; child = child->next) - generate_snippet_query(query, part, child); - break; - - case SEARCH_PART_ANY: - assert(on->children == NULL); - if (part != SEARCH_PART_HEADERS || - !config_getswitch(IMAPOPT_SPHINX_TEXT_EXCLUDES_ODD_HEADERS)) { - if (query->len) buf_putc(query, '|'); - append_escaped(query, &arg, '"'); - } - break; - - default: - /* other SEARCH_PART_* constants */ - assert(on->op >= 0 && on->op < SEARCH_NUM_PARTS); - assert(on->children == NULL); - if (part == on->op) { - if (query->len) buf_putc(query, '|'); - append_escaped(query, &arg, '"'); - } - break; - } - - buf_free(&arg); -} - -static int end_message_snippets(search_text_receiver_t *rx) -{ - sphinx_snippet_receiver_t *tr = (sphinx_snippet_receiver_t *)rx; - struct buf query = BUF_INITIALIZER; - struct buf inner_query = BUF_INITIALIZER; - MYSQL_RES *res = NULL; - MYSQL_ROW row; - int i; - int r; - - if (!tr->conn.mysql) { - r = IMAP_INTERNAL; /* need to call begin_mailbox() */ - goto out; - } - if (!tr->root) { - r = 0; - goto out; - } - - for (i = 0 ; i < SEARCH_NUM_PARTS ; i++) { - if (res) { - mysql_free_result(res); - res = NULL; - } - if (i == SEARCH_PART_ANY) continue; - - if (!tr->super.partsi.len) continue; - - buf_reset(&inner_query); - generate_snippet_query(&inner_query, i, tr->root); - if (!inner_query.len) continue; - - buf_reset(&query); - buf_appendcstr(&query, "CALL SNIPPETS("); - append_escaped(&query, &tr->super.partsi, '\''); - buf_appendcstr(&query, ", "); - append_escaped_cstr(&query, tr->super.indexname, '\''); - buf_appendcstr(&query, ", "); - append_escaped(&query, &inner_query, '\''); - buf_appendcstr(&query, ", 1 AS query_mode, 1 AS allow_empty)"); - - r = doquery(&tr->conn, tr->super.verbose, &query); - if (r) goto out; - - res = mysql_store_result(tr->conn.mysql); - if (!res) continue; - - row = mysql_fetch_row(res); - if (!row) continue; - - if (tr->super.verbose > 1) - syslog(LOG_ERR, "snippet %d \"%s\"", i, row0); - - if (!row00) continue; - r = tr->proc(tr->super.mailbox, tr->super.uid, i, row0, tr->rock); - if (r) break; - } - -out: - if (res) mysql_free_result(res); - buf_free(&query); - buf_free(&inner_query); - return r; -} - -static int end_mailbox_snippets(search_text_receiver_t *rx, - struct mailbox *mailbox - __attribute__((unused))) -{ - sphinx_snippet_receiver_t *tr = (sphinx_snippet_receiver_t *)rx; - - tr->super.mailbox = NULL; - free(tr->super.indexname); - tr->super.indexname = NULL; - - return 0; -} - -static search_text_receiver_t *begin_snippets(void *internalised, - int verbose, - search_snippet_cb_t proc, - void *rock) -{ - sphinx_snippet_receiver_t *tr; - - tr = xzmalloc(sizeof(sphinx_snippet_receiver_t)); - tr->super.super.begin_mailbox = begin_mailbox_snippets; - tr->super.super.begin_message = begin_message; - tr->super.super.begin_part = begin_part; - tr->super.super.append_text = append_text; - tr->super.super.end_part = end_part; - tr->super.super.end_message = end_message_snippets; - tr->super.super.end_mailbox = end_mailbox_snippets; - - tr->super.verbose = verbose; - tr->root = (struct opnode *)internalised; - tr->proc = proc; - tr->rock = rock; - - return &tr->super.super; -} - -static int end_snippets(search_text_receiver_t *rx) -{ - sphinx_snippet_receiver_t *tr = (sphinx_snippet_receiver_t *)rx; - - close_connection(&tr->conn); - return free_receiver(&tr->super); -} - -static int start_daemon(int verbose) -{ - return sphinx_setup(NULL, verbose, /*create*/1); -} - -static int stop_daemon(int verbose) -{ - int r; - - if (verbose) - syslog(LOG_INFO, "Sphinx stopping searchd"); - - r = run_command(SEARCHD, "--config", sphinx_config_file(), - "--syslog-prefix", sphinx_syslog_prefix(), - "--stopwait", (char *)NULL); - if (r) - syslog(LOG_ERR, "Failed to stop searchd: %s", error_message(r)); - return r; -} - -const struct search_engine sphinx_search_engine = { - "Sphinx", - SEARCH_FLAG_CAN_BATCH, - begin_search, - end_search, - begin_update, - end_update, - begin_snippets, - end_snippets, - describe_internalised, - free_internalised, - start_daemon, - stop_daemon, - /* list_files */NULL, /* XXX: fixme */ - /* compact */NULL, - /* deluser */NULL /* XXX: fixme */ -}; -
View file
cyrus-imapd-2.5.tar.gz/imap/search_squat.c
Deleted
@@ -1,1012 +0,0 @@ -/* search_squat.c -- glue code for searching via SQUAT - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <errno.h> -#include <fcntl.h> -#include <stdlib.h> -#include <syslog.h> -#include <string.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -#include "assert.h" -#include "index.h" -#include "global.h" -#include "xmalloc.h" -#include "xstrlcpy.h" -#include "xstrlcat.h" -#include "bitvector.h" - -#include "imap_err.h" -#include "search_engines.h" -#include "squat.h" - -#define DEBUG 0 - -struct opstack { - int op; /* boolean operator to apply */ - int valid; /* whether msg_vector is valid yet */ - bitvector_t msg_vector; - /* merged search results, indexed by uid (so - * that bit 0 is not meaningful) */ -}; - -typedef struct { - search_builder_t super; - struct mailbox *mailbox; - int verbose; - SquatSearchIndex *index; - int fd; - const char *part_types; - int found_validity; - int depth; - int alloc; - struct opstack *stack; -} SquatBuilderData; - -static const char *squat_strerror(int err); - -static const char * const doctypes_by_partSEARCH_NUM_PARTS = { - "mh", - "f", - "t", - "c", - "b", - "s", - "h", - "m" -}; - -/* The document name is of the form - - pnnn.vvv - - Where p is a part_type character (denoting which segment of the message - is represented by the document), nnn is the UID of the message, and vvv - is the UID validity value. - - This function parses the document name and returns the UID - only if the name has the right part type and it corresponds - to a real message UID. - Returns a UID (>=1) or zero on error. -*/ -static unsigned int parse_doc_name(SquatBuilderData *bb, const char *doc_name) -{ - int ch = doc_name0; - const char *t = bb->part_types; - int doc_UID; - - if (ch == 'v' && strncmp(doc_name, "validity.", 9) == 0) { - if ((unsigned) atoi(doc_name + 9) == bb->mailbox->i.uidvalidity) { - bb->found_validity = 1; - } - return 0; - } - - /* make sure that the document part type is one of the ones we're - accepting */ - while (*t != 0 && *t != ch) { - t++; - } - if (*t == 0) { - return 0; - } - - doc_UID = atoi(++doc_name); - while ((*doc_name >= '0' && *doc_name <= '9') || *doc_name == '-') { - ++doc_name; - } - if (*doc_name != 0) { - return 0; - } - - return doc_UID; -} - -#if DEBUG -static void opstack_dump(SquatBuilderData *bb, const char *where) -{ - int i; - char *desc; - struct buf line = BUF_INITIALIZER; - - syslog(LOG_NOTICE, "Squat opstack %s {", where); - for (i = 0 ; i < bb->depth ; i++) { - struct opstack *o = bb->stack+i; - - buf_reset(&line); - buf_printf(&line, "op=%s", search_op_as_string(o->op)); - - buf_printf(&line, " valid=%d", o->valid); - - desc = bv_cstring(&o->msg_vector); - buf_printf(&line, " msg_vector=%s", desc); - free(desc); - - syslog(LOG_NOTICE, "Squat %s", buf_cstring(&line)); - } - syslog(LOG_NOTICE, "Squat }"); - buf_free(&line); -} -#endif - -static struct opstack *opstack_top(SquatBuilderData *bb) -{ - return (bb->depth ? &bb->stackbb->depth-1 : NULL); -} - -static struct opstack *opstack_push(SquatBuilderData *bb, int op) -{ - struct opstack *top; - -#if DEBUG - if (bb->verbose > 1) - syslog(LOG_NOTICE, "Squat opstack_push(op=%s)\n", search_op_as_string(op)); -#endif - - /* push a new op on the stack */ - if (bb->depth+1 > bb->alloc) { - bb->alloc += 16; - bb->stack = xrealloc(bb->stack, bb->alloc * sizeof(struct opstack)); - } - - top = &bb->stackbb->depth++; - top->op = op; - top->valid = 0; - bv_init(&top->msg_vector); - bv_setsize(&top->msg_vector, bb->mailbox->i.last_uid+1); - -#if DEBUG - if (bb->verbose > 1) - opstack_dump(bb, "after push"); -#endif - - return top; -} - -static void opstack_pop(SquatBuilderData *bb) -{ - struct opstack *child; - struct opstack *parent; - -#if DEBUG - if (bb->verbose > 1) - syslog(LOG_NOTICE, "Squat opstack_pop()"); -#endif - - /* pop the last operator off the stack */ - assert(bb->depth); - child = opstack_top(bb); - bb->depth--; - parent = opstack_top(bb); - - if (parent && child->valid) { - /* merge the result with the parent node */ - if (!parent->valid) - bv_copy(&parent->msg_vector, &child->msg_vector); - else if (parent->op == SEARCH_OP_OR) - bv_oreq(&parent->msg_vector, &child->msg_vector); - else if (parent->op == SEARCH_OP_AND) - bv_andeq(&parent->msg_vector, &child->msg_vector); - parent->valid = 1; - } - - bv_free(&child->msg_vector); - -#if DEBUG - if (bb->verbose > 1) - opstack_dump(bb, "after pop"); -#endif -} - -static int drop_indexed_docs(void* closure, const SquatListDoc *doc) -{ - SquatBuilderData* bb = (SquatBuilderData*)closure; - unsigned int uid = parse_doc_name(bb, doc->doc_name); - - if (uid) - bv_clear(&opstack_top(bb)->msg_vector, uid); - return SQUAT_CALLBACK_CONTINUE; -} - -static int fill_with_hits(void* closure, char const* doc) -{ - SquatBuilderData* bb = (SquatBuilderData*)closure; - unsigned int uid = parse_doc_name(bb, doc); - - if (uid) - bv_set(&opstack_top(bb)->msg_vector, uid); - return SQUAT_CALLBACK_CONTINUE; -} - -static void begin_boolean(search_builder_t *bx, int op) -{ - SquatBuilderData *bb = (SquatBuilderData *)bx; - -#if DEBUG - if (bb->verbose > 1) - syslog(LOG_NOTICE, "Squat begin_boolean(op=%s)", search_op_as_string(op)); -#endif - - opstack_push(bb, op); -} - -static void end_boolean(search_builder_t *bx, int op __attribute__((unused))) -{ - SquatBuilderData *bb = (SquatBuilderData *)bx; - -#if DEBUG - if (bb->verbose > 1) - syslog(LOG_NOTICE, "Squat end_boolean()"); -#endif - opstack_pop(bb); -} - -static void match(search_builder_t *bx, int part, const char *str) -{ - SquatBuilderData *bb = (SquatBuilderData *)bx; - struct opstack *parent = opstack_top(bb); - struct opstack *top; - int r; - -#if DEBUG - if (bb->verbose > 1) - syslog(LOG_NOTICE, "Squat match(part=%d str=\"%s\")", part, str); -#endif - - if (!doctypes_by_partpart) - return; - if (parent && parent->op == SEARCH_OP_NOT) - return; - - top = opstack_push(bb, /*doesn't matter*/0); - bb->part_types = doctypes_by_partpart; - - r = squat_search_execute(bb->index, str, strlen(str), - fill_with_hits, bb); - if (r != SQUAT_OK) { - if (squat_get_last_error() == SQUAT_ERR_SEARCH_STRING_TOO_SHORT) - goto out; /* The rest of the search is still viable */ - syslog(LOG_ERR, "SQUAT string list search failed on string %s " - "with part types %s: %s", - str, bb->part_types, squat_strerror(r)); - goto out; - } - top->valid = 1; - -#if DEBUG - if (bb->verbose > 1) - opstack_dump(bb, "after match"); -#endif - -out: - opstack_pop(bb); -} - -static void *get_internalised(search_builder_t *bx - __attribute__((unused))) -{ - return NULL; -} - -static int run(search_builder_t *bx, search_hit_cb_t proc, void *rock); - -static search_builder_t *begin_search(struct mailbox *mailbox, int opts) -{ - SquatBuilderData *bb; - SquatSearchIndex* index; - const char *fname; - int fd; - - if ((opts & SEARCH_MULTIPLE)) { - syslog(LOG_ERR, "Squat does not support multiple-folder searches, sorry"); - /* although it could with some extra work, but why bother */ - return NULL; - } - - fname = mailbox_meta_fname(mailbox, META_SQUAT); - if ((fd = open(fname, O_RDONLY)) < 0) { - if (errno != ENOENT) - syslog(LOG_ERR, "SQUAT failed to open index file %s: %s", - fname, squat_strerror(squat_get_last_error())); - return NULL; /* probably not found. Just bail */ - } - if ((index = squat_search_open(fd)) == NULL) { - syslog(LOG_ERR, "SQUAT failed to open index %s: %s", - fname, squat_strerror(squat_get_last_error())); - close(fd); - return NULL; - } - - bb = xzmalloc(sizeof(SquatBuilderData)); - bb->super.begin_boolean = begin_boolean; - bb->super.end_boolean = end_boolean; - bb->super.match = match; - bb->super.get_internalised = get_internalised; - bb->super.run = run; - - bb->mailbox = mailbox; - bb->verbose = (opts & _SEARCH_VERBOSE_MASK); - bb->index = index; - bb->fd = fd; - - /* Push a boolean node on the stack -- this will be used - * at the end of the search to OR in any unindexed messages */ - opstack_push(bb, SEARCH_OP_OR); - - return &bb->super; -} - -static int add_unindexed(SquatBuilderData *bb) -{ - struct opstack *top = opstack_top(bb); - int r = 0; - - top = opstack_push(bb, /*doesn't matter*/0); - bv_setall(&top->msg_vector); - bv_clear(&top->msg_vector, 0); /* UID 0 is not valid */ - bb->part_types = "tfcbsmh"; - bb->found_validity = 0; - - r = squat_search_list_docs(bb->index, drop_indexed_docs, bb); - if (r != SQUAT_OK) { - syslog(LOG_ERR, "SQUAT failed to get list of indexed documents: %s", - squat_strerror(r)); - r = IMAP_IOERROR; - goto out; - } - if (!bb->found_validity) { - syslog(LOG_ERR, "SQUAT didn't find validity record"); - r = IMAP_IOERROR; - goto out; - } - top->valid = 1; - r = 0; - -#if DEBUG - if (bb->verbose > 1) - opstack_dump(bb, "after adding unindexed"); -#endif - -out: - opstack_pop(bb); - return r; -} - -static int run(search_builder_t *bx, search_hit_cb_t proc, void *rock) -{ - SquatBuilderData *bb = (SquatBuilderData *)bx; - unsigned int uid; - int r = 0; - -#if DEBUG - if (bb->verbose > 1) - syslog(LOG_NOTICE, "Squat end_search()"); -#endif - - /* check we had balanced ->begin_boolean and ->end_boolean calls */ - if (bb->depth != 1) - goto out; - - r = add_unindexed(bb); - if (r) goto out; - - /* Flatten out the final bit vector into a sequence */ - for (uid = 1 ; uid <= bb->mailbox->i.last_uid; uid++) { - if (bv_isset(&bb->stack0.msg_vector, uid)) { - r = proc(bb->mailbox->name, - bb->mailbox->i.uidvalidity, - uid, rock); - if (r) goto out; - } - } - -out: - return r; -} - -static void end_search(search_builder_t *bx) -{ - SquatBuilderData *bb = (SquatBuilderData *)bx; - - while (bb->depth) opstack_pop(bb); - free(bb->stack); - if (bb->index) squat_search_close(bb->index); - if (bb->fd >= 0) close(bb->fd); - free(bx); -} - - -/* - SQUAT index files are organised as follows: - - There is (at most) one index file for each Cyrus mailbox, named - "cyrus.squat", stored in the mailbox directory. - - Source documents are named 'xUID' where UID is the numeric UID of a - message and x is a character denoting a part of the message: 'f' == - FROM, 't' == TO, 'b' == BCC, 'c' == CC, 's' == SUBJECT, 'h' == other - headers, 'm' == the body. So, a message with UID 331 could give rise - to several source documents named "f331", "t331", "b331", "c331", - "s331", "h331" and "m331". - - There is also a special source document named "validity.N" where N - is the uidvalidity nonce for the mailbox. We use this to detect when - the UIDs have been renumbered since we created the index (in which - case the index is useless and is ignored). - - Updating creates new indexes for one or more mailboxes. (We do not - support true incremental updates to an index yet.) The index is created - in "cyrus.squat.tmp" and then, if creation was successful, it is - atomically renamed to "cyrus.squat". This guarantees that we don't - interfere with anyone who has the old index open. -*/ - -/* These stats are gathered 1) per mailbox and 2) for the whole operation. */ -typedef struct { - unsigned long indexed_bytes; /* How many bytes of processed message text - have we indexed? */ - unsigned long indexed_messages; /* How many messages have we indexed? */ - unsigned long index_size; /* How many bytes is the index using? */ - time_t start_time; /* When did this operation start? */ - time_t end_time; /* When did it end? */ -} SquatStats; - -typedef struct { - search_text_receiver_t super; - int verbose; - SquatStats mailbox_stats; - SquatStats total_stats; - SquatIndex *index; - int fd; - SquatSearchIndex *old_index; - int old_fd; - struct mailbox *mailbox; - int valid; - uint32_t uidvalidity; - unsigned int mailbox_count; - /* Tracks which UIDs were indexed in the old - * index (or all 0 if a full update) */ - bitvector_t indexed; - uint32_t uid; - int doc_is_open; - char doc_name100; - struct buf pending_text; -} SquatReceiverData; - -static void start_stats(SquatStats *stats) -{ - stats->index_size = 0; - stats->indexed_bytes = 0; - stats->indexed_messages = 0; - stats->start_time = time(NULL); -} - -static void stop_stats(SquatStats *stats) -{ - stats->end_time = time(NULL); -} - -static void print_stats(const char *which, const SquatStats *stats) -{ - syslog(LOG_NOTICE, "squat: %s indexed %lu messages (%lu bytes) " - "into %lu index bytes in %d seconds\n", - which, - stats->indexed_messages, - stats->indexed_bytes, - stats->index_size, - (int)(stats->end_time - stats->start_time)); -} - -static const char *squat_strerror(int err) -{ - static char buf64; - - switch (err) { - case SQUAT_ERR_SYSERR: - return strerror(errno); - default: - /* There are other error codes, but they only apply for searching, - not index construction */ - snprintf(buf, sizeof(buf), "unknown squat error %d", err); - return buf; - } -} - -/* Cyrus passes the text to index in here, after it has canonicalized - the text. We figure out what source document the text belongs to, - and update the index. */ -static void begin_message(search_text_receiver_t *rx, uint32_t uid) -{ - SquatReceiverData *d = (SquatReceiverData *) rx; - - d->uid = uid; - d->doc_is_open = 0; - d->doc_name0 = '\0'; - buf_init(&d->pending_text); - - d->mailbox_stats.indexed_messages++; - d->total_stats.indexed_messages++; -} - -static void begin_part(search_text_receiver_t *rx, int part) -{ - SquatReceiverData *d = (SquatReceiverData *) rx; - char part_char = 0; - - /* Figure out what the name of the source document is going to be. */ - switch (part) { - case SEARCH_PART_FROM: part_char = 'f'; break; - case SEARCH_PART_TO: part_char = 't'; break; - case SEARCH_PART_CC: part_char = 'c'; break; - case SEARCH_PART_BCC: part_char = 'b'; break; - case SEARCH_PART_SUBJECT: part_char = 's'; break; - case SEARCH_PART_HEADERS: part_char = 'h'; break; - case SEARCH_PART_BODY: - part_char = 'm'; - break; - default: - return; - } - - snprintf(d->doc_name, sizeof(d->doc_name), "%c%d", part_char, d->uid); - d->doc_is_open = 0; - buf_reset(&d->pending_text); - - /* The document will be opened lazily later, once we have - * accumulated more than the minimum amount of text */ -} - -static int do_append(SquatReceiverData *d, const struct buf *text) -{ - int s; /* SQUAT error */ - - if (d->verbose > 3) - syslog(LOG_ERR, "squat: writing %llu bytes into message %u\n", - (unsigned long long)text->len, d->uid); - - s = squat_index_append_document(d->index, text->s, text->len); - if (s != SQUAT_OK) { - syslog(LOG_ERR, "squat: error writing index data " - "for mailbox %s uid %u: %s", - d->mailbox->name, d->uid, - squat_strerror(s)); - return IMAP_IOERROR; - } - d->mailbox_stats.indexed_bytes += text->len; - d->total_stats.indexed_bytes += text->len; - return 0; -} - -static void append_text(search_text_receiver_t *rx, - const struct buf *text) -{ - SquatReceiverData *d = (SquatReceiverData *) rx; - int r = 0; /* IMAP error */ - int s = 0; /* SQUAT error */ - - if (!d->doc_is_open) { - if (text->len + d->pending_text.len < SQUAT_WORD_SIZE) { - /* not enough text yet */ - buf_append(&d->pending_text, text); - return; - } - - /* just went over the threshold */ - if (d->verbose > 2) - syslog(LOG_NOTICE, "squat: opening document part '%s'\n", - d->doc_name); - - s = squat_index_open_document(d->index, d->doc_name); - if (s != SQUAT_OK) { - syslog(LOG_ERR, "squat: error opening document %s " - "for mailbox %s: %s", - d->doc_name, d->mailbox->name, - squat_strerror(s)); - return; - } - d->doc_is_open = 1; - - /* flush any pending text */ - if (d->pending_text.len) - r = do_append(d, &d->pending_text); - buf_reset(&d->pending_text); - } - - if (!r) - r = do_append(d, text); - - /* TODO: propagate an error to the caller */ -} - -static void end_part(search_text_receiver_t *rx, - int part __attribute__((unused))) -{ - SquatReceiverData *d = (SquatReceiverData *) rx; - int s = 0; /* SQUAT error */ - - if (d->doc_is_open) { - s = squat_index_close_document(d->index); - if (s != SQUAT_OK) { - syslog(LOG_ERR, "squat: error closing document %s " - "for mailbox %s uid %u: %s", - d->doc_name, d->mailbox->name, - d->uid, squat_strerror(s)); - return; - } - } - d->doc_is_open = 0; - buf_reset(&d->pending_text); -} - -static int end_message(search_text_receiver_t *rx) -{ - SquatReceiverData *d = (SquatReceiverData *) rx; - - d->uid = 0; - return 0; -} - -/* Let SQUAT tell us what's going on in the expensive - squat_index_finish function. */ -static void stats_callback(void *closure, - SquatStatsEvent *params) -{ - SquatReceiverData *d = (SquatReceiverData *)closure; - - switch (params->generic.type) { - case SQUAT_STATS_COMPLETED_INITIAL_CHAR: - if (d->verbose > 1) { - if (params->completed_initial_char.num_words > 0) { - printf("Processing index character %d, %d total words, " - "temp file size is %d\n", - params->completed_initial_char.completed_char, - params->completed_initial_char.num_words, - params->completed_initial_char.temp_file_size); - } - } - break; - - default: - ; /* do nothing */ - } -} - -/* Populate d->indexed map using document names from SquatSearchIndex backend */ -static int doc_check(void *closure, const SquatListDoc *doc) -{ - SquatReceiverData *d = (SquatReceiverData *)closure; - unsigned long uid; - - /* validity will be replaced with new value in same slot */ - if (!strncmp(doc->doc_name, "validity.", 9)) { - d->uidvalidity = strtoul(doc->doc_name + 9, NULL, 10); - return (1); - } - - if (!strchr("tfcbsmh", doc->doc_name0)) { - syslog(LOG_ERR, "squat: invalid document name: %s", doc->doc_name); - d->valid = 0; - /* TODO: is this right?? */ - return (1); - } - - uid = strtoul(doc->doc_name + 1, NULL, 10); - if (uid > 0) { - bv_set(&d->indexed, uid); - return (1); - } - - /* Remove this UID from the index */ - return (0); -} - -static int begin_mailbox(search_text_receiver_t *rx, - struct mailbox *mailbox, - int incremental) -{ - SquatReceiverData *d = (SquatReceiverData *)rx; - SquatOptions options; - const char *filename; - const char *old_filename; - int fd = -1; - int old_fd = -1; - SquatIndex *index = NULL; - SquatSearchIndex *old_index = NULL; - int r = 0; /* IMAP error code */ - int s = 0; /* SQUAT error code */ - - bv_clearall(&d->indexed); - - filename = mailbox_meta_newfname(mailbox, META_SQUAT); - if ((fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0666)) < 0) { - syslog(LOG_ERR, "squat: unable to create temporary index file %s: %m", - filename); - r = IMAP_IOERROR; - goto out; - } - - options.option_mask = SQUAT_OPTION_TMP_PATH | SQUAT_OPTION_STATISTICS; - options.tmp_path = mailbox_datapath(mailbox); - options.stats_callback = stats_callback; - options.stats_callback_closure = (void *)d; - index = squat_index_init(fd, &options); - if (index == NULL) { - syslog(LOG_ERR, "squat: unable to initialise index %s: %s", - filename, squat_strerror(squat_get_last_error())); - r = IMAP_IOERROR; - goto out; - } - - /* Open existing index if it exists */ - old_filename = mailbox_meta_fname(mailbox, META_SQUAT); - if (incremental) { - old_fd = open(old_filename, O_RDONLY); - /* Silently ignore errors opening the old fd or index - * and fall back to a full update. Probably should - * NOT be silent if the error is anything other than - * a missing index. */ - if (old_fd >= 0) - old_index = squat_search_open(old_fd); - if (old_index == NULL) - incremental = 0; - } - - if (incremental) { - /* Copy existing document names verbatim. They end up with the same - * doc_IDs as in the old index, which makes trie copying much simpler. - */ - d->valid = 1; - d->uidvalidity = 0L; - squat_index_add_existing(index, old_index, doc_check, d); - - if (!d->valid) { - syslog(LOG_ERR, "squat: corrupt old index for mailbox %s, " - "forcing full update", - mailbox->name); - incremental = 0; - } - - if (incremental && - d->uidvalidity != mailbox->i.uidvalidity) { - /* Squat file refers to old mailbox: force full rebuild */ - syslog(LOG_ERR, "squat: mailbox %s uidvalidity changed " - "from %u to %u, forcing full update", - mailbox->name, - mailbox->i.uidvalidity, - d->uidvalidity); - incremental = 0; - } - } - - if (!incremental) { - bv_clearall(&d->indexed); - - /* write an empty document at the beginning to record the validity - nonce */ - snprintf(d->doc_name, sizeof(d->doc_name), - "validity.%u", mailbox->i.uidvalidity); - s = squat_index_open_document(index, d->doc_name); - if (s != SQUAT_OK) { - syslog(LOG_ERR, "squat: cannot write uidvalidity nonce: %s", - squat_strerror(s)); - r = IMAP_IOERROR; - goto out; - } - s = squat_index_close_document(index); - if (s != SQUAT_OK) { - syslog(LOG_ERR, "squat: cannot close document for " - "uidvalidity nonce: %s", - squat_strerror(s)); - r = IMAP_IOERROR; - goto out; - } - } - -out: - /* it isn't obvious, but we need to keep old_index and old_fd - * around until after new_index has been finished. */ - if (r) { - if (index != NULL) squat_index_destroy(index); - d->index = NULL; - if (fd >= 0) close(fd); - d->fd = -1; - - if (old_index != NULL) squat_search_close(old_index); - d->old_index = NULL; - if (old_fd >= 0) close(old_fd); - d->old_fd = -1; - - d->mailbox = NULL; - } - else { - d->index = index; - d->fd = fd; - - d->old_index = old_index; - d->old_fd = old_fd; - - d->mailbox = mailbox; - start_stats(&d->mailbox_stats); - } - return r; -} - -static uint32_t first_unindexed_uid(search_text_receiver_t *rx - __attribute__((unused))) -{ - return 1; -} - -static int is_indexed(search_text_receiver_t *rx, uint32_t uid) -{ - SquatReceiverData *d = (SquatReceiverData *)rx; - - return bv_isset(&d->indexed, uid); -} - -static int end_mailbox(search_text_receiver_t *rx, - struct mailbox *mailbox - __attribute__((unused))) -{ - SquatReceiverData *d = (SquatReceiverData *)rx; - struct stat sb; - int r = 0; /* IMAP error code or syscall return */ - int s = 0; /* SQUAT error code */ - - if (!d->index) - return 0; - - s = squat_index_finish(d->index); - if (s != SQUAT_OK) { - syslog(LOG_ERR, - "squat: failed to close index for mailbox %s (error %d)", - d->mailbox->name, s); - r = IMAP_IOERROR; - goto out; - } - - /* Check how big the resulting file is */ - if (fstat(d->fd, &sb) < 0) { - syslog(LOG_ERR, "squat: unable to stat temporary index file: %m"); - r = IMAP_IOERROR; - goto out; - } - d->mailbox_stats.index_size = sb.st_size; - d->total_stats.index_size += sb.st_size; - - r = close(d->fd); - d->fd = -1; - if (r < 0) { - /* This isn't going to happen unless we're on NFS */ - syslog(LOG_ERR, "squat: unable to complete writing " - "temporary index file: %m"); - r = IMAP_IOERROR; - goto out; - } - - /* OK, we successfully created the index under the temporary file name. - Let's rename it to make it the real index. */ - r = mailbox_meta_rename(d->mailbox, META_SQUAT); - if (r) goto out; - - if (d->verbose) { - stop_stats(&d->mailbox_stats); - print_stats(d->mailbox->name, &d->mailbox_stats); - } - d->mailbox_count++; - r = 0; - -out: - if (d->old_index) squat_search_close(d->old_index); - d->old_index = NULL; - if (d->old_fd >= 0) close(d->old_fd); - d->old_fd = -1; - - d->index = NULL; - if (d->fd >= 0) close(d->fd); - d->fd = -1; - - d->mailbox = NULL; - return r; -} - -static search_text_receiver_t *begin_update(int verbose) -{ - SquatReceiverData *d; - - d = xzmalloc(sizeof(SquatReceiverData)); - d->super.begin_mailbox = begin_mailbox; - d->super.first_unindexed_uid = first_unindexed_uid; - d->super.is_indexed = is_indexed; - d->super.begin_message = begin_message; - d->super.begin_part = begin_part; - d->super.append_text = append_text; - d->super.end_part = end_part; - d->super.end_message = end_message; - d->super.end_mailbox = end_mailbox; - - d->fd = -1; - d->verbose = verbose; - - start_stats(&d->total_stats); - - return &d->super; -} - -static int end_update(search_text_receiver_t *rx) -{ - SquatReceiverData *d = (SquatReceiverData *)rx; - - if (d->verbose && d->mailbox_count > 1) { - stop_stats(&d->total_stats); - print_stats("Total", &d->total_stats); - } - - bv_free(&d->indexed); - free(d); - return 0; -} - -const struct search_engine squat_search_engine = { - "SQUAT", - 0, - begin_search, - end_search, - begin_update, - end_update, - /* begin_snippets */NULL, - /* end_snippets */NULL, - /* describe_internalised */NULL, - /* free_internalised */NULL, - /* start_daemon */NULL, - /* stop_daemon */NULL, - /* list_files */NULL, - /* compact */NULL, - /* deluser */NULL -}; -
View file
cyrus-imapd-2.5.tar.gz/imap/search_test.c
Deleted
@@ -1,349 +0,0 @@ -/* - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <stdlib.h> -#include <stdio.h> -#include <syslog.h> -#include <string.h> -#include <sys/stat.h> - -/* cyrus includes */ -#include "assert.h" -#include "bsearch.h" -#include "exitcodes.h" -#include "global.h" -#include "imap_err.h" -#include "index.h" -#include "search_engines.h" -#include "search_expr.h" -#include "search_query.h" -#include "message.h" -#include "sysexits.h" -#include "util.h" -#include "xmalloc.h" - -#if !HAVE___ATTRIBUTE__ -#define __attribute__(x) -#endif - -static int usage(const char *name); - -int verbose = 0; - -/* ====================================================================== */ - -static void dump_one_folder(const char *key __attribute__((unused)), - void *data, - void *rock __attribute__((unused))) -{ - search_folder_t *folder = data; - int uid; - - printf("mailbox %s\n", folder->mboxname); - printf("min %u\n", search_folder_get_min(folder)); - printf("max %u\n", search_folder_get_max(folder)); - printf("count %u\n", search_folder_get_count(folder)); - printf("highestmodseq %llu\n", (unsigned long long)search_folder_get_highest_modseq(folder)); - search_folder_foreach(folder, uid) { - printf("uid %u\n", uid); - } -} - -static int do_search(const char *mboxname, - int multiple, - const char *userid, - char **words, int nwords) -{ - struct buf querytext = BUF_INITIALIZER; - struct namespace ns; - struct index_init init; - struct index_state *state = NULL; - struct protstream *pin = NULL; - struct protstream *pout = NULL; - struct searchargs *searchargs = NULL; - search_query_t *query = NULL; - int i; - int r; - struct timeval start_time, end_time; - - memset(&init, 0, sizeof(struct index_init)); - - for (i = 0 ; i < nwords ; i++) { - if (i) buf_putc(&querytext, ' '); - buf_appendcstr(&querytext, wordsi); - } - if (verbose) - fprintf(stderr, "search_test: IMAP query is \"%s\"\n", buf_cstring(&querytext)); - buf_putc(&querytext, '\r'); - buf_cstring(&querytext); - - r = mboxname_init_namespace(&ns, /*isadmin*/0); - if (r) { - fprintf(stderr, "Failed to initialise namespace: %s\n", error_message(r)); - goto out; - } - - pin = prot_readmap(querytext.s, querytext.len); - pout = prot_new(/*fd*/0, /*write*/1); - - init.userid = userid; - init.authstate = auth_newstate(userid); - init.out = pout; - - r = index_open(mboxname, &init, &state); - if (r) { - fprintf(stderr, "%s: %s\n", mboxname, error_message(r)); - goto out; - } - - index_checkflags(state, 0, 0); - - searchargs = new_searchargs(".", GETSEARCH_CHARSET_KEYWORD, &ns, userid, init.authstate, /*isadmin*/0); - - gettimeofday(&start_time, NULL); - - r = get_search_program(pin, pout, searchargs); - if (r != '\r') { - fprintf(stderr, "Couldn't parse IMAP search program\n"); - goto out; - } - - query = search_query_new(state, searchargs); - query->multiple = multiple; - query->verbose = verbose; - r = search_query_run(query); - if (r) { - fprintf(stderr, "Failed to run query: %s\n", error_message(r)); - goto out; - } - gettimeofday(&end_time, NULL); - - hash_enumerate(&query->folders_by_name, dump_one_folder, query); - - if (verbose) - fprintf(stderr, "search_test: ran query in %.6f sec\n", - timesub(&start_time, &end_time)); - -out: - if (pin) prot_free(pin); - if (pout) prot_free(pout); - if (searchargs) freesearchargs(searchargs); - search_query_free(query); - index_close(&state); - buf_free(&querytext); - if (init.authstate) auth_freestate(init.authstate); - return !!r; -} - -/* ====================================================================== */ - -static int do_serialise(char **words, int nwords) -{ - const char *userid = "cassandane"; - struct buf querytext = BUF_INITIALIZER; - struct namespace ns; - struct protstream *pin = NULL; - struct protstream *pout = NULL; - struct searchargs *searchargs = NULL; - char *str = NULL; - search_expr_t *e = NULL; - int i; - int r; - struct timeval start_time, end_time; - - for (i = 0 ; i < nwords ; i++) { - if (i) buf_putc(&querytext, ' '); - buf_appendcstr(&querytext, wordsi); - } - if (verbose) - fprintf(stderr, "search_test: IMAP query is \"%s\"\n", buf_cstring(&querytext)); - buf_putc(&querytext, '\r'); - buf_cstring(&querytext); - - r = mboxname_init_namespace(&ns, /*isadmin*/0); - if (r) { - fprintf(stderr, "Failed to initialise namespace: %s\n", error_message(r)); - goto out; - } - - pin = prot_readmap(querytext.s, querytext.len); - pout = prot_new(/*fd*/0, /*write*/1); - - searchargs = new_searchargs(".", GETSEARCH_CHARSET_KEYWORD, &ns, userid, auth_newstate(userid), /*isadmin*/0); - - r = get_search_program(pin, pout, searchargs); - if (r != '\r') { - fprintf(stderr, "Couldn't parse IMAP search program\n"); - goto out; - } - - gettimeofday(&start_time, NULL); - str = search_expr_serialise(searchargs->root); - gettimeofday(&end_time, NULL); - if (verbose) - fprintf(stderr, "search_test: serialised query in %.6f sec\n", - timesub(&start_time, &end_time)); - - gettimeofday(&start_time, NULL); - e = search_expr_unserialise(str); - gettimeofday(&end_time, NULL); - if (verbose) - fprintf(stderr, "search_test: unserialised query in %.6f sec\n", - timesub(&start_time, &end_time)); - -out: - if (pin) prot_free(pin); - if (pout) prot_free(pout); - if (searchargs) freesearchargs(searchargs); - if (e) search_expr_free(e); - free(str); - return !!r; -} - -/* ====================================================================== */ - -int main(int argc, char **argv) -{ - int c; - const char *alt_config = NULL; - const char *userid = NULL; - const char *mboxname = NULL; - enum { SEARCH, SERIALISE } mode = SEARCH; - int multiple = 0; - int r = 0; - - if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { - fatal("must run as the Cyrus user", EC_USAGE); - } - - while ((c = getopt(argc, argv, "C:LMSm:u:v")) != EOF) { - switch (c) { - - case 'C': /* alt config file */ - alt_config = optarg; - break; - - case 'L': - mode = SERIALISE; - break; - - case 'M': - multiple = 1; - break; - - case 'S': - multiple = 0; - break; - - case 'm': - mboxname = optarg; - break; - - case 'u': - userid = optarg; - break; - - case 'v': - verbose++; - break; - - default: - usage(argv0); - break; - } - } - - if (optind == argc) - usage(argv0); - if (mode == SEARCH && !mboxname) - usage(argv0); - - cyrus_init(alt_config, "search_test", - CYRUSINIT_PERROR, CONFIG_NEED_PARTITION_DATA); - - mboxlist_init(0); - mboxlist_open(NULL); - search_attr_init(); - - switch (mode) { - - case SEARCH: - if (!userid) { - userid = mboxname_to_userid(mboxname); - if (!userid) - usage(argv0); - } - - r = do_search(mboxname, multiple, userid, argv+optind, argc-optind); - break; - - case SERIALISE: - r = do_serialise(argv+optind, argc-optind); - break; - } - - mboxlist_close(); - mboxlist_done(); - - cyrus_done(); - - return r; -} - -static int usage(const char *name) -{ - fprintf(stderr, "usage: %s format-options -m mailbox -u userid searchprogram...\n", name); - exit(EC_USAGE); -} - -void fatal(const char* s, int code) -{ - fprintf(stderr, "search_test: %s\n", s); - cyrus_done(); - exit(code); -} - -
View file
cyrus-imapd-2.5.tar.gz/imap/search_xapian.c
Deleted
@@ -1,2498 +0,0 @@ -/* search_xapian.c -- glue code for searching with Xapian - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <errno.h> -#include <fcntl.h> -#include <stdlib.h> -#include <syslog.h> -#include <string.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <dirent.h> - -#include "assert.h" -#include "bitvector.h" -#include "imap_err.h" -#include "global.h" -#include "ptrarray.h" -#include "user.h" -#include "xmalloc.h" -#include "xstrlcpy.h" -#include "xstrlcat.h" -#include "mappedfile.h" -#include "mboxlist.h" -#include "xstats.h" -#include "search_engines.h" -#include "sequence.h" -#include "cyr_lock.h" -#include "xapian_wrap.h" -#include "command.h" - -#define INDEXEDDB_VERSION 2 -#define INDEXEDDB_FNAME "/cyrus.indexed.db" -#define XAPIAN_DIRNAME "/xapian" -#define ACTIVEFILE_METANAME "xapianactive" - -/* Name of columns */ -#define COL_CYRUSID "cyrusid" -static const char * const prefix_by_partSEARCH_NUM_PARTS = { - NULL, - "F", /* FROM */ - "T", /* TO */ - "C", /* CC */ - "B", /* BCC */ - "S", /* SUBJECT */ - "L", /* LISTID */ - "Y", /* TYPE */ - "H", /* HEADERS */ - "D", /* BODY */ -}; - -struct segment -{ - int part; - int sequence; /* forces stable sort order JIC */ - int is_finished; - struct buf text; -}; - - -static const char *xapian_rootdir(const char *tier, const char *partition); -static int xapian_basedir(const char *tier, const char *mboxname, const char *part, - const char *root, char **basedir); - -/* ====================================================================== */ - -/* the "activefile" file lists the tiers and generations of all the - * currently active search databases. The format is space separated - * records tiername:generation, i.e. "meta:0". If there is no file present, - * it is created by finding all the existing search directories (from - * filesystem inspection) and prepending default:nextgen where default - * is the searchdefaulttier value and nextgen is one higher than the - * largest generation found. In the simplest configuration this is - * just ":0" */ - -struct activeitem { - char *tier; - int generation; -}; - -static struct activeitem *activeitem_parse(const char *input) -{ - struct activeitem *res = NULL; - char *num = strrchr(input, ':'); - - if (!num) return NULL; - - res = xzmalloc(sizeof(struct activeitem)); - res->tier = xstrndup(input, num-input); - res->generation = atoi(num+1); - - return res; -} - -static void activeitem_free(struct activeitem *item) -{ - if (!item) return; - free(item->tier); - free(item); -} - -char *activeitem_generate(const char *tier, int generation) -{ - struct buf buf = BUF_INITIALIZER; - buf_printf(&buf, "%s:%d", tier, generation); - return buf_release(&buf); -} - -/* calculate the next name for this tier, by incrementing the generation - * to one higher than any existing active record */ -static char *activefile_nextname(const strarray_t *active, const char *tier) -{ - int max = -1; - int i; - - for (i = 0; i < active->count; i++) { - struct activeitem *item = activeitem_parse(strarray_nth(active, i)); - if (item && !strcmp(item->tier, tier)) { - if (item->generation > max) - max = item->generation; - } - activeitem_free(item); - } - - return activeitem_generate(tier, max+1); -} - -/* filter a list of active records to only those in certain tiers. - * Used to calculate which databases to use as sources for compression */ -static strarray_t *activefile_filter(const strarray_t *active, const strarray_t *tiers, const char *partition) -{ - strarray_t *res = strarray_new(); - int i; - - for (i = 0; i < active->count; i++) { - struct activeitem *item = activeitem_parse(strarray_nth(active, i)); - /* we want to compress anything which can't possibly exist as well - * as anything which matches the filter tiers */ - if (!item || strarray_find(tiers, item->tier, 0) >= 0 || !xapian_rootdir(item->tier, partition)) - strarray_append(res, strarray_nth(active, i)); - activeitem_free(item); - } - - return res; -} - -/* the activefile file is a per-user meta file */ -static char *activefile_fname(const char *mboxname) -{ - const char *userid = mboxname_to_userid(mboxname); - if (!userid) return NULL; - return user_hash_meta(userid, ACTIVEFILE_METANAME); -} - -/* file format is very simple */ -static strarray_t *activefile_read(struct mappedfile *activefile) -{ - return strarray_nsplit(mappedfile_base(activefile), mappedfile_size(activefile), NULL, 1); -} - -/* to write a activefile file safely, we need to do the create .NEW, - * write, fsync, rename dance. This unlocks the original file, so - * callers will need to lock again if they need a locked file. - * The 'mappedfile' API isn't a perfect match for what we need here, - * but it's close enough, and avoids open coding the lock dance. */ -static int activefile_write(struct mappedfile *mf, const strarray_t *new) -{ - char *newname = strconcat(mappedfile_fname(mf), ".NEW", (char *)NULL); - struct mappedfile *newfile = NULL; - int r; - ssize_t nwritten; - char *towrite = NULL; - - r = mappedfile_open(&newfile, newname, MAPPEDFILE_CREATE|MAPPEDFILE_RW); - if (r) goto done; - r = mappedfile_writelock(newfile); - if (r) goto done; - - towrite = strarray_join(new, " "); - nwritten = mappedfile_pwrite(newfile, towrite, strlen(towrite), 0); - free(towrite); - if (nwritten < 0) { - /* commit anyway so mappedfile doesn't have kittens - * about the map being closed dirty */ - r = IMAP_IOERROR; - mappedfile_commit(newfile); - goto done; - } - - r = mappedfile_commit(newfile); - if (r) goto done; - - r = mappedfile_rename(newfile, mappedfile_fname(mf)); - if (r) unlink(newname); - - /* we lose control over the lock here, so we have to release */ - mappedfile_unlock(mf); - -done: - if (newfile) { - mappedfile_unlock(newfile); - mappedfile_close(&newfile); - } - free(newname); - - return r; -} - -/* if the mappedfile has no content, it needs to be initialised - * with some dummy data. Strictly it doesn't, but it makes - * reasoning about everything else easier if there's always a - * file */ - -static void inspect_filesystem(const char *mboxname, const char *partition, - strarray_t *found, strarray_t *bogus); - -static void _activefile_init(const char *mboxname, const char *partition, - struct mappedfile *activefile) -{ - int r = mappedfile_writelock(activefile); - const char *tier = config_getstring(IMAPOPT_DEFAULTSEARCHTIER); - strarray_t *list = NULL; - - /* failed to lock, doh */ - if (r) return; - - /* did someone beat us to it? */ - if (mappedfile_size(activefile)) { - mappedfile_unlock(activefile); - return; - } - - list = strarray_new(); - inspect_filesystem(mboxname, partition, list, NULL); - /* always put the next item on the front so we don't write to any - * existing databases */ - strarray_unshiftm(list, activefile_nextname(list, tier)); - - activefile_write(activefile, list); - - strarray_free(list); -} - -static strarray_t *activefile_open(const char *mboxname, const char *partition, - struct mappedfile **activefile, int write) -{ - char *fname = activefile_fname(mboxname); - int r; - - if (!fname) return NULL; - - /* try to open the file, and populate with initial values if it's empty */ - r = mappedfile_open(activefile, fname, MAPPEDFILE_CREATE|MAPPEDFILE_RW); - if (!r && !mappedfile_size(*activefile)) - _activefile_init(mboxname, partition, *activefile); - free(fname); - - if (r) return NULL; - - /* take the requested lock (a better helper API would allow this to be - * specified as part of the open call, but here's where we are */ - if (write) r = mappedfile_writelock(*activefile); - else r = mappedfile_readlock(*activefile); - if (r) return NULL; - - /* finally, read the contents */ - return activefile_read(*activefile); -} - -/* given an item from the activefile file, and the mboxname and partition - * to calculate the user, find the path. If dostat is true, also stat the - * path and return NULL if it doesn't exist (used for filtering databases - * to actually search in */ -static char *activefile_path(const char *mboxname, const char *part, const char *item, int dostat) -{ - char *basedir = NULL; - struct buf buf = BUF_INITIALIZER; - char *dest = NULL; - struct activeitem *ai = activeitem_parse(item); - - xapian_basedir(ai->tier, mboxname, part, NULL, &basedir); - if (!basedir) goto out; - buf_printf(&buf, "%s%s", basedir, XAPIAN_DIRNAME); - free(basedir); - - if (ai->generation) - buf_printf(&buf, ".%d", ai->generation); - - dest = buf_release(&buf); - - if (dostat) { - struct stat sbuf; - if (stat(dest, &sbuf)) { - if (errno != ENOENT) - syslog(LOG_ERR, "IOERROR: can't read %s for search, check permissions: %m", dest); - free(dest); - dest = NULL; - } - } - -out: - activeitem_free(ai); - return dest; -} - -/* convert an array of activefile items to an array of database paths, - * optionally stripping records where the path doesn't exist */ -static strarray_t *activefile_resolve(const char *mboxname, const char *part, - const strarray_t *items, int dostat) -{ - strarray_t *result = strarray_new(); - int i; - - for (i = 0; i < items->count; i++) { - char *dir = activefile_path(mboxname, part, strarray_nth(items, i), dostat); - if (dir) strarray_appendm(result, dir); - } - - return result; -} - -/* ====================================================================== */ - -/* the filesystem layout is inspectable - this is useful for a couple of - * purposes - both rebuilding the activefile if it's lost, and also finding - * stale "missing" directories after a successful rebuild */ - -struct inspectrock { - const char *mboxname; - const char *partition; - strarray_t *found; - strarray_t *bogus; -}; - -static void inspect_check(const char *key, const char *val __attribute__((unused)), void *rock) -{ - struct inspectrock *ir = (struct inspectrock *)rock; - const char *match = strstr(key, "searchpartition-"); - char *basedir = NULL; - char *tier = NULL; - char *fname = NULL; - DIR *dirh = NULL; - struct dirent *de; - bit64 generation; - const char *rest; - - if (!match) goto out; - tier = xstrndup(key, match - key); - - if (xapian_basedir(tier, ir->mboxname, ir->partition, NULL, &basedir)) - goto out; - - dirh = opendir(basedir); - if (!dirh) goto out; - - while ((de = readdir(dirh))) { - generation = 0; - if (de->d_name0 == '.') continue; - free(fname); - fname = strconcat(basedir, "/", de->d_name, (char *)NULL); - /* only 'xapian' directories allowed */ - if (strncmp(de->d_name, "xapian", 6)) goto bogus; - - /* xapian by itself is tier zero */ - if (de->d_name6) { - /* otherwise it's xapian.generation */ - if (de->d_name6 != '.') goto bogus; - - /* unless it exactly matches digits, it's either got .NEW on the end or is - * likewise bogus, track it */ - if (parsenum(de->d_name + 7, &rest, strlen(de->d_name)-7, &generation) || rest0) - goto bogus; - } - - /* found one! */ - strarray_appendm(ir->found, activeitem_generate(tier, (int)generation)); - continue; - -bogus: - if (ir->bogus) { - strarray_appendm(ir->bogus, fname); - fname = NULL; - } - } - -out: - if (dirh) closedir(dirh); - free(fname); - free(basedir); - free(tier); -} - -static void inspect_filesystem(const char *mboxname, const char *partition, - strarray_t *found, strarray_t *bogus) -{ - struct inspectrock rock; - - rock.mboxname = mboxname; - rock.partition = partition; - rock.found = found; - rock.bogus = bogus; - - config_foreachoverflowstring(inspect_check, &rock); -} - -/* ====================================================================== */ - -/* The "indexed database" contains information about which cyrus messages - * are indexed in this sphinx directory. The keys are mailbox.uidvalidity - * and the values are "version sequence", where sequence is an IMAP-style - * sequence of UIDs. This allows squatter to quickly determine which - * messages are not yet indexed in any active database. */ - -/* parse both the old version 1 (just max UID rather than range) and - * current version sequence from a mapped database value */ -static struct seqset *parse_indexed(const char *data, size_t datalen) -{ - struct seqset *seq = NULL; - const char *rest; - bit64 version; - char *val; - - if (parsenum(data, &rest, datalen, &version)) - return NULL; - - if (*rest++ != ' ') - return NULL; - - switch(version) { - case 1: - { - char buf20; - snprintf(buf, 20, "1:%.*s", (int)(datalen - (rest - data)), rest); - return seqset_parse(buf, NULL, 0); - } - case 2: - val = xstrndup(rest, datalen - (rest - data)); - seq = seqset_parse(val, NULL, 0); - free(val); - return seq; - } - - return NULL; -} - -/* - * Read the most indexed UIDs sequence for the current mailbox - * from the cyrus.indexed DB in each xapian directory and join - * them into a single result. - * Returns 0 on success or an IMAP error code. - */ -static int read_indexed(const strarray_t *paths, - const char *mboxname, - uint32_t uidvalidity, - struct seqset *res, - int verbose) -{ - struct db *db = NULL; - struct buf path = BUF_INITIALIZER; - struct buf key = BUF_INITIALIZER; - const char *data = NULL; - size_t datalen = 0; - int r = 0; - int i; - - buf_printf(&key, "%s.%u", mboxname, uidvalidity); - - for (i = 0; i < paths->count; i++) { - struct seqset *seq; - buf_reset(&path); - buf_printf(&path, "%s%s", strarray_nth(paths, i), INDEXEDDB_FNAME); - if (verbose > 1) - syslog(LOG_INFO, "read_indexed db=%s mailbox=%s uidvalidity=%u", - buf_cstring(&path), mboxname, uidvalidity); - - r = cyrusdb_open(config_getstring(IMAPOPT_SEARCH_INDEXED_DB), - buf_cstring(&path), 0, &db); - if (r == CYRUSDB_NOTFOUND) { - r = 0; - if (verbose > 1) - syslog(LOG_INFO, "read_indexed no db for %s", - buf_cstring(&path)); - continue; - } - if (r) goto out; - - r = cyrusdb_fetch(db, - key.s, key.len, - &data, &datalen, - (struct txn **)NULL); - if (r == CYRUSDB_NOTFOUND) { - r = 0; - cyrusdb_close(db); - db = NULL; - if (verbose > 1) - syslog(LOG_INFO, "read_indexed no record for %s: %.*s", - buf_cstring(&path), (int)key.len, key.s); - continue; - } - if (r) goto out; - - seq = parse_indexed(data, datalen); - if (seq) { - seqset_join(res, seq); - seqset_free(seq); - if (verbose > 1) - syslog(LOG_INFO, "read_indexed seq=%.*s", (int)datalen, data); - } - - cyrusdb_close(db); - db = NULL; - } - -out: - if (db) - cyrusdb_close(db); - buf_free(&key); - buf_free(&path); - - return r; -} - -/* store the given sequence into the already opened cyrus db - * with the given key. If there is an existing sequence in - * the DB, then join this sequence to it, so incremental - * indexing does what you would expect. */ -static int store_indexed(struct db *db, struct txn **tid, - const char *key, size_t keylen, - const struct seqset *val) -{ - struct buf data = BUF_INITIALIZER; - char *str = NULL; - int r; - const char *olddata = NULL; - size_t oldlen = 0; - - r = cyrusdb_fetch(db, key, keylen, &olddata, &oldlen, tid); - if (r == CYRUSDB_NOTFOUND) { - str = seqset_cstring(val); - } - else if (r) return r; - else { - struct seqset *seq = parse_indexed(olddata, oldlen); - if (seq) { - seqset_join(seq, val); - str = seqset_cstring(seq); - seqset_free(seq); - } - else { - str = seqset_cstring(val); - } - } - - if (!str) return 0; - - buf_printf(&data, "%u %s", INDEXEDDB_VERSION, str); - r = cyrusdb_store(db, key, keylen, data.s, data.len, tid); - buf_free(&data); - free(str); - - return r; -} - -/* Given the directory of a xapian database which has just had - * messages indexed into it, add the sequence of UIDs to the - * record for the given mailbox and uidvalidity */ -static int write_indexed(const char *dir, - const char *mboxname, - uint32_t uidvalidity, - struct seqset *seq, - int verbose) -{ - struct buf path = BUF_INITIALIZER; - struct buf key = BUF_INITIALIZER; - struct db *db = NULL; - struct txn *txn = NULL; - int r = 0; - - buf_reset(&path); - buf_printf(&path, "%s%s", dir, INDEXEDDB_FNAME); - - if (verbose) { - char *str = seqset_cstring(seq); - syslog(LOG_INFO, "write_indexed db=%s mailbox=%s uidvalidity=%u uids=%s", - buf_cstring(&path), mboxname, uidvalidity, str); - free(str); - } - - buf_printf(&key, "%s.%u", mboxname, uidvalidity); - - r = cyrusdb_open(config_getstring(IMAPOPT_SEARCH_INDEXED_DB), - buf_cstring(&path), CYRUSDB_CREATE, &db); - if (r) goto out; - - r = store_indexed(db, &txn, key.s, key.len, seq); - if (!r) - r = cyrusdb_commit(db, txn); - else - cyrusdb_abort(db, txn); - -out: - if (db) cyrusdb_close(db); - buf_free(&path); - buf_free(&key); - return r; -} - -/* ====================================================================== */ - -static int parse_cyrusid(const char *cyrusid, - const char **mboxnamep, - unsigned int *uidvalidityp, - unsigned int *uidp) -{ - // user.cassandane.1320711192.196715 - static struct buf buf = BUF_INITIALIZER; - char *p; - - buf_reset(&buf); - buf_appendcstr(&buf, cyrusid); - - p = strrchr(buf_cstring(&buf), '.'); - if (!p) - return 0; - *p++ = '\0'; - *uidp = strtoul(p, NULL, 10); - - p = strrchr(buf.s, '.'); - if (!p) - return 0; - *p++ = '\0'; - *uidvalidityp = strtoul(p, NULL, 10); - - *mboxnamep = buf.s; - - return 1; -} - -static const char *make_cyrusid(struct mailbox *mailbox, uint32_t uid) -{ - static struct buf buf = BUF_INITIALIZER; - // user.cassandane.1320711192.196715 - buf_reset(&buf); - buf_printf(&buf, "%s.%u.%u", - mailbox->name, - mailbox->i.uidvalidity, - uid); - return buf_cstring(&buf); -} - -/* XXX - replace with cyrus_mkdir and cyrus_copyfile */ -static void remove_dir(const char *dir) -{ - run_command("/bin/rm", "-rf", dir, (char *)NULL); -} - -static int copy_files(const char *fromdir, const char *todir) -{ - char *fromdir2 = strconcat(fromdir, "/", (char *)NULL); - int r = run_command("/usr/bin/rsync", "-a", fromdir2, todir, (char *)NULL); - - free(fromdir2); - return r; -} - -/* ====================================================================== */ - -struct opnode -{ - int op; /* SEARCH_OP_* or SEARCH_PART_* constant */ - char *arg; - struct opnode *next; - struct opnode *children; -}; - -typedef struct xapian_builder xapian_builder_t; -struct xapian_builder { - search_builder_t super; - struct mappedfile *activefile; - struct seqset *indexed; - struct mailbox *mailbox; - xapian_db_t *db; - int opts; - struct opnode *root; - ptrarray_t stack; /* points to opnode* */ - int (*proc)(const char *, uint32_t, uint32_t, void *); - void *rock; -}; - -static struct opnode *opnode_new(int op, const char *arg) -{ - struct opnode *on = xzmalloc(sizeof(struct opnode)); - on->op = op; - on->arg = xstrdupnull(arg); - return on; -} - -static void opnode_delete(struct opnode *on) -{ - struct opnode *child; - struct opnode *next; - - for (child = on->children ; child ; child = next) { - next = child->next; - opnode_delete(child); - } - free(on->arg); - free(on); -} - -static void opnode_detach_child(struct opnode *parent, struct opnode *child) -{ - struct opnode **prevp; - - for (prevp = &parent->children ; *prevp ; prevp = &((*prevp)->next)) { - if (*prevp == child) { - *prevp = child->next; - child->next = NULL; - return; - } - } -} - -static void opnode_append_child(struct opnode *parent, struct opnode *child) -{ - struct opnode **tailp; - - for (tailp = &parent->children ; *tailp ; tailp = &((*tailp)->next)) - ; - *tailp = child; - child->next = NULL; -} - -static void opnode_insert_child(struct opnode *parent __attribute__((unused)), - struct opnode *after, - struct opnode *child) -{ - child->next = after->next; - after->next = child; -} - -static void optimise_nodes(struct opnode *parent, struct opnode *on) -{ - struct opnode *child; - struct opnode *next; - - switch (on->op) { - case SEARCH_OP_NOT: - case SEARCH_OP_OR: - case SEARCH_OP_AND: - for (child = on->children ; child ; child = next) { - next = child->next; - optimise_nodes(on, child); - } - if (parent) { - if (!on->children) { - /* empty node - remove it */ - opnode_detach_child(parent, on); - opnode_delete(on); - } - else if (on->op != SEARCH_OP_NOT && !on->children->next) { - /* logical AND or OR with only one child - replace - * the node with its child */ - struct opnode *child = on->children; - opnode_detach_child(on, child); - opnode_insert_child(parent, on, child); - opnode_detach_child(parent, on); - opnode_delete(on); - } - } - break; - } -} - -static xapian_query_t *opnode_to_query(const xapian_db_t *db, struct opnode *on) -{ - struct opnode *child; - xapian_query_t *qq = NULL; - int i; - ptrarray_t childqueries = PTRARRAY_INITIALIZER; - - switch (on->op) { - case SEARCH_OP_NOT: - if (on->children) - qq = xapian_query_new_not(db, opnode_to_query(db, on->children)); - break; - case SEARCH_OP_OR: - case SEARCH_OP_AND: - for (child = on->children ; child ; child = child->next) { - qq = opnode_to_query(db, child); - if (qq) ptrarray_push(&childqueries, qq); - } - qq = NULL; - if (childqueries.count) - qq = xapian_query_new_compound(db, (on->op == SEARCH_OP_OR), - (xapian_query_t **)childqueries.data, - childqueries.count); - break; - case SEARCH_PART_ANY: - /* Xapian does not have a convenient way of search for "any - * field"; instead we fake it by explicitly searching for - * all of the available prefixes */ - for (i = 0 ; i < SEARCH_NUM_PARTS ; i++) { - if (prefix_by_parti != NULL) - ptrarray_push(&childqueries, - xapian_query_new_match(db, prefix_by_parti, on->arg)); - } - qq = xapian_query_new_compound(db, /*is_or*/1, - (xapian_query_t **)childqueries.data, - childqueries.count); - break; - default: - assert(on->arg != NULL); - assert(on->children == NULL); - qq = xapian_query_new_match(db, prefix_by_parton->op, on->arg); - break; - } - ptrarray_fini(&childqueries); - return qq; -} - -static int xapian_run_cb(const char *cyrusid, void *rock) -{ - xapian_builder_t *bb = (xapian_builder_t *)rock; - int r; - const char *mboxname; - unsigned int uidvalidity; - unsigned int uid; - - r = parse_cyrusid(cyrusid, &mboxname, &uidvalidity, &uid); - if (!r) { - syslog(LOG_ERR, "IOERROR: Cannot parse \"%s\" as cyrusid", cyrusid); - return IMAP_IOERROR; - } - - if (!(bb->opts & SEARCH_MULTIPLE)) { - if (strcmp(mboxname, bb->mailbox->name)) - return 0; - if (uidvalidity != bb->mailbox->i.uidvalidity) - return 0; - } - - xstats_inc(SPHINX_RESULT); - return bb->proc(mboxname, uidvalidity, uid, bb->rock); -} - -static int run(search_builder_t *bx, search_hit_cb_t proc, void *rock) -{ - xapian_builder_t *bb = (xapian_builder_t *)bx; - xapian_query_t *qq = NULL; - int r = 0; - - if (bb->db == NULL) - return IMAP_NOTFOUND; /* there's no index for this user */ - - optimise_nodes(NULL, bb->root); - qq = opnode_to_query(bb->db, bb->root); - - bb->proc = proc; - bb->rock = rock; - - r = xapian_query_run(bb->db, qq, xapian_run_cb, bb); - if (r) goto out; - - /* add in the unindexed uids as false positives */ - if ((bb->opts & SEARCH_UNINDEXED)) { - uint32_t uid; - for (uid = seqset_firstnonmember(bb->indexed); - uid <= bb->mailbox->i.last_uid ; uid++) { - xstats_inc(SPHINX_UNINDEXED); - r = proc(bb->mailbox->name, bb->mailbox->i.uidvalidity, uid, rock); - if (r) goto out; - } - } - -out: - if (qq) xapian_query_free(qq); - return r; -} - -static void begin_boolean(search_builder_t *bx, int op) -{ - xapian_builder_t *bb = (xapian_builder_t *)bx; - struct opnode *top = ptrarray_tail(&bb->stack); - struct opnode *on = opnode_new(op, NULL); - if (top) - opnode_append_child(top, on); - else - bb->root = on; - ptrarray_push(&bb->stack, on); - if (SEARCH_VERBOSE(bb->opts)) - syslog(LOG_INFO, "begin_boolean(op=%s)", search_op_as_string(op)); -} - -static void end_boolean(search_builder_t *bx, int op __attribute__((unused))) -{ - xapian_builder_t *bb = (xapian_builder_t *)bx; - if (SEARCH_VERBOSE(bb->opts)) - syslog(LOG_INFO, "end_boolean"); - ptrarray_pop(&bb->stack); -} - -static void match(search_builder_t *bx, int part, const char *str) -{ - xapian_builder_t *bb = (xapian_builder_t *)bx; - struct opnode *top = ptrarray_tail(&bb->stack); - struct opnode *on; - - if (!str) return; - if (SEARCH_VERBOSE(bb->opts)) - syslog(LOG_INFO, "match(part=%s, str=\"%s\")", - search_part_as_string(part), str); - - xstats_inc(SPHINX_MATCH); - - on = opnode_new(part, str); - if (top) - opnode_append_child(top, on); - else - bb->root = on; -} - -static void *get_internalised(search_builder_t *bx) -{ - xapian_builder_t *bb = (xapian_builder_t *)bx; - struct opnode *on = bb->root; - bb->root = NULL; - optimise_nodes(NULL, on); - return on; -} - -static char *describe_internalised(void *internalised __attribute__((unused))) -{ - return xstrdup("--xapian query--"); -} - -static void free_internalised(void *internalised) -{ - struct opnode *on = (struct opnode *)internalised; - if (on) opnode_delete(on); -} - -static search_builder_t *begin_search(struct mailbox *mailbox, int opts) -{ - xapian_builder_t *bb; - strarray_t *dirs = NULL; - strarray_t *active = NULL; - int r; - - xapian_init(); - - bb = xzmalloc(sizeof(xapian_builder_t)); - bb->super.begin_boolean = begin_boolean; - bb->super.end_boolean = end_boolean; - bb->super.match = match; - bb->super.get_internalised = get_internalised; - bb->super.run = run; - - bb->mailbox = mailbox; - bb->opts = opts; - - /* need to hold a read-only lock on the activefile file until the search - * has completed to ensure no databases are deleted out from under us */ - active = activefile_open(mailbox->name, mailbox->part, &bb->activefile, /*write*/0); - if (!active) goto out; - - /* only try to open directories with databases in them */ - dirs = activefile_resolve(mailbox->name, mailbox->part, active, /*dostat*/1); - if (!dirs || !dirs->count) goto out; - - /* if there are directories, open the databases */ - r = xapian_db_open((const char **)dirs->data, &bb->db); - if (r) goto out; - - /* read the list of all indexed messages to allow (optional) false positives - * for unindexed messages */ - bb->indexed = seqset_init(0, SEQ_MERGE); - r = read_indexed(dirs, mailbox->name, mailbox->i.uidvalidity, bb->indexed, /*verbose*/0); - if (r) goto out; - - if ((opts & SEARCH_MULTIPLE)) - xstats_inc(SPHINX_MULTIPLE); - else - xstats_inc(SPHINX_SINGLE); - -out: - strarray_free(dirs); - strarray_free(active); - /* XXX - error return? */ - return &bb->super; -} - -static void end_search(search_builder_t *bx) -{ - xapian_builder_t *bb = (xapian_builder_t *)bx; - - seqset_free(bb->indexed); - ptrarray_fini(&bb->stack); - if (bb->root) opnode_delete(bb->root); - - if (bb->db) xapian_db_close(bb->db); - - /* now that the databases are closed, it's safe to unlock - * the active file */ - if (bb->activefile) { - mappedfile_unlock(bb->activefile); - mappedfile_close(&bb->activefile); - } - - free(bx); -} - -/* ====================================================================== */ - -/* base class for both update and snippet receivers */ -typedef struct xapian_receiver xapian_receiver_t; -struct xapian_receiver -{ - search_text_receiver_t super; - int verbose; - struct mailbox *mailbox; - uint32_t uid; - int part; - unsigned int parts_total; - int truncate_warning; - ptrarray_t segs; -}; - -/* receiver used for updating the index */ -typedef struct xapian_update_receiver xapian_update_receiver_t; -struct xapian_update_receiver -{ - xapian_receiver_t super; - xapian_dbw_t *dbw; - struct mappedfile *activefile; - unsigned int uncommitted; - unsigned int commits; - struct seqset *oldindexed; - struct seqset *indexed; - strarray_t *activedirs; -}; - -/* receiver used for extracting snippets after a search */ -typedef struct xapian_snippet_receiver xapian_snippet_receiver_t; -struct xapian_snippet_receiver -{ - xapian_receiver_t super; - xapian_snipgen_t *snipgen; - struct opnode *root; - search_snippet_cb_t proc; - void *rock; -}; - -/* Maximum size of a query, determined empirically, is a little bit - * under 8MB. That seems like more than enough, so let's limit the - * total amount of parts text to 4 MB. */ -#define MAX_PARTS_SIZE (4*1024*1024) - -static const char *xapian_rootdir(const char *tier, const char *partition) -{ - char *confkey; - const char *root; - if (!partition) - partition = config_getstring(IMAPOPT_DEFAULTPARTITION); - confkey = strconcat(tier, "searchpartition-", partition, NULL); - root = config_getoverflowstring(confkey, NULL); - free(confkey); - return root; -} - -/* Returns in *basedirp a new string which must be free()d */ -static int xapian_basedir(const char *tier, - const char *mboxname, const char *partition, - const char *root, char **basedirp) -{ - char *basedir = NULL; - struct mboxname_parts parts; - char c2, d2; - int r; - - mboxname_init_parts(&parts); - - if (!root) - root = xapian_rootdir(tier, partition); - if (!root) { - r = IMAP_PARTITION_UNKNOWN; - goto out; - } - - r = mboxname_to_parts(mboxname, &parts); - if (r) goto out; - if (!parts.userid) { - r = IMAP_PARTITION_UNKNOWN; - goto out; - } - - if (parts.domain) - basedir = strconcat(root, - FNAME_DOMAINDIR, - dir_hash_b(parts.domain, config_fulldirhash, d), - "/", parts.domain, - "/", dir_hash_b(parts.userid, config_fulldirhash, c), - FNAME_USERDIR, - parts.userid, - (char *)NULL); - else - basedir = strconcat(root, - "/", dir_hash_b(parts.userid, config_fulldirhash, c), - FNAME_USERDIR, - parts.userid, - (char *)NULL); - - r = 0; - -out: - if (!r && basedirp) - *basedirp = basedir; - else - free(basedir); - mboxname_free_parts(&parts); - return r; -} - -static int check_directory(const char *dir, int verbose, int create) -{ - int r; - char *dummyfile = NULL; - struct stat sb; - - r = stat(dir, &sb); - if (r < 0) { - if (errno != ENOENT) { - /* something went wrong - permissions problem most likely */ - syslog(LOG_ERR, "IOERROR: unable to stat %s: %m", dir); - r = IMAP_IOERROR; - goto out; - } - /* the directory is just missing */ - if (!create) { - /* caller doesn't care that much */ - r = IMAP_NOTFOUND; - goto out; - } - if (verbose) - syslog(LOG_INFO, "Building directory %s", dir); - dummyfile = strconcat(dir, "/dummy", (char *)NULL); - cyrus_mkdir(dummyfile, 0700); - r = stat(dir, &sb); - if (r < 0) { - /* something went wrong - permissions problem most likely */ - syslog(LOG_ERR, "IOERROR: unable to stat %s: %m", dir); - r = IMAP_IOERROR; - goto out; - } - } - -out: - free(dummyfile); - return r; -} - -static int flush(search_text_receiver_t *rx) -{ - xapian_update_receiver_t *tr = (xapian_update_receiver_t *)rx; - int r = 0; - struct timeval start, end; - - if (!tr->uncommitted) return 0; - - assert(tr->dbw); - - gettimeofday(&start, NULL); - r = xapian_dbw_commit_txn(tr->dbw); - if (r) goto out; - gettimeofday(&end, NULL); - - syslog(LOG_INFO, "Xapian committed %u updates in %.6f sec", - tr->uncommitted, timesub(&start, &end)); - - /* We write out the indexed list for the mailbox only after successfully - * updating the index, to avoid a future instance not realising that - * there are unindexed messages should we fail to index */ - r = write_indexed(strarray_nth(tr->activedirs, 0), - tr->super.mailbox->name, tr->super.mailbox->i.uidvalidity, - tr->indexed, tr->super.verbose); - if (r) goto out; - - tr->uncommitted = 0; - tr->commits++; - -out: - return r; -} - -static void free_segments(xapian_receiver_t *tr) -{ - int i; - struct segment *seg; - - for (i = 0 ; i < tr->segs.count ; i++) { - seg = (struct segment *)ptrarray_nth(&tr->segs, i); - buf_free(&seg->text); - free(seg); - } - ptrarray_truncate(&tr->segs, 0); -} - -static void begin_message(search_text_receiver_t *rx, uint32_t uid) -{ - xapian_receiver_t *tr = (xapian_receiver_t *)rx; - - tr->uid = uid; - free_segments(tr); - tr->parts_total = 0; - tr->truncate_warning = 0; -} - -static void begin_part(search_text_receiver_t *rx, int part) -{ - xapian_receiver_t *tr = (xapian_receiver_t *)rx; - - tr->part = part; -} - -static void append_text(search_text_receiver_t *rx, - const struct buf *text) -{ - xapian_receiver_t *tr = (xapian_receiver_t *)rx; - struct segment *seg; - - if (tr->part) { - unsigned len = text->len; - if (tr->parts_total + len > MAX_PARTS_SIZE) { - if (!tr->truncate_warning++) - syslog(LOG_ERR, "Xapian: truncating text from " - "message mailbox %s uid %u", - tr->mailbox->name, tr->uid); - len = MAX_PARTS_SIZE - tr->parts_total; - } - if (len) { - tr->parts_total += len; - - seg = (struct segment *)ptrarray_tail(&tr->segs); - if (!seg || seg->is_finished || seg->part != tr->part) { - seg = (struct segment *)xzmalloc(sizeof(*seg)); - seg->sequence = tr->segs.count; - seg->part = tr->part; - ptrarray_append(&tr->segs, seg); - } - buf_appendmap(&seg->text, text->s, len); - } - } -} - -static void end_part(search_text_receiver_t *rx, - int part __attribute__((unused))) -{ - xapian_receiver_t *tr = (xapian_receiver_t *)rx; - struct segment *seg; - - seg = (struct segment *)ptrarray_tail(&tr->segs); - if (seg) - seg->is_finished = 1; - - if (tr->verbose > 1) - syslog(LOG_NOTICE, "Xapian: %llu bytes in part %s", - (seg ? (unsigned long long)seg->text.len : 0), - search_part_as_string(tr->part)); - - tr->part = 0; -} - -static int compare_segs(const void **v1, const void **v2) -{ - const struct segment *s1 = *(const struct segment **)v1; - const struct segment *s2 = *(const struct segment **)v2; - int r; - - r = s1->part - s2->part; - if (!r) - r = s1->sequence - s2->sequence; - return r; -} - -static int end_message_update(search_text_receiver_t *rx) -{ - xapian_update_receiver_t *tr = (xapian_update_receiver_t *)rx; - int i; - struct segment *seg; - int r = 0; - - if (!tr->dbw) return IMAP_INTERNAL; - - r = xapian_dbw_begin_doc(tr->dbw, make_cyrusid(tr->super.mailbox, tr->super.uid)); - if (r) goto out; - - ptrarray_sort(&tr->super.segs, compare_segs); - - for (i = 0 ; i < tr->super.segs.count ; i++) { - seg = (struct segment *)ptrarray_nth(&tr->super.segs, i); - r = xapian_dbw_doc_part(tr->dbw, &seg->text, prefix_by_partseg->part); - if (r) goto out; - } - - if (!tr->uncommitted) { - r = xapian_dbw_begin_txn(tr->dbw); - if (r) goto out; - } - r = xapian_dbw_end_doc(tr->dbw); - if (r) goto out; - ++tr->uncommitted; - /* track that this UID was indexed. Use SEQ_MERGE to avoid a bitty sequence - * with lots of holes in it if messages have been expunged meanwhile. */ - if (!tr->indexed) { - tr->indexed = seqset_init(0, SEQ_MERGE); - } - seqset_add(tr->indexed, tr->super.uid, 1); - -out: - tr->super.uid = 0; - return r; -} - -static int begin_mailbox_update(search_text_receiver_t *rx, - struct mailbox *mailbox, - int flags __attribute__((unused))) -{ - xapian_update_receiver_t *tr = (xapian_update_receiver_t *)rx; - char *fname = activefile_fname(mailbox->name); - strarray_t *active = NULL; - int r = 0; - - /* not an indexable mailbox, fine - return a code to avoid - * trying to index each message as well */ - if (!fname) { - r = IMAP_MAILBOX_NONEXISTENT; - goto out; - } - - /* XXX - there's an annoying lock inversion going on here. We have to release - * the mailbox lock and then re-establish it after we have the xapianactive file - * open. We should really just get an mboxname passed down to this layer, but - * that's a much bigger rewrite due to disparate actions being squeezed into an - * identical API */ - mailbox_unlock_index(mailbox, NULL); - - /* XXX - if not incremental, we actually want to throw away all existing up to - * this point and write a new one, so we should launch a new file and then - * reindex using the same algorithm as the "compress" codepath. The - * problem is that the index is per user, not per mailbox */ - - /* we grab an activefile writelock to index. Strictly we don't need it, but - * doing this guarantees we never write under a client which is reading, which - * avoids this: - * - * IOERROR: Xapian: caught exception: : DatabaseModifiedError: The revision - * being read has been discarded - you should call Xapian::Database::reopen() - * and retry the operation - * - * in theory, this will go away eventually, and we can switch back to write: 0 - * in this code. - * - * http://grokbase.com/t/xapian/xapian-discuss/0667ppbks8/#20060608j8x5aeept49dv5fm8d02xkczgr - * - * "This is almost invariably caused by updating a database while reading - * from it. If two updates are committed before the read completes, you - * get this error (it's DatabaseModifiedError). It's a bit of a pain - * and will be going away in the future, but it's not too hard to design - * to avoid it happening at least." - */ - active = activefile_open(mailbox->name, mailbox->part, &tr->activefile, /*write*/1); - if (!active || !active->count) goto out; - - /* doesn't matter if the first one doesn't exist yet, we'll create it */ - tr->activedirs = activefile_resolve(mailbox->name, mailbox->part, active, /*dostat*/0); - if (!tr->activedirs || !tr->activedirs->count) goto out; - - /* create the directory if needed */ - r = check_directory(strarray_nth(tr->activedirs, 0), tr->super.verbose, /*create*/1); - if (r) goto out; - - /* open the DB */ - r = xapian_dbw_open(strarray_nth(tr->activedirs, 0), &tr->dbw); - if (r) goto out; - - /* read the indexed data from every directory so know what still needs indexing */ - tr->oldindexed = seqset_init(0, SEQ_MERGE); - r = read_indexed(tr->activedirs, mailbox->name, mailbox->i.uidvalidity, - tr->oldindexed, tr->super.verbose); - if (r) goto out; - - /* XXX - and of course we have to lock again! (XXX - no support for the nonblocking bit - * on this second lock... *sigh*) We don't have the flags to know that we wanted it */ - r = mailbox_lock_index(mailbox, LOCK_SHARED); - if (r) goto out; - - tr->super.mailbox = mailbox; - -out: - free(fname); - strarray_free(active); - return r; -} - -static uint32_t first_unindexed_uid(search_text_receiver_t *rx) -{ - xapian_update_receiver_t *tr = (xapian_update_receiver_t *)rx; - - if (!tr->oldindexed) - return 1; - - return seqset_firstnonmember(tr->oldindexed); -} - -static int is_indexed(search_text_receiver_t *rx, uint32_t uid) -{ - xapian_update_receiver_t *tr = (xapian_update_receiver_t *)rx; - - return (seqset_ismember(tr->oldindexed, uid) || seqset_ismember(tr->indexed, uid)); -} - -static int end_mailbox_update(search_text_receiver_t *rx, - struct mailbox *mailbox - __attribute__((unused))) -{ - xapian_update_receiver_t *tr = (xapian_update_receiver_t *)rx; - int r = 0; - - r = flush(rx); - - /* flush before cleaning up, since indexed data is written by flush */ - if (tr->indexed) { - seqset_free(tr->indexed); - tr->indexed = NULL; - } - if (tr->oldindexed) { - seqset_free(tr->oldindexed); - tr->oldindexed = NULL; - } - - tr->super.mailbox = NULL; - - if (tr->dbw) { - xapian_dbw_close(tr->dbw); - tr->dbw = NULL; - } - - /* don't unlock until DB is committed */ - if (tr->activefile) { - mappedfile_unlock(tr->activefile); - mappedfile_close(&tr->activefile); - tr->activefile = NULL; - } - - if (tr->activedirs) { - strarray_free(tr->activedirs); - tr->activedirs = NULL; - } - - return r; -} - -static search_text_receiver_t *begin_update(int verbose) -{ - xapian_update_receiver_t *tr; - - xapian_init(); - - tr = xzmalloc(sizeof(xapian_update_receiver_t)); - tr->super.super.begin_mailbox = begin_mailbox_update; - tr->super.super.first_unindexed_uid = first_unindexed_uid; - tr->super.super.is_indexed = is_indexed; - tr->super.super.begin_message = begin_message; - tr->super.super.begin_part = begin_part; - tr->super.super.append_text = append_text; - tr->super.super.end_part = end_part; - tr->super.super.end_message = end_message_update; - tr->super.super.end_mailbox = end_mailbox_update; - tr->super.super.flush = flush; - - tr->super.verbose = verbose; - - return &tr->super.super; -} - -static void free_receiver(xapian_receiver_t *tr) -{ - free_segments(tr); - ptrarray_fini(&tr->segs); - free(tr); -} - -static int end_update(search_text_receiver_t *rx) -{ - xapian_update_receiver_t *tr = (xapian_update_receiver_t *)rx; - - free_receiver(&tr->super); - - return 0; -} - -static int begin_mailbox_snippets(search_text_receiver_t *rx, - struct mailbox *mailbox, - int incremental __attribute__((unused))) -{ - xapian_snippet_receiver_t *tr = (xapian_snippet_receiver_t *)rx; - - tr->super.mailbox = mailbox; - - return 0; -} - -/* Find match terms for the given part and add them to the Xapian - * snippet generator. */ -static void generate_snippet_terms(xapian_snipgen_t *snipgen, - int part, - struct opnode *on) -{ - struct opnode *child; - - switch (on->op) { - - case SEARCH_OP_NOT: - case SEARCH_OP_OR: - case SEARCH_OP_AND: - for (child = on->children ; child ; child = child->next) - generate_snippet_terms(snipgen, part, child); - break; - - case SEARCH_PART_ANY: - assert(on->children == NULL); - if (part != SEARCH_PART_HEADERS || - !config_getswitch(IMAPOPT_SPHINX_TEXT_EXCLUDES_ODD_HEADERS)) { - xapian_snipgen_add_match(snipgen, on->arg); - } - break; - - default: - /* other SEARCH_PART_* constants */ - assert(on->op >= 0 && on->op < SEARCH_NUM_PARTS); - assert(on->children == NULL); - if (part == on->op) { - xapian_snipgen_add_match(snipgen, on->arg); - } - break; - } -} - -static int end_message_snippets(search_text_receiver_t *rx) -{ - xapian_snippet_receiver_t *tr = (xapian_snippet_receiver_t *)rx; - struct buf snippets = BUF_INITIALIZER; - unsigned int context_length; - int i; - struct segment *seg; - int last_part = -1; - int r; - - if (!tr->snipgen) { - r = IMAP_INTERNAL; /* need to call begin_mailbox() */ - goto out; - } - if (!tr->root) { - r = 0; - goto out; - } - - ptrarray_sort(&tr->super.segs, compare_segs); - - for (i = 0 ; i < tr->super.segs.count ; i++) { - seg = (struct segment *)ptrarray_nth(&tr->super.segs, i); - - if (seg->part != last_part) { - - if (last_part != -1) { - r = xapian_snipgen_end_doc(tr->snipgen, &snippets); - if (!r && snippets.len) - r = tr->proc(tr->super.mailbox, tr->super.uid, last_part, snippets.s, tr->rock); - if (r) break; - } - - /* TODO: UINT_MAX doesn't behave as expected, which is probably - * a bug, but really any value larger than a reasonable Subject - * length will do */ - context_length = (seg->part == SEARCH_PART_HEADERS || seg->part == SEARCH_PART_BODY ? 5 : 1000000); - r = xapian_snipgen_begin_doc(tr->snipgen, context_length); - if (r) break; - - generate_snippet_terms(tr->snipgen, seg->part, tr->root); - } - - r = xapian_snipgen_doc_part(tr->snipgen, &seg->text); - if (r) break; - - last_part = seg->part; - } - - if (last_part != -1) { - r = xapian_snipgen_end_doc(tr->snipgen, &snippets); - if (!r && snippets.len) - r = tr->proc(tr->super.mailbox, tr->super.uid, last_part, snippets.s, tr->rock); - } - -out: - buf_free(&snippets); - return r; -} - -static int end_mailbox_snippets(search_text_receiver_t *rx, - struct mailbox *mailbox - __attribute__((unused))) -{ - xapian_snippet_receiver_t *tr = (xapian_snippet_receiver_t *)rx; - - tr->super.mailbox = NULL; - - return 0; -} - -static search_text_receiver_t *begin_snippets(void *internalised, - int verbose, - search_snippet_cb_t proc, - void *rock) -{ - xapian_snippet_receiver_t *tr; - - xapian_init(); - - tr = xzmalloc(sizeof(xapian_snippet_receiver_t)); - tr->super.super.begin_mailbox = begin_mailbox_snippets; - tr->super.super.begin_message = begin_message; - tr->super.super.begin_part = begin_part; - tr->super.super.append_text = append_text; - tr->super.super.end_part = end_part; - tr->super.super.end_message = end_message_snippets; - tr->super.super.end_mailbox = end_mailbox_snippets; - - tr->super.verbose = verbose; - tr->root = (struct opnode *)internalised; - tr->snipgen = xapian_snipgen_new(); - tr->proc = proc; - tr->rock = rock; - - return &tr->super.super; -} - -static int end_snippets(search_text_receiver_t *rx) -{ - xapian_snippet_receiver_t *tr = (xapian_snippet_receiver_t *)rx; - - if (tr->snipgen) xapian_snipgen_free(tr->snipgen); - - free_receiver(&tr->super); - - return 0; -} - -static int list_files(const char *userid, strarray_t *files) -{ - char *mboxname = mboxname_user_mbox(userid, NULL); - struct mboxlist_entry *mbentry = NULL; - char *fname = NULL; - DIR *dirh = NULL; - struct dirent *de; - struct stat sb; - strarray_t *active = NULL; - strarray_t *dirs = NULL; - struct mappedfile *activefile = NULL; - int r; - int i; - - r = mboxlist_lookup(mboxname, &mbentry, NULL); - if (r == IMAP_MAILBOX_NONEXISTENT) { - /* no user, no worries */ - r = 0; - goto out; - } - if (r) { - syslog(LOG_ERR, "IOERROR: failed to lookup %s", mboxname); - goto out; - } - - active = activefile_open(mboxname, mbentry->partition, &activefile, /*write*/0); - if (!active) goto out; - dirs = activefile_resolve(mboxname, mbentry->partition, active, /*dostat*/1); - - for (i = 0; i < dirs->count; i++) { - const char *basedir = strarray_nth(dirs, i); - - dirh = opendir(basedir); - if (!dirh) continue; - - while ((de = readdir(dirh))) { - if (de->d_name0 == '.') continue; - free(fname); - fname = strconcat(basedir, "/", de->d_name, (char *)NULL); - r = stat(fname, &sb); - if (!r && S_ISREG(sb.st_mode)) { - strarray_appendm(files, fname); - fname = NULL; - } - } - - closedir(dirh); - dirh = NULL; - } - -out: - if (activefile) { - mappedfile_unlock(activefile); - mappedfile_close(&activefile); - } - strarray_free(active); - strarray_free(dirs); - free(fname); - mboxlist_entry_free(&mbentry); - free(mboxname); - - return 0; -} - -struct mbdata { - uint32_t uidvalidity; - bitvector_t uids; -}; - -struct mbfilter { - hash_table mboxes; - struct db *indexed; - struct txn **tid; - char *destpath; - int flags; -}; - -static int copyindexed_cb(void *rock, - const char *key, size_t keylen, - const char *data, size_t datalen) -{ - struct mbfilter *filter = (struct mbfilter *)rock; - struct seqset *seq = parse_indexed(data, datalen); - int r = 0; - if (seq) { - r = store_indexed(filter->indexed, filter->tid, key, keylen, seq); - seqset_free(seq); - } - return r; -} - -static void free_mbdata(void *rock) -{ - struct mbdata *data = (struct mbdata *)rock; - if (!data) return; - bv_free(&data->uids); - free(data); -} - -static int mbox_vector(const char *mboxname, struct mbfilter *filter) -{ - struct mbdata *mbdata = xzmalloc(sizeof(struct mbdata)); - struct buf key = BUF_INITIALIZER; - const char *data = NULL; - size_t datalen = 0; - struct seqset *seq = NULL; - struct mailbox *mailbox = NULL; - struct index_record record; - int verbose = SEARCH_VERBOSE(filter->flags); - unsigned recno; - int r; - - r = mailbox_open_irl(mboxname, &mailbox); - if (r) { - /* XXX - this is just a workaround for bugs in mboxlist_allusermbox */ - if (r == IMAP_MAILBOX_NONEXISTENT) r = 0; - goto done; - } - - buf_printf(&key, "%s.%u", mboxname, mailbox->i.uidvalidity); - - r = cyrusdb_fetch(filter->indexed, - key.s, key.len, - &data, &datalen, - (struct txn **)NULL); - - if (r == CYRUSDB_NOTFOUND) { - r = 0; - goto done; - } - if (r) goto done; - - seq = parse_indexed(data, datalen); - if (!seq) goto done; - - mbdata->uidvalidity = mailbox->i.uidvalidity; - bv_setsize(&mbdata->uids, mailbox->i.last_uid); - - if (verbose) - printf("Vectoring %s\n", mboxname); - - for (recno = 1; recno <= mailbox->i.num_records; recno++) { - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) goto done; - - if (record.system_flags & FLAG_EXPUNGED) - continue; - - /* we don't expect it to be in this index, don't check for it */ - if (!seqset_ismember(seq, record.uid)) - continue; - - bv_set(&mbdata->uids, record.uid); - } - - /* yay, we have succeeded */ - hash_insert(mboxname, mbdata, &filter->mboxes); - mbdata = NULL; - -done: - buf_free(&key); - if (seq) seqset_free(seq); - if (mailbox) mailbox_close(&mailbox); - if (mbdata) free_mbdata(mbdata); - return r; -} - -static int mbox_vector_cb(void *rock, - const char *key, size_t keylen, - const char *val __attribute__((unused)), - size_t vallen __attribute__((unused))) -{ - char *mboxname = xstrndup(key, keylen); - struct mbfilter *filter = (struct mbfilter *)rock; - int r = mbox_vector(mboxname, filter); - free(mboxname); - return r; -} - -static int build_mbfilter(const char *userid, struct mbfilter *filter) -{ - construct_hash_table(&filter->mboxes, 1024, 0); - return mboxlist_allusermbox(userid, mbox_vector_cb, filter, /*include_deleted*/0); -} - -static void free_mbfilter(struct mbfilter *filter) -{ - free_hash_table(&filter->mboxes, free_mbdata); - if (filter->tid) cyrusdb_abort(filter->indexed, *filter->tid); - cyrusdb_close(filter->indexed); - free(filter->destpath); -} - -static int mbdata_exists_cb(const char *cyrusid, void *rock) -{ - struct mbfilter *filter = (struct mbfilter *)rock; - int verbose = SEARCH_VERBOSE(filter->flags); - const char *mboxname; - unsigned int uidvalidity; - unsigned int uid; - struct mbdata *data; - int res = 0; - - if (!parse_cyrusid(cyrusid, &mboxname, &uidvalidity, &uid)) - goto out; /* failed to parse -> not exists */ - - data = (struct mbdata *)hash_lookup(mboxname, &filter->mboxes); - - /* is it an identical mailbox? */ - if (!data) goto out; - if (data->uidvalidity != uidvalidity) goto out; - - /* then check if the UID exists */ - res = bv_isset(&data->uids, uid); - - if (res) { - /* if we find it again, then we don't need a second copy */ - bv_clear(&data->uids, uid); - } - -out: - if (res) { - if (verbose > 2) - printf("filter check %s: EXISTS\n", cyrusid); - } - else { - if (verbose > 1) - printf("filter check %s: MISSING\n", cyrusid); - } - - if (filter->flags & SEARCH_COMPACT_FILTER) - return res; - - /* don't delete anything */ - return 1; -} - -static void notify_filter_cb(const char *mboxname, void *data, void *rock) -{ - struct mbdata *mbdata = (struct mbdata *)data; - struct mbfilter *filter = (struct mbfilter *)rock; - struct seqset *seq = seqset_init(0, SEQ_SPARSE); - int verbose = SEARCH_VERBOSE(filter->flags); - int uid; - - if (verbose) - printf("checking for unindexed messages in %s\n", mboxname); - - /* now we just read the bitvector and look for trouble! */ - for (uid = bv_next_set(&mbdata->uids, 0) ; - uid != -1 ; - uid = bv_next_set(&mbdata->uids, uid+1)) - seqset_add(seq, uid, 1); - - if (seq->len) { - char *seqstr = seqset_cstring(seq); - syslog(LOG_ERR, "IOERROR: unindexed messages in %s: %s", mboxname, seqstr); - if (verbose) - printf("IOERROR: unindexed messages in %s: %s\n", mboxname, seqstr); - free(seqstr); - } - - seqset_free(seq); -} - -static void notify_missing_messages(struct mbfilter *filter) -{ - hash_enumerate(&filter->mboxes, notify_filter_cb, filter); -} - -static int create_filter(const strarray_t *srcpaths, const char *destpath, - int flags, struct mbfilter *filter) -{ - struct buf buf = BUF_INITIALIZER; - int r = 0; - int i; - - memset(filter, 0, sizeof(struct mbfilter)); - filter->flags = flags; - filter->destpath = xstrdup(destpath); - - /* build the cyrus.indexed.db from the contents of the source dirs */ - - buf_reset(&buf); - buf_printf(&buf, "%s%s", destpath, INDEXEDDB_FNAME); - r = cyrusdb_open(config_getstring(IMAPOPT_SEARCH_INDEXED_DB), - buf_cstring(&buf), CYRUSDB_CREATE, &filter->indexed); - if (r) { - printf("ERROR: failed to open indexed %s\n", buf_cstring(&buf)); - goto done; - } - for (i = 0; i < srcpaths->count; i++) { - struct db *db = NULL; - buf_reset(&buf); - buf_printf(&buf, "%s%s", strarray_nth(srcpaths, i), INDEXEDDB_FNAME); - r = cyrusdb_open(config_getstring(IMAPOPT_SEARCH_INDEXED_DB), - buf_cstring(&buf), 0, &db); - if (r) { - r = 0; - continue; - } - r = cyrusdb_foreach(db, "", 0, NULL, copyindexed_cb, filter, NULL); - cyrusdb_close(db); - if (r) { - printf("ERROR: failed to process indexed db %s\n", strarray_nth(srcpaths, i)); - goto done; - } - } - if (filter->tid) r = cyrusdb_commit(filter->indexed, *filter->tid); - if (r) { - printf("ERROR: failed to commit indexed %s\n", destpath); - goto done; - } - -done: - return r; -} - -static int search_filter(const char *userid, const strarray_t *srcpaths, - const char *destpath, int flags) -{ - struct mbfilter filter; - int verbose = SEARCH_VERBOSE(flags); - int r; - - r = create_filter(srcpaths, destpath, flags, &filter); - if (r) goto done; - - if (verbose) - printf("Building vector table for %s\n", userid); - r = build_mbfilter(userid, &filter); - if (r) goto done; - - if (verbose) - printf("Filtering database %s\n", destpath); - - r = xapian_filter(destpath, (const char **)srcpaths->data, - mbdata_exists_cb, &filter); - if (r) goto done; - - if (verbose) - printf("done %s\n", destpath); - - if (flags & SEARCH_COMPACT_AUDIT) - notify_missing_messages(&filter); - -done: - free_mbfilter(&filter); - return r; -} - -static int reindex_mb(void *rock, - const char *key, size_t keylen, - const char *data, size_t datalen) -{ - struct mbfilter *filter = (struct mbfilter *)rock; - char *mboxname = xstrndup(key, keylen); - struct seqset *seq = parse_indexed(data, datalen); - xapian_update_receiver_t *tr = NULL; - struct mailbox *mailbox = NULL; - ptrarray_t batch = PTRARRAY_INITIALIZER; - struct index_record record; - int verbose = SEARCH_VERBOSE(filter->flags); - uint32_t recno; - int r = 0; - int i; - char *dot; - uint32_t uidvalidity; - - dot = strrchr(mboxname, '.'); - *dot++ = '\0'; - uidvalidity = atol(dot); - - if (!seq) goto done; - - r = mailbox_open_irl(mboxname, &mailbox); - if (r) goto done; - - if (mailbox->i.uidvalidity != uidvalidity) goto done; /* returns 0, nothing to index */ - - for (recno = 1; recno <= mailbox->i.num_records; recno++) { - r = mailbox_read_index_record(mailbox, recno, &record); - if (r) goto done; - - if (record.system_flags & FLAG_EXPUNGED) - continue; - - /* it wasn't in the previous index, skip it */ - if (!seqset_ismember(seq, record.uid)) - continue; - - /* add the record to the list */ - ptrarray_append(&batch, message_new_from_record(mailbox, &record)); - } - - if (batch.count) { - tr = (xapian_update_receiver_t *)begin_update(verbose); - /* XXX - errors here could leak... */ - /* game on */ - mailbox_unlock_index(mailbox, NULL); - /* open the DB */ - r = xapian_dbw_open(filter->destpath, &tr->dbw); - if (r) goto done; - tr->super.mailbox = mailbox; - - /* preload */ - for (i = 0 ; i < batch.count ; i++) { - message_t *msg = ptrarray_nth(&batch, i); - const char *fname; - - r = message_get_fname(msg, &fname); - if (r) goto done; - r = warmup_file(fname, 0, 0); - if (r) goto done; /* means we failed to open a file, - so we'll fail later anyway */ - } - - /* index the messages */ - for (i = 0 ; i < batch.count ; i++) { - message_t *msg = ptrarray_nth(&batch, i); - r = index_getsearchtext(msg, &tr->super.super, 0); - message_unref(&msg); - } - if (r) goto done; - if (tr->uncommitted) { - r = xapian_dbw_commit_txn(tr->dbw); - if (r) goto done; - } - } - -done: - if (tr) { - if (tr->indexed) seqset_free(tr->indexed); - if (tr->dbw) xapian_dbw_close(tr->dbw); - free_receiver(&tr->super); - } - mailbox_close(&mailbox); - ptrarray_fini(&batch); - free(mboxname); - seqset_free(seq); - return r; -} - -static int search_reindex(const char *userid, const strarray_t *srcpaths, - const char *destpath, int flags) -{ - struct buf buf = BUF_INITIALIZER; - struct mbfilter filter; - int verbose = SEARCH_VERBOSE(flags); - int r; - - r = create_filter(srcpaths, destpath, flags, &filter); - if (r) goto done; - - if (verbose) - printf("Reindexing messages for %s\n", userid); - - r = cyrusdb_foreach(filter.indexed, "", 0, NULL, reindex_mb, &filter, NULL); - if (r) { - printf("ERROR: failed to reindex to %s\n", destpath); - goto done; - } - - if (verbose) - printf("done %s\n", destpath); - -done: - free_mbfilter(&filter); - buf_free(&buf); - return r; -} - -static int search_compress(const char *userid, const strarray_t *srcpaths, - const char *destpath, int flags) -{ - struct buf buf = BUF_INITIALIZER; - struct mbfilter filter; - int verbose = SEARCH_VERBOSE(flags); - int r; - - r = create_filter(srcpaths, destpath, flags, &filter); - if (r) goto done; - - if (verbose) - printf("Compressing messages for %s\n", userid); - - r = xapian_compact_dbs(destpath, (const char **)srcpaths->data); - if (r) { - printf("ERROR: failed to compress to %s\n", destpath); - goto done; - } - - if (verbose) - printf("done %s\n", destpath); - -done: - free_mbfilter(&filter); - buf_free(&buf); - return r; -} - -static int compact_dbs(const char *userid, const char *tempdir, - const strarray_t *srctiers, const char *desttier, int flags) -{ - char *mboxname = mboxname_user_mbox(userid, NULL); - struct mboxlist_entry *mbentry = NULL; - struct mappedfile *activefile = NULL; - strarray_t *dirs = NULL; - strarray_t *active = NULL; - strarray_t *tochange = NULL; - char *newdest = NULL; - char *destdir = NULL; - char *tempdestdir = NULL; - char *activestr = NULL; - struct buf mytempdir = BUF_INITIALIZER; - struct buf buf = BUF_INITIALIZER; - int verbose = SEARCH_VERBOSE(flags); - int r = 0; - int i; - - r = mboxlist_lookup(mboxname, &mbentry, NULL); - if (r == IMAP_MAILBOX_NONEXISTENT) { - /* no user, no worries */ - r = 0; - goto out; - } - if (r) { - syslog(LOG_ERR, "IOERROR: failed to lookup %s", mboxname); - goto out; - } - - xapian_init(); - - /* take an exclusive lock on the activefile file */ - active = activefile_open(mboxname, mbentry->partition, &activefile, /*write*/1); - if (!active || !active->count) goto out; - - activestr = strarray_join(active, ","); - - /* read the activefile file, taking down the names of all paths with a - * level less than or equal to that requested */ - tochange = activefile_filter(active, srctiers, mbentry->partition); - if (!tochange || !tochange->count) goto out; - - /* find out which items actually exist from the set to be compressed - first pass */ - dirs = activefile_resolve(mboxname, mbentry->partition, tochange, /*dostat*/1); - if (!dirs || !dirs->count) goto out; - /* NOTE: it's safe to keep this list even over the unlock/relock because we - * always write out a new first item if necessary, so these will never be - * written to after we release the lock - if they don't have content now, - * they never will */ - - /* register the target name first, and put it at the end of the file */ - newdest = activefile_nextname(active, desttier); - strarray_push(active, newdest); - - if (verbose) { - char *target = strarray_join(tochange, ","); - printf("compressing %s to %s for %s (active %s)\n", target, newdest, mboxname, activestr); - free(target); - } - - /* are we going to change the first active? We need to start indexing to - * a new location! */ - if (strarray_find(tochange, strarray_nth(active, 0), 0) >= 0) { - /* always recalculate the first name once the destination is chosen, - * because we may be compressing to the default tier for some reason */ - char *newstart = activefile_nextname(active, config_getstring(IMAPOPT_DEFAULTSEARCHTIER)); - if (verbose) { - printf("adding new initial search location %s\n", newstart); - } - strarray_unshiftm(active, newstart); - } - - destdir = activefile_path(mboxname, mbentry->partition, newdest, /*dostat*/0); - tempdestdir = strconcat(destdir, ".NEW", (char *)NULL); - - /* write the new file and release the exclusive lock */ - activefile_write(activefile, active); - mappedfile_unlock(activefile); - - /* take a shared lock */ - mappedfile_readlock(activefile); - - /* reread and ensure our 'directory zero' is still directory zero, - * otherwise abort now */ - { - strarray_t *newactive = activefile_read(activefile); - if (strarray_cmp(active, newactive)) { - if (verbose) { - printf("aborting compact of %s, lost the race early\n", mboxname); - } - strarray_free(newactive); - goto out; - } - strarray_free(newactive); - } - - /* run the compress to tmpfs */ - if (tempdir) - buf_printf(&mytempdir, "%s/xapian.%d", tempdir, getpid()); - /* or just directly in place */ - else - buf_printf(&mytempdir, "%s", tempdestdir); - - /* make sure the destination path exists */ - r = cyrus_mkdir(buf_cstring(&mytempdir), 0755); - if (r) goto out; - /* and doesn't contain any junk */ - remove_dir(buf_cstring(&mytempdir)); - r = mkdir(buf_cstring(&mytempdir), 0755); - if (r) goto out; - - if (dirs->count == 1 && (flags & SEARCH_COMPACT_COPYONE)) { - if (verbose) { - printf("only one source, copying directly to %s\n", tempdestdir); - } - cyrus_mkdir(tempdestdir, 0755); - remove_dir(tempdestdir); - r = copy_files(dirs->data0, tempdestdir); - } - else if (dirs->count) { - if (verbose) { - printf("compacting databases\n"); - } - if (flags & SEARCH_COMPACT_FILTER) { - r = search_filter(userid, dirs, buf_cstring(&mytempdir), flags); - if (r) { - printf("ERROR: failed to filter to %s", buf_cstring(&mytempdir)); - goto out; - } - } - else if (flags & SEARCH_COMPACT_REINDEX) { - r = search_reindex(userid, dirs, buf_cstring(&mytempdir), flags); - if (r) { - printf("ERROR: failed to reindex to %s", buf_cstring(&mytempdir)); - goto out; - } - } - else { - r = search_compress(userid, dirs, buf_cstring(&mytempdir), flags); - if (r) { - printf("ERROR: failed to reindex to %s", buf_cstring(&mytempdir)); - goto out; - } - } - - /* move the tmpfs files to a temporary name in our target directory */ - if (tempdir) { - if (verbose) { - printf("copying from tempdir to destination\n"); - } - cyrus_mkdir(tempdestdir, 0755); - remove_dir(tempdestdir); - r = copy_files(buf_cstring(&mytempdir), tempdestdir); - if (r) { - printf("Failed to rsync from %s to %s", buf_cstring(&mytempdir), tempdestdir); - goto out; - } - } - } - - /* release and take an exclusive lock on activefile */ - mappedfile_unlock(activefile); - mappedfile_writelock(activefile); - - /* check that we still have 'directory zero'. If not, delete all - * temporary files and abort */ - { - strarray_t *newactive = activefile_read(activefile); - if (strarray_cmp(active, newactive)) { - if (verbose) { - printf("aborting compact of %s, lost the race late\n", mboxname); - } - strarray_free(newactive); - goto out; - } - strarray_free(newactive); - } - - if (dirs->count) { - /* create a new target name one greater than the highest in the - * activefile file for our target directory. Rename our DB to - * that path, then rewrite activefile removing all the source - * items */ - if (verbose) { - printf("renaming tempdir into place\n"); - } - remove_dir(destdir); - r = rename(tempdestdir, destdir); - if (r) { - printf("ERROR: failed to rename into place %s to %s\n", tempdestdir, destdir); - goto out; - } - } - else { - if (verbose) { - printf("nothing compacted, cleaning up %s\n", newdest); - } - strarray_append(tochange, newdest); - } - - for (i = 0; i < tochange->count; i++) - strarray_remove_all(active, tochange->datai); - - activefile_write(activefile, active); - - /* release the lock */ - mappedfile_unlock(activefile); - - if (verbose) { - char *alist = strarray_join(active, ","); - printf("finished compact of %s (active %s)\n", mboxname, alist); - free(alist); - } - - /* finally remove all directories on disk of the source dbs */ - for (i = 0; i < dirs->count; i++) - remove_dir(dirs->datai); - - /* XXX - readdir and remove other directories as well */ - -out: - if (tempdestdir) - remove_dir(tempdestdir); - if (mytempdir.len) - remove_dir(buf_cstring(&mytempdir)); - strarray_free(dirs); - strarray_free(active); - strarray_free(tochange); - buf_free(&mytempdir); - buf_free(&buf); - free(newdest); - free(activestr); - free(destdir); - free(tempdestdir); - mappedfile_unlock(activefile); - mappedfile_close(&activefile); - mboxlist_entry_free(&mbentry); - free(mboxname); - - return r; -} - -/* cleanup */ -static void delete_one(const char *key, const char *val __attribute__((unused)), void *rock) -{ - const char *mboxname = (const char *)rock; - const char *partition = NULL; - char *tier = NULL; - char *basedir = NULL; - - partition = strstr(key, "searchpartition-"); - if (!partition) return; - tier = xstrndup(key, partition - key); - partition += 16; /* skip over name */ - - xapian_basedir(tier, mboxname, partition, NULL, &basedir); - if (basedir) - remove_dir(basedir); - - free(basedir); - free(tier); -} - -static int delete_user(const char *userid) -{ - char *mboxname = mboxname_user_mbox(userid, /*subfolder*/NULL); - char *activename = activefile_fname(mboxname); - - config_foreachoverflowstring(delete_one, mboxname); - unlink(activename); - - free(activename); - free(mboxname); - - return 0; -} - - -const struct search_engine xapian_search_engine = { - "Xapian", - SEARCH_FLAG_CAN_BATCH, - begin_search, - end_search, - begin_update, - end_update, - begin_snippets, - end_snippets, - describe_internalised, - free_internalised, - /*start_daemon*/NULL, - /*stop_daemon*/NULL, - list_files, - compact_dbs, - delete_user /* XXX: fixme */ -}; -
View file
cyrus-imapd-2.5.tar.gz/imap/sequence.c
Changed
@@ -332,7 +332,7 @@ * Return the first number in the sequence, or 0 * if the sequence is empty. */ -EXPORTED unsigned seqset_first(const struct seqset *seq) +HIDDEN unsigned seqset_first(const struct seqset *seq) { return (seq->len ? seq->set0.low : 0); } @@ -341,20 +341,11 @@ * Return the last number in the sequence, or 0 * if the sequence is empty. */ -EXPORTED unsigned seqset_last(const struct seqset *seq) +HIDDEN unsigned seqset_last(const struct seqset *seq) { return (seq->len ? seq->setseq->len-1.high : 0); } -/* NOTE: this assumes normalised, and also assumes that '1' is - * the first element */ -EXPORTED unsigned seqset_firstnonmember(const struct seqset *seq) -{ - if (!seq->len) return 1; - if (seq->set0.low != 1) return 1; - return seq->set0.high + 1; -} - /* * Iteration interface for sequences. Returns the next number * in the sequence, or 0 if the end of the sequence has been @@ -393,7 +384,7 @@ * Merge the numbers in seqset `b' into seqset `a'. */ /* NOTE - not sort safe! */ -EXPORTED void seqset_join(struct seqset *a, const struct seqset *b) +EXPORTED void seqset_join(struct seqset *a, struct seqset *b) { if (a->len + b->len > a->alloc) { a->alloc = a->len + b->len; @@ -408,57 +399,65 @@ seqset_simplify(a); } -static void format_num(struct buf *buf, unsigned i) +/* + * Parse a seqset from the given string and append it to the chain + * of seqsets at `*l'. + */ +HIDDEN void seqset_append(struct seqset **l, char *sequence, unsigned maxval) { - if (i == UINT_MAX) - buf_putc(buf, '*'); - else - buf_printf(buf, "%u", i); + struct seqset **tail = l; + + while (*tail) { + if (!maxval) maxval = (*tail)->maxval; + tail = &(*tail)->nextseq; + } + + *tail = seqset_parse(sequence, NULL, maxval); } +#define SEQGROW 300 + /* * Format the seqset `seq' as a string. Returns a newly allocated * string which must be free()d by the caller. */ EXPORTED char *seqset_cstring(const struct seqset *seq) { - struct buf buf = BUF_INITIALIZER; + unsigned alloc = 0; + unsigned offset = 0; + char *base = NULL; unsigned i; if (!seq) return NULL; - if (!seq->len) return NULL; for (i = 0; i < seq->len; i++) { + /* ensure we have space */ + if (alloc < offset + 30) { + alloc += SEQGROW; + base = xrealloc(base, alloc); + } + /* join with comma if not the first item */ - if (i) buf_putc(&buf, ','); + if (i) baseoffset++ = ','; /* single value only */ if (seq->seti.low == seq->seti.high) - format_num(&buf, seq->seti.low); + sprintf(base+offset, "%u", seq->seti.low); - /* value range */ - else { - format_num(&buf, seq->seti.low); - buf_putc(&buf, ':'); - format_num(&buf, seq->seti.high); - } - } + /* special case - end of the list */ + else if (seq->seti.high == UINT_MAX) + sprintf(base+offset, "%u:*", seq->seti.low); - return buf_release(&buf); -} - -/* - * Duplicate the given seqset. - */ -EXPORTED struct seqset *seqset_dup(const struct seqset *l) -{ - struct seqset *newl; + /* value range */ + else + sprintf(base+offset, "%u:%u", seq->seti.low, + seq->seti.high); - newl = (struct seqset *)xmemdup(l, sizeof(*l)); - newl->set = (struct seq_range *)xmemdup(newl->set, - newl->alloc * sizeof(struct seq_range)); + /* find the end */ + while (baseoffset) offset++; + } - return newl; + return base; } /* @@ -466,8 +465,12 @@ */ EXPORTED void seqset_free(struct seqset *l) { - if (!l) return; + struct seqset *n; - free(l->set); - free(l); + while(l) { + n = l->nextseq; + free(l->set); + free(l); + l = n; + } }
View file
cyrus-imapd-2.5.tar.gz/imap/sequence.h
Changed
@@ -59,6 +59,7 @@ unsigned prev; unsigned maxval; int flags; + struct seqset *nextseq; }; #define SEQ_SPARSE 1 @@ -73,14 +74,13 @@ extern struct seqset *seqset_parse(const char *sequence, struct seqset *set, unsigned maxval); -extern void seqset_join(struct seqset *a, const struct seqset *b); +extern void seqset_join(struct seqset *a, struct seqset *b); +extern void seqset_append(struct seqset **l, char *sequence, unsigned maxval); extern int seqset_ismember(struct seqset *set, unsigned num); extern unsigned seqset_getnext(struct seqset *set); extern unsigned seqset_first(const struct seqset *set); -extern unsigned seqset_firstnonmember(const struct seqset *set); extern unsigned seqset_last(const struct seqset *set); extern char *seqset_cstring(const struct seqset *set); -extern struct seqset *seqset_dup(const struct seqset *); -extern void seqset_free(struct seqset *set); +extern void seqset_free(struct seqset *l); #endif /* SEQUENCE_H */
View file
cyrus-imapd-2.5.tar.gz/imap/smmapd.c
Changed
@@ -105,7 +105,6 @@ extern int optind; static struct protstream *map_in, *map_out; -static const char *smmapd_clienthost; /* current namespace */ static struct namespace map_namespace; @@ -131,8 +130,6 @@ prot_free(map_out); } - smmapd_clienthost = "local"; - map_in = map_out = NULL; cyrus_reset_stdio(); @@ -219,14 +216,12 @@ char **argv __attribute__((unused)), char **envp __attribute__((unused))) { - const char *localip, *remoteip; + map_in = prot_new(0, 0); map_out = prot_new(1, 1); prot_setflushonread(map_in, map_out); prot_settimeout(map_in, 360); - smmapd_clienthost = get_clienthost(0, &localip, &remoteip); - if (begin_handling() != 0) shut_down(0); /* prepare for new connection */ @@ -468,25 +463,19 @@ switch (r) { case -1: - /* reply already sent */ - break; + /* reply already sent */ + break; case 0: - if (config_getswitch(IMAPOPT_AUDITLOG)) - syslog(LOG_NOTICE, "auditlog: ok userid=<%s> client=<%s>", key, smmapd_clienthost); prot_printf(map_out, SIZE_T_FMT ":OK %s,", 3+strlen(key), key); break; case IMAP_MAILBOX_NONEXISTENT: - if (config_getswitch(IMAPOPT_AUDITLOG)) - syslog(LOG_NOTICE, "auditlog: nonexistent userid=<%s> client=<%s>", key, smmapd_clienthost); prot_printf(map_out, SIZE_T_FMT ":NOTFOUND %s,", 9+strlen(error_message(r)), error_message(r)); break; case IMAP_QUOTA_EXCEEDED: - if (config_getswitch(IMAPOPT_AUDITLOG)) - syslog(LOG_NOTICE, "auditlog: overquota userid=<%s> client=<%s>", key, smmapd_clienthost); if (!config_getswitch(IMAPOPT_LMTP_OVER_QUOTA_PERM_FAILURE)) { prot_printf(map_out, SIZE_T_FMT ":TEMP %s,", strlen(error_message(r))+5, error_message(r)); @@ -495,8 +484,6 @@ /* fall through - permanent failure */ default: - if (config_getswitch(IMAPOPT_AUDITLOG)) - syslog(LOG_NOTICE, "auditlog: failed userid=<%s> client=<%s>", key, smmapd_clienthost); if (errstring) prot_printf(map_out, SIZE_T_FMT ":PERM %s (%s),", 5+strlen(error_message(r))+3+strlen(errstring),
View file
cyrus-imapd-2.5.tar.gz/imap/sphinxmgr_client.c
Deleted
@@ -1,162 +0,0 @@ -/* sphinxmgr_client.c - client for talking to the cyr_sphinxmgr daemon - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/un.h> -#include <fcntl.h> -#include <stdlib.h> -#include <syslog.h> -#include <string.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -#include "imap_err.h" -#include "global.h" -#include "retry.h" -#include "xmalloc.h" -#include "xstrlcpy.h" -#include "xstrlcat.h" - -static int sphinxmgr_request(const char *req, size_t reqsize, - char *reply, size_t maxreply) -{ - const char *sockname = config_getstring(IMAPOPT_SPHINXMGR_SOCKET); - struct sockaddr_un asun; - int r; - int s = -1; - - memset(&asun, 0, sizeof(asun)); - asun.sun_family = AF_UNIX; - strlcpy(asun.sun_path, sockname, sizeof(asun.sun_path)); - - s = socket(PF_UNIX, SOCK_STREAM, 0); - if (s < 0) { - syslog(LOG_ERR, "socket(PF_UNIX): %m"); - r = IMAP_IOERROR; - goto out; - } - - r = connect(s, (struct sockaddr *)&asun, sizeof(asun)); - if (r < 0) { - syslog(LOG_ERR, "connect to %s failed: %m", sockname); - r = IMAP_IOERROR; - goto out; - } - - r = retry_write(s, req, reqsize); - if (r < 0) { - syslog(LOG_ERR, "write to %s failed: %m", sockname); - r = IMAP_IOERROR; - goto out; - } - - r = read(s, reply, maxreply-1); - if (r < 0) { - syslog(LOG_ERR, "read from %s failed: %m", sockname); - r = IMAP_IOERROR; - goto out; - } - replyr = '\0'; - r = 0; - -out: - if (s >= 0) close(s); - return r; -} - -int sphinxmgr_getsock(const char *mboxname, char **socknamep) -{ - int r; - char *p; - char buf1024; - - snprintf(buf, sizeof(buf), "GETSOCK %s\n", mboxname); - r = sphinxmgr_request(buf, strlen(buf), buf, sizeof(buf)); - if (r) return r; - if (strncmp(buf, "OK ", 3)) { - syslog(LOG_ERR, "sphinxmgr returned failure: %s", buf); - return IMAP_IOERROR; - } - p = strpbrk(buf+3, " \t\n\r"); - if (p) *p = '\0'; - *socknamep = xstrdup(buf+3); - return 0; -} - -int sphinxmgr_getconf(const char *mboxname, char **configp) -{ - int r; - char *p; - char buf1024; - - snprintf(buf, sizeof(buf), "GETCONF %s\n", mboxname); - r = sphinxmgr_request(buf, strlen(buf), buf, sizeof(buf)); - if (r) return r; - if (strncmp(buf, "OK ", 3)) { - syslog(LOG_ERR, "sphinxmgr returned failure: %s", buf); - return IMAP_IOERROR; - } - p = strpbrk(buf+3, " \t\n\r"); - if (p) *p = '\0'; - *configp = xstrdup(buf+3); - return 0; -} - -int sphinxmgr_stop(const char *mboxname) -{ - int r; - char buf1024; - - snprintf(buf, sizeof(buf), "STOP %s\n", mboxname); - r = sphinxmgr_request(buf, strlen(buf), buf, sizeof(buf)); - if (r) return r; - if (strncmp(buf, "OK ", 3)) { - syslog(LOG_ERR, "sphinxmgr returned failure: %s", buf); - return IMAP_IOERROR; - } - return 0; -} -
View file
cyrus-imapd-2.5.tar.gz/imap/sphinxmgr_client.h
Deleted
@@ -1,54 +0,0 @@ -/* sphinxmgr_client.h - client for talking to the cyr_sphinxmgr daemon - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __CYRUS_SPHINXMGR_CLIENT_H__ -#define __CYRUS_SPHINXMGR_CLIENT_H__ - -#include <sys/socket.h> -#include <sys/un.h> -#include "mailbox.h" - -int sphinxmgr_getsock(const char *mboxname, char **socknamep); -int sphinxmgr_getconf(const char *mboxname, char **configp); -int sphinxmgr_stop(const char *mboxname); - -#endif
View file
cyrus-imapd-2.5.tar.gz/imap/squat_dump.c
Deleted
@@ -1,217 +0,0 @@ -/* squat_dump.c -- SQUAT-based index dumping tool - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * $Id: squatter.c,v 1.26 2010/06/28 12:04:20 brong Exp $ - */ - -/* - This tool dumps out a SQUAT index in various ways. It's useful - for debugging. - - Currently hardcoded for SQUAT, doesn't use struct search_engine. -*/ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <stdlib.h> -#include <stdio.h> -#include <errno.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <fcntl.h> -#include <syslog.h> -#include <string.h> - -#include "assert.h" -#include "global.h" -#include "exitcodes.h" -#include "imap/imap_err.h" -#include "xmalloc.h" -#include "xstrlcpy.h" -#include "xstrlcat.h" -#include "mboxlist.h" -#include "mailbox.h" -#include "mboxname.h" -#include "squat.h" -#include "util.h" - -extern char *optarg; -extern int optind; - -static int usage(const char *name) -{ - fprintf(stderr, - "usage: %s -C <alt_config> mailbox ...\n", - name); - - exit(EC_USAGE); -} - -static const char *squat_strerror(int err) -{ - static char buf64; - - switch (err) { - case SQUAT_ERR_SYSERR: - return strerror(errno); - default: - /* There are other error codes, but they only apply for searching, - not index construction */ - snprintf(buf, sizeof(buf), "unknown squat error %d", err); - return buf; - } -} - -static int dump_doc(void *closure __attribute__((unused)), - const SquatListDoc *doc) -{ - printf("DOC %s %llu\n", doc->doc_name, doc->size); - return SQUAT_CALLBACK_CONTINUE; -} - -/* This is called once for each mailbox we're told to dump. */ -static int dump_one(char *name) -{ - struct mboxlist_entry *mbentry = NULL; - struct mailbox *mailbox = NULL; - int r; - int fd = -1; - SquatSearchIndex *index = NULL; - char *fname = NULL; - - /* Skip remote mailboxes */ - r = mboxlist_lookup(name, &mbentry, NULL); - if (r) { - fprintf(stderr, "error opening looking up %s: %s\n", - name, error_message(r)); - return 1; - } - - if (mbentry->mbtype & MBTYPE_REMOTE) { - mboxlist_entry_free(&mbentry); - return 0; - } - - mboxlist_entry_free(&mbentry); - - r = mailbox_open_irl(name, &mailbox); - if (r) { - fprintf(stderr, "error opening mailbox %s: %s\n", - name, error_message(r)); - return 1; - } - - fname = xstrdup(mailbox_meta_fname(mailbox, META_SQUAT)); - - mailbox_close(&mailbox); - - fd = open(fname, O_RDONLY, 0); - if (fd < 0) { - fprintf(stderr, "error opening file %s: %s\n", - fname, error_message(errno)); - goto out; - } - - index = squat_search_open(fd); - if (index == NULL) { - fprintf(stderr, "error opening index %s: %s\n", - fname, squat_strerror(squat_get_last_error())); - goto out; - } - - printf("MAILBOX %s\n", name); - - r = squat_search_list_docs(index, dump_doc, NULL); - if (r != SQUAT_OK) { - fprintf(stderr, "error listing index %s: %s\n", - fname, squat_strerror(r)); - goto out; - } - -out: - if (fd >= 0) close(fd); - if (index != NULL) squat_search_close(index); - free(fname); - return 0; -} - -int main(int argc, char **argv) -{ - int opt; - char *alt_config = NULL; - int i; - - if ((geteuid()) == 0 && (become_cyrus(/*ismaster*/0) != 0)) { - fatal("must run as the Cyrus user", EC_USAGE); - } - - while ((opt = getopt(argc, argv, "C:")) != EOF) { - switch (opt) { - case 'C': /* alt config file */ - alt_config = optarg; - break; - - default: - usage(argv0); - } - } - - cyrus_init(alt_config, "squat_dump", 0, CONFIG_NEED_PARTITION_DATA); - - mboxlist_init(0); - mboxlist_open(NULL); - - if (optind == argc) - usage(argv0); - - for (i = optind; i < argc; i++) - dump_one(argvi); - - mboxlist_close(); - mboxlist_done(); - - cyrus_done(); - - return 0; -}
View file
cyrus-imapd-2.5.tar.gz/imap/squatter.c
Changed
@@ -1,6 +1,6 @@ /* squatter.c -- SQUAT-based message indexing tool * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. + * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -41,10 +41,30 @@ */ /* - This is the tool that creates/updates search indexes for Cyrus mailboxes. - - Despite the name, it handles whichever search engine in configured - by the 'search_engine' option in imapd.conf. + This is the tool that creates SQUAT indexes for Cyrus mailboxes. + + SQUAT index files are organised as follows: + + There is (at most) one index file for each Cyrus mailbox, named + "cyrus.squat", stored in the mailbox directory. + + Source documents are named 'xUID' where UID is the numeric UID of a + message and x is a character denoting a part of the message: 'f' == + FROM, 't' == TO, 'b' == BCC, 'c' == CC, 's' == SUBJECT, 'h' == other + headers, 'm' == the body. So, a messge with UID 331 could give rise + to several source documents named "f331", "t331", "b331", "c331", + "s331", "h331" and "m331". + + There is also a special source document named "validity.N" where N + is the validitity nonce for the mailbox. We use this to detect when + the UIDs have been renumbered since we created the index (in which + case the index is useless and is ignored). + + This tool creates new indexes for one or more mailboxes. (We do not + support incremental updates to an index yet.) The index is created + in "cyrus.squat.tmp" and then, if creation was successful, it is + atomically renamed to "cyrus.squat". This guarantees that we don't + interfere with anyone who has the old index open. */ #include <config.h> @@ -56,32 +76,25 @@ #include <stdio.h> #include <sys/stat.h> #include <sys/types.h> -#include <sys/poll.h> -#include <errno.h> #include <fcntl.h> #include <syslog.h> #include <string.h> #include "annotate.h" #include "assert.h" -#include "bsearch.h" #include "mboxlist.h" #include "global.h" #include "exitcodes.h" #include "imap/imap_err.h" -#include "search_engines.h" -#include "sync_log.h" #include "mailbox.h" #include "xmalloc.h" #include "xstrlcpy.h" #include "xstrlcat.h" -#include "ptrarray.h" -#include "tok.h" -#include "acl.h" #include "seen.h" #include "mboxname.h" +#include "map.h" +#include "squat.h" #include "index.h" -#include "message.h" #include "util.h" extern char *optarg; @@ -90,724 +103,561 @@ /* current namespace */ static struct namespace squat_namespace; +/* These stats are gathered 1) per mailbox and 2) for the whole operation. */ +typedef struct { + unsigned long indexed_bytes; /* How many bytes of processed message text + have we indexed? */ + unsigned long indexed_messages; /* How many messages have we indexed? */ + unsigned long index_size; /* How many bytes is the index using? */ + time_t start_time; /* When did this operation start? */ + time_t end_time; /* When did it end? */ +} SquatStats; + const int SKIP_FUZZ = 60; static int verbose = 0; +static int mailbox_count = 0; +static int skip_unmodified = 0; static int incremental_mode = 0; -static int recursive_flag = 0; -static int annotation_flag = 0; -static int running_daemon = 0; -static int sleepmicroseconds = 0; -static const char *temp_root_dir = NULL; -static search_text_receiver_t *rx = NULL; +static SquatStats total_stats; + +static void start_stats(SquatStats *stats) +{ + stats->index_size = 0; + stats->indexed_bytes = 0; + stats->indexed_messages = 0; + stats->start_time = time(NULL); +} -static const char *name_starts_from = NULL; +static void stop_stats(SquatStats *stats) +{ + stats->end_time = time(NULL); +} -static void shut_down(int code) __attribute__((noreturn)); +static void print_stats(FILE *out, SquatStats *stats) +{ + fprintf(out, "Indexed %lu messages (%lu bytes) " + "into %lu index bytes in %d seconds\n", + stats->indexed_messages, stats->indexed_bytes, + stats->index_size, (int)(stats->end_time - stats->start_time)); +} static int usage(const char *name) { fprintf(stderr, - "usage: %s -C <alt_config> -v -s -a mailbox...\n", + "usage: %s -C <alt_config> -r -s -a -v mailbox...\n", name); - fprintf(stderr, - "usage: %s -C <alt_config> -v -s -a -u user...\n", - name); - fprintf(stderr, - " %s -C <alt_config> -v -s -a -r mailbox ...\n", - name); - fprintf(stderr, - " %s -C <alt_config> -v -s -d -n channel -R\n", - name); - fprintf(stderr, - " %s -C <alt_config> -v -s -f synclogfile\n", - name); - + exit(EC_USAGE); } -/* ====================================================================== */ - -static void become_daemon(void) +static void fatal_syserror(const char *s) { - pid_t pid; - int nfds = getdtablesize(); - int nullfd; - int fd; - - nullfd = open("/dev/null", O_RDWR, 0); - if (nullfd < 0) { - perror("/dev/null"); - exit(1); - } - dup2(nullfd, 0); - dup2(nullfd, 1); - dup2(nullfd, 2); - for (fd = 3 ; fd < nfds ; fd++) - close(fd); /* this will close nullfd too */ - - pid = fork(); - if (pid == -1) { - perror("fork"); - exit(1); - } - - if (pid) - exit(0); /* parent */ + perror(s); + exit(99); } -/* ====================================================================== */ - -/* This is called once for each mailbox we're told to index. */ -static int index_one(const char *name, int blocking) +static void fatal_squat_error(const char *s) { - mbentry_t *mbentry = NULL; - struct mailbox *mailbox = NULL; - int r; - char extnameMAX_MAILBOX_BUFFER; - int flags = 0; - - if (incremental_mode) - flags |= SEARCH_UPDATE_INCREMENTAL; - - /* Convert internal name to external */ - (*squat_namespace.mboxname_toexternal)(&squat_namespace, name, - NULL, extname); - - /* Skip remote mailboxes */ - r = mboxlist_lookup(name, &mbentry, NULL); - if (r) { - if (verbose) { - printf("error looking up %s: %s\n", - extname, error_message(r)); - } - syslog(LOG_INFO, "error looking up %s: %s\n", - extname, error_message(r)); - - return r; - } - if (mbentry->mbtype & MBTYPE_REMOTE) { - mboxlist_entry_free(&mbentry); - return 0; - } + int err = squat_get_last_error(); - mboxlist_entry_free(&mbentry); - - /* make sure the mailbox (or an ancestor) has - /vendor/cmu/cyrus-imapd/squat set to "true" */ - if (annotation_flag) { - char bufMAX_MAILBOX_BUFFER = "", *p; - struct buf attrib = BUF_INITIALIZER; - int domainlen = 0; - - if (config_virtdomains && (p = strchr(name, '!'))) - domainlen = p - name + 1; - - strlcpy(buf, name, sizeof(buf)); - - /* since mailboxes inherit /vendor/cmu/cyrus-imapd/squat, - we need to iterate all the way up to "" (server entry) */ - while (1) { - r = annotatemore_lookup(buf, "/vendor/cmu/cyrus-imapd/squat", "", - &attrib); - - if (r || /* error */ - attrib.s || /* found an entry */ - !buf0) { /* done recursing */ - break; - } - - p = strrchr(buf, '.'); /* find parent mailbox */ - - if (p && (p - buf > domainlen)) /* don't split subdomain */ - *p = '\0'; - else if (!bufdomainlen) /* server entry */ - buf0 = '\0'; - else /* domain entry */ - bufdomainlen = '\0'; - } - - if (r || !attrib.s || strcasecmp(attrib.s, "true")) { - buf_free(&attrib); - return 0; - } - buf_free(&attrib); - } - -again: - if (blocking) - r = mailbox_open_irl(name, &mailbox); - else - r = mailbox_open_irlnb(name, &mailbox); - - if (r == IMAP_MAILBOX_LOCKED) { - if (verbose) syslog(LOG_INFO, "mailbox %s locked, retrying", extname); - return r; + switch (err) { + case SQUAT_ERR_SYSERR: + perror(s); + break; + default: + /* There are other error codes, but they only apply for searching, + not index construction */ + fprintf(stderr, "SQUAT: Unknown error %d (%s)\n", err, s); } - if (r) { - if (verbose) { - printf("error opening %s: %s\n", extname, error_message(r)); - } - syslog(LOG_INFO, "error opening %s: %s\n", extname, error_message(r)); - return r; - } + exit(98); +} - syslog(LOG_INFO, "indexing mailbox %s... ", extname); - if (verbose > 0) { - printf("Indexing mailbox %s... ", extname); - } +/* ====================================================================== */ - r = search_update_mailbox(rx, mailbox, flags); +/* uid_info is used to track which messages exist in old squat index, by + * parsing document names (e.g: m456. is part of message UID 456). + */ - mailbox_close(&mailbox); +struct uid_item { + unsigned long uid; + int flagged; +}; - /* in non-blocking (rolling) mode, only do one batch per mailbox at - * a time for fairness IRIS-2471. The squatter will re-insert the - * mailbox in the queue */ - if (blocking && r == IMAP_AGAIN) goto again; +struct uid_info { + struct uid_item *list; + unsigned long len; + unsigned long uidvalidity; + int valid; +}; - return r; +static void uid_info_init(struct uid_info *uid_info, unsigned long exists) +{ + uid_info->list = xmalloc((exists + 1) * sizeof(struct uid_item)); + uid_info->len = exists; + uid_info->uidvalidity = 0L; + uid_info->valid = 1; } -static int addmbox(char *name, - int matchlen __attribute__((unused)), - int maycreate __attribute__((unused)), - void *rock) +static void uid_info_free(struct uid_info *uid_info) { - strarray_t *sa = (strarray_t *) rock; - - if (strcmpsafe(name, name_starts_from) < 0) - return 0; - if (mboxname_isdeletedmailbox(name, NULL)) - return 0; + if (uid_info->list != NULL) + free(uid_info->list); - strarray_append(sa, name); - return 0; + uid_info->list = NULL; } -static void expand_mboxnames(strarray_t *sa, int nmboxnames, - const char **mboxnames, int user_mode) +static void uid_item_init(struct uid_item *uid_item, unsigned long uid) { - int i; - char bufMAX_MAILBOX_PATH + 1; - - if (!nmboxnames) { - assert(!recursive_flag); - strlcpy(buf, "*", sizeof(buf)); - (*squat_namespace.mboxlist_findall) (&squat_namespace, buf, 1, - 0, 0, addmbox, sa); - } - - for (i = 0; i < nmboxnames; i++) { - if (user_mode) { - char *mbox = mboxname_user_mbox(mboxnamesi, NULL); - strlcpy(buf, mbox, sizeof(buf)); - free(mbox); - } - else { - /* Translate any separators in mailboxname */ - (*squat_namespace.mboxname_tointernal) (&squat_namespace, - mboxnamesi, NULL, buf); - } - strarray_append(sa, buf); - if (recursive_flag || user_mode) { - strlcat(buf, ".*", sizeof(buf)); - (*squat_namespace.mboxlist_findall) (&squat_namespace, buf, 1, - 0, 0, addmbox, sa); - } - } + uid_item->uid = uid; + uid_item->flagged = 0; } -static int do_indexer(const strarray_t *sa) +static struct uid_item *find_uid_item(struct uid_info *uid_info, + unsigned long uid) { - int r = 0; - int i; - - rx = search_begin_update(verbose); - if (rx == NULL) - return 0; /* no indexer defined */ - - for (i = 0 ; i < sa->count ; i++) { - r = index_one(sa->datai, /*blocking*/1); - if (r == IMAP_MAILBOX_NONEXISTENT) - r = 0; - if (r == IMAP_MAILBOX_LOCKED) - r = 0; /* XXX - try again? */ - if (r) break; - if (sleepmicroseconds) - usleep(sleepmicroseconds); + struct uid_item *list = uid_info->list; + unsigned long first = 0; + unsigned long last = uid_info->len; + + /* Binary chop on sorted array */ + while (first < last) { + unsigned long middle = (first + last) / 2; + + if (listmiddle.uid == uid) + return (&listmiddle); + if (listmiddle.uid < uid) + first = middle + 1; + else + last = middle; } - - search_end_update(rx); - - return r; + return (NULL); } -static int index_single_message(const char *mboxname, uint32_t uid) +/* Populate uid_info map using document names from SquatSearchIndex backend */ +static int doc_check(void *closure, const SquatListDoc *doc) { - int r; - struct mailbox *mailbox = NULL; - message_t *msg = NULL; - int begun = 0; - struct index_record record; - - r = mailbox_open_irl(mboxname, &mailbox); - if (r) goto out; - - r = rx->begin_mailbox(rx, mailbox, /*incremental*/1); - if (r) goto out; - begun = 1; + struct uid_info *uid_info = (struct uid_info *)closure; + struct uid_item *uid_item; + unsigned long uid; + + /* validity will be replaced with new value in same slot */ + if (!strncmp(doc->doc_name, "validity.", 9)) { + uid_info->uidvalidity = strtoul(doc->doc_name + 9, NULL, 10); + return (1); + } - if (rx->is_indexed(rx, uid)) goto out; + if (!strchr("tfcbsmh", doc->doc_name0)) { + syslog(LOG_ERR, "Invalid document name: %s", doc->doc_name); + uid_info->valid = 0; + return (1); + } - r = mailbox_find_index_record(mailbox, uid, &record, NULL); - if (r) goto out; + uid = strtoul(doc->doc_name + 1, NULL, 10); + if ((uid > 0) && (uid_item = find_uid_item(uid_info, uid))) { + uid_item->flagged = 1; + return (1); + } - if (record.system_flags & (FLAG_EXPUNGED|FLAG_UNLINKED)) goto out; + /* Remove this UID from the index */ + return (0); +} - msg = message_new_from_record(mailbox, &record); - if (!msg) goto out; +/* ====================================================================== */ - if (verbose) fprintf(stderr, "squatter: indexing mailbox:%s uid:%u\n", - mboxname, uid); - r = index_getsearchtext(msg, rx, 0); +typedef struct { + SquatStats *mailbox_stats; + SquatIndex *index; + struct mailbox *mailbox; +} SquatReceiverData; -out: - if (begun) { - int r2 = rx->end_mailbox(rx, mailbox); - if (r2 && !r) r = r2; - } - message_unref(&msg); - mailbox_close(&mailbox); - return r; -} - -static int do_indexfrom(const char *fromfile) +/* Cyrus passes the text to index in here, after it has canonicalized + the text. We figure out what source document the text belongs to, + and update the index. */ +static void search_text_receiver(int uid, int part, int cmd, + char const *text, int text_len, + void *rock) { - int r; - FILE *fp; - unsigned lineno = 0; - const char *p; - tok_t tok; - const char *mboxname; - uint32_t uid; - char bufMAX_MAILBOX_BUFFER+128; - - rx = search_begin_update(verbose); - if (rx == NULL) /* no indexer defined */ - return 0; + SquatReceiverData *d = (SquatReceiverData *) rock; + + if ((cmd & SEARCHINDEX_CMD_BEGINPART) != 0) { + char buf100; + char part_char = 0; + + /* Figure out what the name of the source document is going to be. */ + switch (part) { + case SEARCHINDEX_PART_FROM: part_char = 'f'; break; + case SEARCHINDEX_PART_TO: part_char = 't'; break; + case SEARCHINDEX_PART_CC: part_char = 'c'; break; + case SEARCHINDEX_PART_BCC: part_char = 'b'; break; + case SEARCHINDEX_PART_SUBJECT: part_char = 's'; break; + case SEARCHINDEX_PART_HEADERS: part_char = 'h'; break; + case SEARCHINDEX_PART_BODY: + part_char = 'm'; + d->mailbox_stats->indexed_messages++; + total_stats.indexed_messages++; + break; + default: + fatal("Unknown search_text part", EC_SOFTWARE); + } - fp = fopen(fromfile, "r"); - if (!fp) { - r = errno; - perror(fromfile); - goto out; - } + snprintf(buf, sizeof(buf), "%c%d", part_char, uid); + + /* don't index document parts that are going to be empty (or too + short to search) */ + if ((cmd & SEARCHINDEX_CMD_ENDPART) != 0 + && ((cmd & SEARCHINDEX_CMD_APPENDPART) == 0 + || text_len < SQUAT_WORD_SIZE)) { + if (verbose > 2) { + printf("Skipping tiny document part '%s' (size %d)\n", buf, + (cmd & SEARCHINDEX_CMD_APPENDPART) == + 0 ? 0 : text_len); + } + return; + } - while (fgets(buf, sizeof(buf), fp)) { - lineno++; - if (buf0 == '#') continue; - tok_initm(&tok, buf, "\t", TOK_EMPTY|TOK_TRIMRIGHT); - - /* first token is an mboxname */ - mboxname = tok_next(&tok); - if (!mboxname) { -syntax_error: - fprintf(stderr, "%s:%u: syntax error, skipping\n", - fromfile, lineno); - continue; + if (verbose > 2) { + printf("Opening document part '%s'\n", buf); } - /* 2nd token is a uid */ - p = tok_next(&tok); - if (!p) goto syntax_error; - uid = strtoul(p, NULL, 0); - if (!uid) goto syntax_error; - - /* no more tokens on the line */ - p = tok_next(&tok); - if (p) goto syntax_error; - - r = index_single_message(mboxname, uid); - if (r) { - fprintf(stderr, "Failed to index mailbox \"%s\" uid %u: %s\n", - mboxname, uid, error_message(r)); - /* ignore errors */ - r = 0; + if (squat_index_open_document(d->index, buf) != SQUAT_OK) { + fatal_squat_error("Writing index"); } } -out: - if (fp) fclose(fp); - search_end_update(rx); - return r; -} - -static int squatter_build_query(search_builder_t *bx, const char *query) -{ - tok_t tok = TOK_INITIALIZER(query, NULL, 0); - char *p; - char *q; - int r = 0; - int part; - int utf8 = charset_lookupname("utf-8"); - - while ((p = tok_next(&tok))) { - if (!strncasecmp(p, "__begin:", 8)) { - q = p + 8; - if (!strcasecmp(q, "and")) - bx->begin_boolean(bx, SEARCH_OP_AND); - else if (!strcasecmp(q, "or")) - bx->begin_boolean(bx, SEARCH_OP_OR); - else if (!strcasecmp(q, "not")) - bx->begin_boolean(bx, SEARCH_OP_NOT); - else - goto error; - continue; - } - if (!strncasecmp(p, "__end:", 6)) { - q = p + 6; - if (!strcasecmp(q, "and")) - bx->end_boolean(bx, SEARCH_OP_AND); - else if (!strcasecmp(q, "or")) - bx->end_boolean(bx, SEARCH_OP_OR); - else if (!strcasecmp(q, "not")) - bx->end_boolean(bx, SEARCH_OP_NOT); - else - goto error; - continue; + if ((cmd & SEARCHINDEX_CMD_APPENDPART) != 0) { + if (verbose > 3) { + printf("Writing %d bytes into message %d\n", text_len, uid); } - /* everything else is a ->match() of some kind */ - q = strchr(p, ':'); - if (q) q++; - if (!q) { - part = SEARCH_PART_ANY; - q = p; + if (squat_index_append_document(d->index, text, text_len) != SQUAT_OK) { + fatal_squat_error("Writing index data"); } - else if (!strncasecmp(p, "to:", 3)) - part = SEARCH_PART_TO; - else if (!strncasecmp(p, "from:", 5)) - part = SEARCH_PART_FROM; - else if (!strncasecmp(p, "cc:", 3)) - part = SEARCH_PART_CC; - else if (!strncasecmp(p, "bcc:", 4)) - part = SEARCH_PART_BCC; - else if (!strncasecmp(p, "subject:", 8)) - part = SEARCH_PART_SUBJECT; - else if (!strncasecmp(p, "listid:", 7)) - part = SEARCH_PART_LISTID; - else if (!strncasecmp(p, "contenttype:", 12)) - part = SEARCH_PART_TYPE; - else if (!strncasecmp(p, "header:", 7)) - part = SEARCH_PART_HEADERS; - else if (!strncasecmp(p, "body:", 5)) - part = SEARCH_PART_BODY; - else - goto error; - - q = charset_convert(q, utf8, charset_flags); - bx->match(bx, part, q); - free(q); + d->mailbox_stats->indexed_bytes += text_len; + total_stats.indexed_bytes += text_len; } - r = 0; -out: - tok_fini(&tok); - return r; - -error: - syslog(LOG_ERR, "bad query expression at \"%s\"", p); - r = IMAP_PROTOCOL_ERROR; - goto out; + if ((cmd & SEARCHINDEX_CMD_ENDPART) != 0) { + if (squat_index_close_document(d->index) != SQUAT_OK) { + fatal_squat_error("Writing index update"); + } + } } -static int print_search_hit(const char *mboxname, uint32_t uidvalidity, - uint32_t uid, void *rock) +/* Let SQUAT tell us what's going on in the expensive + squat_index_finish function. */ +static void stats_callback(void *closure __attribute__ ((unused)), + SquatStatsEvent *params) { - int single = *(int *)rock; + switch (params->generic.type) { + case SQUAT_STATS_COMPLETED_INITIAL_CHAR: + if (verbose > 1) { + if (params->completed_initial_char.num_words > 0) { + printf("Processing index character %d, %d total words, " + "temp file size is %d\n", + params->completed_initial_char.completed_char, + params->completed_initial_char.num_words, + params->completed_initial_char.temp_file_size); + } + } + break; - if (single) - printf("uid %u\n", uid); - else - printf("mailbox %s\nuidvalidity %u\nuid %u\n", mboxname, uidvalidity, uid); - return 0; + default: + ; /* do nothing */ + } } -static int compact_mbox(const char *userid, const strarray_t *srctiers, - const char *desttier, int flags) +/* Squat a single open mailbox */ +static int squat_single(struct index_state *state, int incremental) { - return search_compact(userid, temp_root_dir, srctiers, desttier, flags); -} + struct mailbox *mailbox = state->mailbox; + char *fname; + char *newfname; + SquatStats stats; + SquatOptions options; + SquatReceiverData data; + SquatSearchIndex *old_index = NULL; + char uid_validity_buf30; + struct uid_info uid_info; + struct uid_item *uid_item; + struct stat index_file_info; + uint32_t msgno; + int new_index_fd = -1; + int old_index_fd = -1; + int r = 0; /* Using IMAP_* not SQUAT_* return codes here */ + + uid_info_init(&uid_info, state->exists); + + newfname = mailbox_meta_newfname(mailbox, META_SQUAT); + if ((new_index_fd = open(newfname, O_CREAT | O_TRUNC | O_WRONLY, 0666)) < 0) + fatal_syserror("Unable to create temporary index file"); + + options.option_mask = SQUAT_OPTION_TMP_PATH | SQUAT_OPTION_STATISTICS; + options.tmp_path = mailbox_datapath(mailbox); + options.stats_callback = stats_callback; + options.stats_callback_closure = NULL; + data.index = squat_index_init(new_index_fd, &options); + if (data.index == NULL) + fatal_squat_error("Initializing index"); + + uid_item = uid_info.list; + for (msgno = 1; msgno <= state->exists; msgno++) { + uid_item_init(&uid_itemmsgno - 1, index_getuid(state, msgno)); + } + /* Add zero UID as an end of list marker: uid_info_init() assigned space */ + uid_item_init(&uid_itemstate->exists, 0); + + /* Open existing index if it exists */ + fname = mailbox_meta_fname(mailbox, META_SQUAT); + old_index_fd = -1; + old_index = NULL; + if (incremental && + ((old_index_fd = open(fname, O_RDONLY)) >= 0) && + (old_index = squat_search_open(old_index_fd)) == NULL) { + close(old_index_fd); + old_index_fd = -1; + } -static int do_compact(const strarray_t *mboxnames, const strarray_t *srctiers, - const char *desttier, int flags) -{ - char *prev_userid = NULL; - int i; - int r = 0; + /* Fall back to full update if open() or squat_search_open() failed */ + if (!old_index) + incremental = 0; + + if (incremental) { + /* Copy existing document names verbatim. They end up with the same + * doc_IDs as in the old index, which makes trie copying much simpler. + */ + uid_info.valid = 1; + uid_info.uidvalidity = 0L; + squat_index_add_existing(data.index, old_index, doc_check, + &uid_info); + + if (!uid_info.valid) { + syslog(LOG_ERR, + "Corrupt squat index for %s, retrying without incremental", + mailbox->name); + r = IMAP_IOERROR; + goto bail; + } - for (i = 0 ; i < mboxnames->count ; i++) { - const char *userid = mboxname_to_userid(mboxnames->datai); - if (!userid) continue; + if (uid_info.uidvalidity != mailbox->i.uidvalidity) { + /* Squat file refers to old mailbox: force full rebuild */ + r = IMAP_IOERROR; + goto bail; + } + } else { + /* write an empty document at the beginning to record the validity + nonce */ + snprintf(uid_validity_buf, sizeof(uid_validity_buf), + "validity.%u", mailbox->i.uidvalidity); + if (squat_index_open_document(data.index, uid_validity_buf) != SQUAT_OK + || squat_index_close_document(data.index) != SQUAT_OK) { + fatal_squat_error("Writing index"); + } + } - if (!strcmpsafe(prev_userid, userid)) - continue; + data.mailbox = mailbox; + data.mailbox_stats = &stats; + start_stats(&stats); - r = compact_mbox(userid, srctiers, desttier, flags); - if (r) break; + uid_item = uid_info.list; + for (msgno = 1; msgno <= state->exists; msgno++) { + uint32_t uid = index_getuid(state, msgno); + /* Scan uid_item list for matching UID (ascending order, 0 termination) */ + while (uid_item->uid && (uid_item->uid < uid)) + uid_item++; - free(prev_userid); - prev_userid = xstrdupnull(userid); + if ((uid_item->uid == uid) && uid_item->flagged) + continue; - if (sleepmicroseconds) - usleep(sleepmicroseconds); + /* This UID didn't appear in the old index file */ + index_getsearchtext_single(state, msgno, search_text_receiver, + &data); + uid_item->flagged = 1; } - free(prev_userid); - return r; -} - -static int do_search(const char *query, int single, const strarray_t *mboxnames) -{ - struct mailbox *mailbox = NULL; - int i; - int r; - search_builder_t *bx; - int opts = SEARCH_VERBOSE(verbose); + if (squat_index_finish(data.index) != SQUAT_OK) { + if (incremental) { + syslog(LOG_ERR, + "Corrupt squat index %s, retrying without incremental update", + mailbox->name); + r = IMAP_IOERROR; + goto bail; + } + /* Just give up if not incremental */ + fatal_squat_error("Closing index"); + } - if (!single) - opts |= SEARCH_MULTIPLE; + /* Check how big the resulting file is */ + if (fstat(new_index_fd, &index_file_info) < 0) { + fatal_syserror("Unable to stat temporary index file"); + } + stats.index_size = index_file_info.st_size; + total_stats.index_size += index_file_info.st_size; - for (i = 0 ; i < mboxnames->count ; i++) { - const char *mboxname = mboxnames->datai; + if (close(new_index_fd) < 0) { + fatal_syserror("Unable to complete writing temporary index file"); + } + new_index_fd = -1; - r = mailbox_open_irl(mboxname, &mailbox); - if (r) { - fprintf(stderr, "Cannot open mailbox %s: %s\n", - mboxname, error_message(r)); - continue; - } - if (single) - printf("mailbox %s\n", mboxname); - - bx = search_begin_search(mailbox, opts); - if (bx) { - r = squatter_build_query(bx, query); - if (!r) - r = bx->run(bx, print_search_hit, &single); - search_end_search(bx); - } + /* OK, we successfully created the index under the temporary file name. + Let's rename it to make it the real index. */ + if (mailbox_meta_rename(mailbox, META_SQUAT) < 0) { + fatal_syserror("Unable to rename temporary index file"); + } - mailbox_close(&mailbox); + stop_stats(&stats); + if (verbose > 0) { + print_stats(stdout, &stats); } - return 0; -} +bail: + if (old_index) + squat_search_close(old_index); + if (old_index_fd >= 0) + close(old_index_fd); + if (new_index_fd >= 0) + close(new_index_fd); + uid_info_free(&uid_info); -static void add_user(strarray_t *folders, const char *user) -{ - char *mbox = mboxname_user_mbox(user, NULL); - char *expr = strconcat(mbox, ".*", (char *)NULL); + return (r); +} - strarray_append(folders, mbox); - (*squat_namespace.mboxlist_findall) (&squat_namespace, expr, 1, - 0, 0, addmbox, folders); +/* This is called once for each mailbox we're told to index. */ +static int index_me(char *name, int matchlen __attribute__((unused)), + int maycreate __attribute__((unused)), + void *rock) { + mbentry_t *mbentry = NULL; + struct index_state *state = NULL; + int r; + char *fname; + struct stat sbuf; + char extnameMAX_MAILBOX_BUFFER; + int use_annot = *((int *) rock); - free(expr); - free(mbox); -} + /* Convert internal name to external */ + (*squat_namespace.mboxname_toexternal)(&squat_namespace, name, + NULL, extname); -static strarray_t *read_sync_log_items(sync_log_reader_t *slr) -{ - const char *args3; - strarray_t *folders = strarray_new(); + /* Skip remote mailboxes */ + r = mboxlist_lookup(name, &mbentry, NULL); + if (r) { + if (verbose) { + printf("error opening looking up %s: %s\n", + extname, error_message(r)); + } + syslog(LOG_INFO, "error opening looking up %s: %s\n", + extname, error_message(r)); - while (sync_log_reader_getitem(slr, args) == 0) { - if (!strcmp(args0, "APPEND")) { - if (!mboxname_isdeletedmailbox(args1, NULL)) - strarray_add(folders, args1); - } - else if (!strcmp(args0, "USER")) - add_user(folders, args1); + return 1; + } + if (mbentry->mbtype & MBTYPE_REMOTE) { + mboxlist_entry_free(&mbentry); + return 0; } - return folders; -} + mboxlist_entry_free(&mbentry); -static int do_synclogfile(const char *synclogfile) -{ - strarray_t *folders = NULL; - sync_log_reader_t *slr; - int nskipped = 0; - int i; - int r; + /* make sure the mailbox (or an ancestor) has + /vendor/cmu/cyrus-imapd/squat set to "true" */ + if (use_annot) { + char bufMAX_MAILBOX_BUFFER = "", *p; + struct buf attrib = BUF_INITIALIZER; + int domainlen = 0; - slr = sync_log_reader_create_with_filename(synclogfile); - r = sync_log_reader_begin(slr); - if (r) goto out; - folders = read_sync_log_items(slr); - sync_log_reader_end(slr); - - /* sort folders for locality of reference in file processing mode */ - strarray_sort(folders, cmpstringp_raw); - - signals_poll(); - - /* have some due items in the queue, try to index them */ - rx = search_begin_update(verbose); - for (i = 0; i < folders->count; i++) { - const char *mboxname = strarray_nth(folders, i); - if (verbose > 1) - syslog(LOG_INFO, "do_synclogfile: indexing %s", mboxname); - r = index_one(mboxname, /*blocking*/1); - if (r == IMAP_MAILBOX_NONEXISTENT) - r = 0; - if (r == IMAP_MAILBOX_LOCKED || r == IMAP_AGAIN) { - nskipped++; - if (nskipped > 10000) { - syslog(LOG_ERR, "IOERROR: skipped too many times at %s", mboxname); - break; - } - r = 0; - /* try again at the end */ - strarray_append(folders, mboxname); - } - if (r) { - syslog(LOG_ERR, "IOERROR: failed to index %s: %s", - mboxname, error_message(r)); - break; - } - if (sleepmicroseconds) - usleep(sleepmicroseconds); - } - search_end_update(rx); - rx = NULL; + if (config_virtdomains && (p = strchr(name, '!'))) + domainlen = p - name + 1; -out: - strarray_free(folders); - sync_log_reader_free(slr); - return r; -} + strlcpy(buf, name, sizeof(buf)); -static void do_rolling(const char *channel) -{ - strarray_t *folders = NULL; - sync_log_reader_t *slr; - int i; - int r; + /* since mailboxes inherit /vendor/cmu/cyrus-imapd/squat, + we need to iterate all the way up to "" (server entry) */ + while (1) { + r = annotatemore_lookup(buf, "/vendor/cmu/cyrus-imapd/squat", "", + &attrib); - slr = sync_log_reader_create_with_channel(channel); + if (r || /* error */ + attrib.s || /* found an entry */ + !buf0) { /* done recursing */ + break; + } - for (;;) { - signals_poll(); - if (shutdown_file(NULL, 0)) - shut_down(EC_TEMPFAIL); + p = strrchr(buf, '.'); /* find parent mailbox */ - r = sync_log_reader_begin(slr); - if (r) { /* including IMAP_AGAIN */ - usleep(100000); /* 1/10th second */ - continue; + if (p && (p - buf > domainlen)) /* don't split subdomain */ + *p = '\0'; + else if (!bufdomainlen) /* server entry */ + buf0 = '\0'; + else /* domain entry */ + bufdomainlen = '\0'; } - folders = read_sync_log_items(slr); - - if (folders->count) { - /* have some due items in the queue, try to index them */ - rx = search_begin_update(verbose); - for (i = 0; i < folders->count; i++) { - const char *mboxname = strarray_nth(folders, i); - if (verbose > 1) - syslog(LOG_INFO, "do_rolling: indexing %s", mboxname); - r = index_one(mboxname, /*blocking*/0); - if (r == IMAP_AGAIN || r == IMAP_MAILBOX_LOCKED) { - /* XXX: alternative, just append to strarray_t *folders ... */ - sync_log_channel(channel, "APPEND %s", mboxname); - } - if (sleepmicroseconds) - usleep(sleepmicroseconds); - } - search_end_update(rx); - rx = NULL; + if (r || !attrib.s || strcasecmp(attrib.s, "true")) { + buf_free(&attrib); + return 0; } - - strarray_free(folders); - folders = NULL; + buf_free(&attrib); } - /* XXX - we don't really get here... */ - strarray_free(folders); - sync_log_reader_free(slr); -} - -/* - * Run a search daemon in such a way that the natural shutdown - * mechanism for Cyrus (sending a SIGTERM to the master process) - * will cleanly shut down the search daemon too. For Sphinx - * this currently means running a loop in a forked process whose - * job it is to live in the master process' process group and thus - * receive the SIGTERM that master re-sends. - */ -static void do_run_daemon(void) -{ - int r; + r = index_open(name, NULL, &state); + if (r) { + if (verbose) { + printf("error opening %s: %s\n", extname, error_message(r)); + } + syslog(LOG_INFO, "error opening %s: %s\n", extname, error_message(r)); - /* We start the daemon before forking. This eliminates a - * race condition during slot startup by ensuring that - * Sphinx is fully running before the rolling squatter - * tries to use it. */ - r = search_start_daemon(verbose); - if (r) exit(EC_TEMPFAIL); + return 1; + } - /* tell shut_down() to shut down the searchd too */ - running_daemon = 1; + fname = mailbox_meta_fname(state->mailbox, META_SQUAT); + + /* process only changed mailboxes if skip option delected. */ + if (skip_unmodified && !stat(fname, &sbuf)) { + if (SKIP_FUZZ + state->mailbox->index_mtime < sbuf.st_mtime) { + syslog(LOG_DEBUG, "skipping mailbox %s", extname); + if (verbose > 0) { + printf("Skipping mailbox %s\n", extname); + } + index_close(&state); + return 0; + } + } - become_daemon(); - signals_set_shutdown(&shut_down); - signals_add_handlers(0); + syslog(LOG_INFO, "indexing mailbox %s... ", extname); + if (verbose > 0) { + printf("Indexing mailbox %s... ", extname); + } - for (;;) { - signals_poll(); /* will call shut_down() after SIGTERM */ - poll(NULL, 0, -1); /* sleeps until signalled */ + if (!incremental_mode || (squat_single(state, 1) != 0)) { + /* Fall back to complete squat */ + squat_single(state, 0); } -} -static void shut_down(int code) -{ - if (running_daemon) - search_stop_daemon(verbose); - seen_done(); - mboxlist_close(); - mboxlist_done(); - annotatemore_close(); - annotate_done(); + index_close(&state); + mailbox_count++; - cyrus_done(); + return 0; +} - exit(code); +static int addmbox(char *name, + int matchlen __attribute__((unused)), + int maycreate __attribute__((unused)), + void *rock) +{ + strarray_t *sa = (strarray_t *) rock; + strarray_append(sa, name); + return 0; } int main(int argc, char **argv) { int opt; char *alt_config = NULL; - int r = IMAP_NOTFOUND; - strarray_t mboxnames = STRARRAY_INITIALIZER; - const char *query = NULL; - int background = 1; - const char *channel = "squatter"; - const char *synclogfile = NULL; - int init_flags = CYRUSINIT_PERROR; - int multi_folder = 0; - int user_mode = 0; - int compact_flags = 0; - const char *fromfile = NULL; - strarray_t *srctiers = NULL; - const char *desttier; - enum { UNKNOWN, INDEXER, INDEXFROM, SEARCH, ROLLING, SYNCLOG, - START_DAEMON, STOP_DAEMON, RUN_DAEMON, COMPACT } mode = UNKNOWN; + int rflag = 0, use_annot = 0; + int i; + char bufMAX_MAILBOX_PATH + 1; + int r; if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { fatal("must run as the Cyrus user", EC_USAGE); @@ -815,100 +665,22 @@ setbuf(stdout, NULL); - while ((opt = getopt(argc, argv, "C:I:N:RAXT:S:Fc:de:f:mn:rsiavz:t:ou")) != EOF) { + while ((opt = getopt(argc, argv, "C:rsiav")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; break; - case 'A': - compact_flags |= SEARCH_COMPACT_AUDIT; - break; - - case 'F': - compact_flags |= SEARCH_COMPACT_FILTER; - break; - - case 'X': - compact_flags |= SEARCH_COMPACT_REINDEX; - break; - - case 'N': - name_starts_from = optarg; - break; - - case 'I': /* indexer, using specified mbox/uids in file */ - if (mode != UNKNOWN && mode != INDEXFROM) usage(argv0); - fromfile = optarg; - mode = INDEXFROM; - break; - - case 'R': /* rolling indexer */ - if (mode != UNKNOWN) usage(argv0); - mode = ROLLING; - incremental_mode = 1; /* always incremental if rolling */ - break; - - case 'S': /* sleep time in seconds */ - sleepmicroseconds = (atof(optarg) * 1000000); - break; - - case 'T': /* temporary root directory for search */ - temp_root_dir = optarg; - break; - - /* This option is deliberately undocumented, for testing only */ - case 'c': /* daemon control mode */ - if (mode != UNKNOWN) usage(argv0); - if (!strcmp(optarg, "start")) - mode = START_DAEMON; - else if (!strcmp(optarg, "stop")) - mode = STOP_DAEMON; - else if (!strcmp(optarg, "run")) - mode = RUN_DAEMON; - else - usage(argv0); - break; - - case 'd': /* foreground (with -R) */ - background = 0; - break; - - /* This option is deliberately undocumented, for testing only */ - case 'e': /* add a search term */ - if (mode != UNKNOWN && mode != SEARCH) usage(argv0); - query = optarg; - mode = SEARCH; - break; - - case 'f': /* alternate synclogfile used in SYNCLOG mode */ - synclogfile = optarg; - mode = SYNCLOG; - break; - - /* This option is deliberately undocumented, for testing only */ - case 'm': /* multi-folder in SEARCH mode */ - if (mode != UNKNOWN && mode != SEARCH) usage(argv0); - multi_folder = 1; - mode = SEARCH; - break; - - case 'n': /* sync channel name (with -R) */ - channel = optarg; - break; - - case 'o': /* copy one DB rather than compressing */ - compact_flags |= SEARCH_COMPACT_COPYONE; - break; - case 'v': /* verbose */ verbose++; break; case 'r': /* recurse */ - if (mode != UNKNOWN && mode != INDEXER) usage(argv0); - recursive_flag = 1; - mode = INDEXER; + rflag = 1; + break; + + case 's': /* skip unmodifed */ + skip_unmodified = 1; break; case 'i': /* incremental mode */ @@ -916,25 +688,7 @@ break; case 'a': /* use /squat annotation */ - if (mode != UNKNOWN && mode != INDEXER) usage(argv0); - annotation_flag = 1; - mode = INDEXER; - break; - - case 'z': - if (mode != UNKNOWN && mode != COMPACT) usage(argv0); - desttier = optarg; - mode = COMPACT; - break; - - case 't': - if (mode != UNKNOWN && mode != COMPACT) usage(argv0); - srctiers = strarray_split(optarg, ",", 0); - mode = COMPACT; - break; - - case 'u': - user_mode = 1; + use_annot = 1; break; default: @@ -942,23 +696,9 @@ } } - compact_flags |= SEARCH_VERBOSE(verbose); - - if (mode == UNKNOWN) - mode = INDEXER; + cyrus_init(alt_config, "squatter", 0, CONFIG_NEED_PARTITION_DATA); - /* fork and close fds if required */ - if (mode == ROLLING && background) { - become_daemon(); - init_flags &= ~CYRUSINIT_PERROR; - } - - if (mode == COMPACT && (!desttier || !srctiers)) { - /* need both src and dest for compact */ - usage("squatter"); - } - - cyrus_init(alt_config, "squatter", init_flags, CONFIG_NEED_PARTITION_DATA); + syslog(LOG_NOTICE, "indexing mailboxes"); /* Set namespace -- force standard (internal) */ if ((r = mboxname_init_namespace(&squat_namespace, 1)) != 0) { @@ -971,58 +711,55 @@ mboxlist_init(0); mboxlist_open(NULL); - if (mode == ROLLING || mode == SYNCLOG) { - signals_set_shutdown(&shut_down); - signals_add_handlers(0); + start_stats(&total_stats); + + if (optind == argc) { + strarray_t sa = STRARRAY_INITIALIZER; + + if (rflag) { + fprintf(stderr, "please specify a mailbox to recurse from\n"); + exit(EC_USAGE); + } + assert(!rflag); + strlcpy(buf, "*", sizeof(buf)); + (*squat_namespace.mboxlist_findall) (&squat_namespace, buf, 1, + 0, 0, addmbox, &sa); + + for (i = 0 ; i < sa.count ; i++) { + index_me(sa.datai, strlen(sa.datai), 0, &use_annot); + /* Ignore errors: most will be mailboxes moving around */ + } + strarray_fini(&sa); } - switch (mode) { - case UNKNOWN: - break; - case INDEXER: - /* -r requires at least one mailbox */ - if (recursive_flag && optind == argc) usage(argv0); - expand_mboxnames(&mboxnames, argc-optind, (const char **)argv+optind, user_mode); - syslog(LOG_NOTICE, "indexing mailboxes"); - r = do_indexer(&mboxnames); - syslog(LOG_NOTICE, "done indexing mailboxes"); - break; - case INDEXFROM: - syslog(LOG_NOTICE, "indexing messages"); - r = do_indexfrom(fromfile); - syslog(LOG_NOTICE, "done indexing messages"); - break; - case SEARCH: - if (recursive_flag && optind == argc) usage(argv0); - expand_mboxnames(&mboxnames, argc-optind, (const char **)argv+optind, user_mode); - r = do_search(query, !multi_folder, &mboxnames); - break; - case ROLLING: - do_rolling(channel); - /* never returns */ - break; - case SYNCLOG: - r = do_synclogfile(synclogfile); - break; - case START_DAEMON: - if (optind != argc) usage("squatter"); - search_start_daemon(verbose); - break; - case STOP_DAEMON: - if (optind != argc) usage("squatter"); - search_stop_daemon(verbose); - break; - case RUN_DAEMON: - if (optind != argc) usage("squatter"); - do_run_daemon(); - break; - case COMPACT: - if (recursive_flag && optind == argc) usage(argv0); - expand_mboxnames(&mboxnames, argc-optind, (const char **)argv+optind, user_mode); - r = do_compact(&mboxnames, srctiers, desttier, compact_flags); - break; + for (i = optind; i < argc; i++) { + /* Translate any separators in mailboxname */ + (*squat_namespace.mboxname_tointernal) (&squat_namespace, argvi, + NULL, buf); + index_me(buf, 0, 0, &use_annot); + if (rflag) { + strlcat(buf, ".*", sizeof(buf)); + (*squat_namespace.mboxlist_findall) (&squat_namespace, buf, 1, + 0, 0, index_me, + &use_annot); + } + } + + if (verbose > 0 && mailbox_count > 1) { + stop_stats(&total_stats); + printf("Total over all mailboxes: "); + print_stats(stdout, &total_stats); } - strarray_fini(&mboxnames); - shut_down(r ? EC_TEMPFAIL : 0); + syslog(LOG_NOTICE, "done indexing mailboxes"); + + seen_done(); + mboxlist_close(); + mboxlist_done(); + annotatemore_close(); + annotate_done(); + + cyrus_done(); + + return 0; }
View file
cyrus-imapd-2.5.tar.gz/imap/statuscache.h
Changed
@@ -49,11 +49,6 @@ #define FNAME_STATUSCACHEDB "/statuscache.db" #define STATUSCACHE_VERSION 4 -/* Return the filename of the statuscache database, - * used for XWARMUP. Returns a new string which must - * be free()d by the caller. */ -extern char *statuscache_filename(void); - /* open the statuscache db */ extern void statuscache_open(void);
View file
cyrus-imapd-2.5.tar.gz/imap/statuscache_db.c
Changed
@@ -63,7 +63,6 @@ #include "mailbox.h" #include "seen.h" #include "util.h" -#include "xmalloc.h" #include "xstrlcpy.h" #include "statuscache.h" @@ -73,33 +72,32 @@ static struct db *statuscachedb; static int statuscache_dbopen = 0; -char *statuscache_filename(void) +EXPORTED void statuscache_open(void) { - const char *fname = config_getstring(IMAPOPT_STATUSCACHE_DB_PATH); + const char *fname = NULL; + int ret; + char *tofree = NULL; - if (fname) - return xstrdup(fname); + if (!fname) + fname = config_getstring(IMAPOPT_STATUSCACHE_DB_PATH); /* create db file name */ - return strconcat(config_dir, FNAME_STATUSCACHEDB, (char *)NULL); -} - -EXPORTED void statuscache_open(void) -{ - char *fname = statuscache_filename(); - int ret; + if (!fname) { + tofree = strconcat(config_dir, FNAME_STATUSCACHEDB, (char *)NULL); + fname = tofree; + } ret = cyrusdb_open(DB, fname, CYRUSDB_CREATE, &statuscachedb); if (ret != 0) { syslog(LOG_ERR, "DBERROR: opening %s: %s", fname, cyrusdb_strerror(ret)); syslog(LOG_ERR, "statuscache in degraded mode"); - goto out; - } + return; + } + + free(tofree); statuscache_dbopen = 1; -out: - free(fname); } EXPORTED void statuscache_close(void) @@ -273,6 +271,8 @@ if (!statuscache_dbopen) return IMAP_NO_NOSUCHMSG; + memset(sdata, 0, sizeof(struct statusdata)); + /* Check if there is an entry in the database */ do { r = cyrusdb_fetch(statuscachedb, key, keylen, &data, &datalen, NULL);
View file
cyrus-imapd-2.5.tar.gz/imap/sync_client.c
Changed
@@ -103,9 +103,6 @@ static int connect_once = 0; static int background = 0; static int do_compress = 0; -static int no_copyback = 0; - -static char *prev_userid; #define CAPA_CRC_VERSIONS (CAPA_COMPRESS<<1) @@ -156,7 +153,6 @@ { fprintf(stderr, "Fatal error: %s\n", s); syslog(LOG_ERR, "Fatal error: %s", s); - abort(); exit(code); } @@ -201,13 +197,12 @@ static int find_reserve_all(struct sync_name_list *mboxname_list, struct sync_folder_list *master_folders, struct sync_folder_list *replica_folders, - struct sync_reserve_list *reserve_list) + struct sync_reserve_list *reserve_guids) { struct sync_name *mbox; struct sync_folder *rfolder; struct sync_msgid_list *part_list; struct mailbox *mailbox = NULL; - modseq_t xconvmodseq; int r = 0; /* Find messages we want to upload that are available on server */ @@ -231,19 +226,9 @@ goto bail; } - xconvmodseq = 0; - if (mailbox_has_conversations(mailbox)) { - r = mailbox_get_xconvmodseq(mailbox, &xconvmodseq); - if (r) { - syslog(LOG_ERR, "IOERROR: Failed to get xconvmodseq %s: %s", - mbox->name, error_message(r)); - goto bail; - } - } - /* mailbox is open from here, no exiting without closing it! */ - part_list = sync_reserve_partlist(reserve_list, mailbox->part); + part_list = sync_reserve_partlist(reserve_guids, mailbox->part); sync_folder_list_add(master_folders, mailbox->uniqueid, mailbox->name, mailbox->mbtype, @@ -252,7 +237,7 @@ mailbox->i.highestmodseq, 0, mailbox->i.recentuid, mailbox->i.recenttime, mailbox->i.pop3_last_login, - mailbox->i.pop3_show_after, NULL, xconvmodseq); + mailbox->i.pop3_show_after, NULL); rfolder = sync_folder_lookup(replica_folders, mailbox->uniqueid); if (rfolder) @@ -309,71 +294,60 @@ struct sync_msgid_list *part_list) { const char *cmd = "RESERVE"; - struct sync_msgid *msgid = part_list->head; + struct sync_msgid *msgid; struct sync_folder *folder; - struct dlist *kl = NULL; + struct dlist *kl; struct dlist *kin = NULL; struct dlist *ki; - int r = 0; + int r; + + if (!part_list->toupload) + return 0; /* nothing to reserve */ if (!replica_folders->head) return 0; /* nowhere to reserve */ - while (msgid) { - int n = 0; - - if (!part_list->toupload) - goto done; /* got them all */ - - kl = dlist_newkvlist(NULL, cmd); - dlist_setatom(kl, "PARTITION", partition); - - ki = dlist_newlist(kl, "MBOXNAME"); - for (folder = replica_folders->head; folder; folder = folder->next) - dlist_setatom(ki, "MBOXNAME", folder->name); - - ki = dlist_newlist(kl, "GUID"); - for (; msgid; msgid = msgid->next) { - if (!msgid->need_upload) continue; - if (n > 8192) break; - dlist_setatom(ki, "GUID", message_guid_encode(&msgid->guid)); - /* we will re-add the "need upload" if we get a MISSING response */ - msgid->need_upload = 0; - part_list->toupload--; - n++; - } - - sync_send_apply(kl, sync_out); - - r = sync_parse_response(cmd, sync_in, &kin); - if (r) goto done; + kl = dlist_newkvlist(NULL, cmd); + dlist_setatom(kl, "PARTITION", partition); - r = mark_missing(kin, part_list); - if (r) goto done; + ki = dlist_newlist(kl, "MBOXNAME"); + for (folder = replica_folders->head; folder; folder = folder->next) + dlist_setatom(ki, "MBOXNAME", folder->name); - dlist_free(&kl); - dlist_free(&kin); + ki = dlist_newlist(kl, "GUID"); + for (msgid = part_list->head; msgid; msgid = msgid->next) { + if (!msgid->need_upload) continue; + dlist_setatom(ki, "GUID", message_guid_encode(&msgid->guid)); + /* we will re-add the "need upload" if we get a MISSING response */ + msgid->need_upload = 0; + part_list->toupload--; } -done: + sync_send_apply(kl, sync_out); dlist_free(&kl); + + r = sync_parse_response(cmd, sync_in, &kin); + if (r) return r; + + r = mark_missing(kin, part_list); dlist_free(&kin); + return r; } static int reserve_messages(struct sync_name_list *mboxname_list, struct sync_folder_list *master_folders, struct sync_folder_list *replica_folders, - struct sync_reserve_list *reserve_list) + struct sync_reserve_list *reserve_guids) { struct sync_reserve *reserve; int r; r = find_reserve_all(mboxname_list, master_folders, - replica_folders, reserve_list); + replica_folders, reserve_guids); if (r) return r; - for (reserve = reserve_list->head; reserve; reserve = reserve->next) { + for (reserve = reserve_guids->head; reserve; reserve = reserve->next) { r = reserve_partition(reserve->part, replica_folders, reserve->list); if (r) return r; } @@ -473,8 +447,6 @@ time_t pop3_show_after = 0; struct dlist *al = NULL; struct sync_annot_list *annots = NULL; - modseq_t xconvmodseq = 0; - if (!folder_list) goto parse_err; if (!dlist_getatom(kl, "UNIQUEID", &uniqueid)) goto parse_err; if (!dlist_getatom(kl, "MBOXNAME", &mboxname)) goto parse_err; @@ -491,12 +463,10 @@ /* optional */ dlist_getdate(kl, "POP3_SHOW_AFTER", &pop3_show_after); dlist_getatom(kl, "MBOXTYPE", &mboxtype); - dlist_getnum64(kl, "XCONVMODSEQ", &xconvmodseq); if (dlist_getlist(kl, "ANNOTATIONS", &al)) decode_annotations(al, &annots); - sync_folder_list_add(folder_list, uniqueid, mboxname, mboxlist_string_to_mbtype(mboxtype), part, acl, @@ -505,8 +475,7 @@ highestmodseq, sync_crc, recentuid, recenttime, pop3_last_login, - pop3_show_after, annots, - xconvmodseq); + pop3_show_after, annots); } else goto parse_err; @@ -795,8 +764,8 @@ newrecord.uid = mailbox->i.last_uid + 1; /* copy the file in to place */ - oldfname = xstrdup(mailbox_record_fname(mailbox, &oldrecord)); - newfname = xstrdup(mailbox_record_fname(mailbox, &newrecord)); + oldfname = xstrdup(mailbox_message_fname(mailbox, oldrecord.uid)); + newfname = xstrdup(mailbox_message_fname(mailbox, newrecord.uid)); r = mailbox_copyfile(oldfname, newfname, 0); free(oldfname); free(newfname); @@ -832,21 +801,18 @@ } static int fetch_file(struct mailbox *mailbox, unsigned long uid, - struct index_record *rp, struct sync_msgid_list *part_list) + struct index_record *rp) { const char *cmd = "FETCH"; struct dlist *kin = NULL; struct dlist *kl; int r = 0; - struct sync_msgid *msgid; + const char *fname = dlist_reserve_path(mailbox->part, &rp->guid); struct message_guid *guid = NULL; - size_t size = 0; - const char *fname = NULL; - - msgid = sync_msgid_lookup(part_list, &rp->guid); + struct stat sbuf; /* already reserved? great */ - if (msgid && !msgid->need_upload) + if (stat(fname, &sbuf) == 0) return 0; kl = dlist_newkvlist(NULL, cmd); @@ -860,20 +826,13 @@ r = sync_parse_response(cmd, sync_in, &kin); if (r) return r; - if (!dlist_tofile(kin->head, NULL, &guid, &size, &fname)) { + if (!dlist_tofile(kin->head, NULL, &guid, NULL, NULL)) { r = IMAP_MAILBOX_NONEXISTENT; goto done; } - if (message_guid_equal(guid, &rp->guid) && (size == rp->size)) { - msgid = sync_msgid_insert(part_list, &rp->guid); - msgid->need_upload = 0; - msgid->size = size; - msgid->fname = xstrdup(fname); - } - else { + if (!message_guid_equal(guid, &rp->guid)) r = IMAP_IOERROR; - } done: dlist_free(&kin); @@ -881,7 +840,7 @@ } static int copy_remote(struct mailbox *mailbox, uint32_t uid, - struct dlist *kr, struct sync_msgid_list *part_list) + struct dlist *kr) { struct index_record record; struct dlist *ki; @@ -895,8 +854,10 @@ /* choose the destination UID */ record.uid = mailbox->i.last_uid + 1; + /* already fetched the file in the parse phase */ + /* append the file */ - r = sync_append_copyfile(mailbox, &record, annots, part_list); + r = sync_append_copyfile(mailbox, &record, annots); sync_annot_list_free(&annots); @@ -911,8 +872,7 @@ static int copyback_one_record(struct mailbox *mailbox, struct index_record *rp, const struct sync_annot_list *annots, - struct dlist *kaction, - struct sync_msgid_list *part_list) + struct dlist *kaction) { int r; @@ -932,7 +892,7 @@ dlist_setnum32(kaction, "EXPUNGE", rp->uid); } else { - r = fetch_file(mailbox, rp->uid, rp, part_list); + r = fetch_file(mailbox, rp->uid, rp); if (r) return r; if (kaction) dlist_setnum32(kaction, "COPYBACK", rp->uid); @@ -944,12 +904,12 @@ * end, so is preferable */ else { /* grab the file */ - r = fetch_file(mailbox, rp->uid, rp, part_list); + r = fetch_file(mailbox, rp->uid, rp); if (r) return r; /* make sure we're actually making changes now */ if (!kaction) return 0; /* append the file */ - r = sync_append_copyfile(mailbox, rp, annots, part_list); + r = sync_append_copyfile(mailbox, rp, annots); if (r) return r; } @@ -1017,10 +977,10 @@ struct index_record *record) { syslog(LOG_NOTICE, "SYNCNOTICE: %s uid:%u modseq:" MODSEQ_FMT " " - "last_updated:%lu internaldate:%lu flags:(%s) cid:" CONV_FMT, + "last_updated:%lu internaldate:%lu flags:(%s)", name, record->uid, record->modseq, record->last_updated, record->internaldate, - make_flags(mailbox, record), record->cid); + make_flags(mailbox, record)); } static void log_mismatch(const char *reason, struct mailbox *mailbox, @@ -1038,110 +998,105 @@ struct index_record *rp, const struct sync_annot_list *mannots, const struct sync_annot_list *rannots, - struct dlist *kaction, - struct sync_msgid_list *part_list) + struct dlist *kaction) { + int diff = 0; int i; int r; - /* if both ends are expunged, then we do no more processing. This - * allows a split brain cleanup to not break things forever. It - * does mean that an expunged message might not replicate in that - * case, but the only way to fix this is add ANOTHER special flag - * for BROKEN and only ignore GUID mismatches in that case, after - * moving the message up. I guess we could force UNLINK immediately - * too... hmm. Not today. */ - - if ((mp->system_flags & FLAG_EXPUNGED) && (rp->system_flags & FLAG_EXPUNGED)) - return 0; + /* are there any differences? */ + if (mp->modseq != rp->modseq) + diff = 1; + else if (mp->last_updated != rp->last_updated) + diff = 1; + else if (mp->internaldate != rp->internaldate) + diff = 1; + else if (mp->system_flags != rp->system_flags) + diff = 1; + else if (!message_guid_equal(&mp->guid, &rp->guid)) + diff = 1; + else if (mp->cid != rp->cid) + diff = 1; + else if (diff_annotations(mannots, rannots)) + diff = 1; + else { + for (i = 0; i < MAX_USER_FLAGS/32; i++) { + if (mp->user_flagsi != rp->user_flagsi) + diff = 1; + } + } - /* first of all, check that GUID matches. If not, we have had a split - * brain, so the messages both need to be fixed up to their new UIDs. - * After this function succeeds, both the local and remote copies of this - * current UID will be actually EXPUNGED, so the earlier return applies. */ - if (!message_guid_equal(&mp->guid, &rp->guid)) { - char *mguid = xstrdup(message_guid_encode(&mp->guid)); - char *rguid = xstrdup(message_guid_encode(&rp->guid)); - syslog(LOG_ERR, "SYNCERROR: guid mismatch %s %u (%s %s)", - mailbox->name, mp->uid, rguid, mguid); - free(rguid); - free(mguid); - /* we will need to renumber both ends to get in sync */ - - /* ORDERING - always lower GUID first */ - if (message_guid_cmp(&mp->guid, &rp->guid) > 0) { - r = copyback_one_record(mailbox, rp, rannots, kaction, part_list); - if (!r) r = renumber_one_record(mp, kaction); + /* if differences we'll have to rewrite to bump the modseq + * so that regular replication will cause an update */ + if (diff) { + /* interesting case - expunged locally */ + if (mp->system_flags & FLAG_EXPUNGED) { + /* if expunged, fall through - the rewrite will lift + * the modseq to force the change to stick */ } - else { - r = renumber_one_record(mp, kaction); - if (!r) r = copyback_one_record(mailbox, rp, rannots, kaction, part_list); + else if (rp->system_flags & FLAG_EXPUNGED) { + /* mark expunged - rewrite will cause both sides to agree + * again */ + mp->system_flags |= FLAG_EXPUNGED; } - return r; - } + /* general case */ + else { + if (!message_guid_equal(&mp->guid, &rp->guid)) { + char *mguid = xstrdup(message_guid_encode(&mp->guid)); + char *rguid = xstrdup(message_guid_encode(&rp->guid)); + syslog(LOG_ERR, "SYNCERROR: guid mismatch %s %u (%s %s)", + mailbox->name, mp->uid, rguid, mguid); + free(rguid); + free(mguid); + /* we will need to renumber both ends to get in sync */ + + /* ORDERING - always lower GUID first */ + if (message_guid_cmp(&mp->guid, &rp->guid) > 0) { + r = copyback_one_record(mailbox, rp, rannots, kaction); + if (!r) r = renumber_one_record(mp, kaction); + } + else { + r = renumber_one_record(mp, kaction); + if (!r) r = copyback_one_record(mailbox, rp, + rannots, kaction); + } - /* are there any differences? */ - if (mp->modseq != rp->modseq) - goto diff; - if (mp->last_updated != rp->last_updated) - goto diff; - if (mp->internaldate != rp->internaldate) - goto diff; - if ((mp->system_flags & FLAGS_GLOBAL) != rp->system_flags) - goto diff; - if (mp->cid != rp->cid) - goto diff; - if (diff_annotations(mannots, rannots)) - goto diff; - for (i = 0; i < MAX_USER_FLAGS/32; i++) { - if (mp->user_flagsi != rp->user_flagsi) - goto diff; - } - if (mp->cid != rp->cid) - goto diff; - - /* no changes found, whoopee */ - return 0; + return r; + } - diff: - /* if differences we'll have to rewrite to bump the modseq - * so that regular replication will cause an update */ + /* is the replica "newer"? */ + if (rp->modseq > mp->modseq && + rp->last_updated >= mp->last_updated) { + /* then copy all the flag data over from the replica */ + mp->system_flags = rp->system_flags; + for (i = 0; i < MAX_USER_FLAGS/32; i++) + mp->user_flagsi = rp->user_flagsi; - /* interesting case - expunged locally */ - if (mp->system_flags & FLAG_EXPUNGED) { - /* if expunged, fall through - the rewrite will lift - * the modseq to force the change to stick */ - } - else if (rp->system_flags & FLAG_EXPUNGED) { - /* mark expunged - rewrite will cause both sides to agree - * again */ - mp->system_flags |= FLAG_EXPUNGED; - } + log_mismatch("more recent on replica", mailbox, mp, rp); - /* otherwise, is the replica "newer"? Better grab those flags */ - else { - if (rp->modseq > mp->modseq && - rp->last_updated >= mp->last_updated) { - log_mismatch("more recent on replica", mailbox, mp, rp); - /* then copy all the flag data over from the replica */ - mp->system_flags = (rp->system_flags & FLAGS_GLOBAL) | - (mp->system_flags & FLAGS_LOCAL); - mp->cid = rp->cid; - for (i = 0; i < MAX_USER_FLAGS/32; i++) - mp->user_flagsi = rp->user_flagsi; + if (kaction) { + r = apply_annotations(mailbox, mp, mannots, rannots, 0); + if (r) return r; + } + } + else { + if (kaction) { + r = apply_annotations(mailbox, mp, mannots, rannots, 1); + if (r) return r; + } + } } - } - /* are we making changes yet? */ - if (!kaction) return 0; + /* are we making changes yet? */ + if (!kaction) return 0; - /* even expunged messages get annotations synced */ - r = apply_annotations(mailbox, mp, mannots, rannots, 0); - if (r) return r; + /* this will bump the modseq and force a resync either way :) */ + r = mailbox_rewrite_index_record(mailbox, mp); + if (r) return r; + } - /* this will bump the modseq and force a resync either way :) */ - return mailbox_rewrite_index_record(mailbox, mp); + return 0; } @@ -1149,8 +1104,7 @@ struct dlist *ki, uint32_t last_uid, modseq_t highestmodseq, - struct dlist *kaction, - struct sync_msgid_list *part_list) + struct dlist *kaction) { struct index_record mrecord; struct index_record rrecord; @@ -1181,7 +1135,7 @@ r = compare_one_record(mailbox, &mrecord, &rrecord, mannots, rannots, - kaction, part_list); + kaction); if (r) goto out; /* increment both */ recno++; @@ -1206,7 +1160,7 @@ syslog(LOG_ERR, "SYNCERROR: only exists on replica %s %u (%s)", mailbox->name, rrecord.uid, message_guid_encode(&rrecord.guid)); - r = copyback_one_record(mailbox, &rrecord, rannots, kaction, part_list); + r = copyback_one_record(mailbox, &rrecord, rannots, kaction); if (r) goto out; } /* only increment replica */ @@ -1246,7 +1200,7 @@ mailbox->name, rrecord.uid); /* going to need this one */ - r = copyback_one_record(mailbox, &rrecord, rannots, kaction, part_list); + r = copyback_one_record(mailbox, &rrecord, rannots, kaction); if (r) goto out; ki = ki->next; @@ -1260,8 +1214,7 @@ return r; } -static int mailbox_full_update(const char *mboxname, - struct sync_reserve_list *reserve_list) +static int mailbox_full_update(const char *mboxname) { const char *cmd = "FULLMAILBOX"; struct mailbox *mailbox = NULL; @@ -1279,8 +1232,6 @@ struct sync_annot_list *mannots = NULL; struct sync_annot_list *rannots = NULL; int remote_modseq_was_higher = 0; - modseq_t xconvmodseq = 0; - struct sync_msgid_list *part_list; kl = dlist_setatom(NULL, cmd, mboxname); sync_send_lookup(kl, sync_out); @@ -1324,15 +1275,10 @@ goto done; } - /* optional */ - dlist_getnum64(kl, "XCONVMODSEQ", &xconvmodseq); - /* we'll be updating it! */ r = mailbox_open_iwl(mboxname, &mailbox); if (r) goto done; - part_list = sync_reserve_partlist(reserve_list, mailbox->part); - /* if local UIDVALIDITY is lower, copy from remote, otherwise * remote will copy ours when we sync */ if (mailbox->i.uidvalidity < uidvalidity) { @@ -1340,7 +1286,6 @@ ", updating %u => %u", mailbox->name, mailbox->i.uidvalidity, uidvalidity); mailbox_index_dirty(mailbox); - mboxname_setuidvalidity(mailbox->name, uidvalidity); mailbox->i.uidvalidity = uidvalidity; } @@ -1357,7 +1302,7 @@ } r = mailbox_update_loop(mailbox, kr->head, last_uid, - highestmodseq, NULL, part_list); + highestmodseq, NULL); if (r) { syslog(LOG_ERR, "SYNCNOTICE: failed to prepare update for %s: %s", mailbox->name, error_message(r)); @@ -1366,16 +1311,9 @@ /* OK - now we're committed to make changes! */ - /* this is safe because "larger than" logic is embedded - * inside update_xconvmodseq */ - if (mailbox_has_conversations(mailbox)) { - r = mailbox_update_xconvmodseq(mailbox, xconvmodseq, /*force */0); - if (r) goto done; - } - kaction = dlist_newlist(NULL, "ACTION"); r = mailbox_update_loop(mailbox, kr->head, last_uid, - highestmodseq, kaction, part_list); + highestmodseq, kaction); if (r) goto cleanup; /* if replica still has a higher last_uid, bump our local @@ -1403,7 +1341,7 @@ dlist_setnum32(kuids, "UID", dlist_num(ka)); } else if (!strcmp(ka->name, "COPYBACK")) { - r = copy_remote(mailbox, dlist_num(ka), kr, part_list); + r = copy_remote(mailbox, dlist_num(ka), kr); if (r) goto cleanup; dlist_setnum32(kuids, "UID", dlist_num(ka)); } @@ -1452,7 +1390,6 @@ { /* look for any mismatches */ unsigned options = mailbox->i.options & MAILBOX_OPTIONS_MASK; - modseq_t xconvmodseq = 0; if (!remote) return 0; if (remote->mbtype != mailbox->mbtype) return 0; @@ -1468,13 +1405,6 @@ if (remote->sync_crc != sync_crc_calc(mailbox, /*force*/0)) return 0; - if (mailbox_has_conversations(mailbox)) { - int r = mailbox_get_xconvmodseq(mailbox, &xconvmodseq); - if (r) return 0; - - if (remote->xconvmodseq != xconvmodseq) return 0; - } - /* compare annotations */ { struct sync_annot_list *mannots = NULL; @@ -1494,7 +1424,7 @@ static int update_mailbox_once(struct sync_folder *local, struct sync_folder *remote, - struct sync_reserve_list *reserve_list, + struct sync_reserve_list *reserve_guids, int is_repeat) { struct sync_msgid_list *part_list; @@ -1554,7 +1484,7 @@ if (is_unchanged(mailbox, remote)) goto done; - part_list = sync_reserve_partlist(reserve_list, mailbox->part); + part_list = sync_reserve_partlist(reserve_guids, mailbox->part); r = sync_mailbox(mailbox, remote, part_list, kl, kupload, 1); if (r) goto done; @@ -1589,22 +1519,19 @@ static int update_mailbox(struct sync_folder *local, struct sync_folder *remote, - struct sync_reserve_list *reserve_list) + struct sync_reserve_list *reserve_guids) { - int r = update_mailbox_once(local, remote, reserve_list, 0); - - /* never retry - other end should always sync cleanly */ - if (no_copyback) return r; + int r = update_mailbox_once(local, remote, reserve_guids, 0); if (r == IMAP_AGAIN) { - r = mailbox_full_update(local->name, reserve_list); - if (!r) r = update_mailbox_once(local, remote, reserve_list, 1); + r = mailbox_full_update(local->name); + if (!r) r = update_mailbox_once(local, remote, reserve_guids, 1); } else if (r == IMAP_SYNC_CHECKSUM) { syslog(LOG_ERR, "CRC failure on sync for %s, trying full update", local->name); - r = mailbox_full_update(local->name, reserve_list); - if (!r) r = update_mailbox_once(local, remote, reserve_list, 1); + r = mailbox_full_update(local->name); + if (!r) r = update_mailbox_once(local, remote, reserve_guids, 1); } return r; @@ -1794,21 +1721,43 @@ /* ====================================================================== */ +static void folderlist_remove(struct sync_folder_list *list, const char *prefix) +{ + struct sync_folder *item; + + for (item = list->head; item; item = item->next) { + if (mboxname_is_prefix(item->name, prefix)) + item->mark = 1; + } +} + +static void renamelist_remove(struct sync_rename_list *list, const char *prefix) +{ + struct sync_rename *item; + + for (item = list->head; item; item = item->next) { + if (mboxname_is_prefix(item->oldname, prefix)) { + list->done++; + item->done = 1; + } + } +} + static int do_folders(struct sync_name_list *mboxname_list, struct sync_folder_list *replica_folders, int delete_remote) { int r; struct sync_folder_list *master_folders; struct sync_rename_list *rename_folders; - struct sync_reserve_list *reserve_list; + struct sync_reserve_list *reserve_guids; struct sync_folder *mfolder, *rfolder; master_folders = sync_folder_list_create(); rename_folders = sync_rename_list_create(); - reserve_list = sync_reserve_list_create(SYNC_MSGID_LIST_HASH_SIZE); + reserve_guids = sync_reserve_list_create(SYNC_MSGID_LIST_HASH_SIZE); r = reserve_messages(mboxname_list, master_folders, - replica_folders, reserve_list); + replica_folders, reserve_guids); if (r) { syslog(LOG_ERR, "reserve messages: failed: %s", error_message(r)); goto bail; @@ -1823,8 +1772,22 @@ if (rfolder->mark) continue; rfolder->mark = 1; - /* does it need a rename? partition change is a rename too */ - if (strcmp(mfolder->name, rfolder->name) || strcmp(mfolder->part, rfolder->part)) { + /* does it need a rename? */ + if (strcmp(mfolder->name, rfolder->name)) { + sync_rename_list_add(rename_folders, mfolder->uniqueid, rfolder->name, + mfolder->name, mfolder->part, mfolder->uidvalidity); + if (mboxname_isusermailbox(mfolder->name, 1) && + mboxname_isusermailbox(rfolder->name, 1)) { + /* user rename - strip all other folders from consideration. They + * will be picked up by the full user sync later */ + folderlist_remove(master_folders, mfolder->name); + renamelist_remove(rename_folders, rfolder->name); + folderlist_remove(replica_folders, rfolder->name); + } + } + + /* partition change is a rename too */ + else if (strcmp(mfolder->part, rfolder->part)) { sync_rename_list_add(rename_folders, mfolder->uniqueid, rfolder->name, mfolder->name, mfolder->part, mfolder->uidvalidity); } @@ -1890,7 +1853,7 @@ * it was successfully renamed above, so just use mfolder->name for * all commands */ rfolder = sync_folder_lookup(replica_folders, mfolder->uniqueid); - r = update_mailbox(mfolder, rfolder, reserve_list); + r = update_mailbox(mfolder, rfolder, reserve_guids); if (r) { syslog(LOG_ERR, "do_folders(): update failed: %s '%s'", mfolder->name, error_message(r)); @@ -1901,7 +1864,7 @@ bail: sync_folder_list_free(&master_folders); sync_rename_list_free(&rename_folders); - sync_reserve_list_free(&reserve_list); + sync_reserve_list_free(&reserve_guids); return r; } @@ -2331,6 +2294,21 @@ } } +static void remove_folder(char *name, struct sync_action_list *list, + int chk_child) +{ + struct sync_action *action; + size_t len = strlen(name); + + for (action = list->head ; action ; action = action->next) { + if (!strncmp(name, action->name, len) && + ((action->namelen == '\0') || + (chk_child && (action->namelen == '.')))) { + action->active = 0; + } + } +} + /* ====================================================================== */ static int do_sync_mailboxes(struct sync_name_list *mboxname_list, @@ -2403,8 +2381,6 @@ sync_action_list_add(meta_list, NULL, args1); else if (!strcmp(args0, "SIEVE")) sync_action_list_add(meta_list, NULL, args1); - else if (!strcmp(args0, "APPEND")) /* just a mailbox event */ - sync_action_list_add(mailbox_list, args1, NULL); else if (!strcmp(args0, "MAILBOX")) sync_action_list_add(mailbox_list, args1, NULL); else if (!strcmp(args0, "UNMAILBOX")) @@ -2426,21 +2402,61 @@ /* Optimise out redundant clauses */ for (action = user_list->head; action; action = action->next) { + char inboxnameMAX_MAILBOX_BUFFER; + char deletednameMAX_MAILBOX_BUFFER; + + /* USER action overrides any MAILBOX action on any of the + * user's mailboxes or any META, SEEN or SUB/UNSUB + * action for same user */ + (sync_namespace.mboxname_tointernal)(&sync_namespace, "INBOX", + action->user, inboxname); + remove_folder(inboxname, mailbox_list, 1); + remove_folder(inboxname, unmailbox_list, 1); + + /* remove deleted namespace items as well */ + if (mboxlist_delayed_delete_isenabled()) { + mboxname_todeleted(inboxname, deletedname, 0); + remove_folder(deletedname, mailbox_list, 1); + remove_folder(deletedname, unmailbox_list, 1); + } + /* remove per-user items */ remove_meta(action->user, meta_list); remove_meta(action->user, seen_list); remove_meta(action->user, sub_list); + + /* add back in inbox, because it may have a rename to detect, woot */ + sync_action_list_add(mailbox_list, inboxname, NULL); } /* duplicate removal for unuser - we also strip all the user events */ for (action = unuser_list->head; action; action = action->next) { + char inboxnameMAX_MAILBOX_BUFFER; + char deletednameMAX_MAILBOX_BUFFER; + + /* USER action overrides any MAILBOX action on any of the + * user's mailboxes or any META, SEEN or SUB/UNSUB + * action for same user */ + (sync_namespace.mboxname_tointernal)(&sync_namespace, "INBOX", + action->user, inboxname); + remove_folder(inboxname, mailbox_list, 1); + remove_folder(inboxname, unmailbox_list, 1); + + /* remove deleted namespace items as well */ + if (mboxlist_delayed_delete_isenabled()) { + mboxname_todeleted(inboxname, deletedname, 0); + remove_folder(deletedname, mailbox_list, 1); + remove_folder(deletedname, unmailbox_list, 1); + } + /* remove per-user items */ remove_meta(action->user, meta_list); remove_meta(action->user, seen_list); remove_meta(action->user, sub_list); - - /* unuser trumps user */ remove_meta(action->user, user_list); + + /* add back in inbox, because it may have a rename to detect, woot */ + sync_action_list_add(mailbox_list, inboxname, NULL); } for (action = meta_list->head; action; action = action->next) { @@ -2978,39 +2994,6 @@ } } -static int cb_allmbox(void *rock __attribute__((unused)), - const char *key, size_t keylen, - const char *val __attribute__((unused)), - size_t vallen __attribute__((unused))) -{ - char *mboxname = xstrndup(key, keylen); - const char *userid; - int r = 0; - - if (mboxname_isdeletedmailbox(mboxname, NULL)) - goto done; - - userid = mboxname_to_userid(mboxname); - - if (userid && strcmpsafe(userid, prev_userid)) { - printf("%s\n", userid); - r = do_user(userid); - if (r) { - if (verbose) - fprintf(stderr, "Error from do_user(%s): bailing out!\n", userid); - syslog(LOG_ERR, "Error in do_user(%s): bailing out!", userid); - goto done; - } - free(prev_userid); - prev_userid = xstrdup(userid); - } - -done: - free(mboxname); - - return r; -} - /* ====================================================================== */ static struct sasl_callback mysasl_cb = { @@ -3023,7 +3006,6 @@ MODE_UNKNOWN = -1, MODE_REPEAT, MODE_USER, - MODE_ALLUSER, MODE_MAILBOX, MODE_META }; @@ -3054,7 +3036,7 @@ setbuf(stdout, NULL); - while ((opt = getopt(argc, argv, "C:vlS:F:f:w:t:d:n:rRumsozOA")) != EOF) { + while ((opt = getopt(argc, argv, "C:vlS:F:f:w:t:d:n:rRumsoz")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; @@ -3111,12 +3093,6 @@ mode = MODE_REPEAT; break; - case 'A': - if (mode != MODE_UNKNOWN) - fatal("Mutually exclusive options defined", EC_USAGE); - mode = MODE_ALLUSER; - break; - case 'u': if (mode != MODE_UNKNOWN) fatal("Mutually exclusive options defined", EC_USAGE); @@ -3143,11 +3119,6 @@ #endif break; - case 'O': - /* don't copy changes back from server */ - no_copyback = 1; - break; - default: usage("sync_client"); } @@ -3259,16 +3230,6 @@ replica_disconnect(); break; - case MODE_ALLUSER: - /* Open up connection to server */ - replica_connect(channel); - - if (mboxlist_allmbox(optind < argc ? argvoptind : NULL, cb_allmbox, NULL, 0)) - exit_rc = 1; - - replica_disconnect(); - break; - case MODE_MAILBOX: /* Open up connection to server */ replica_connect(channel);
View file
cyrus-imapd-2.5.tar.gz/imap/sync_log.c
Changed
@@ -526,7 +526,6 @@ } if (slr->fd_is_ours && slr->fd >= 0) { - lock_unlock(slr->fd, slr->work_file); close(slr->fd); slr->fd = -1; }
View file
cyrus-imapd-2.5.tar.gz/imap/sync_log.h
Changed
@@ -64,9 +64,6 @@ #define sync_log_sieve(user) \ sync_log("META %s\n", user) -#define sync_log_append(name) \ - sync_log("APPEND %s\n", name) - #define sync_log_mailbox(name) \ sync_log("MAILBOX %s\n", name)
View file
cyrus-imapd-2.5.tar.gz/imap/sync_reset.c
Changed
@@ -126,11 +126,39 @@ static int reset_single(const char *userid) { - struct sync_name_list *sublist = sync_name_list_create(); - struct sync_name_list *mblist = sync_name_list_create(); + struct sync_name_list *list = NULL; struct sync_name *item; + char bufMAX_MAILBOX_NAME; int r = 0; + /* Nuke subscriptions */ + list = sync_name_list_create(); + r = mboxlist_allsubs(userid, addmbox_sub, list); + if (r) goto fail; + + /* ignore failures here - the subs file gets deleted soon anyway */ + for (item = list->head; item; item = item->next) { + (void)mboxlist_changesub(item->name, userid, sync_authstate, 0, 0, 0); + } + sync_name_list_free(&list); + + /* Nuke normal folders */ + list = sync_name_list_create(); + + (sync_namespacep->mboxname_tointernal)(sync_namespacep, "INBOX", + userid, buf); + + /* deleted namespace items if enabled */ + if (mboxlist_delayed_delete_isenabled()) { + char deletednameMAX_MAILBOX_BUFFER; + mboxname_todeleted(buf, deletedname, 0); + strlcat(deletedname, ".*", sizeof(deletedname)); + r = (sync_namespace.mboxlist_findall)(&sync_namespace, deletedname, 1, + sync_userid, sync_authstate, + addmbox, (void *)list); + if (r) goto fail; + } + /* XXX: adding an entry to userdeny_db here would avoid the need to * protect against new logins with external proxy rules - Cyrus could * maintain its own safety */ @@ -138,34 +166,29 @@ /* first, disconnect all current connections for this user */ proc_killuser(userid); - r = mboxlist_allsubs(userid, addmbox_sub, sublist); + strlcat(buf, ".*", sizeof(buf)); + r = (sync_namespacep->mboxlist_findall)(sync_namespacep, buf, 1, + sync_userid, sync_authstate, + addmbox, (void *)list); if (r) goto fail; - /* ignore failures here - the subs file gets deleted soon anyway */ - for (item = sublist->head; item; item = item->next) { - (void)mboxlist_changesub(item->name, userid, sync_authstate, 0, 0, 0); - } - - r = mboxlist_allusermbox(userid, addmbox_sub, mblist, /*incdel*/1); - if (r) goto fail; - - for (item = mblist->head; item; item = item->next) { - r = mboxlist_deletemailbox(item->name, 1, sync_userid, + for (item = list->head; item; item = item->next) { + r = mboxlist_deletemailbox(item->name, 1, sync_userid, sync_authstate, NULL, 0, 1, 0); - if (r == IMAP_MAILBOX_NONEXISTENT) { - printf("skipping already removed mailbox %s\n", item->name); - } - else if (r) goto fail; - /* XXX - cheap and nasty hack around actually cleaning up the entry */ - r = mboxlist_deleteremote(item->name, NULL); - if (r) goto fail; + if (r) goto fail; } + /* Nuke inbox (recursive nuke possible?) */ + (sync_namespacep->mboxname_tointernal)(sync_namespacep, "INBOX", + userid, buf); + r = mboxlist_deletemailbox(buf, 1, sync_userid, + sync_authstate, NULL, 0, 1, 0); + if (r && (r != IMAP_MAILBOX_NONEXISTENT)) goto fail; + r = user_deletedata(userid, 1); fail: - sync_name_list_free(&sublist); - sync_name_list_free(&mblist); + sync_name_list_free(&list); return r; }
View file
cyrus-imapd-2.5.tar.gz/imap/sync_server.c
Changed
@@ -74,11 +74,6 @@ #include "annotate.h" #include "append.h" #include "auth.h" -#ifdef WITH_DAV -#include "caldav_db.h" -#include "carddav_db.h" -#include "dav_db.h" -#endif #include "dlist.h" #include "exitcodes.h" #include "global.h" @@ -133,8 +128,6 @@ static int sync_starttls_done = 0; static int sync_compress_done = 0; -static int opt_force = 0; - /* commands that have specific names */ static void cmdloop(void); static void cmd_authenticate(char *mech, char *resp); @@ -258,14 +251,11 @@ /* load the SASL plugins */ global_sasl_init(1, 1, mysasl_cb); - while ((opt = getopt(argc, argv, "p:f")) != EOF) { + while ((opt = getopt(argc, argv, "p:")) != EOF) { switch(opt) { case 'p': /* external protection */ extprops_ssf = atoi(optarg); break; - case 'f': - opt_force = 1; - break; default: usage(); } @@ -293,12 +283,6 @@ statuscache_open(); } -#ifdef WITH_DAV - dav_init(); - caldav_init(); - carddav_init(); -#endif - return 0; } @@ -459,12 +443,6 @@ proc_cleanup(); -#ifdef WITH_DAV - carddav_done(); - caldav_done(); - dav_done(); -#endif - if (config_getswitch(IMAPOPT_STATUSCACHE)) { statuscache_close(); statuscache_done(); @@ -515,7 +493,6 @@ prot_flush(sync_out); } syslog(LOG_ERR, "Fatal error: %s", s); - abort(); shut_down(code); } @@ -1025,14 +1002,16 @@ struct sync_reserve *res; struct sync_reserve_list *l = *reserve_listp; struct sync_msgid *msg; + const char *fname; int hash_size = l->hash_size; struct partition_list *p, *pl = NULL; for (res = l->head; res; res = res->next) { for (msg = res->list->head; msg; msg = msg->next) { - if (!msg->fname) continue; pl = partition_list_add(res->part, pl); - unlink(msg->fname); + + fname = dlist_reserve_path(res->part, &msg->guid); + unlink(fname); } } sync_reserve_list_free(reserve_listp); @@ -1044,11 +1023,6 @@ snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu", config_partitiondir(p->name), (unsigned long)getpid()); rmdir(buf); - - /* and the archive partition too */ - snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu", - config_archivepartitiondir(p->name), (unsigned long)getpid()); - rmdir(buf); } partition_list_free(pl); @@ -1076,8 +1050,6 @@ if (r) return; - /* XXX - this is just on the replica, but still - we shouldn't hold - * the lock for so long */ for (recno = 1; recno <= mailbox->i.num_records; recno++) { /* ok to skip errors here - just means they'll be uploaded * rather than reserved */ @@ -1097,8 +1069,8 @@ continue; /* Attempt to reserve this message */ - mailbox_msg_path = mailbox_record_fname(mailbox, &record); - stage_msg_path = dlist_reserve_path(part, record.system_flags & FLAG_ARCHIVED, &record.guid); + mailbox_msg_path = mailbox_message_fname(mailbox, record.uid); + stage_msg_path = dlist_reserve_path(part, &record.guid); /* check that the sha1 of the file on disk is correct */ memset(&record2, 0, sizeof(struct index_record)); @@ -1120,9 +1092,6 @@ continue; } - item->size = record.size; - item->fname = xstrdup(stage_msg_path); /* track the correct location */ - item->is_archive = record.system_flags & FLAG_ARCHIVED ? 1 : 0; item->need_upload = 0; part_list->toupload--; @@ -1222,7 +1191,7 @@ static int do_quota(struct dlist *kin) { const char *root; - quota_t limitsQUOTA_NUMRESOURCES; + int limitsQUOTA_NUMRESOURCES; if (!dlist_getatom(kin, "ROOT", &root)) return IMAP_PROTOCOL_BAD_PARAMETERS; @@ -1233,8 +1202,7 @@ /* ====================================================================== */ static int mailbox_compare_update(struct mailbox *mailbox, - struct dlist *kr, int doupdate, - struct sync_msgid_list *part_list) + struct dlist *kr, int doupdate) { struct index_record mrecord; struct index_record rrecord; @@ -1244,7 +1212,6 @@ struct sync_annot_list *rannots = NULL; int r; int i; - int has_append = 0; rrecord.uid = 0; for (ki = kr->head; ki; ki = ki->next) { @@ -1276,53 +1243,42 @@ /* found a match, check for updates */ if (rrecord.uid == mrecord.uid) { - /* if they're both EXPUNGED then ignore everything else */ - if ((mrecord.system_flags & FLAG_EXPUNGED) && - (rrecord.system_flags & FLAG_EXPUNGED)) - continue; - /* higher modseq on the replica is an error */ if (rrecord.modseq > mrecord.modseq) { - if (opt_force) { - syslog(LOG_NOTICE, "forcesync: higher modseq on replica %s %u (" MODSEQ_FMT " > " MODSEQ_FMT ")", - mailbox->name, mrecord.uid, rrecord.modseq, mrecord.modseq); - } - else { - syslog(LOG_ERR, "SYNCERROR: higher modseq on replica %s %u (" MODSEQ_FMT " > " MODSEQ_FMT ")", - mailbox->name, mrecord.uid, rrecord.modseq, mrecord.modseq); - r = IMAP_SYNC_CHECKSUM; - goto out; - } - } - - /* GUID mismatch is an error straight away, it only ever happens if we - * had a split brain - and it will take a full sync to sort out the mess */ - if (!message_guid_equal(&mrecord.guid, &rrecord.guid)) { - syslog(LOG_ERR, "SYNCERROR: guid mismatch %s %u", + syslog(LOG_ERR, "SYNCERROR: higher modseq on replica %s %u", mailbox->name, mrecord.uid); r = IMAP_SYNC_CHECKSUM; goto out; } - /* if it's already expunged on the replica, but alive on the master, - * that's bad */ - if (!(mrecord.system_flags & FLAG_EXPUNGED) && - (rrecord.system_flags & FLAG_EXPUNGED)) { - syslog(LOG_ERR, "SYNCERROR: expunged on replica %s %u", - mailbox->name, mrecord.uid); - r = IMAP_SYNC_CHECKSUM; - goto out; + /* everything else only matters if we're not expunged */ + if (!(mrecord.system_flags & FLAG_EXPUNGED)) { + /* GUID mismatch on a non-expunged record is an error straight away */ + if (!message_guid_equal(&mrecord.guid, &rrecord.guid)) { + syslog(LOG_ERR, "SYNCERROR: guid mismatch %s %u", + mailbox->name, mrecord.uid); + r = IMAP_SYNC_CHECKSUM; + goto out; + } + + /* if it's already expunged on the replica, but alive on the master, + * that's bad */ + if (rrecord.system_flags & FLAG_EXPUNGED) { + syslog(LOG_ERR, "SYNCERROR: expunged on replica %s %u", + mailbox->name, mrecord.uid); + r = IMAP_SYNC_CHECKSUM; + goto out; + } } /* skip out on the first pass */ if (!doupdate) continue; - rrecord.cid = mrecord.cid; rrecord.modseq = mrecord.modseq; rrecord.last_updated = mrecord.last_updated; rrecord.internaldate = mrecord.internaldate; - rrecord.system_flags = (mrecord.system_flags & FLAGS_GLOBAL) | - (rrecord.system_flags & FLAGS_LOCAL); + rrecord.system_flags = (mrecord.system_flags & ~FLAG_UNLINKED) | + (rrecord.system_flags & FLAG_UNLINKED); for (i = 0; i < MAX_USER_FLAGS/32; i++) rrecord.user_flagsi = mrecord.user_flagsi; @@ -1364,20 +1320,15 @@ if (!doupdate) continue; mrecord.silent = 1; - r = sync_append_copyfile(mailbox, &mrecord, mannots, part_list); + r = sync_append_copyfile(mailbox, &mrecord, mannots); if (r) { syslog(LOG_ERR, "IOERROR: failed to append file %s %d", mailbox->name, recno); goto out; } - - has_append = 1; } } - if (has_append) - sync_log_append(mailbox->name); - r = 0; out: @@ -1386,9 +1337,8 @@ return r; } -static int do_mailbox(struct dlist *kin, struct sync_reserve_list *reserve_list) +static int do_mailbox(struct dlist *kin) { - struct sync_msgid_list *part_list; /* fields from the request */ const char *uniqueid; const char *partition; @@ -1409,9 +1359,6 @@ uint32_t options; - /* optional fields */ - modseq_t xconvmodseq = 0; - struct mailbox *mailbox = NULL; struct dlist *kr; struct dlist *ka = NULL; @@ -1457,7 +1404,6 @@ dlist_getlist(kin, "ANNOTATIONS", &ka); dlist_getdate(kin, "POP3_SHOW_AFTER", &pop3_show_after); dlist_getatom(kin, "MBOXTYPE", &mboxtype); - dlist_getnum64(kin, "XCONVMODSEQ", &xconvmodseq); options = sync_parse_options(options_str); mbtype = mboxlist_string_to_mbtype(mboxtype); @@ -1466,8 +1412,7 @@ if (r == IMAP_MAILBOX_NONEXISTENT) { r = mboxlist_createsync(mboxname, mbtype, partition, sync_userid, sync_authstate, - options, uidvalidity, - highestmodseq, acl, + options, uidvalidity, acl, uniqueid, &mailbox); /* set a highestmodseq of 0 so ALL changes are future * changes and get applied */ @@ -1486,104 +1431,43 @@ goto done; } - part_list = sync_reserve_partlist(reserve_list, mailbox->part); - /* hold the annotate state open */ mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate); /* and make it hold a transaction open */ annotate_state_begin(astate); if (strcmp(mailbox->uniqueid, uniqueid)) { - if (opt_force) { - syslog(LOG_NOTICE, "forcesync: fixing uniqueid %s (%s => %s)", - mboxname, mailbox->uniqueid, uniqueid); - free(mailbox->uniqueid); - mailbox->uniqueid = xstrdup(uniqueid); - mailbox->header_dirty = 1; - } - else { - syslog(LOG_ERR, "Mailbox uniqueid changed %s (%s => %s) - retry", - mboxname, mailbox->uniqueid, uniqueid); - r = IMAP_MAILBOX_MOVED; - goto done; - } + syslog(LOG_ERR, "Mailbox uniqueid changed %s (%s => %s) - retry", + mboxname, mailbox->uniqueid, uniqueid); + r = IMAP_MAILBOX_MOVED; + goto done; } /* skip out now, it's going to mismatch for sure! */ if (highestmodseq < mailbox->i.highestmodseq) { - if (opt_force) { - syslog(LOG_NOTICE, "forcesync: higher modseq on replica %s - " - MODSEQ_FMT " < " MODSEQ_FMT, - mboxname, highestmodseq, mailbox->i.highestmodseq); - } - else { - syslog(LOG_ERR, "higher modseq on replica %s - " - MODSEQ_FMT " < " MODSEQ_FMT, - mboxname, highestmodseq, mailbox->i.highestmodseq); - r = IMAP_SYNC_CHECKSUM; - goto done; - } + syslog(LOG_ERR, "higher modseq on replica %s - " + MODSEQ_FMT " < " MODSEQ_FMT, + mboxname, highestmodseq, mailbox->i.highestmodseq); + r = IMAP_SYNC_CHECKSUM; + goto done; } /* skip out now, it's going to mismatch for sure! */ if (uidvalidity < mailbox->i.uidvalidity) { - if (opt_force) { - syslog(LOG_NOTICE, "forcesync: higher uidvalidity on replica %s - %u < %u", - mboxname, uidvalidity, mailbox->i.uidvalidity); - } - else { - syslog(LOG_ERR, "higher uidvalidity on replica %s - %u < %u", - mboxname, uidvalidity, mailbox->i.uidvalidity); - r = IMAP_SYNC_CHECKSUM; - goto done; - } + syslog(LOG_ERR, "higher uidvalidity on replica %s - %u < %u", + mboxname, uidvalidity, mailbox->i.uidvalidity); + r = IMAP_SYNC_CHECKSUM; + goto done; } /* skip out now, it's going to mismatch for sure! */ if (last_uid < mailbox->i.last_uid) { - if (opt_force) { - syslog(LOG_NOTICE, "forcesync: higher last_uid on replica %s - %u < %u", - mboxname, last_uid, mailbox->i.last_uid); - } - else { - syslog(LOG_ERR, "higher last_uid on replica %s - %u < %u", - mboxname, last_uid, mailbox->i.last_uid); - r = IMAP_SYNC_CHECKSUM; - goto done; - } - } - - /* NOTE - this is optional */ - if (mailbox_has_conversations(mailbox) && xconvmodseq) { - modseq_t ourxconvmodseq = 0; - - r = mailbox_get_xconvmodseq(mailbox, &ourxconvmodseq); - if (r) { - syslog(LOG_ERR, "Failed to read xconvmodseq for %s: %s", - mboxname, error_message(r)); - goto done; - } - - /* skip out now, it's going to mismatch for sure! */ - if (xconvmodseq < ourxconvmodseq) { - if (opt_force) { - syslog(LOG_NOTICE, "forcesync: higher xconvmodseq on replica %s - %llu < %llu", - mboxname, xconvmodseq, ourxconvmodseq); - } - else { - syslog(LOG_ERR, "higher xconvmodseq on replica %s - %llu < %llu", - mboxname, xconvmodseq, ourxconvmodseq); - r = IMAP_SYNC_CHECKSUM; - goto done; - } - } + syslog(LOG_ERR, "higher last_uid on replica %s - %u < %u", + mboxname, last_uid, mailbox->i.last_uid); + r = IMAP_SYNC_CHECKSUM; + goto done; } - r = mailbox_compare_update(mailbox, kr, 0, part_list); - if (r) goto done; - - /* now we're committed to writing something no matter what happens! */ - /* always take the ACL from the master, it's not versioned */ if (strcmp(mailbox->acl, acl)) { mailbox_set_acl(mailbox, acl, 0); @@ -1591,6 +1475,9 @@ if (r) goto done; } + r = mailbox_compare_update(mailbox, kr, 0); + if (r) goto done; + /* take all mailbox (not message) annotations - aka metadata, * they're not versioned either */ if (ka) @@ -1605,16 +1492,14 @@ goto done; } - r = mailbox_compare_update(mailbox, kr, 1, part_list); + r = mailbox_compare_update(mailbox, kr, 1); if (r) { abort(); return r; } mailbox_index_dirty(mailbox); - if (!opt_force) { - assert(mailbox->i.last_uid <= last_uid); - } + assert(mailbox->i.last_uid <= last_uid); mailbox->i.last_uid = last_uid; mailbox->i.recentuid = recentuid; mailbox->i.recenttime = recenttime; @@ -1626,24 +1511,17 @@ (mailbox->i.options & ~MAILBOX_OPTIONS_MASK); /* this happens all the time! */ - if (mailbox->i.highestmodseq != highestmodseq) { - mboxname_setmodseq(mailbox->name, highestmodseq); + if (mailbox->i.highestmodseq < highestmodseq) { mailbox->i.highestmodseq = highestmodseq; } /* this happens rarely, so let us know */ - if (mailbox->i.uidvalidity != uidvalidity) { - syslog(LOG_NOTICE, "%s uidvalidity changed, updating %u => %u", + if (mailbox->i.uidvalidity < uidvalidity) { + syslog(LOG_ERR, "%s uidvalidity higher on master, updating %u => %u", mailbox->name, mailbox->i.uidvalidity, uidvalidity); - /* make sure nothing new gets created with a lower value */ - mboxname_setuidvalidity(mailbox->name, uidvalidity); mailbox->i.uidvalidity = uidvalidity; } - if (mailbox_has_conversations(mailbox)) { - r = mailbox_update_xconvmodseq(mailbox, xconvmodseq, opt_force); - } - done: sync_annot_list_free(&mannots); sync_annot_list_free(&rannots); @@ -2296,11 +2174,8 @@ return IMAP_PROTOCOL_BAD_PARAMETERS; fname = mboxname_datapath(partition, mboxname, uid); - if (stat(fname, &sbuf) == -1) { - fname = mboxname_archivepath(partition, mboxname, uid); - if (stat(fname, &sbuf) == -1) - return IMAP_MAILBOX_NONEXISTENT; - } + if (stat(fname, &sbuf) == -1) + return IMAP_MAILBOX_NONEXISTENT; kl = dlist_setfile(NULL, "MESSAGE", partition, &tmp_guid, sbuf.st_size, fname); sync_send_response(kl, sync_out); @@ -2366,22 +2241,17 @@ for (ki = kin->head; ki; ki = ki->next) { struct message_guid *guid; const char *part; - size_t size; - const char *fname; /* XXX - complain more? */ - if (!dlist_tofile(ki, &part, &guid, &size, &fname)) + if (!dlist_tofile(ki, &part, &guid, NULL, NULL)) continue; part_list = sync_reserve_partlist(reserve_list, part); msgid = sync_msgid_insert(part_list, guid); - if (!msgid->need_upload) - continue; - - msgid->size = size; - msgid->fname = xstrdup(fname); - msgid->need_upload = 0; - part_list->toupload--; + if (msgid->need_upload) { + msgid->need_upload = 0; + part_list->toupload--; + } } return 0; @@ -2428,7 +2298,7 @@ else if (!strcmp(kin->name, "ANNOTATION")) r = do_annotation(kin); else if (!strcmp(kin->name, "MAILBOX")) - r = do_mailbox(kin, reserve_list); + r = do_mailbox(kin); else if (!strcmp(kin->name, "QUOTA")) r = do_quota(kin); else if (!strcmp(kin->name, "SEEN"))
View file
cyrus-imapd-2.5.tar.gz/imap/sync_support.c
Changed
@@ -255,10 +255,6 @@ r = sync_getflags(fl, mailbox, record); if (r) return r; - /* OK if it doesn't have one */ - record->cid = NULLCONVERSATION; - dlist_gethex64(kr, "CID", &record->cid); - /* the ANNOTATIONS list is optional too */ if (salp && dlist_getlist(kr, "ANNOTATIONS", &fl)) decode_annotations(fl, salp); @@ -340,7 +336,7 @@ *lp = NULL; } -struct sync_msgid *sync_msgid_lookup(const struct sync_msgid_list *l, +struct sync_msgid *sync_msgid_lookup(struct sync_msgid_list *l, struct message_guid *guid) { int offset = message_guid_hash(guid, l->hash_size); @@ -436,8 +432,7 @@ time_t recenttime, time_t pop3_last_login, time_t pop3_show_after, - struct sync_annot_list *annots, - modseq_t xconvmodseq) + struct sync_annot_list *annots) { struct sync_folder *result = xzmalloc(sizeof(struct sync_folder)); @@ -465,7 +460,6 @@ result->pop3_last_login = pop3_last_login; result->pop3_show_after = pop3_show_after; result->annots = annots; /* NOTE: not a copy! */ - result->xconvmodseq = xconvmodseq; result->mark = 0; result->reserve = 0; @@ -649,7 +643,7 @@ *lp = NULL; } -void sync_encode_quota_limits(struct dlist *kl, const quota_t limitsQUOTA_NUMRESOURCES) +void sync_encode_quota_limits(struct dlist *kl, const int limitsQUOTA_NUMRESOURCES) { int res; @@ -668,7 +662,7 @@ } } -void sync_decode_quota_limits(/*const*/ struct dlist *kl, quota_t limitsQUOTA_NUMRESOURCES) +void sync_decode_quota_limits(/*const*/ struct dlist *kl, int limitsQUOTA_NUMRESOURCES) { uint32_t limit = 0; int res; @@ -1130,8 +1124,8 @@ { struct sync_annot *item = xzmalloc(sizeof(struct sync_annot)); - item->entry = xstrdupnull(entry); - item->userid = xstrdupnull(userid); + item->entry = xstrdup(entry); + item->userid = xstrdup(userid); buf_copy(&item->value, value); item->mark = 0; @@ -1338,29 +1332,24 @@ struct sync_msgid_list *part_list, struct dlist *kupload) { - struct sync_msgid *msgid; + struct sync_msgid *msgid = sync_msgid_insert(part_list, &record->guid); const char *fname; - /* we'll trust that it exists - if not, we'll bail later, - * but right now we're under locks, so be fast */ - fname = mailbox_record_fname(mailbox, record); - if (!fname) return IMAP_MAILBOX_BADNAME; - - msgid = sync_msgid_insert(part_list, &record->guid); - /* already uploaded, great */ if (!msgid->need_upload) return 0; + /* we'll trust that it exists - if not, we'll bail later, + * but right now we're under locks, so be fast */ + fname = mailbox_message_fname(mailbox, record->uid); + if (!fname) return IMAP_MAILBOX_BADNAME; + dlist_setfile(kupload, "MESSAGE", mailbox->part, &record->guid, record->size, fname); /* note that we will be sending it, so it doesn't need to be * sent again */ - msgid->size = record->size; - msgid->fname = xstrdup(fname); msgid->need_upload = 0; - msgid->is_archive = record->system_flags & FLAG_ARCHIVED ? 1 : 0; part_list->toupload--; return 0; @@ -1373,7 +1362,6 @@ int printrecords) { struct sync_annot_list *annots = NULL; - modseq_t xconvmodseq = 0; int r = 0; dlist_setatom(kl, "UNIQUEID", mailbox->uniqueid); @@ -1394,11 +1382,6 @@ dlist_setnum32(kl, "SYNC_CRC", sync_crc_calc(mailbox, /*force*/0)); if (mailbox->quotaroot) dlist_setatom(kl, "QUOTAROOT", mailbox->quotaroot); - if (mailbox_has_conversations(mailbox)) { - r = mailbox_get_xconvmodseq(mailbox, &xconvmodseq); - if (!r && xconvmodseq) - dlist_setnum64(kl, "XCONVMODSEQ", xconvmodseq); - } /* always send mailbox annotations */ r = read_annotations(mailbox, NULL, &annots); @@ -1465,8 +1448,6 @@ dlist_setnum32(il, "SIZE", record.size); dlist_setatom(il, "GUID", message_guid_encode(&record.guid)); - dlist_sethex64(il, "CID", record.cid); - r = read_annotations(mailbox, &record, &annots); if (r) goto done; @@ -1549,53 +1530,45 @@ int sync_append_copyfile(struct mailbox *mailbox, struct index_record *record, - const struct sync_annot_list *annots, - const struct sync_msgid_list *part_list) + const struct sync_annot_list *annots) { - const char *destname; + const char *fname, *destname; struct message_guid tmp_guid; - struct sync_msgid *item; int r; message_guid_copy(&tmp_guid, &record->guid); - item = sync_msgid_lookup(part_list, &record->guid); - - if (!item || !item->fname) { + fname = dlist_reserve_path(mailbox->part, &tmp_guid); + if (!fname) { r = IMAP_IOERROR; syslog(LOG_ERR, "IOERROR: Failed to reserve file %s", message_guid_encode(&tmp_guid)); return r; } - r = message_parse(item->fname, record); + r = message_parse(fname, record); if (r) { /* deal with unlinked master records */ if (record->system_flags & FLAG_EXPUNGED) { - /* no need to set 'needs cleanup' here, it's already expunged */ record->system_flags |= FLAG_UNLINKED; goto just_write; } - syslog(LOG_ERR, "IOERROR: failed to parse %s", item->fname); + syslog(LOG_ERR, "IOERROR: failed to parse %s", fname); return r; } if (!message_guid_equal(&tmp_guid, &record->guid)) { syslog(LOG_ERR, "IOERROR: guid mismatch on parse %s (%s)", - item->fname, message_guid_encode(&record->guid)); + fname, message_guid_encode(&record->guid)); return IMAP_IOERROR; } - /* put back to archive if original was archived, gain single instance store */ - if (item->is_archive) - record->system_flags |= FLAG_ARCHIVED; - - destname = mailbox_record_fname(mailbox, record); + destname = mailbox_message_fname(mailbox, record->uid); cyrus_mkdir(destname, 0755); - r = mailbox_copyfile(item->fname, destname, 0); + r = mailbox_copyfile(fname, destname, 0); if (r) { syslog(LOG_ERR, "IOERROR: Failed to copy %s to %s", - item->fname, destname); + fname, destname); return r; } @@ -1730,9 +1703,9 @@ diff++; if (!diff) - diff = strcmpnull(a->entry, b->entry); + diff = strcmp(a->entry, b->entry); if (!diff) - diff = strcmpnull(a->userid, b->userid); + diff = strcmp(a->userid, b->userid); if (!diff && diff_value) diff = buf_cmp(&a->value, &b->value);
View file
cyrus-imapd-2.5.tar.gz/imap/sync_support.h
Changed
@@ -85,11 +85,7 @@ struct sync_msgid *next; struct sync_msgid *hash_next; struct message_guid guid; - struct body *body; - size_t size; - char *fname; - unsigned int need_upload:1; - unsigned int is_archive:1; + int need_upload; }; struct sync_msgid_list { @@ -109,7 +105,7 @@ void sync_msgid_remove(struct sync_msgid_list *l, struct message_guid *guid); -struct sync_msgid *sync_msgid_lookup(const struct sync_msgid_list *list, +struct sync_msgid *sync_msgid_lookup(struct sync_msgid_list *list, struct message_guid *guid); void sync_msgid_list_free(struct sync_msgid_list **list); @@ -152,7 +148,6 @@ time_t pop3_last_login; time_t pop3_show_after; struct sync_annot_list *annots; - modseq_t xconvmodseq; struct quota quota; int mark; int reserve; /* Folder has been processed by reserve operation */ @@ -178,8 +173,7 @@ time_t recenttime, time_t pop3_last_login, time_t pop3_show_after, - struct sync_annot_list *annot, - modseq_t xconvmodseq); + struct sync_annot_list *annots); struct sync_folder *sync_folder_lookup(struct sync_folder_list *l, const char *uniqueid); @@ -227,7 +221,7 @@ struct sync_quota { struct sync_quota *next; char *root; - quota_t limitsQUOTA_NUMRESOURCES; + int limitsQUOTA_NUMRESOURCES; int done; }; @@ -247,8 +241,8 @@ void sync_quota_list_free(struct sync_quota_list **lp); -void sync_encode_quota_limits(struct dlist *kl, const quota_t limitsQUOTA_NUMRESOURCES); -void sync_decode_quota_limits(/*const*/ struct dlist *kl, quota_t limitsQUOTA_NUMRESOURCES); +void sync_encode_quota_limits(struct dlist *kl, const int limitsQUOTA_NUMRESOURCES); +void sync_decode_quota_limits(/*const*/ struct dlist *kl, int limitsQUOTA_NUMRESOURCES); /* ====================================================================== */ @@ -417,8 +411,7 @@ struct sync_annot_list **annotsp); int sync_append_copyfile(struct mailbox *mailbox, struct index_record *record, - const struct sync_annot_list *sal, - const struct sync_msgid_list *part_list); + const struct sync_annot_list *sal); /* ====================================================================== */
View file
cyrus-imapd-2.5.tar.gz/imap/tls.c
Changed
@@ -1377,7 +1377,7 @@ #else -int tls_enabled(void) +EXPORTED int tls_enabled(void) { return 0; }
View file
cyrus-imapd-2.5.tar.gz/imap/unexpunge.c
Changed
@@ -71,15 +71,14 @@ static int verbose = 0; static int unsetdeleted = 0; -static const char *addflag = NULL; static void usage(void) { fprintf(stderr, "unexpunge -C <altconfig> -l <mailbox>\n" - "unexpunge -C <altconfig> -t time-interval -d -v -f flag mailbox\n" - "unexpunge -C <altconfig> -a -d -v -f flag <mailbox>\n" - "unexpunge -C <altconfig> -u -d -v -f flag <mailbox> <uid>...\n"); + "unexpunge -C <altconfig> -t time-interval -d -v mailbox\n" + "unexpunge -C <altconfig> -a -d -v <mailbox>\n" + "unexpunge -C <altconfig> -u -d -v <mailbox> <uid>...\n"); exit(-1); } @@ -174,20 +173,18 @@ const char *mboxname) { uint32_t recno; + uint32_t olduid; struct index_record record; - struct index_record newrecord; - annotate_state_t *astate = NULL; unsigned uidnum = 0; char oldfnameMAX_MAILBOX_PATH; const char *fname; - const char *userid = mboxname_to_userid(mailbox->name); int r = 0; *numrestored = 0; for (recno = 1; recno <= mailbox->i.num_records; recno++) { r = mailbox_read_index_record(mailbox, recno, &record); - if (r) goto done; + if (r) return r; /* still active */ if (!(record.system_flags & FLAG_EXPUNGED)) @@ -211,66 +208,38 @@ /* otherwise we want this one */ } - /* work on a copy */ - newrecord = record; + /* mark the old one unlinked so we don't see it again */ + olduid = record.uid; + record.system_flags |= FLAG_UNLINKED; + r = mailbox_rewrite_index_record(mailbox, &record); + if (r) return r; /* duplicate the old filename */ - fname = mailbox_record_fname(mailbox, &record); + fname = mailbox_message_fname(mailbox, olduid); xstrncpy(oldfname, fname, MAX_MAILBOX_PATH); /* bump the UID, strip the flags */ - newrecord.uid = mailbox->i.last_uid + 1; - newrecord.system_flags &= ~FLAG_EXPUNGED; + record.uid = mailbox->i.last_uid + 1; + record.system_flags &= ~(FLAG_UNLINKED|FLAG_EXPUNGED); if (unsetdeleted) - newrecord.system_flags &= ~FLAG_DELETED; + record.system_flags &= ~FLAG_DELETED; /* copy the message file */ - fname = mailbox_record_fname(mailbox, &newrecord); + fname = mailbox_message_fname(mailbox, record.uid); r = mailbox_copyfile(oldfname, fname, 0); - if (r) goto done; - - /* add the flag if requested */ - if (addflag) { - int userflag = 0; - r = mailbox_user_flag(mailbox, addflag, &userflag, 1); - if (r) goto done; - newrecord.user_flagsuserflag/32 |= 1<<(userflag&31); - } + if (r) return r; /* and append the new record */ - r = mailbox_append_index_record(mailbox, &newrecord); - if (r) goto done; - - /* ensure we have an astate connected to the destination - * mailbox, so that the annotation txn will be committed - * when we close the mailbox */ - r = mailbox_get_annotate_state(mailbox, newrecord.uid, &astate); - if (r) goto done; - - /* and copy over any annotations */ - r = annotate_msg_copy(mailbox, record.uid, - mailbox, newrecord.uid, - userid); - if (r) goto done; + mailbox_append_index_record(mailbox, &record); if (verbose) printf("Unexpunged %s: %u => %u\n", - mboxname, record.uid, newrecord.uid); - - /* mark the old one unlinked so we don't see it again */ - record.system_flags |= FLAG_UNLINKED | FLAG_NEEDS_CLEANUP; - r = mailbox_rewrite_index_record(mailbox, &record); - if (r) goto done; + mboxname, olduid, record.uid); (*numrestored)++; } - /* better get that seen to */ - if (*numrestored) - mailbox->i.options |= OPT_MAILBOX_NEEDS_UNLINK; - -done: - return r; + return 0; } int main(int argc, char *argv) @@ -292,7 +261,7 @@ fatal("must run as the Cyrus user", EC_USAGE); } - while ((opt = getopt(argc, argv, "C:laudt:f:v")) != EOF) { + while ((opt = getopt(argc, argv, "C:laudt:v")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; @@ -343,10 +312,6 @@ unsetdeleted = 1; break; - case 'f': - addflag = optarg; - break; - case 'v': verbose = 1; break; @@ -372,11 +337,6 @@ sync_log_init(); - if (addflag && addflag0 == '\\') { - syslog(LOG_ERR, "can't set a system flag"); - fatal("can't set a system flag", EC_SOFTWARE); - } - /* Set namespace -- force standard (internal) */ if ((r = mboxname_init_namespace(&unex_namespace, 1)) != 0) { syslog(LOG_ERR, "%s", error_message(r));
View file
cyrus-imapd-2.5.tar.gz/imap/upgrade_index.c
Changed
@@ -126,7 +126,7 @@ /* CRC failed, drop through */ } - fname = mailbox_record_fname(mailbox, record); + fname = mailbox_message_fname(mailbox, record->uid); if (message_parse(fname, record)) { /* failed to create, don't try to write */ @@ -198,7 +198,7 @@ * something isn't correctly mounted. We don't want to wipe out * all the index records due to IOERRORs just because the admin * made a temporary mistake */ - datadirname = mailbox_datapath(mailbox); + datadirname = mailbox_message_fname(mailbox, 0); if (stat(datadirname, &sbuf)) { syslog(LOG_ERR, "IOERROR: unable to find data directory %s " "for mailbox %s, refusing to upgrade", @@ -232,10 +232,9 @@ /* upgrade other fields as necessary */ if (!mailbox->i.highestmodseq) - mailbox->i.highestmodseq = mboxname_nextmodseq(mailbox->name, 0); + mailbox->i.highestmodseq = 1; if (!mailbox->i.uidvalidity) - mailbox->i.uidvalidity = mboxname_nextuidvalidity(mailbox->name, - time(NULL)); + mailbox->i.uidvalidity = time(0); /* minor version wasn't updated religiously in the early days, * so we need to use the old offset instead */
View file
cyrus-imapd-2.5.tar.gz/imap/user.c
Changed
@@ -77,7 +77,6 @@ #include "mboxname.h" #include "proc.h" #include "quota.h" -#include "search_engines.h" #include "seen.h" #include "user.h" #include "util.h" @@ -205,36 +204,6 @@ /* delete sieve scripts */ user_deletesieve(userid); - /* NOTE: even if conversations aren't enabled, we want to clean up */ - - /* delete conversations file */ - fname = conversations_getuserpath(userid); - (void) unlink(fname); - free(fname); - - /* delete highestmodseq file */ - fname = user_hash_meta(userid, "modseq"); - (void) unlink(fname); - free(fname); - - /* XXX: one could make an argument for keeping the UIDVALIDITY - * file forever, so that UIDVALIDITY never gets reused. */ - - /* delete uidvalidity file */ - fname = user_hash_meta(userid, "uidvalidity"); - (void) unlink(fname); - free(fname); - - /* delete all the search engine data (if any) */ - search_deluser(userid); - -#ifdef WITH_DAV - /* delete all the calendar alarms for the user */ - struct caldav_alarm_db *alarmdb = caldav_alarm_open(); - caldav_alarm_delete_user(alarmdb, userid); - caldav_alarm_close(alarmdb); -#endif //WITH_DAV - proc_killuser(userid); return 0; @@ -397,7 +366,7 @@ /* move sieve scripts */ user_renamesieve(olduser, newuser); } - + return r; } @@ -506,7 +475,7 @@ return r; } -EXPORTED char *user_hash_meta(const char *userid, const char *suffix) +static char *user_hash_meta(const char *userid, const char *suffix) { struct mboxname_parts parts; const char *domain;
View file
cyrus-imapd-2.5.tar.gz/imap/user.h
Changed
@@ -74,7 +74,4 @@ /* find the subscriptions file for user */ char *user_hash_subs(const char *user); -/* find any sort of file for the user */ -char *user_hash_meta(const char *userid, const char *suffix); - #endif
View file
cyrus-imapd-2.5.tar.gz/imap/xapian_wrap.cpp
Deleted
@@ -1,534 +0,0 @@ - -#include <config.h> -#include <sys/types.h> -#include <syslog.h> - -extern "C" { -#include "imap_err.h" -#include "xmalloc.h" -#include "xapian_wrap.h" -}; - -#include <xapian.h> - -#define SLOT_CYRUSID 0 - -/* ====================================================================== */ - -void xapian_init(void) -{ - static int init = 0; - static /* NOT const */ char enable_ngrams = "XAPIAN_CJK_NGRAM=1"; - - if (!init) { - putenv(enable_ngrams); - init = 1; - } -} - -/* ====================================================================== */ - -int xapian_compact_dbs(const char *dest, const char **sources) -{ - int r = 0; - - try { - Xapian::Compactor *c = new Xapian::Compactor; - - while (*sources) { - c->add_source(*sources++); - } - - c->set_destdir(dest); - - /* we never write to compresion targets again */ - c->set_compaction_level(Xapian::Compactor::FULLER); - - c->set_multipass(true); - - c->compact(); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - - return r; -} - -/* ====================================================================== */ - -struct xapian_dbw -{ - Xapian::WritableDatabase *database; - Xapian::Stem *stemmer; - Xapian::TermGenerator *term_generator; - Xapian::Document *document; -}; - -int xapian_dbw_open(const char *path, xapian_dbw_t **dbwp) -{ - xapian_dbw_t *dbw = (xapian_dbw_t *)xzmalloc(sizeof(xapian_dbw_t)); - int r = 0; - - try { - int action = Xapian::DB_CREATE_OR_OPEN; - dbw->database = new Xapian::WritableDatabase(path, action); - dbw->term_generator = new Xapian::TermGenerator(); - dbw->stemmer = new Xapian::Stem("en"); - dbw->term_generator->set_stemmer(*dbw->stemmer); - dbw->term_generator->set_stemming_strategy(Xapian::TermGenerator::STEM_ALL); - } - catch (const Xapian::DatabaseLockError &err) { - /* somebody else is already indexing this user. They may be doing a different - * mailbox, so we need to re-insert this mailbox into the queue! */ - r = IMAP_MAILBOX_LOCKED; - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - - if (r) - xapian_dbw_close(dbw); - else - *dbwp = dbw; - - return r; -} - -void xapian_dbw_close(xapian_dbw_t *dbw) -{ - if (!dbw) return; - try { - delete dbw->database; - delete dbw->term_generator; - delete dbw->stemmer; - delete dbw->document; - free(dbw); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - } -} - - -int xapian_dbw_begin_txn(xapian_dbw_t *dbw) -{ - int r = 0; - try { - dbw->database->begin_transaction(); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - return r; -} - -int xapian_dbw_commit_txn(xapian_dbw_t *dbw) -{ - int r = 0; - try { - dbw->database->commit_transaction(); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - return r; -} - -int xapian_dbw_cancel_txn(xapian_dbw_t *dbw) -{ - int r = 0; - try { - dbw->database->cancel_transaction(); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - return r; -} - -int xapian_dbw_begin_doc(xapian_dbw_t *dbw, const char *cyrusid) -{ - int r = 0; - try { - if (dbw->document) { - delete dbw->document; - dbw->document = 0; - } - dbw->document = new Xapian::Document(); - dbw->document->add_value(SLOT_CYRUSID, cyrusid); - dbw->term_generator->set_document(*dbw->document); - dbw->term_generator->set_termpos(1); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - return r; -} - -int xapian_dbw_doc_part(xapian_dbw_t *dbw, const struct buf *part, const char *prefix) -{ - int r = 0; - try { - dbw->term_generator->index_text(Xapian::Utf8Iterator(part->s, part->len), 1, prefix); - dbw->term_generator->increase_termpos(); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - return r; -} - -int xapian_dbw_end_doc(xapian_dbw_t *dbw) -{ - int r = 0; - try { - dbw->database->add_document(*dbw->document); - delete dbw->document; - dbw->document = 0; - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - return r; -} - -/* ====================================================================== */ - -struct xapian_db -{ - std::string *paths; - Xapian::Database *database; - Xapian::Stem *stemmer; - Xapian::QueryParser *parser; -}; - -int xapian_db_open(const char **paths, xapian_db_t **dbp) -{ - xapian_db_t *db = (xapian_db_t *)xzmalloc(sizeof(xapian_db_t)); - const char *thispath = "(unknown)"; - int r = 0; - - try { - db->paths = new std::string(); - db->database = new Xapian::Database(); - while (*paths) { - thispath = *paths++; - db->database->add_database(Xapian::Database(thispath)); - db->paths->append(thispath); - db->paths->append(" "); - thispath = "(unknown)"; - } - db->stemmer = new Xapian::Stem("en"); - db->parser = new Xapian::QueryParser; - db->parser->set_stemming_strategy(Xapian::QueryParser::STEM_ALL); - db->parser->set_stemmer(*db->stemmer); - db->parser->set_default_op(Xapian::Query::OP_AND); - db->parser->set_database(*db->database); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - thispath, err.get_description().c_str()); - r = IMAP_IOERROR; - } - - if (r) - xapian_db_close(db); - else - *dbp = db; - - return r; -} - -void xapian_db_close(xapian_db_t *db) -{ - try { - delete db->database; - delete db->stemmer; - delete db->parser; - delete db->paths; - free(db); - } - catch (const Xapian::Error &err) { - /* XXX - memory leak? */ - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - } -} - -xapian_query_t *xapian_query_new_match(const xapian_db_t *db, const char *prefix, const char *str) -{ - try { - // We don't use FLAG_BOOLEAN because Cyrus is doing boolean for us - // TODO: FLAG_AUTO_SYNONYMS - // quote the query for phrase management - std::string quoted = std::string("\"") + str + "\""; - Xapian::Query query = db->parser->parse_query( - quoted, - (Xapian::QueryParser::FLAG_PHRASE | - Xapian::QueryParser::FLAG_LOVEHATE | - Xapian::QueryParser::FLAG_WILDCARD), - std::string(prefix)); - return (xapian_query_t *)new Xapian::Query(query); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - return 0; - } -} - -xapian_query_t *xapian_query_new_compound(const xapian_db_t *db __attribute__((unused)), - int is_or, xapian_query_t **children, int n) -{ - try { - // I want to use std::initializer_list<Xapian::Query*> here - // but that requires "experimental" gcc C++0x support :( - std::vector<Xapian::Query*> v; - for (int i = 0 ; i < n ; i++) - v.push_back((Xapian::Query *)childreni); - Xapian::Query *compound = new Xapian::Query( - is_or ? Xapian::Query::OP_OR : Xapian::Query::OP_AND, - v.begin(), v.end()); - // 'compound' owns a refcount on each child. We need to - // drop the one we got when we allocated the children - for (int i = 0 ; i < n ; i++) - delete (Xapian::Query *)childreni; - return (xapian_query_t *)compound; - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - return 0; - } -} - -/* Xapian does not have an OP_NOT. WTF? We fake it with - * OP_AND_NOT where the left child is MatchAll */ -xapian_query_t *xapian_query_new_not(const xapian_db_t *db __attribute__((unused)), - xapian_query_t *child) -{ - try { - Xapian::Query *qq = new Xapian::Query( - Xapian::Query::OP_AND_NOT, - Xapian::Query::MatchAll, - *(Xapian::Query *)child); - // 'compound' owns a refcount on each child. We need to - // drop the one we got when we allocated the children - delete (Xapian::Query *)child; - return (xapian_query_t *)qq; - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - return 0; - } -} - -void xapian_query_free(xapian_query_t *qq) -{ - try { - Xapian::Query *query = (Xapian::Query *)qq; - delete query; - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - } -} - -int xapian_query_run(const xapian_db_t *db, const xapian_query_t *qq, - int (*cb)(const char *cyrusid, void *rock), void *rock) -{ - const Xapian::Query *query = (const Xapian::Query *)qq; - int r = 0; - - try { - Xapian::Enquire enquire(*db->database); - enquire.set_query(*query); - Xapian::MSet matches = enquire.get_mset(0, db->database->get_doccount()); - for (Xapian::MSetIterator i = matches.begin() ; i != matches.end() ; ++i) { - Xapian::Document d = i.get_document(); - std::string cyrusid = d.get_value(SLOT_CYRUSID); - /* ignore documents with no cyrusid. Shouldn't happen, but has been seen */ - if (cyrusid.length() == 0) { - syslog(LOG_ERR, "IOERROR: Xapian: zero length cyrusid for document id %u in index files %s", - d.get_docid(), db->paths->c_str()); - continue; - } - r = cb(cyrusid.c_str(), rock); - if (r) break; - } - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - - return r; -} - -struct xapian_snipgen -{ - Xapian::Stem *stemmer; - Xapian::SnippetGenerator *snippet_generator; -}; - -xapian_snipgen_t *xapian_snipgen_new(void) -{ - xapian_snipgen_t *snipgen = NULL; - - try { - snipgen = (xapian_snipgen_t *)xzmalloc(sizeof(xapian_snipgen_t)); - - snipgen->stemmer = new Xapian::Stem("en"); - snipgen->snippet_generator = new Xapian::SnippetGenerator; - snipgen->snippet_generator->set_stemmer(*snipgen->stemmer); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - } - - return snipgen; -} - -void xapian_snipgen_free(xapian_snipgen_t *snipgen) -{ - try { - delete snipgen->snippet_generator; - delete snipgen->stemmer; - free(snipgen); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - } -} - -int xapian_snipgen_add_match(xapian_snipgen_t *snipgen, const char *match) -{ - int r = 0; - - try { - snipgen->snippet_generator->add_match(match); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - - return r; -} - -int xapian_snipgen_begin_doc(xapian_snipgen_t *snipgen, unsigned int context_length) -{ - int r = 0; - - try { - snipgen->snippet_generator->reset(); - snipgen->snippet_generator->set_context_length(context_length); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - - return r; -} - -int xapian_snipgen_doc_part(xapian_snipgen_t *snipgen, const struct buf *part) -{ - int r = 0; - - try { - snipgen->snippet_generator->accept_text(Xapian::Utf8Iterator(part->s, part->len)); - snipgen->snippet_generator->increase_termpos(); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - - return r; -} - -int xapian_snipgen_end_doc(xapian_snipgen_t *snipgen, struct buf *buf) -{ - int r = 0; - - try { - buf_reset(buf); - buf_appendcstr(buf, snipgen->snippet_generator->get_snippets().c_str()); - buf_cstring(buf); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s: %s", - err.get_context().c_str(), err.get_description().c_str()); - r = IMAP_IOERROR; - } - - return r; -} - -/* cb returns true if document should be copied, false if not */ -int xapian_filter(const char *dest, const char **sources, - int (*cb)(const char *cyrusid, void *rock), - void *rock) -{ - int r = 0; - int count = 0; - - try { - /* create a destination database */ - Xapian::WritableDatabase destdb = Xapian::WritableDatabase(dest, Xapian::DB_CREATE); - - /* With multiple databases as above, the docids are interleaved, so it - * might be worth trying to open each source and copy its documents to - * destdb in turn for better locality of reference, and so better cache - * use. -- Olly on the mailing list */ - while (*sources) { - Xapian::Database srcdb = Xapian::Database(*sources++); - - /* copy all matching documents to the new DB */ - for (Xapian::ValueIterator it = srcdb.valuestream_begin(SLOT_CYRUSID); - it != srcdb.valuestream_end(SLOT_CYRUSID); it++) { - if (cb((*it).c_str(), rock)) { - destdb.add_document(srcdb.get_document(it.get_docid())); - } - } - } - - /* commit all changes explicitly */ - destdb.commit(); - } - catch (const Xapian::Error &err) { - syslog(LOG_ERR, "IOERROR: Xapian: caught exception: %s", - err.get_description().c_str()); - r = IMAP_IOERROR; - } - - return r; -} -
View file
cyrus-imapd-2.5.tar.gz/imap/xapian_wrap.h
Deleted
@@ -1,91 +0,0 @@ -/* xapian_wrap.h -- C++ hiding wrapper API for Xapian - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __CYRUS_IMAP_XAPIAN_WRAP__ -#define __CYRUS_IMAP_XAPIAN_WRAP__ - -#include "util.h" - -typedef struct xapian_dbw xapian_dbw_t; -typedef struct xapian_db xapian_db_t; -typedef struct xapian_query xapian_query_t; -typedef struct xapian_snipgen xapian_snipgen_t; - -extern void xapian_init(void); - -/* compaction interface */ -extern int xapian_compact_dbs(const char *dest, const char **sources); - -/* write-side interface */ -extern int xapian_dbw_open(const char *path, xapian_dbw_t **dbwp); -extern void xapian_dbw_close(xapian_dbw_t *dbw); -extern int xapian_dbw_begin_txn(xapian_dbw_t *dbw); -extern int xapian_dbw_commit_txn(xapian_dbw_t *dbw); -extern int xapian_dbw_cancel_txn(xapian_dbw_t *dbw); -extern int xapian_dbw_begin_doc(xapian_dbw_t *dbw, const char *cyrusid); -extern int xapian_dbw_doc_part(xapian_dbw_t *dbw, const struct buf *part, const char *prefix); -extern int xapian_dbw_end_doc(xapian_dbw_t *dbw); - -/* query-side interface */ -extern int xapian_db_open(const char **paths, xapian_db_t **dbp); -extern void xapian_db_close(xapian_db_t *); -extern xapian_query_t *xapian_query_new_match(const xapian_db_t *, const char *prefix, const char *term); -extern xapian_query_t *xapian_query_new_compound(const xapian_db_t *, int is_or, xapian_query_t **children, int n); -extern xapian_query_t *xapian_query_new_not(const xapian_db_t *, xapian_query_t *); -extern void xapian_query_free(xapian_query_t *); -extern int xapian_query_run(const xapian_db_t *, const xapian_query_t *, - int (*cb)(const char *cyrusid, void *rock), void *rock); - -/* snippets interface */ -extern xapian_snipgen_t *xapian_snipgen_new(void); -extern void xapian_snipgen_free(xapian_snipgen_t *); -extern int xapian_snipgen_add_match(xapian_snipgen_t *snipgen, const char *match); -extern int xapian_snipgen_begin_doc(xapian_snipgen_t *snipgen, unsigned context_length); -extern int xapian_snipgen_doc_part(xapian_snipgen_t *snipgen, const struct buf *part); -extern int xapian_snipgen_end_doc(xapian_snipgen_t *snipgen, struct buf *); - -/* filter interface */ -extern int xapian_filter(const char *dest, const char **sources, - int (*cb)(const char *cyrusid, void *rock), - void *rock); - -#endif
View file
cyrus-imapd-2.5.tar.gz/imap/xcal.c
Deleted
@@ -1,1205 +0,0 @@ -/* xcal.c -- Routines for converting iCalendar to/from xCal - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#include <stdio.h> /* for snprintf() */ -#include <stddef.h> /* for offsetof() macro */ -#include <syslog.h> - -#include <libxml/tree.h> - -#include "httpd.h" -#include "util.h" -#include "version.h" -#include "xcal.h" - - -extern icalvalue_kind icalproperty_kind_to_value_kind(icalproperty_kind kind); -extern const char* icalrecur_freq_to_string(icalrecurrencetype_frequency kind); -extern const char* icalrecur_weekday_to_string(icalrecurrencetype_weekday kind); - - -/* - * Determine the type (kind) of an iCalendar property value. - */ -const char *icalproperty_value_kind_as_string(icalproperty *prop) -{ - icalvalue_kind kind = ICAL_NO_VALUE; - icalparameter *val_param; - - val_param = icalproperty_get_first_parameter(prop, ICAL_VALUE_PARAMETER); - if (val_param) { - /* Use the kind specified in the VALUE param */ - kind = icalparameter_value_to_value_kind( - icalparameter_get_value(val_param)); - } - - if (kind == ICAL_NO_VALUE) { - icalvalue *value = icalproperty_get_value(prop); - - if (value) { - /* Use the kind determined from the property value */ - kind = icalvalue_isa(value); - } - } - - if (kind == ICAL_NO_VALUE) { - /* Use the default kind for the property */ - kind = icalproperty_kind_to_value_kind(icalproperty_isa(prop)); - } - - switch (kind) { - case ICAL_X_VALUE: - return "unknown"; - - case ICAL_ACTION_VALUE: - case ICAL_CARLEVEL_VALUE: - case ICAL_CLASS_VALUE: - case ICAL_CMD_VALUE: - case ICAL_METHOD_VALUE: - case ICAL_QUERYLEVEL_VALUE: - case ICAL_STATUS_VALUE: - case ICAL_TRANSP_VALUE: - return "text"; - - default: - return icalvalue_kind_to_string(kind); - } -} - - -/* - * Construct an ISO.8601.2004 string for an iCalendar Date/Date-Time. - */ -const char *icaltime_as_iso_string(const struct icaltimetype tt) -{ - static char str21; - const char *fmt; - - if (tt.is_date) fmt = "%04d-%02d-%02d"; - else if (tt.is_utc) fmt = "%04d-%02d-%02dT%02d:%02d:%02dZ"; - else fmt = "%04d-%02d-%02dT%02d:%02d:%02d"; - - snprintf(str, sizeof(str), fmt, tt.year, tt.month, tt.day, - tt.hour, tt.minute, tt.second); - - return str; -} - - -/* - * Construct an ISO.8601.2004 string for an iCalendar UTC Offset. - */ -const char *icalvalue_utcoffset_as_iso_string(const icalvalue* value) -{ - static char str10; - const char *fmt; - int off, h, m, s; - char sign; - - off = icalvalue_get_utcoffset(value); - - if (abs(off) == off) sign = '+'; - else sign = '-'; - - h = off/3600; - m = (off - (h*3600))/ 60; - s = (off - (h*3600) - (m*60)); - - if (s > 0) fmt = "%c%02d:%02d:%02d"; - else fmt = "%c%02d:%02d"; - - snprintf(str, sizeof(str), fmt, sign, abs(h), abs(m), abs(s)); - - return str; -} - - -static const struct { - const char *str; - int limit; - size_t offset; -} recurmap = -{ - { "bysecond", ICAL_BY_SECOND_SIZE, - offsetof(struct icalrecurrencetype, by_second) }, - { "byminute", ICAL_BY_MINUTE_SIZE, - offsetof(struct icalrecurrencetype, by_minute) }, - { "byhour", ICAL_BY_HOUR_SIZE, - offsetof(struct icalrecurrencetype, by_hour) }, - { "byday", ICAL_BY_DAY_SIZE, - offsetof(struct icalrecurrencetype, by_day) }, - { "bymonthday", ICAL_BY_MONTHDAY_SIZE, - offsetof(struct icalrecurrencetype, by_month_day) }, - { "byyearday", ICAL_BY_YEARDAY_SIZE, - offsetof(struct icalrecurrencetype, by_year_day) }, - { "byweekno", ICAL_BY_WEEKNO_SIZE, - offsetof(struct icalrecurrencetype, by_week_no) }, - { "bymonth", ICAL_BY_MONTH_SIZE, - offsetof(struct icalrecurrencetype, by_month) }, - { "bysetpos", ICAL_BY_SETPOS_SIZE, - offsetof(struct icalrecurrencetype, by_set_pos) }, - { 0, 0, 0 }, -}; - - -/* - * Add iCalendar recur-rule-parts to a structured element. - */ -void icalrecurrencetype_add_as_xxx(struct icalrecurrencetype *recur, void *obj, - void (*add_int)(void *, const char *, int), - void (*add_str)(void *, const char *, - const char *)) -{ - int i, j; - - if (recur->freq == ICAL_NO_RECURRENCE) return; - - add_str(obj, "freq", icalrecur_freq_to_string(recur->freq)); - - /* until and count are mutually exclusive */ - if (recur->until.year) { - add_str(obj, "until", icaltime_as_iso_string(recur->until)); - } - else if (recur->count) add_int(obj, "count", recur->count); - - if (recur->interval != 1) add_int(obj, "interval", recur->interval); - - /* Monday is the default, so no need to include it */ - if (recur->week_start != ICAL_MONDAY_WEEKDAY && - recur->week_start != ICAL_NO_WEEKDAY) { - const char *daystr; - - daystr = icalrecur_weekday_to_string( - icalrecurrencetype_day_day_of_week(recur->week_start)); - add_str(obj, "wkst", daystr); - } - - /* The BY* parameters can each take a list of values. - * - * Each of the lists is terminated with the value - * ICAL_RECURRENCE_ARRAY_MAX unless the the list is full. - */ - for (j = 0; recurmapj.str; j++) { - short *array = (short *)((size_t) recur + recurmapj.offset); - int limit = recurmapj.limit - 1; - - for (i = 0; i < limit && arrayi != ICAL_RECURRENCE_ARRAY_MAX; i++) { - if (j == 3) { /* BYDAY */ - const char *daystr; - int pos; - - daystr = icalrecur_weekday_to_string( - icalrecurrencetype_day_day_of_week(arrayi)); - pos = icalrecurrencetype_day_position(arrayi); - - if (pos != 0) { - char temp20; - - snprintf(temp, sizeof(temp), "%d%s", pos, daystr); - daystr = temp; - } - - add_str(obj, recurmapj.str, daystr); - } - else add_int(obj, recurmapj.str, arrayi); - } - } -} - - -/* - * Add an XML element for an iCalendar Period. - */ -static void icalperiodtype_add_as_xml_element(xmlNodePtr xtype, - struct icalperiodtype p) -{ - const char *start; - const char *end; - - start = icaltime_as_iso_string(p.start); - xmlNewTextChild(xtype, NULL, BAD_CAST "start", BAD_CAST start); - - if (!icaltime_is_null_time(p.end)) { - end = icaltime_as_iso_string(p.end); - xmlNewTextChild(xtype, NULL, BAD_CAST "end", BAD_CAST end); - } - else { - end = icaldurationtype_as_ical_string(p.duration); - xmlNewTextChild(xtype, NULL, BAD_CAST "duration", BAD_CAST end); - } -} - - -/* - * Add an iCalendar recur-rule-part to a XML recur element. - */ -static void icalrecur_add_int_as_xml_element(void *xrecur, const char *rpart, - int i) -{ - char ibuf20; - - snprintf(ibuf, sizeof(ibuf), "%d", i); - xmlNewTextChild((xmlNodePtr) xrecur, NULL, BAD_CAST rpart, BAD_CAST ibuf); -} - -static void icalrecur_add_string_as_xml_element(void *xrecur, const char *rpart, - const char *s) -{ - xmlNewTextChild((xmlNodePtr) xrecur, NULL, BAD_CAST rpart, BAD_CAST s); -} - - -/* - * Construct an XML element for an iCalendar parameter. - */ -static xmlNodePtr icalparameter_as_xml_element(icalparameter *param) -{ - icalparameter_kind kind; - icalparameter_value value; - const char *kind_string, *type_string, *value_string; - xmlNodePtr xparam; - - kind = icalparameter_isa(param); - - switch (kind) { - case ICAL_X_PARAMETER: - kind_string = icalparameter_get_xname(param); - break; - - case ICAL_IANA_PARAMETER: - kind_string = icalparameter_get_iana_name(param); - break; - - default: - kind_string = icalparameter_kind_to_string(kind); - if (kind_string) break; - - case ICAL_NO_PARAMETER: - case ICAL_ANY_PARAMETER: - icalerror_set_errno(ICAL_BADARG_ERROR); - return NULL; - } - - /* Get value type */ - switch (kind) { - case ICAL_ALTREP_PARAMETER: - case ICAL_DIR_PARAMETER: - type_string = "uri"; - break; - - case ICAL_DELEGATEDFROM_PARAMETER: - case ICAL_DELEGATEDTO_PARAMETER: - case ICAL_MEMBER_PARAMETER: - case ICAL_SENTBY_PARAMETER: - type_string = "cal-address"; - break; - - case ICAL_RSVP_PARAMETER: - type_string = "boolean"; - break; - - default: - type_string = "text"; - break; - } - - /* XXX Need to handle multi-valued parameters */ - value = icalparameter_get_value(param); - if (value == ICAL_VALUE_X) value_string = icalparameter_get_xvalue(param); - else value_string = icalparameter_enum_to_string(value); - if (!value_string) return NULL; - - xparam = xmlNewNode(NULL, BAD_CAST lcase(icalmemory_tmp_copy(kind_string))); - xmlNewTextChild(xparam, NULL, BAD_CAST type_string, BAD_CAST value_string); - - return xparam; -} - - -/* - * Add the proper XML element for an iCalendar value. - */ -static void icalproperty_add_value_as_xml_element(xmlNodePtr xprop, - icalproperty *prop) - -{ - const char *type, *str = NULL; - xmlNodePtr xtype; - const icalvalue *value; - char buf40; - - /* Add type */ - type = lcase(icalmemory_tmp_copy( - icalproperty_value_kind_as_string(prop))); - xtype = xmlNewChild(xprop, NULL, BAD_CAST type, NULL); - - - /* Add value */ - value = icalproperty_get_value(prop); - - switch (icalvalue_isa(value)) { - case ICAL_DATE_VALUE: - str = icaltime_as_iso_string(icalvalue_get_date(value)); - break; - - case ICAL_DATETIME_VALUE: - str = icaltime_as_iso_string(icalvalue_get_datetime(value)); - break; - - case ICAL_DATETIMEPERIOD_VALUE: { - struct icaldatetimeperiodtype dtp = - icalvalue_get_datetimeperiod(value); - - if (!icaltime_is_null_time(dtp.time)) { - str = icaltime_as_iso_string(dtp.time); - break; - } - else { - icalperiodtype_add_as_xml_element(xtype, dtp.period); - return; - } - } - - case ICAL_GEO_VALUE: { - struct icalgeotype geo = icalvalue_get_geo(value); - - snprintf(buf, sizeof(buf), "%f", geo.lat); - xmlNewTextChild(xtype, NULL, BAD_CAST "latitude", BAD_CAST buf); - snprintf(buf, sizeof(buf), "%f", geo.lon); - xmlNewTextChild(xtype, NULL, BAD_CAST "longitude", BAD_CAST buf); - return; - } - - case ICAL_PERIOD_VALUE: - icalperiodtype_add_as_xml_element(xtype, icalvalue_get_period(value)); - return; - - case ICAL_RECUR_VALUE: { - struct icalrecurrencetype recur = icalvalue_get_recur(value); - - icalrecurrencetype_add_as_xxx(&recur, xtype, - &icalrecur_add_int_as_xml_element, - &icalrecur_add_string_as_xml_element); - return; - } - - case ICAL_REQUESTSTATUS_VALUE: { - struct icalreqstattype stat = icalvalue_get_requeststatus(value); - - if (!stat.desc) stat.desc = icalenum_reqstat_desc(stat.code); - - snprintf(buf, sizeof(buf), "%u.%u", - icalenum_reqstat_major(stat.code), - icalenum_reqstat_minor(stat.code)); - xmlNewTextChild(xtype, NULL, BAD_CAST "code", BAD_CAST buf); - xmlNewTextChild(xtype, NULL, BAD_CAST "description", BAD_CAST stat.desc); - if (stat.debug) - xmlNewTextChild(xtype, NULL, BAD_CAST "data", BAD_CAST stat.debug); - - return; - } - - case ICAL_TRIGGER_VALUE: { - struct icaltriggertype trig = icalvalue_get_trigger(value); - - if (!icaltime_is_null_time(trig.time)) - str = icaltime_as_iso_string(trig.time); - else - str = icaldurationtype_as_ical_string(trig.duration); - break; - } - - case ICAL_UTCOFFSET_VALUE: - str = icalvalue_utcoffset_as_iso_string(value); - break; - - default: - str = icalvalue_as_ical_string(value); - break; - } - - if (str) xmlAddChild(xtype, xmlNewText(BAD_CAST str)); -} - - -/* - * Construct an XML element for an iCalendar property. - */ -static xmlNodePtr icalproperty_as_xml_element(icalproperty *prop) -{ - icalproperty_kind prop_kind; - const char *x_name, *property_name = NULL; - icalparameter *param; - xmlNodePtr xprop, xparams = NULL; - - if (!prop) return NULL; - - prop_kind = icalproperty_isa(prop); - x_name = icalproperty_get_x_name(prop); - - if (prop_kind == ICAL_X_PROPERTY && x_name) - property_name = x_name; - else - property_name = icalproperty_kind_to_string(prop_kind); - - if (!property_name) { - icalerror_warn("Got a property of an unknown kind."); - return NULL; - } - - /* Create property */ - xprop = xmlNewNode(NULL, - BAD_CAST lcase(icalmemory_tmp_copy(property_name))); - - - /* Add parameters */ - for (param = icalproperty_get_first_parameter(prop, ICAL_ANY_PARAMETER); - param != 0; - param = icalproperty_get_next_parameter(prop, ICAL_ANY_PARAMETER)) { - - if (icalparameter_isa(param) == ICAL_VALUE_PARAMETER) continue; - - if (!xparams) - xparams = xmlNewChild(xprop, NULL, BAD_CAST "parameters", NULL); - - xmlAddChild(xparams, icalparameter_as_xml_element(param)); - } - - - /* Add value */ - /* XXX Need to handle multi-valued properties */ - icalproperty_add_value_as_xml_element(xprop, prop); - - return xprop; -} - - -/* - * Construct a XML element for an iCalendar component. - */ -static xmlNodePtr icalcomponent_as_xml_element(icalcomponent *comp) -{ - icalcomponent *c; - icalproperty *p; - icalcomponent_kind kind; - const char* kind_string; - xmlNodePtr xcomp, xprops = NULL, xsubs = NULL; - - if (!comp) return NULL; - - kind = icalcomponent_isa(comp); - switch (kind) { - case ICAL_NO_COMPONENT: - return NULL; - break; - - case ICAL_X_COMPONENT: - kind_string = ""; //comp->x_name; - break; - - default: - kind_string = icalcomponent_kind_to_string(kind); - } - - - /* Create component */ - xcomp = xmlNewNode(NULL, - BAD_CAST lcase(icalmemory_tmp_copy(kind_string))); - - - /* Add properties */ - for (p = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY); - p; - p = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY)) { - - if (!xprops) - xprops = xmlNewChild(xcomp, NULL, BAD_CAST "properties", NULL); - - xmlAddChild(xprops, icalproperty_as_xml_element(p)); - } - - - /* Add sub-components */ - for (c = icalcomponent_get_first_component(comp, ICAL_ANY_COMPONENT); - c; - c = icalcomponent_get_next_component(comp, ICAL_ANY_COMPONENT)) { - - if (!xsubs) - xsubs = xmlNewChild(xcomp, NULL, BAD_CAST "components", NULL); - - xmlAddChild(xsubs, icalcomponent_as_xml_element(c)); - } - - return xcomp; -} - - -/* - * Construct a xcal string for an iCalendar component. - */ -char *icalcomponent_as_xcal_string(icalcomponent *ical) -{ - xmlDocPtr doc; - xmlNodePtr root, xcomp; - xmlChar *buf; - int bufsiz; - - if (!ical) return NULL; - - doc = xmlNewDoc(BAD_CAST "1.0"); - root = xmlNewNode(NULL, BAD_CAST "icalendar"); - xmlNewNs(root, BAD_CAST XML_NS_ICALENDAR, NULL); - xmlDocSetRootElement(doc, root); - - xcomp = icalcomponent_as_xml_element(ical); - - xmlAddChild(root, xcomp); - - if (!xmlStrcmp(xcomp->name, BAD_CAST "vcalendar")) { - /* Complete iCalendar stream */ - xmlDocDumpFormatMemoryEnc(doc, &buf, &bufsiz, "utf-8", - config_httpprettytelemetry); - } - else { - /* Single iCalendar object */ - xmlBufferPtr xbuf = xmlBufferCreate(); - - bufsiz = xmlNodeDump(xbuf, doc, xcomp, - 0, config_httpprettytelemetry); - buf = xmlBufferDetach(xbuf); - xmlBufferFree(xbuf); - } - - xmlFreeDoc(doc); - - return (char *) buf; -} - - -/* Add an iCalendar recurrence rule part to icalrecurrencetype. - * - * XXX The following structure is opaque libical, but for some stupid - * reason the icalrecur_add_by*rules() functions require it even though - * all they use is the rt field. MUST keep this in sync with icalrecur.c - */ -struct icalrecur_parser { - const char* rule; - char* copy; - char* this_clause; - char* next_clause; - - struct icalrecurrencetype rt; -}; - -extern icalrecurrencetype_frequency icalrecur_string_to_freq(const char* str); -extern void icalrecur_add_byrules(struct icalrecur_parser *parser, short *array, - int size, char* vals); -extern void icalrecur_add_bydayrules(struct icalrecur_parser *parser, - const char* vals); - -struct icalrecurrencetype *icalrecur_add_rule(struct icalrecurrencetype **rt, - const char *rpart, void *data, - int (*get_int)(void *), - const char* (*get_str)(void *)) -{ - static struct icalrecur_parser parser; - - if (!*rt) { - /* Initialize */ - *rt = &parser.rt; - icalrecurrencetype_clear(*rt); - } - - if (!strcmp(rpart, "freq")) { - (*rt)->freq = icalrecur_string_to_freq(get_str(data)); - } - else if (!strcmp(rpart, "count")) { - (*rt)->count = get_int(data); - } - else if (!strcmp(rpart, "until")) { - (*rt)->until = icaltime_from_string(get_str(data)); - } - else if (!strcmp(rpart, "interval")) { - (*rt)->interval = get_int(data); - if ((*rt)->interval < 1) (*rt)->interval = 1; /* MUST be >= 1 */ - } - else if (!strcmp(rpart, "wkst")) { - (*rt)->week_start = icalrecur_string_to_weekday(get_str(data)); - } - else if (!strcmp(rpart, "byday")) { - icalrecur_add_bydayrules(&parser, get_str(data)); - } - else { - int i; - - for (i = 0; recurmapi.str && strcmp(rpart, recurmapi.str); i++); - - if (recurmapi.str) { - short *array = - (short *)((size_t) *rt + recurmapi.offset); - int limit = recurmapi.limit; - - icalrecur_add_byrules(&parser, array, limit, - icalmemory_tmp_copy(get_str(data))); - } - else { - syslog(LOG_WARNING, "Unknown recurrence rule-part: %s", rpart); - icalrecurrencetype_clear(*rt); - *rt = NULL; - } - } - - return *rt; -} - - -int xmlElementContent_to_int(void *content) -{ - return atoi((const char *) content); -} - -const char *xmlElementContent_to_str(void *content) -{ - return (const char *) content; -} - - -/* - * Construct an iCalendar property value from XML content. - */ -static icalvalue *xml_element_to_icalvalue(xmlNodePtr xtype, - icalvalue_kind kind) -{ - icalvalue *value = NULL; - xmlNodePtr node; - xmlChar *content = NULL; - - switch (kind) { - - case ICAL_GEO_VALUE: { - struct icalgeotype geo; - - node = xmlFirstElementChild(xtype); - if (!node) { - syslog(LOG_WARNING, "Missing <latitude> XML element"); - break; - } - else if (xmlStrcmp(node->name, BAD_CAST "latitude")) { - syslog(LOG_WARNING, - "Expected <latitude> XML element, received %s", node->name); - break; - } - - content = xmlNodeGetContent(node); - geo.lat = atof((const char *) content); - - node = xmlNextElementSibling(node); - if (!node) { - syslog(LOG_WARNING, "Missing <longitude> XML element"); - break; - } - else if (xmlStrcmp(node->name, BAD_CAST "longitude")) { - syslog(LOG_WARNING, - "Expected <longitude> XML element, received %s", node->name); - break; - } - - xmlFree(content); - content = xmlNodeGetContent(node); - geo.lon = atof((const char *) content); - - value = icalvalue_new_geo(geo); - - break; - } - - case ICAL_PERIOD_VALUE: { - struct icalperiodtype p; - - p.start = p.end = icaltime_null_time(); - p.duration = icaldurationtype_from_int(0); - - node = xmlFirstElementChild(xtype); - if (!node) { - syslog(LOG_WARNING, "Missing <start> XML element"); - break; - } - else if (xmlStrcmp(node->name, BAD_CAST "start")) { - syslog(LOG_WARNING, - "Expected <start> XML element, received %s", node->name); - break; - } - - content = xmlNodeGetContent(node); - p.start = icaltime_from_string((const char *) content); - if (icaltime_is_null_time(p.start)) break; - - node = xmlNextElementSibling(node); - if (!node) { - syslog(LOG_WARNING, "Missing <end> / <duration> XML element"); - break; - } - else if (!xmlStrcmp(node->name, BAD_CAST "end")) { - xmlFree(content); - content = xmlNodeGetContent(node); - p.end = icaltime_from_string((const char *) content); - if (icaltime_is_null_time(p.end)) break; - } - else if (!xmlStrcmp(node->name, BAD_CAST "duration")) { - xmlFree(content); - content = xmlNodeGetContent(node); - p.duration = icaldurationtype_from_string((const char *) content); - if (icaldurationtype_as_int(p.duration) == 0) break; - } - else { - syslog(LOG_WARNING, - "Expected <end> / <duration> XML element, received %s", - node->name); - break; - } - - value = icalvalue_new_period(p); - - break; - } - - case ICAL_RECUR_VALUE: { - struct icalrecurrencetype *rt = NULL; - - for (node = xmlFirstElementChild(xtype); node; - node = xmlNextElementSibling(node)) { - - content = xmlNodeGetContent(node); - rt = icalrecur_add_rule(&rt, (const char *) node->name, content, - &xmlElementContent_to_int, - &xmlElementContent_to_str); - xmlFree(content); - content = NULL; - if (!rt) break; - } - - if (rt && rt->freq != ICAL_NO_RECURRENCE) - value = icalvalue_new_recur(*rt); - - break; - } - - case ICAL_REQUESTSTATUS_VALUE: { - struct icalreqstattype rst = { ICAL_UNKNOWN_STATUS, NULL, NULL }; - short maj, min; - - node = xmlFirstElementChild(xtype); - if (!node) { - syslog(LOG_WARNING, "Missing <code> XML element"); - break; - } - else if (xmlStrcmp(node->name, BAD_CAST "code")) { - syslog(LOG_WARNING, - "Expected <code> XML element, received %s", node->name); - break; - } - - content = xmlNodeGetContent(node); - if (sscanf((const char *) content, "%hd.%hd", &maj, &min) == 2) { - rst.code = icalenum_num_to_reqstat(maj, min); - } - if (rst.code == ICAL_UNKNOWN_STATUS) { - syslog(LOG_WARNING, "Unknown request-status code"); - break; - } - - node = xmlNextElementSibling(node); - if (!node) { - syslog(LOG_WARNING, "Missing <description> XML element"); - break; - } - else if (xmlStrcmp(node->name, BAD_CAST "description")) { - syslog(LOG_WARNING, - "Expected <description> XML element, received %s", - node->name); - break; - } - - xmlFree(content); - content = xmlNodeGetContent(node); - rst.desc = (const char *) content; - - node = xmlNextElementSibling(node); - if (node) { - if (xmlStrcmp(node->name, BAD_CAST "data")) { - syslog(LOG_WARNING, - "Expected <data> XML element, received %s", node->name); - break; - } - - xmlFree(content); - content = xmlNodeGetContent(node); - rst.debug = (const char *) content; - } - - value = icalvalue_new_requeststatus(rst); - break; - } - - case ICAL_UTCOFFSET_VALUE: { - int n, utcoffset, hours, minutes, seconds = 0; - char sign; - - content = xmlNodeGetContent(xtype); - n = sscanf((const char *) content, "%c%02d:%02d:%02d", - &sign, &hours, &minutes, &seconds); - - if (n < 3) { - syslog(LOG_WARNING, "Unexpected utc-offset format"); - break; - } - - utcoffset = hours*3600 + minutes*60 + seconds; - - if (sign == '-') utcoffset = -utcoffset; - - value = icalvalue_new_utcoffset(utcoffset); - break; - } - - default: - content = xmlNodeGetContent(xtype); - value = icalvalue_new_from_string(kind, (const char *) content); - break; - } - - if (content) xmlFree(content); - - return value; -} - - -/* - * Construct an iCalendar property from a XML element. - */ -static icalproperty *xml_element_to_icalproperty(xmlNodePtr xprop) -{ - const char *propname, *typestr; - icalproperty_kind kind; - icalproperty *prop = NULL; - icalvalue_kind valkind; - icalvalue *value; - xmlNodePtr node; - - /* Get the property type */ - propname = ucase(icalmemory_tmp_copy((const char *) xprop->name)); - kind = icalenum_string_to_property_kind(propname); - if (kind == ICAL_NO_PROPERTY) { - syslog(LOG_WARNING, "Unknown xCal property type: %s", propname); - return NULL; - } - - /* Create new property */ - prop = icalproperty_new(kind); - if (!prop) { - syslog(LOG_ERR, "Creation of new %s property failed", propname); - return NULL; - } - if (kind == ICAL_X_PROPERTY) icalproperty_set_x_name(prop, propname); - - - /* Add parameters */ - node = xmlFirstElementChild(xprop); - if (node && !xmlStrcmp(node->name, BAD_CAST "parameters")) { - xmlNodePtr xparam; - - for (xparam = xmlFirstElementChild(node); xparam; - xparam = xmlNextElementSibling(xparam)) { - char *paramname = - ucase(icalmemory_tmp_copy((const char *) xparam->name)); - xmlChar *paramval = xmlNodeGetContent(xmlFirstElementChild(xparam)); - - /* XXX Need to handle multi-valued parameters */ - icalproperty_set_parameter_from_string(prop, paramname, - (const char *) paramval); - - xmlFree(paramval); - } - - node = xmlNextElementSibling(node); - } - - /* Get the value type */ - if (!node) { - syslog(LOG_WARNING, "Missing xCal value for %s property", - propname); - return NULL; - } - typestr = ucase(icalmemory_tmp_copy((const char *) node->name)); - valkind = !strcmp(typestr, "UNKNOWN") ? ICAL_X_VALUE : - icalenum_string_to_value_kind(typestr); - if (valkind == ICAL_NO_VALUE) { - syslog(LOG_WARNING, "Unknown xCal value type for %s property: %s", - propname, typestr); - return NULL; - } - else if (valkind == ICAL_TEXT_VALUE) { - /* "text" also includes enumerated types - grab type from property */ - valkind = icalproperty_kind_to_value_kind(kind); - } - - - /* Add value */ - /* XXX Need to handle multi-valued properties */ - value = xml_element_to_icalvalue(node, valkind); - if (!value) { - syslog(LOG_ERR, "Parsing %s property value failed", propname); - goto error; - } - - icalproperty_set_value(prop, value); - - - /* Sanity check */ - if ((node = xmlNextElementSibling(node))) { - syslog(LOG_WARNING, - "Unexpected XML element in property: %s", node->name); - goto error; - } - - return prop; - - error: - icalproperty_free(prop); - return NULL; -} - - -/* - * Construct an iCalendar component from a XML element. - */ -static icalcomponent *xml_element_to_icalcomponent(xmlNodePtr xcomp) -{ - icalcomponent_kind kind; - icalcomponent *comp = NULL; - xmlNodePtr node, xprop, xsub; - - if (!xcomp) return NULL; - - /* Get component type */ - kind = - icalenum_string_to_component_kind( - ucase(icalmemory_tmp_copy((const char *) xcomp->name))); - if (kind == ICAL_NO_COMPONENT) { - syslog(LOG_WARNING, "Unknown xCal component type: %s", xcomp->name); - return NULL; - } - - /* Create new component */ - comp = icalcomponent_new(kind); - if (!comp) { - syslog(LOG_ERR, "Creation of new %s component failed", xcomp->name); - return NULL; - } - - /* Add properties */ - node = xmlFirstElementChild(xcomp); - if (!node || xmlStrcmp(node->name, BAD_CAST "properties")) { - syslog(LOG_WARNING, - "Expected <properties> XML element, received %s", node->name); - goto error; - } - for (xprop = xmlFirstElementChild(node); xprop; - xprop = xmlNextElementSibling(xprop)) { - icalproperty *prop = xml_element_to_icalproperty(xprop); - - if (!prop) goto error; - - icalcomponent_add_property(comp, prop); - } - - /* Add sub-components */ - if (!(node = xmlNextElementSibling(node))) return comp; - - if (xmlStrcmp(node->name, BAD_CAST "components")) { - syslog(LOG_WARNING, - "Expected <components> XML element, received %s", node->name); - goto error; - } - - for (xsub = xmlFirstElementChild(node); xsub; - xsub = xmlNextElementSibling(xsub)) { - icalcomponent *sub = xml_element_to_icalcomponent(xsub); - - if (!sub) goto error; - - icalcomponent_add_component(comp, sub); - } - - /* Sanity check */ - if ((node = xmlNextElementSibling(node))) { - syslog(LOG_WARNING, - "Unexpected XML element in component: %s", node->name); - goto error; - } - - return comp; - - error: - icalcomponent_free(comp); - return NULL; -} - - -/* - * Construct an iCalendar component from an xCal string. - */ -icalcomponent *xcal_string_as_icalcomponent(const char *str) -{ - xmlParserCtxtPtr ctxt; - xmlDocPtr doc = NULL; - xmlNodePtr root; - icalcomponent *ical = NULL; - - if (!str) return NULL; - - /* Parse the XML request */ - ctxt = xmlNewParserCtxt(); - if (ctxt) { - doc = xmlCtxtReadMemory(ctxt, str, strlen(str), NULL, NULL, - XML_PARSE_NOWARNING); - xmlFreeParserCtxt(ctxt); - } - if (!doc) { - syslog(LOG_WARNING, "XML parse error"); - return NULL; - } - - /* Get root element */ - if (!(root = xmlDocGetRootElement(doc)) || - xmlStrcmp(root->name, BAD_CAST "icalendar") || - xmlStrcmp(root->ns->href, BAD_CAST XML_NS_ICALENDAR)) { - syslog(LOG_WARNING, - "XML root element is not %s:icalendar", XML_NS_ICALENDAR); - goto done; - } - - ical = xml_element_to_icalcomponent(xmlFirstElementChild(root)); - - done: - xmlFreeDoc(doc); - - return ical; -} - - -const char *begin_xcal(struct buf *buf) -{ - /* Begin xCal stream */ - buf_reset(buf); - buf_printf_markup(buf, 0, "<?xml version=\"1.0\" encoding=\"utf-8\"?>"); - buf_printf_markup(buf, 0, "<icalendar xmlns=\"%s\">", XML_NS_ICALENDAR); - buf_printf_markup(buf, 1, "<vcalendar>"); - buf_printf_markup(buf, 2, "<properties>"); - buf_printf_markup(buf, 3, "<prodid>"); - buf_printf_markup(buf, 4, "<text>-//CyrusIMAP.org/Cyrus %s//EN</text>", - cyrus_version()); - buf_printf_markup(buf, 3, "</prodid>"); - buf_printf_markup(buf, 3, "<version>"); - buf_printf_markup(buf, 4, "<text>2.0</text>"); - buf_printf_markup(buf, 3, "</version>"); - buf_printf_markup(buf, 2, "</properties>"); - buf_printf_markup(buf, 2, "<components>"); - - return ""; -} - - -void end_xcal(struct buf *buf) -{ - /* End xCal stream */ - buf_reset(buf); - buf_printf_markup(buf, 2, "</components>"); - buf_printf_markup(buf, 1, "</vcalendar>"); - buf_printf_markup(buf, 0, "</icalendar>"); -} - - -/* libxml2 replacement functions for those missing in older versions */ -#if (LIBXML_VERSION < 20800) -xmlChar *xmlBufferDetach(xmlBufferPtr buf) -{ - xmlChar *ret; - - if (!buf) return NULL; - - ret = buf->content; - buf->content = NULL; - buf->use = buf->size = 0; - - return ret; -} - - -#if (LIBXML_VERSION < 20703) -xmlNodePtr xmlFirstElementChild(xmlNodePtr node) -{ - if (!node) return NULL; - - for (node = node->children; node; node = node->next) { - if (node->type == XML_ELEMENT_NODE) return (node); - } - - return NULL; -} - - -xmlNodePtr xmlNextElementSibling(xmlNodePtr node) -{ - if (!node) return NULL; - - for (node = node->next; node; node = node->next) { - if (node->type == XML_ELEMENT_NODE) return (node); - } - - return NULL; -} -#endif /* < 2.7.3 */ -#endif /* < 2.8.0 */
View file
cyrus-imapd-2.5.tar.gz/imap/xcal.h
Deleted
@@ -1,82 +0,0 @@ -/* xcal.h -- Routines for converting iCalendar to/from xCal - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#include <libical/ical.h> - -#include "util.h" - -#define XML_NS_ICALENDAR "urn:ietf:params:xml:ns:icalendar-2.0" - -extern const char *icalproperty_value_kind_as_string(icalproperty *prop); -extern const char *icaltime_as_iso_string(const struct icaltimetype tt); -extern const char *icalvalue_utcoffset_as_iso_string(const icalvalue* value); -extern void icalrecurrencetype_add_as_xxx(struct icalrecurrencetype *recur, - void *obj, - void (*add_int)(void *, const char *, - int), - void (*add_str)(void *, const char *, - const char *)); -extern struct icalrecurrencetype * -icalrecur_add_rule(struct icalrecurrencetype **rt, - const char *rpart, void *data, - int (*get_int)(void *), - const char* (*get_str)(void *)); - -extern char *icalcomponent_as_xcal_string(icalcomponent* comp); -extern icalcomponent *xcal_string_as_icalcomponent(const char *str); -extern const char *begin_xcal(struct buf *buf); -extern void end_xcal(struct buf *buf); - -/* libxml2 replacement functions for those missing in older versions */ -#if (LIBXML_VERSION < 20800) -#include <libxml/tree.h> - -extern xmlChar *xmlBufferDetach(xmlBufferPtr buf); - -#if (LIBXML_VERSION < 20703) -extern xmlNodePtr xmlFirstElementChild(xmlNodePtr parent); -extern xmlNodePtr xmlNextElementSibling(xmlNodePtr node); -#endif /* < 2.7.3 */ -#endif /* < 2.8.0 */
View file
cyrus-imapd-2.5.tar.gz/imap/xstats.c
Deleted
@@ -1,52 +0,0 @@ -/* xstats.c -- trivial stats for searching - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <config.h> -#include "xstats.h" - - -EXPORTED uint32_t xstatsXSTATS_NUM_METRICS; -EXPORTED const char *xstats_namesXSTATS_NUM_METRICS = { -#define X(x) _STRINGIFY(x) -#include "xstats_metrics.h" -#undef X -};
View file
cyrus-imapd-2.5.tar.gz/imap/xstats.h
Deleted
@@ -1,71 +0,0 @@ -/* xstats.h -- trivial stats for searching - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __CYRUS_IMAP_XSTATS_H__ -#define __CYRUS_IMAP_XSTATS_H__ - -#ifdef HAVE_INTTYPES_H -# include <inttypes.h> -#elif defined(HAVE_STDINT_H) -# include <stdint.h> -#endif -#include <config.h> - -#define _PASTE(a,b) a##b -#define _STRINGIFY(x) #x - -enum -{ -#define X(x) _PASTE(XSTATS_,x) -#include "xstats_metrics.h" -#undef X - XSTATS_NUM_METRICS -}; -extern uint32_t xstatsXSTATS_NUM_METRICS; /* 32b wrapping counters */ -extern const char *xstats_namesXSTATS_NUM_METRICS; - -#define xstats_inc(m) (xstats_PASTE(XSTATS_,m)++) -#define xstats_add(m, x) (xstats_PASTE(XSTATS_,m) += (x)) - -/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ - -#endif /* __CYRUS_IMAP_XSTATS_H__ */
View file
cyrus-imapd-2.5.tar.gz/imap/xstats_metrics.h
Deleted
@@ -1,62 +0,0 @@ -/* xstats_metric.h -- canonical list of trivial stats for searching - * - * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ -X(CONV_LOAD), -X(CONV_SAVE), -X(CONV_GET_MODSEQ), -X(CONV_NEW), -X(MSGDATA_LOAD), -X(MESSAGE_MAP), -X(SEARCH_EVALUATE), -X(SEARCH_HEADER), -X(SEARCH_CACHE_HEADER), -X(SEARCH_BODY), -X(SEARCH_TRIVIAL), -X(SEARCH_RESULT), -X(SPHINX_MULTIPLE), -X(SPHINX_SINGLE), -X(SPHINX_MATCH), -X(SPHINX_CONNECT), -X(SPHINX_CLOSE), -X(SPHINX_QUERY), -X(SPHINX_ROW), -X(SPHINX_RESULT), -X(SPHINX_UNINDEXED),
View file
cyrus-imapd-2.5.tar.gz/imap/zoneinfo_db.c
Deleted
@@ -1,323 +0,0 @@ -/* zoneinfo_db.c -- zoneinfo DB routines - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include <config.h> - -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#include <ctype.h> -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <syslog.h> - -#include "assert.h" -#include "cyrusdb.h" -#include "exitcodes.h" -#include "global.h" -#include "tok.h" -#include "util.h" -#include "xmalloc.h" - -#include "zoneinfo_db.h" - -#define DB config_zoneinfo_db - -struct db *zoneinfodb; -static int zoneinfo_dbopen = 0; -static struct buf databuf = BUF_INITIALIZER; - -EXPORTED int zoneinfo_open(const char *fname) -{ - int ret; - char *tofree = NULL; - - if (!fname) fname = config_getstring(IMAPOPT_ZONEINFO_DB_PATH); - - /* create db file name */ - if (!fname) { - tofree = strconcat(config_dir, FNAME_ZONEINFODB, (char *)NULL); - fname = tofree; - } - - ret = cyrusdb_open(DB, fname, CYRUSDB_CREATE, &zoneinfodb); - if (ret != 0) { - syslog(LOG_ERR, "DBERROR: opening %s: %s", fname, - cyrusdb_strerror(ret)); - } - else zoneinfo_dbopen = 1; - - free(tofree); - - return ret; -} - -void zoneinfo_close(struct txn *tid) -{ - if (zoneinfo_dbopen) { - int r; - - if (tid) { - r = cyrusdb_commit(zoneinfodb, tid); - if (r) { - syslog(LOG_ERR, "DBERROR: error committing zoneinfo: %s", - cyrusdb_strerror(r)); - } - } - r = cyrusdb_close(zoneinfodb); - if (r) { - syslog(LOG_ERR, "DBERROR: error closing zoneinfo: %s", - cyrusdb_strerror(r)); - } - zoneinfo_dbopen = 0; - - buf_free(&databuf); - } -} - -void zoneinfo_done(void) -{ - /* DB->done() handled by cyrus_done() */ -} - -static int parse_zoneinfo(const char *data, int datalen, - struct zoneinfo *zi, int all) -{ - const char *dend = data + datalen; - unsigned version; - char *p; - - memset(zi, 0, sizeof(struct zoneinfo)); - - /* version SP type SP dtstamp SP (string *(TAB string)) */ - - version = strtoul(data, &p, 10); - if (version != ZONEINFO_VERSION) return CYRUSDB_IOERROR; - - if (p < dend) zi->type = strtoul(p, &p, 10); - if (p < dend) zi->dtstamp = strtol(p, &p, 10); - - if (all && p < dend) { - size_t len = dend - ++p; - char *str = xstrndup(p, len); - tok_t tok; - - tok_initm(&tok, str, "\t", TOK_FREEBUFFER); - while ((str = tok_next(&tok))) appendstrlist(&zi->data, str); - tok_fini(&tok); - } - - return 0; -} - -EXPORTED int zoneinfo_lookup(const char *tzid, struct zoneinfo *zi) -{ - const char *data = NULL; - size_t datalen; - int r; - - /* Don't access DB if it hasn't been opened */ - if (!zoneinfo_dbopen) return CYRUSDB_INTERNAL; - - assert(tzid); - - /* Check if there is an entry in the database */ - do { - r = cyrusdb_fetch(zoneinfodb, tzid, strlen(tzid), &data, &datalen, NULL); - } while (r == CYRUSDB_AGAIN); - - if (r || !data || (datalen < 6)) return r ? r : CYRUSDB_IOERROR; - - return parse_zoneinfo(data, datalen, zi, 1); -} - -EXPORTED int zoneinfo_store(const char *tzid, struct zoneinfo *zi, struct txn **tid) -{ - struct strlist *sl; - const char *sep; - int r; - - /* Don't access DB if it hasn't been opened */ - if (!zoneinfo_dbopen) return CYRUSDB_INTERNAL; - - assert(tzid && zi); - - /* version SP type SP dtstamp SP (string *(TAB string)) */ - buf_reset(&databuf); - buf_printf(&databuf, "%u %u %ld ", ZONEINFO_VERSION, zi->type, zi->dtstamp); - for (sl = zi->data, sep = ""; sl; sl = sl->next, sep = "\t") - buf_printf(&databuf, "%s%s", sep, sl->s); - - r = cyrusdb_store(zoneinfodb, tzid, strlen(tzid), - buf_cstring(&databuf), buf_len(&databuf), tid); - - if (r != CYRUSDB_OK) { - syslog(LOG_ERR, "DBERROR: error updating zoneinfo: %s (%s)", - tzid, cyrusdb_strerror(r)); - } - - return r; -} - - -static int tzmatch(const char *str, const char *pat) -{ - for ( ; *pat; str++, pat++) { - /* End of string and more pattern */ - if (!*str && *pat != '*') return 0; - - switch (*pat) { - case '*': - /* Collapse consecutive stars */ - while (*++pat == '*') continue; - - /* Trailing star matches anything */ - if (!*pat) return 1; - - while (*str) if (tzmatch(str++, pat)) return 1; - return 0; - - case ' ': - case '_': - /* Treat ' ' == '_' */ - if (*str != ' ' && *str != '_') return 0; - break; - - default: - /* Case-insensitive comparison */ - if (tolower(*str) != tolower(*pat)) return 0; - break; - } - } - - /* Did we reach end of string? */ - return (!*str); -} - - -struct findrock { - const char *find; - int tzid_only; - time_t changedsince; - int (*proc)(); - void *rock; -}; - -static int find_p(void *rock, - const char *tzid, size_t tzidlen, - const char *data, size_t datalen) -{ - struct findrock *frock = (struct findrock *) rock; - struct zoneinfo zi; - - if (parse_zoneinfo(data, datalen, &zi, 0)) return 0; - - switch (zi.type) { - case ZI_INFO: return 0; - - case ZI_LINK: - if (frock->tzid_only) return 0; - break; - - case ZI_ZONE: - if (zi.dtstamp <= frock->changedsince) return 0; - break; - } - - if (!frock->find) return 1; - if (frock->tzid_only) return (tzidlen == strlen(frock->find)); - return tzmatch(tzid, frock->find); -} - -static int find_cb(void *rock, - const char *tzid, size_t tzidlen, - const char *data, size_t datalen) -{ - struct findrock *frock = (struct findrock *) rock; - struct zoneinfo zi; - int r; - - r = parse_zoneinfo(data, datalen, &zi, 1); - if (!r) { - struct strlist *linkto = NULL; - - if (zi.type == ZI_LINK) { - linkto = zi.data; - zi.data = NULL; - - tzid = linkto->s; - tzidlen = strlen(tzid); - r = zoneinfo_lookup(tzid, &zi); - } - if (!r) r = (*frock->proc)(tzid, tzidlen, &zi, frock->rock); - freestrlist(zi.data); - freestrlist(linkto); - } - - return r; -} - -EXPORTED int zoneinfo_find(const char *find, int tzid_only, time_t changedsince, - int (*proc)(), void *rock) -{ - struct findrock frock; - - /* Don't access DB if it hasn't been opened */ - if (!zoneinfo_dbopen) return CYRUSDB_INTERNAL; - - assert(proc); - - frock.find = find; - frock.tzid_only = tzid_only; - frock.changedsince = changedsince; - frock.proc = proc; - frock.rock = rock; - - if (!find || !tzid_only) find = ""; - - /* process each matching entry in our database */ - return cyrusdb_foreach(zoneinfodb, (char *) find, strlen(find), - &find_p, &find_cb, &frock, NULL); -}
View file
cyrus-imapd-2.5.tar.gz/imap/zoneinfo_db.h
Deleted
@@ -1,97 +0,0 @@ -/* zoneinfo_db.h -- zoneinfo DB routines - * - * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The name "Carnegie Mellon University" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For permission or any legal - * details, please contact - * Carnegie Mellon University - * Center for Technology Transfer and Enterprise Creation - * 4615 Forbes Avenue - * Suite 302 - * Pittsburgh, PA 15213 - * (412) 268-7393, fax: (412) 268-7395 - * innovation@andrew.cmu.edu - * - * 4. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by Computing Services - * at Carnegie Mellon University (http://www.cmu.edu/computing/)." - * - * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO - * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE - * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef ZONEINFO_DB_H -#define ZONEINFO_DB_H - -#include <time.h> - -#include "annotate.h" /* for strlist functionality */ - -/* name of the zoneinfo directory */ -#define FNAME_ZONEINFODIR "/zoneinfo" - -/* name of the zoneinfo database */ -#define FNAME_ZONEINFODB "/zoneinfo.db" -#define ZONEINFO_VERSION 1 - -#define INFO_TZID ".info" -#define zoneinfo_lookup_info(zi) zoneinfo_lookup(INFO_TZID, zi) - -struct zoneinfo { - unsigned type; - time_t dtstamp; - struct strlist *data; -}; - -/* zoneinfo record types */ -enum { - ZI_ZONE = 0, - ZI_LINK, - ZI_INFO -}; - -/* open the zoneinfo db */ -extern int zoneinfo_open(const char *name); - -/* lookup a single zoneinfo entry and return result, or error if it - doesn't exist or doesn't have the fields we need */ -extern int zoneinfo_lookup(const char *tzid, struct zoneinfo *zi); - -/* store a zoneinfo entry */ -extern int zoneinfo_store(const char *tzid, struct zoneinfo *zi, - struct txn **tid); - -/* process all zoneinfo entries (optionally matching 'find') */ -extern int zoneinfo_find(const char *find, int tzid_only, time_t changedsince, - int (*proc)(const char *tzid, int tzidlen, - struct zoneinfo *zi, void *rock), - void *rock); - -/* close the database (optionally committing txn) */ -extern void zoneinfo_close(struct txn *tid); - -/* done with database stuff */ -extern void zoneinfo_done(void); - -#endif /* ZONEINFO_DB_H */
View file
cyrus-imapd-2.5.tar.gz/imtest/imtest.c
Changed
@@ -1175,6 +1175,20 @@ gotsigint = 1; } +static int haveinput(struct protstream *s) +{ + /* Is something currently pending in our protstream's buffer? */ +#ifdef HAVE_SSL + if (s->cnt == 0 && s->tls_conn != NULL) { + /* Maybe there's data pending in the SSL buffer? */ + int n = SSL_pending(s->tls_conn); + if (verbose) printf("SSL_pending=%d\n", n); + return n; + } +#endif + return s->cnt; +} + /* This needs to support 3 modes: * * 1. Terminal Interface Only @@ -1339,7 +1353,7 @@ bufcount = '\0'; printf("%s", buf); } - } while (pin->cnt > 0); + } while (haveinput(pin) > 0); } else if ((FD_ISSET(fd, &rset)) && (FD_ISSET(sock, &wset)) && (donewritingfile == 0)) { /* This does input for both socket and file modes */
View file
cyrus-imapd-2.5.tar.gz/lib/assert.c
Changed
@@ -54,6 +54,5 @@ snprintf(buf, sizeof(buf), "Internal error: assertion failed: %s: %d%s%s", file, line, expr ? ": " : "", expr ? expr : ""); - abort(); fatal(buf, EC_SOFTWARE); }
View file
cyrus-imapd-2.5.tar.gz/lib/bitvector.c
Changed
@@ -54,9 +54,7 @@ #define MAX(a,b) ((a)>(b)?(a):(b)) #endif -#define BITS_PER_UNIT 8 #define vidx(x) ((x) >> 3) -#define visaligned(x) (!((x) & 0x7)) #define vmask(x) (1 << ((x) & 0x7)) #define vtailmask(x) ((unsigned char)(0xff << ((x) & 0x7))) #define vlen(x) vidx((x)+7) @@ -160,99 +158,14 @@ unsigned int i; bv_ensure(a, b->length); + if (!a->length) + return; n = vlen(b->length+1); for (i = 0 ; i <= n ; i++) a->bitsi |= b->bitsi; a->length = MAX(a->length, b->length); } -/* - * Returns the bit position of the next set bit which is after or equal - * to position 'start'. Passing start = 0 returns the first set bit. - * Returns a bit position or -1 if there are no more set bits. - */ -EXPORTED int bv_next_set(const bitvector_t *bv, int start) -{ - int i; - - if (start < 0 || start >= (int)bv->length) return -1; - - for (i = start ; i < (int)bv->length && !visaligned(i) ; i++) - if (bv->bitsvidx(i) & vmask(i)) - return i; - - while (i < (int)bv->length) { - if (!bv->bitsvidx(i)) { - i += BITS_PER_UNIT; - } - else { - if (bv->bitsvidx(i) & vmask(i)) - return i; - i++; - } - } - - return -1; -} - -/* - * Returns the bit position of the previous set bit which is before or - * equal to position 'start'. Passing start = bv->vector-1 returns the - * last set bit. Returns a bit position or -1 if there are no more set - * bits. - */ -EXPORTED int bv_prev_set(const bitvector_t *bv, int start) -{ - int i; - - if (start < 0 || start >= (int)bv->length) return -1; - - for (i = start ; i < (int)bv->length && !visaligned(i) ; i--) - if (bv->bitsvidx(i) & vmask(i)) - return i; - - while (i >= 0) { - if (!bv->bitsvidx(i)) { - i -= BITS_PER_UNIT; - } - else { - if (bv->bitsvidx(i) & vmask(i)) - return i; - i--; - } - } - - return -1; -} - -EXPORTED int bv_first_set(const bitvector_t *bv) -{ - return bv_next_set(bv, 0); -} - -EXPORTED int bv_last_set(const bitvector_t *bv) -{ - return bv_prev_set(bv, bv->length-1); -} - -static unsigned int bitcount(unsigned int i) -{ - /* http://stackoverflow.com/questions/109023/how-to-count-the-number-of-set-bits-in-a-32-bit-integer */ - i = i - ((i >> 1) & 0x55555555); - i = (i & 0x33333333) + ((i >> 2) & 0x33333333); - return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; -} - -EXPORTED unsigned bv_count(const bitvector_t *bv) -{ - unsigned i; - unsigned int n = 0; - - for (i = 0 ; i < bv->length ; i += BITS_PER_UNIT) - n += bitcount(bv->bitsvidx(i)); - return n; -} - /* Returns a string which describes the state of the bitvector, * useful for debugging. Returns a new string which must be free'd * by the caller */
View file
cyrus-imapd-2.5.tar.gz/lib/bitvector.h
Changed
@@ -53,8 +53,6 @@ { unsigned int length; unsigned int alloc; - /* TODO: should use natural word size, uint32_t or uint64_t, - * for faster searching in bv_next_set() */ unsigned char *bits; }; @@ -71,11 +69,6 @@ extern void bv_clear(bitvector_t *, unsigned int); extern void bv_andeq(bitvector_t *a, const bitvector_t *b); extern void bv_oreq(bitvector_t *a, const bitvector_t *b); -extern int bv_next_set(const bitvector_t *, int start); -extern int bv_prev_set(const bitvector_t *, int start); -extern int bv_first_set(const bitvector_t *); -extern int bv_last_set(const bitvector_t *); -extern unsigned bv_count(const bitvector_t *); extern char *bv_cstring(const bitvector_t *); extern void bv_free(bitvector_t *);
View file
cyrus-imapd-2.5.tar.gz/lib/charset.c
Changed
@@ -44,47 +44,15 @@ #include <stdlib.h> #include <string.h> -#include "assert.h" #include "charset.h" #include "xmalloc.h" #include "chartable.h" -#include "hash.h" -#include "htmlchar.h" #include "util.h" -#define U_REPLACEMENT 0xfffd - -#define unicode_isvalid(c) \ - (!((c >= 0xd800 && c <= 0xdfff) || ((unsigned)c > 0x10ffff))) - -char QPSAFECHAR256 = { -/* control chars are unsafe */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* http://en.wikipedia.org/wiki/Quoted-printable */ -/* All printable ASCII characters (decimal values between 33 and 126) */ -/* may be represented by themselves, except "=" (decimal 61). */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, -/* all high bits are unsafe */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - struct qp_state { int isheader; int bytesleft; - unsigned char lastoctet; + int codepoint; }; struct b64_state { @@ -92,10 +60,6 @@ int codepoint; }; -struct unfold_state { - int state; -}; - struct table_state { const struct charmap (*curtable)256; const struct charmap (*initialtable)256; @@ -124,42 +88,6 @@ size_t offset; }; -enum html_state { - HDATA, - HTAGOPEN, - HENDTAGOPEN, - HTAGNAME, - HSCTAG, - HTAGPARAMS, - HCHARACTER, - HCHARACTER2, - HCHARACTERHASH, - HCHARACTERHEX, - HCHARACTERDEC, - HSCRIPTDATA, - HSCRIPTLT, - HSTYLEDATA, - HSTYLELT, - HBOGUSCOMM, - HMUDECOPEN, - HCOMMSTART, - HCOMMSTARTDASH, - HCOMM, - HCOMMENDDASH, - HCOMMEND, - HCOMMENDBANG -}; - -struct striphtml_state { - struct buf name; -#define HBEGIN (1<<0) -#define HEND (1<<1) - unsigned int ends; - /* state stack */ - int depth; - enum html_state stack2; -}; - struct convert_rock; typedef void convertproc_t(struct convert_rock *rock, int c); @@ -174,14 +102,11 @@ #define GROWSIZE 100 -int charset_debug; -static const char *convert_name(struct convert_rock *rock); - #define XX 127 /* * Table for decoding hexadecimal in quoted-printable */ -static const unsigned char index_hex256 = { +static const char index_hex256 = { XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, @@ -224,52 +149,19 @@ }; #define CHAR64(c) (index_64(unsigned char)(c)) -EXPORTED int encoding_lookupname(const char *s) -{ - switch (s0) { - case '7': - if (!strcasecmp(s, "7BIT")) - return ENCODING_NONE; - break; - case '8': - if (!strcasecmp(s, "8BIT")) - return ENCODING_NONE; - break; - case 'B': - case 'b': - if (!strcasecmp(s, "BASE64")) - return ENCODING_BASE64; - if (!strcasecmp(s, "BINARY")) - return ENCODING_NONE; - break; - case 'Q': - case 'q': - if (!strcasecmp(s, "QUOTED-PRINTABLE")) - return ENCODING_QP; - break; - } - return ENCODING_UNKNOWN; -} - EXPORTED const char *encoding_name(int encoding) { switch (encoding) { - case ENCODING_NONE: return "NONE"; - case ENCODING_QP: return "QUOTED-PRINTABLE"; - case ENCODING_BASE64: return "BASE64"; - case ENCODING_UNKNOWN: return "UNKNOWN"; - default: return "WTF"; + case ENCODING_NONE: return "none"; + case ENCODING_QP: return "quoted-printable"; + case ENCODING_BASE64: return "base64"; + case ENCODING_UNKNOWN: return "unknown"; + default: return "wtf"; } } static inline void convert_putc(struct convert_rock *rock, int c) { - if (charset_debug) { - if ((unsigned)c < 0xff) - fprintf(stderr, "%s(0x%x = '%c')\n", convert_name(rock), c, c); - else - fprintf(stderr, "%s(0x%x)\n", convert_name(rock), c); - } rock->f(rock, c); } @@ -296,54 +188,32 @@ struct qp_state *s = (struct qp_state *)rock->state; int val; - assert(c == U_REPLACEMENT || (unsigned)c <= 0xff); - if (s->bytesleft) { s->bytesleft--; - - /* the replacement char is not part of a valid sequence */ - if (c == U_REPLACEMENT) { -invalid: - /* RFC2045 says "...A reasonable approach by a robust - * implementation might be to include the "=" character - * and the following character in the decoded data without - * any transformation..." */ - convert_putc(rock->next, '='); - if (!s->bytesleft) - convert_putc(rock->next, s->lastoctet); - convert_putc(rock->next, c); - s->bytesleft = 0; - return; - } - - /* detect and swallow soft line breaks */ - if (s->bytesleft == 1 && c == '\r') { - s->lastoctet = c; + val = HEXCHAR(c); + if (val == XX) { + /* mark invalid regardless */ + s->codepoint = -1; return; } - if (s->bytesleft == 0 && s->lastoctet == '\r') { - if (c == '\n') - return; - goto invalid; + if (s->codepoint != -1) { + /* don't blat the invalid marker, but still absorb + * the second char */ + s->codepoint = (s->codepoint << 4) + val; } - - val = HEXCHAR(c); - if (val == XX) - goto invalid; - /* if we got this far, we have two valid hex chars */ if (!s->bytesleft) { - val |= (HEXCHAR(s->lastoctet) << 4); - assert((unsigned)val <= 0xff); - convert_putc(rock->next, val); + if (s->codepoint == -1) + convert_putc(rock->next, 0xfffd); + else + convert_putc(rock->next, s->codepoint & 0xff); } - s->lastoctet = c; return; } /* start an encoded byte */ if (c == '=') { s->bytesleft = 2; - s->lastoctet = 0; + s->codepoint = 0; return; } @@ -383,132 +253,51 @@ } } -/* - * This filter unfolds folded RFC2822 header field lines, i.e. it strips - * a CRLF pair only if the first character after the CRLF is LWS, and - * leaves other CRLF or lone CR or LF alone. In particular the CRLFs - * which *separate* different header fields are preserved. This allows - * the 'keep' and 'merge' whitespace options to behave as expected when - * the search engine is term-based, i.e. uses whitespace and punctuation - * to find indexing terms. - */ -static void unfold2uni(struct convert_rock *rock, int c) +static void stripnl2uni(struct convert_rock *rock, int c) { - struct unfold_state *s = (struct unfold_state *)rock->state; - - switch (s->state) { - case 0: - if (c == '\r') - s->state = 1; - else - convert_putc(rock->next, c); - break; - case 1: - if (c == '\n') - s->state = 2; - else { - convert_putc(rock->next, '\r'); - convert_putc(rock->next, c); - s->state = 0; - } - break; - case 2: - if (c != ' ' && c != '\t') { - convert_putc(rock->next, '\r'); - convert_putc(rock->next, '\n'); - } + if (c != '\r' && c != '\n') convert_putc(rock->next, c); - s->state = 0; - break; - } } -/* Given an octet which is a codepoint in some 7bit or 8bit character - * set, or the Unicode replacement character, emit the corresponding - * Unicode codepoint. */ static void table2uni(struct convert_rock *rock, int c) { struct table_state *s = (struct table_state *)rock->state; - struct charmap *map; + struct charmap *map = (struct charmap *)&s->curtable0c & 0xff; - if (c == U_REPLACEMENT) { + /* propagate errors */ + if (c == 0xfffd) { convert_putc(rock->next, c); return; } - assert((unsigned)c <= 0xff); - map = (struct charmap *)&s->curtable0c & 0xff; if (map->c) convert_putc(rock->next, map->c); s->curtable = s->initialtable + map->next; } -/* Given an octet in a UTF-8 encoded string, possibly emit a Unicode - * code point */ static void utf8_2uni(struct convert_rock *rock, int c) { struct table_state *s = (struct table_state *)rock->state; - if (c == U_REPLACEMENT) { -emit_replacement: - convert_putc(rock->next, U_REPLACEMENT); - s->bytesleft = 0; - s->codepoint = 0; + /* propagate errors */ + if (c == 0xfffd) { + convert_putc(rock->next, c); return; } - assert((unsigned)c <= 0xff); - - /* - * The following bytes are never valid in UTF-8 streams: - * - * C0-C1 could only be used for overlong encoding of - * basic ASCII characters - * F5-FD start bytes of sequences that could only encode - * numbers larger than the 0x10FFFF limit of Unicode - * FE-FF not valid start bytes or anything else - * - * Thanks to http://en.wikipedia.org/wiki/UTF-8 - * - * These checks are interspersed with bitwise checks below. - * - * When we see a valid leading character but a sequence has - * not finished, we have detected an ill-formed sequence. The - * correct thing to do according to Section 3.9 of the Unicode - * standard 6.0 is to jettison the current sequence, emit the - * Replacement character and begin a new sequence with the new - * character. From the standard: - * - * For example, with the input UTF-8 code unit sequence - * <C2 41 42>, such a UTF-8 conversion process must not - * return <U+FFFD> or <U+FFFD, U+0042>, because either of - * those outputs would be the result of misinterpreting - * a well-formed subsequence as being part of the ill-formed - * subsequence. The expected return value for such a process - * would instead be <U+FFFD, U+0041, U+0042>. - */ - if ((c & 0xf8) == 0xf0) { /* 11110xxx */ /* first of a 4 char sequence */ - if (s->bytesleft) /* incomplete sequence */ - convert_putc(rock->next, U_REPLACEMENT); - if (c >= 0xf5 && c <= 0xf7) goto emit_replacement; s->bytesleft = 3; s->codepoint = c & 0x07; /* 00000111 */ } else if ((c & 0xf0) == 0xe0) { /* 1110xxxx */ /* first of a 3 char sequence */ - if (s->bytesleft) /* incomplete sequence */ - convert_putc(rock->next, U_REPLACEMENT); s->bytesleft = 2; s->codepoint = c & 0x0f; /* 00001111 */ } else if ((c & 0xe0) == 0xc0) { /* 110xxxxx */ /* first of a 2 char sequence */ - if (s->bytesleft) /* incomplete sequence */ - convert_putc(rock->next, U_REPLACEMENT); - if (c == 0xc0 || c == 0xc1) goto emit_replacement; s->bytesleft = 1; s->codepoint = c & 0x1f; /* 00011111 */ } @@ -518,40 +307,30 @@ s->codepoint = (s->codepoint << 6) + (c & 0x3f); /* 00111111 */ s->bytesleft--; if (!s->bytesleft) { - convert_putc(rock->next, s->codepoint); + convert_putc(rock->next, s->codepoint); s->codepoint = 0; } } - else - goto emit_replacement; - } - else if (c >= 0xf8 && c <= 0xff) { - goto emit_replacement; } else { /* plain ASCII char */ - if (s->bytesleft) /* incomplete sequence */ - convert_putc(rock->next, U_REPLACEMENT); convert_putc(rock->next, c); s->bytesleft = 0; s->codepoint = 0; } } -/* Given an octet in a UTF-7 encoded string, possibly emit a Unicode - * code point */ static void utf7_2uni (struct convert_rock *rock, int c) { struct table_state *s = (struct table_state *)rock->state; - if (c == U_REPLACEMENT) { + /* propagate errors */ + if (c == 0xfffd) { convert_putc(rock->next, c); return; } - assert((unsigned)c <= 0xff); - if (c & 0x80) { /* skip 8-bit chars */ - convert_putc(rock->next, U_REPLACEMENT); + convert_putc(rock->next, 0xfffd); return; } @@ -608,12 +387,6 @@ } } -/* - * Given a Unicode codepoint, emit one or more Unicode codepoints in - * search-normalised form (having applied recursive Unicode - * decomposition, like U+2026 HORIZONTAL ELLIPSIS to the three - * characters U+2E U+2E U+2E). - */ static void uni2searchform(struct convert_rock *rock, int c) { struct canon_state *s = (struct canon_state *)rock->state; @@ -621,8 +394,10 @@ int code; unsigned char table16, table8; - if (c == U_REPLACEMENT) { - convert_putc(rock->next, c); + /* invalid character becomes an Oxff - that's illegal utf-8, + * so it won't match */ + if (c == 0xfffd) { + convert_putc(rock->next, 0xff); return; } @@ -685,62 +460,8 @@ } } -/* - * Given a Unicode codepoint, emit one or more Unicode codepoints in - * HTML form, suitable for generating search snippets. - */ -static void uni2html(struct convert_rock *rock, int c) -{ - struct canon_state *s = (struct canon_state *)rock->state; - - if (c == U_REPLACEMENT) { - convert_putc(rock->next, c); - return; - } - - if (c == '<') { - convert_cat(rock->next, "<"); - return; - } - - if (c == '>') { - convert_cat(rock->next, ">"); - return; - } - - if (c == '&') { - convert_cat(rock->next, "&"); - return; - } - - /* special case: whitespace or control characters */ - if (c == ' ' || c == '\r' || c == '\n') { - if (s->flags & CHARSET_SKIPSPACE) { - return; - } - if (s->flags & CHARSET_MERGESPACE) { - if (s->seenspace) - return; - s->seenspace = 1; - c = ' '; /* one SPACE char */ - } - } - else - s->seenspace = 0; - - convert_putc(rock->next, c); -} - -/* Given a Unicode codepoint, emit valid UTF-8 encoded octets */ static void uni2utf8(struct convert_rock *rock, int c) { - if (!unicode_isvalid(c)) - c = U_REPLACEMENT; - - /* UTF-8 can encode code points up to 0x7fffffff, but the currently - * defined last valid codepoint is 0x10ffff so we only handle that - * range. */ - if (c > 0xffff) { convert_putc(rock->next, 0xF0 + ((c >> 18) & 0x07)); convert_putc(rock->next, 0x80 + ((c >> 12) & 0x3f)); @@ -767,7 +488,7 @@ int i, cur; unsigned char b = (unsigned char)c; - if (c == U_REPLACEMENT) { + if (c == 0xfffd) { c = 0xff; /* searchable by invalid character! */ } @@ -808,7 +529,6 @@ s->offset++; } -/* Given an octet, append it to a buffer */ static void byte2buffer(struct convert_rock *rock, int c) { struct buf *buf = (struct buf *)rock->state; @@ -816,576 +536,6 @@ buf_putc(buf, c & 0xff); } -/* - * The HTML5 standard mandates that certain Unicode code points - * cannot be generated using &#nnn; numerical character references, - * and should generate a parse error. This function detects them. - */ -static int html_uiserror(int c) -{ - static const struct { - unsigned int mask, lo, hi; - } ranges = { - { ~0U, 0x0001, 0x0008 }, - { ~0U, 0x000b, 0x000b }, - { ~0U, 0x000e, 0x001f }, - { ~0U, 0x007f, 0x009f }, - { ~0U, 0xfdd0, 0xfdef }, - { 0xffff, 0xfffe, 0xffff } - }; - unsigned int i; - - for (i = 0 ; i < VECTOR_SIZE(ranges) ; i++) { - unsigned c2 = (unsigned)c & rangesi.mask; - if (c2 >= rangesi.lo && c2 <= rangesi.hi) - return 1; - } - return 0; -} - -static void html_saw_character(struct convert_rock *rock) -{ - struct striphtml_state *s = (struct striphtml_state *)rock->state; - const char *ent = buf_cstring(&s->name); - int c; - static const int compat_chars = { - /* Mappings of old numeric character codepoints between 0x80 and - * 0x9f inclusive, defined for backwards compatibility by HTML5, - * to Unicode code points. Note that some of these are mapped - * to codepoints which are mandated to be parse errors. */ - 0x20AC, /* EURO SIGN (€) */ - 0x0081, /* <control> */ - 0x201A, /* SINGLE LOW-9 QUOTATION MARK (‚) */ - 0x0192, /* LATIN SMALL LETTER F WITH HOOK (ƒ) */ - 0x201E, /* DOUBLE LOW-9 QUOTATION MARK („) */ - 0x2026, /* HORIZONTAL ELLIPSIS (…) */ - 0x2020, /* DAGGER (†) */ - 0x2021, /* DOUBLE DAGGER (‡) */ - 0x02C6, /* MODIFIER LETTER CIRCUMFLEX ACCENT (ˆ) */ - 0x2030, /* PER MILLE SIGN (‰) */ - 0x0160, /* LATIN CAPITAL LETTER S WITH CARON (Š) */ - 0x2039, /* SINGLE LEFT-POINTING ANGLE QUOTATION MARK (‹) */ - 0x0152, /* LATIN CAPITAL LIGATURE OE (Œ) */ - 0x008D, /* <control> */ - 0x017D, /* LATIN CAPITAL LETTER Z WITH CARON (Ž) */ - 0x008F, /* <control> */ - 0x0090, /* <control> */ - 0x2018, /* LEFT SINGLE QUOTATION MARK (‘) */ - 0x2019, /* RIGHT SINGLE QUOTATION MARK (’) */ - 0x201C, /* LEFT DOUBLE QUOTATION MARK (“) */ - 0x201D, /* RIGHT DOUBLE QUOTATION MARK (”) */ - 0x2022, /* BULLET (•) */ - 0x2013, /* EN DASH (–) */ - 0x2014, /* EM DASH (—) */ - 0x02DC, /* SMALL TILDE (˜) */ - 0x2122, /* TRADE MARK SIGN (™) */ - 0x0161, /* LATIN SMALL LETTER S WITH CARON (š) */ - 0x203A, /* SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (›) */ - 0x0153, /* LATIN SMALL LIGATURE OE (œ) */ - 0x009D, /* <control> */ - 0x017E, /* LATIN SMALL LETTER Z WITH CARON (ž) */ - 0x0178 /* LATIN CAPITAL LETTER Y WITH DIAERESIS (Ÿ) */ - }; - - if (charset_debug) - fprintf(stderr, "html_saw_character(%s)\n", ent); - - if (ent0 == '#') { - if (ent1 == 'x' || ent1 == 'X') - c = strtoul(ent+2, NULL, 16); - else - c = strtoul(ent+1, NULL, 10); - /* no need for format error checking, the lexer did that */ - - /* Perform character mapping and validation per - * http://dev.w3.org/html5/spec/tokenization.html#consume-a-character-reference - */ - if (c == 0) - c = U_REPLACEMENT; - else if (c >= 0x80 && c <= 0x9f) - c = compat_charsc-0x80; - else if (!unicode_isvalid(c)) - c = U_REPLACEMENT; /* invalid Unicode codepoint */ - - if (html_uiserror(c)) { - /* the HTML5 spec says this is a parse error, but it's - * unclear what that means for us; we choose to strip the - * character but could also emit the replacement character. */ - return; - } - } - else { - c = htmlchar_from_string(ent); - if (c == -1) { - c = U_REPLACEMENT; /* unknown character */ - } - else if (c > 0xffff) { - /* Hack to handle a small minority of named characters - * which map to a sequence of two Unicode codepoints. */ - convert_putc(rock->next, (c>>16) & 0xffff); - convert_putc(rock->next, c & 0xffff); - return; - } - } - convert_putc(rock->next, c); -} - -static const char *html_state_as_string(enum html_state state) -{ - switch (state) { - case HDATA: return "HDATA"; - case HTAGOPEN: return "HTAGOPEN"; - case HENDTAGOPEN: return "HENDTAGOPEN"; - case HTAGNAME: return "HTAGNAME"; - case HSCTAG: return "HSCTAG"; - case HTAGPARAMS: return "HTAGPARAMS"; - case HCHARACTER: return "HCHARACTER"; - case HCHARACTER2: return "HCHARACTER2"; - case HCHARACTERHASH: return "HCHARACTERHASH"; - case HCHARACTERHEX: return "HCHARACTERHEX"; - case HCHARACTERDEC: return "HCHARACTERDEC"; - case HSCRIPTDATA: return "HSCRIPTDATA"; - case HSCRIPTLT: return "HSCRIPTLT"; - case HSTYLEDATA: return "HSTYLEDATA"; - case HSTYLELT: return "HSTYLELT"; - case HBOGUSCOMM: return "HBOGUSCOMM"; - case HMUDECOPEN: return "HMUDECOPEN"; - case HCOMMSTART: return "HCOMMSTART"; - case HCOMMSTARTDASH: return "HCOMMSTARTDASH"; - case HCOMM: return "HCOMM"; - case HCOMMENDDASH: return "HCOMMENDDASH"; - case HCOMMEND: return "HCOMMEND"; - case HCOMMENDBANG: return "HCOMMENDBANG"; - } - return "wtf?"; -} - -static void html_push(struct striphtml_state *s, enum html_state state) -{ - assert(s->depth < (int)(sizeof(s->stack)/sizeof(s->stack0))); - if (charset_debug) - fprintf(stderr, "html_push(%s)\n", html_state_as_string(state)); - s->stacks->depth++ = state; -} - -static int html_pop(struct striphtml_state *s) -{ - assert(s->depth > 0); - if (charset_debug) - fprintf(stderr, "html_pop()\n"); - return s->stack--s->depth; -} - -static void html_go(struct striphtml_state *s, enum html_state state) -{ - assert(s->depth > 0); - if (charset_debug) - fprintf(stderr, "html_go(%s)\n", html_state_as_string(state)); - s->stacks->depth-1 = state; -} - -static enum html_state html_top(struct striphtml_state *s) -{ - assert(s->depth > 0); - return s->stacks->depth-1; -} - -static int is_phrasing(char *tag) -{ - static const char * const phrasing_tags = { - "a", "q", "cite", "em", "strong", "small", - "mark", "dfn", "abbr", "time", "progress", - "meter", "code", "var", "samp", "kbd", - "sub", "sup", "span", "i", "b", "bdo", - "ruby", "ins", "del" - }; - static struct hash_table hash = HASH_TABLE_INITIALIZER; - - if (hash.table == NULL) { - unsigned int i; - construct_hash_table(&hash, VECTOR_SIZE(phrasing_tags), 0); - for (i = 0 ; i < VECTOR_SIZE(phrasing_tags) ; i++) - hash_insert(phrasing_tagsi, (void *)1, &hash); - } - - return (hash_lookup(lcase(tag), &hash) == (void *)1); -} - -static void html_saw_tag(struct convert_rock *rock) -{ - struct striphtml_state *s = (struct striphtml_state *)rock->state; - char *tag; - enum html_state state = html_top(s); - - buf_cstring(&s->name); - tag = s->name.s; - - if (charset_debug) - fprintf(stderr, "html_saw_tag() state=%s tag=\"%s\" ends=%s,%s\n", - html_state_as_string(state), tag, - (s->ends & HBEGIN ? "BEGIN" : "-"), - (s->ends & HEND ? "END" : "-")); - - /* gnb:TODO: what are we supposed to do with a nested <script> tag? */ - - if (!strcasecmp(tag, "script")) { - if (state == HDATA && s->ends == HBEGIN) - html_go(s, HSCRIPTDATA); - else if (state == HSCRIPTDATA && s->ends == HEND) - html_go(s, HDATA); - /* BEGIN,END pair is doesn't affect state */ - } - else if (!strcasecmp(tag, "style")) { - if (state == HDATA && s->ends == HBEGIN) - html_go(s, HSTYLEDATA); - else if (state == HSTYLEDATA && s->ends == HEND) - html_go(s, HDATA); - /* BEGIN,END pair is doesn't affect state */ - } - else if (!is_phrasing(tag)) { - convert_putc(rock->next, ' '); - } - /* otherwise, no change */ -} - -/* - * Note we don't use isalnum - the test has to be in US-ASCII always - * regardless of the charset of the text or the locale of this process - */ -#define html_isalpha(c) \ - (((c) >= 'a' && (c) <= 'z') || \ - ((c) >= 'A' && (c) <= 'Z')) -#define html_isxdigit(c) \ - (((c) >= '0' && (c) <= '9') || \ - ((c) >= 'a' && (c) <= 'f') || \ - ((c) >= 'A' && (c) <= 'F')) -#define html_isdigit(c) \ - (((c) >= '0' && (c) <= '9')) -#define html_isspace(c) \ - ((c) == ' ' || (c) == '\t' || (c) == '\r' || (c) == '\n') - -void striphtml2uni(struct convert_rock *rock, int c) -{ - struct striphtml_state *s = (struct striphtml_state *)rock->state; - -restart: - switch (html_top(s)) { - case HDATA: - if (c == '<') { - html_push(s, HTAGOPEN); - buf_reset(&s->name); - } - else if (c == '&') { - html_go(s, HCHARACTER); - buf_reset(&s->name); - } - else { - convert_putc(rock->next, c); - } - break; - - case HSCRIPTDATA: /* 8.2.4.6 Script data state */ - if (c == '<') { - html_push(s, HSCRIPTLT); - } - /* else, strip the character */ - break; - - case HSCRIPTLT: /* 8.2.4.17 Script data less-than sign state */ - /* TODO: deal with <! inside SCRIPT tags properly per - * http://dev.w3.org/html5/spec/tokenization.html#script-data-less-than-sign-state - */ - if (c == '/') { - s->ends = HEND; - html_go(s, HTAGNAME); - buf_reset(&s->name); - } - else { - /* naked <, emit it and restart with current character */ - convert_putc(rock->next, c); - html_pop(s); - goto restart; - } - break; - - case HSTYLEDATA: - if (c == '<') { - html_push(s, HSTYLELT); - } - /* else, strip the character */ - break; - - case HSTYLELT: - if (c == '/') { - s->ends = HEND; - html_go(s, HTAGNAME); - buf_reset(&s->name); - } - else { - /* naked <, emit it and restart with current character */ - convert_putc(rock->next, c); - html_pop(s); - goto restart; - } - break; - - case HCHARACTER: /* 8.2.4.2 Character reference in data state */ - /* This is the "consume a character reference" algorithm - * rewritten to use additional lexical states */ - if (c == '#') { - buf_putc(&s->name, c); - html_go(s, HCHARACTERHASH); - } - else if (html_isalpha(c)) { - buf_putc(&s->name, c); - html_go(s, HCHARACTER2); - } - else { - /* naked & - emit the & and restart */ - convert_putc(rock->next, '&'); - html_go(s, HDATA); - goto restart; - } - break; - - case HCHARACTERHASH: - /* '&' then '#' */ - if (c == 'x' || c == 'X') { - buf_putc(&s->name, c); - html_go(s, HCHARACTERHEX); - } - else if (html_isdigit(c)) { - buf_putc(&s->name, c); - html_go(s, HCHARACTERDEC); - } - else { - /* naked &# - emit the &# and restart */ - convert_putc(rock->next, '&'); - convert_putc(rock->next, '#'); - html_go(s, HDATA); - goto restart; - } - break; - - case HCHARACTER2: - if (html_isalpha(c)) { - buf_putc(&s->name, c); - /* TODO: we're supposed to look this up - * to see if it's an known character so that - * ¬it; is parsed as the 4 chars - * '¬' 'i' 't' ';' */ - } - else { - html_saw_character(rock); - html_go(s, HDATA); - if (c != ';') { - /* character reference not correctly terminated - - * restart with the next character */ - goto restart; - } - } - break; - - case HCHARACTERHEX: - if (html_isxdigit(c)) { - buf_putc(&s->name, c); - } - else { - html_saw_character(rock); - html_go(s, HDATA); - if (c != ';') { - /* character reference not correctly terminated - - * restart with the next character */ - goto restart; - } - } - break; - - case HCHARACTERDEC: - if (html_isdigit(c)) { - buf_putc(&s->name, c); - } - else { - html_saw_character(rock); - html_go(s, HDATA); - if (c != ';') { - /* character reference not correctly terminated - - * restart with the next character */ - goto restart; - } - } - break; - - case HTAGOPEN: /* 8.2.4.8 Tag open state */ - if (c == '!') { - /* markup declaration open delimiter - let's just assume - * it's a comment */ - html_pop(s); - html_go(s, HMUDECOPEN); - buf_reset(&s->name); - } - else if (c == '/') { - html_go(s, HENDTAGOPEN); - } - else if (html_isalpha(c)) { - s->ends = HBEGIN; - buf_putc(&s->name, c); - html_go(s, HTAGNAME); - } - else { - /* apparently a naked <, emit the < and restart */ - convert_putc(rock->next, '<'); - html_pop(s); - goto restart; - } - break; - - case HENDTAGOPEN: /* 8.2.4.9 End tag open state */ - if (html_isalpha(c)) { - s->ends = HEND; - buf_putc(&s->name, c); - html_go(s, HTAGNAME); - } - else { - /* error */ - html_pop(s); - } - break; - - case HTAGNAME: /* 8.2.4.10 Tag name state */ - /* gnb:TODO handle > embedded in "param" */ - if (html_isspace(c)) { - html_go(s, HTAGPARAMS); - } - else if (c == '/') { - html_go(s, HSCTAG); - } - else if (c == '>') { - html_pop(s); - html_saw_tag(rock); - } - else if (html_isalpha(c)) { - buf_putc(&s->name, c); - } - else { - /* error */ - html_pop(s); - } - break; - - case HSCTAG: /* 8.2.4.43 Self-closing start tag state */ - if (c == '>') { - s->ends = HBEGIN|HEND; - html_pop(s); - html_saw_tag(rock); - } - else { - /* whatever, keep stripping tag parameters */ - html_go(s, HTAGPARAMS); - } - break; - - case HTAGPARAMS: /* ignores all text until next '>' */ - if (c == '>') { - html_pop(s); - html_saw_tag(rock); - } - else if (c == '/') { - html_go(s, HSCTAG); - } - break; - - case HBOGUSCOMM: /* 8.2.4.44 Bogus comment state */ - /* strip all text until closing > */ - if (c == '>') { - html_go(s, HDATA); - } - break; - - case HMUDECOPEN: /* 8.2.4.45 Markup declaration open state */ - if (c == '-') { - buf_putc(&s->name, c); - if (s->name.len == 2) - html_go(s, HCOMMSTART); - } - else { - /* ignore DOCTYPE or CDATA */ - html_go(s, HBOGUSCOMM); - goto restart; /* in case it's a > */ - } - break; - - case HCOMMSTART: /* 8.2.4.46 Comment start state */ - if (c == '-') - html_go(s, HCOMMSTARTDASH); - else if (c == '>') - html_go(s, HDATA); /* very short comment <!--> */ - else - html_go(s, HCOMM); - break; - - case HCOMMSTARTDASH: /* 8.2.4.47 Comment start dash state */ - if (c == '-') - html_go(s, HCOMMEND); - else if (c == '>') - html_go(s, HDATA); /* incorrectly formed -> comment end */ - else - html_go(s, HCOMM); - /* else strip */ - break; - - case HCOMM: /* 8.2.4.48 Comment state */ - if (c == '-') - html_go(s, HCOMMENDDASH); - /* else strip */ - break; - - case HCOMMENDDASH: /* 8.2.4.49 Comment end dash state */ - if (c == '-') - html_go(s, HCOMMEND); /* -- pair in comment */ - else - html_go(s, HCOMM); /* lone - in comment */ - break; - - case HCOMMEND: /* 8.2.4.50 Comment end state */ - if (c == '>') - html_go(s, HDATA); /* correctly formed --> comment end */ - else if (c == '!') - html_go(s, HCOMMENDBANG); /* --! in a comment */ - else if (c != '-') - html_go(s, HCOMM); /* -- in the middle of a comment */ - /* else, --- in comment, strip */ - break; - - case HCOMMENDBANG: /* 8.2.4.51 Comment end bang state */ - if (c == '-') - html_go(s, HCOMMENDDASH); /* --!- in comment */ - else if (c == '>') - html_go(s, HDATA); /* --!> at end of comment */ - else - html_go(s, HCOMM); /* --! in the middle of a comment */ - break; - - } -} - -static const char *convert_name(struct convert_rock *rock) -{ - if (rock->f == b64_2byte) return "b64_2byte"; - if (rock->f == byte2buffer) return "byte2buffer"; - if (rock->f == byte2search) return "byte2search"; - if (rock->f == qp2byte) return "qp2byte"; - if (rock->f == striphtml2uni) return "striphtml2uni"; - if (rock->f == unfold2uni) return "unfold2uni"; - if (rock->f == table2uni) return "table2uni"; - if (rock->f == uni2searchform) return "uni2searchform"; - if (rock->f == uni2html) return "uni2html"; - if (rock->f == uni2utf8) return "uni2utf8"; - if (rock->f == utf7_2uni) return "utf7_2uni"; - if (rock->f == utf8_2uni) return "utf8_2uni"; - return "wtf"; -} - /* convert_rock manipulation routines */ static void table_switch(struct convert_rock *rock, int charset_num) @@ -1393,7 +543,7 @@ struct table_state *state = (struct table_state *)rock->state; /* wipe any current state */ - memset(state, 0, sizeof(struct table_state)); + memset(state, 0, sizeof(struct table_state)); /* it's a table based lookup */ if (chartables_charset_tablecharset_num.table) { @@ -1464,15 +614,6 @@ basic_free(rock); } -static void striphtml_free(struct convert_rock *rock) -{ - if (rock && rock->state) { - struct striphtml_state *s = (struct striphtml_state *)rock->state; - buf_free(&s->name); - } - basic_free(rock); -} - static void convert_free(struct convert_rock *rock) { struct convert_rock *next; while (rock) { @@ -1507,11 +648,10 @@ return rock; } -static struct convert_rock *unfold_init(struct convert_rock *next) +static struct convert_rock *stripnl_init(struct convert_rock *next) { struct convert_rock *rock = xzmalloc(sizeof(struct convert_rock)); - rock->state = xzmalloc(sizeof(struct unfold_state)); - rock->f = unfold2uni; + rock->f = stripnl2uni; rock->next = next; return rock; } @@ -1521,10 +661,7 @@ struct convert_rock *rock = xzmalloc(sizeof(struct convert_rock)); struct canon_state *s = xzmalloc(sizeof(struct canon_state)); s->flags = flags; - if ((flags & CHARSET_SNIPPET)) - rock->f = uni2html; - else - rock->f = uni2searchform; + rock->f = uni2searchform; rock->state = s; rock->next = next; return rock; @@ -1584,20 +721,6 @@ return rock; } -struct convert_rock *striphtml_init(struct convert_rock *next) -{ - struct convert_rock *rock = xzmalloc(sizeof(struct convert_rock)); - struct striphtml_state *s = xzmalloc(sizeof(struct striphtml_state)); - /* gnb:TODO: if a DOCTYPE is present, sniff it to detect XHTML rules */ - html_push(s, HDATA); - rock->state = (void *)s; - rock->f = striphtml2uni; - rock->cleanup = striphtml_free; - rock->next = next; - return rock; -} - - /* API */ /* @@ -1732,7 +855,7 @@ static void mimeheader_cat(struct convert_rock *target, const char *s) { - struct convert_rock *input, *unfold; + struct convert_rock *input, *stripnl; int eatspace = 0; const char *start, *endcharset, *encoding, *end; int len; @@ -1745,7 +868,7 @@ input = table_init(0, target); /* note: we assume the caller of this function has already * determined that all newlines are followed by whitespace */ - unfold = unfold_init(input); + stripnl = stripnl_init(input); start = s; while ((start = (const char*) strchr(start, '=')) != 0) { @@ -1774,7 +897,7 @@ if (!eatspace) { len = start - s - 1; table_switch(input, 0); /* US_ASCII */ - convert_catn(unfold, s, len); + convert_catn(stripnl, s, len); } /* @@ -1784,7 +907,7 @@ charset = lookup_buf(start, endcharset-start); if (charset < 0) { /* Unrecognized charset, nothing will match here */ - convert_putc(input, U_REPLACEMENT); /* unknown character */ + convert_putc(input, 0xfffd); /* unknown character */ } else { struct convert_rock *extract; @@ -1813,11 +936,11 @@ /* Copy over the tail part of the input string */ if (*s) { table_switch(input, 0); /* US_ASCII */ - convert_cat(unfold, s); + convert_cat(stripnl, s); } /* just free these ones, the rest can be cleaned up by the sender */ - basic_free(unfold); + basic_free(stripnl); basic_free(input); } @@ -2010,7 +1133,7 @@ /* implement the loop here so we can check on the search each time */ for (i = 0; i < len; i++) { - convert_putc(input, (unsigned char)msg_basei); + convert_putc(input, msg_basei); if (search_havematch(tosearch)) break; } @@ -2022,19 +1145,16 @@ } /* This is based on charset_searchfile above. */ -EXPORTED int charset_extract(void (*cb)(const struct buf *, void *), - void *rock, - const struct buf *data, - int charset, int encoding, - const char *subtype, int flags) +EXPORTED int charset_extractitem(index_search_text_receiver_t receiver, + void *rock, int uid, + const char *msg_base, size_t len, + int charset, int encoding, int flags, + int rpart, int rcmd) { struct convert_rock *input, *tobuffer; struct buf *out; size_t i; - if (charset_debug) - fprintf(stderr, "charset_extract()\n"); - /* Initialize character set mapping */ if (charset < 0 || charset >= chartables_num_charsets) return 0; @@ -2043,18 +1163,6 @@ tobuffer = buffer_init(); input = uni_init(tobuffer); input = canon_init(flags, input); - - if (!strcmpsafe(subtype, "HTML")) { - if ((flags & CHARSET_SKIPHTML)) { - /* silently pretend we indexed it, but actually ignore it */ - convert_free(input); - return 1; - } - /* this is text/html data, so we can make ourselves useful by - * stripping html tags, css and js. */ - input = striphtml_init(input); - } - input = table_init(charset, input); switch (encoding) { @@ -2081,17 +1189,17 @@ /* point to the buffer for easy block sending */ out = (struct buf *)tobuffer->state; - for (i = 0; i < data->len; i++) { - convert_putc(input, (unsigned char)data->si); + for (i = 0; i < len; i++) { + convert_putc(input, msg_basei); /* process a block of output every so often */ if (buf_len(out) > 4096) { - cb(out, rock); + receiver(uid, rpart, rcmd, out->s, out->len, rock); buf_reset(out); } } if (out->len) { /* finish it */ - cb(out, rock); + receiver(uid, rpart, rcmd, out->s, out->len, rock); } convert_free(input); @@ -2099,6 +1207,16 @@ return 1; } +EXPORTED int charset_extractfile(index_search_text_receiver_t receiver, void *rock, + int uid, const char *msg_base, size_t len, + int charset, int encoding, int flags) +{ + return charset_extractitem(receiver, rock, uid, msg_base, len, + charset, encoding, flags, + SEARCHINDEX_PART_BODY, + SEARCHINDEX_CMD_APPENDPART); +} + /* * Decode the MIME body part (per RFC 2045) of @len bytes located at * @msg_base having the content transfer @encoding. Returns a pointer @@ -2230,41 +1348,3 @@ return (b64_len ? retval : NULL); } -/* returns a buffer which the caller must free */ -EXPORTED char *charset_encode_mimeheader(const char *header, size_t len) -{ - struct buf buf = BUF_INITIALIZER; - size_t n; - int need_quote = 0; - - if (!header) return NULL; - - if (!len) len = strlen(header); - - for (n = 0; n < len; n++) { - unsigned char this = headern; - if (QPSAFECHARthis || this == ' ') continue; - need_quote = 1; - break; - } - - if (need_quote) { - buf_printf(&buf, "=?UTF-8?Q?"); - for (n = 0; n < len; n++) { - unsigned char this = headern; - if (QPSAFECHARthis) { - buf_putc(&buf, (char)this); - } - else { - buf_printf(&buf, "=%02X", this); - } - } - buf_printf(&buf, "?="); - } - else { - buf_setmap(&buf, header, len); - } - - return buf_release(&buf); -} -
View file
cyrus-imapd-2.5.tar.gz/lib/charset.h
Changed
@@ -50,17 +50,12 @@ #define CHARSET_SKIPDIACRIT (1<<0) #define CHARSET_SKIPSPACE (1<<1) #define CHARSET_MERGESPACE (1<<2) -#define CHARSET_SKIPHTML (1<<3) -#define CHARSET_SNIPPET (1<<4) #define CHARSET_UNKNOWN_CHARSET (-1) -#include "util.h" - typedef int comp_pat; typedef int charset_index; -extern int encoding_lookupname(const char *name); extern const char *encoding_name(int); /* ensure up to MAXTRANSLATION times expansion into buf */ @@ -87,18 +82,57 @@ extern char *charset_to_utf8(const char *msg_base, size_t len, charset_index charset, int encoding); extern int charset_search_mimeheader(const char *substr, comp_pat *pat, const char *s, int flags); -extern char *charset_encode_mimeheader(const char *header, size_t len); +/* Definitions for charset_extractfile */ + +/* These constants are passed into the index_search_text_receiver_t callback to + tell it which part of the message is being sent down */ +#define SEARCHINDEX_PART_FROM 1 +#define SEARCHINDEX_PART_TO 2 +#define SEARCHINDEX_PART_CC 3 +#define SEARCHINDEX_PART_BCC 4 +#define SEARCHINDEX_PART_SUBJECT 5 +#define SEARCHINDEX_PART_HEADERS 6 /* headers OTHER than the above headers */ +#define SEARCHINDEX_PART_BODY 7 + +/* These constants tell the index_search_text_receiver_t callback what is happening. */ +#define SEARCHINDEX_CMD_BEGINPART 0x01 /* starting a new part */ +#define SEARCHINDEX_CMD_APPENDPART 0x02 /* recording some text that belongs to the part */ +#define SEARCHINDEX_CMD_ENDPART 0x04 /* done with the part */ +#define SEARCHINDEX_CMD_STUFFPART 0x07 /* All of the above in one invocation */ -/* Definitions for charset_extract */ +/* This function gets called at least once for each part of every message. + The invocations form a sequence: + CMD_BEGINPART <part1> + CMD_APPENDPART <part1, text, text_len> (1 or more times) + CMD_ENDPART <part1> + ... + CMD_BEGINPART <partN> + CMD_APPENDPART <partN, text, text_len> (1 or more times) + CMD_ENDPART <partN> + BEGIN, APPEND and/or END operations on the same part may be combined into one call by + ORing the 'cmds' flags. + + The parts need not arrive in any particular order, but each part + can only participate in one BEGIN ... APPEND ... END sequence, and + the sequences for different parts cannot be interleaved. +*/ +typedef void index_search_text_receiver_t(int UID, int part, int cmds, + char const* text, int text_len, void* rock); /* Extract the body text for the message denoted by 'uid', convert its - text to the canonical form for searching, and pass the converted text - down in a series of invocations of the callback 'cb'. This is - called by index_getsearchtext to extract the MIME body parts. */ -extern int charset_extract(void (*cb)(const struct buf *text, void *rock), - void *rock, - const struct buf *data, - charset_index charset, int encoding, - const char *subtype, int flags); + text to the canonical form for searching, and pass the converted + text down in a series of invocations to the callback function with + part=SEARCHINDEX_PART_BODY and cmds=CMD_APPENDPART. This is called + by index_getsearchtextmsg to extract the MIME body parts. */ +extern int charset_extractitem(index_search_text_receiver_t receiver, + void* rock, int uid, const char *msg_base, + size_t len, charset_index charset, + int encoding, int flags, + int rpart, int rcmd); +extern int charset_extractfile(index_search_text_receiver_t receiver, + void* rock, int uid, const char *msg_base, + size_t len, charset_index charset, + int encoding, int flags); + #endif /* INCLUDED_CHARSET_H */
View file
cyrus-imapd-2.5.tar.gz/lib/command.c
Changed
@@ -43,6 +43,7 @@ #include <config.h> #include <sys/types.h> +#include <stdio.h> #include <syslog.h> #include <stdlib.h> #ifdef HAVE_UNISTD_H @@ -50,16 +51,11 @@ #endif #include <errno.h> #include <sys/wait.h> -#include <unistd.h> #include "imap/imap_err.h" -#include "xmalloc.h" -#include "command.h" #include "signals.h" #include "strarray.h" -static int wait_for_child(const char *argv0, pid_t pid); - EXPORTED int run_command(const char *argv0, ...) { va_list va; @@ -90,146 +86,6 @@ } else { /* in parent */ - r = wait_for_child(argv0, pid); - } - -out: - strarray_fini(&argv); - return r; -} - -#define PIPE_READ 0 -#define PIPE_WRITE 1 - -EXPORTED int command_popen(struct command **cmdp, const char *mode, const char *argv0, ...) -{ - va_list va; - const char *p; - strarray_t argv = STRARRAY_INITIALIZER; - pid_t pid; - int r = 0; - struct command *cmd; - int do_stdin = (strchr(mode, 'w') != NULL); - int do_stdout = (strchr(mode, 'r') != NULL); - int stdin_pipe2 = { -1, -1 }; - int stdout_pipe2 = { -1, -1 }; - - strarray_append(&argv, argv0); - - va_start(va, argv0); - while ((p = va_arg(va, const char *))) - strarray_append(&argv, p); - va_end(va); - - if (do_stdin) { - r = pipe(stdin_pipe); - if (r) { - syslog(LOG_ERR, "Failed to pipe(): %m"); - r = IMAP_SYS_ERROR; - goto out; - } - } - - if (do_stdout) { - r = pipe(stdout_pipe); - if (r) { - syslog(LOG_ERR, "Failed to pipe(): %m"); - r = IMAP_SYS_ERROR; - goto out; - } - } - - pid = fork(); - if (pid < 0) { - syslog(LOG_ERR, "Failed to fork: %m"); - r = IMAP_SYS_ERROR; - goto out; - } - - if (!pid) { - /* in child */ - if (do_stdin) { - close(stdin_pipePIPE_WRITE); - dup2(stdin_pipePIPE_READ, STDIN_FILENO); - close(stdin_pipePIPE_READ); - } - if (do_stdout) { - close(stdout_pipePIPE_READ); - dup2(stdout_pipePIPE_WRITE, STDOUT_FILENO); - close(stdout_pipePIPE_WRITE); - } - - r = execv(argv0, argv.data); - syslog(LOG_ERR, "Failed to execute %s: %m", argv0); - exit(1); - } - - /* in parent */ - cmd = xzmalloc(sizeof(struct command)); - cmd->argv0 = xstrdup(argv0); - cmd->pid = pid; - if (do_stdin) - cmd->stdin_prot = prot_new(stdin_pipePIPE_WRITE, /*write*/1); - if (do_stdout) - cmd->stdout_prot = prot_new(stdout_pipePIPE_READ, /*write*/0); - *cmdp = cmd; - -out: - if (stdin_pipePIPE_READ >= 0) close(stdin_pipePIPE_READ); - if (stdout_pipePIPE_WRITE >= 0) close(stdout_pipePIPE_WRITE); - if (r) { - if (stdin_pipePIPE_WRITE >= 0) close(stdin_pipePIPE_WRITE); - if (stdout_pipePIPE_READ >= 0) close(stdout_pipePIPE_READ); - } - strarray_fini(&argv); - return r; -} - -EXPORTED int command_pclose(struct command **cmdp) -{ - struct command *cmd = (cmdp ? *cmdp : NULL); - int r; - - if (!cmd) return 0; - - if (cmd->stdin_prot) { - prot_flush(cmd->stdin_prot); - close(cmd->stdin_prot->fd); - prot_free(cmd->stdin_prot); - } - - if (cmd->stdout_prot) { - close(cmd->stdout_prot->fd); - prot_free(cmd->stdout_prot); - } - - r = wait_for_child(cmd->argv0, cmd->pid); - - free(cmd->argv0); - free(cmd); - *cmdp = NULL; - - return r; -} - -EXPORTED int command_done_stdin(struct command *cmd) -{ - int r = 0; - - if (cmd->stdin_prot) { - r = prot_flush(cmd->stdin_prot); - close(cmd->stdin_prot->fd); - prot_free(cmd->stdin_prot); - cmd->stdin_prot = NULL; - } - return r; -} - -static int wait_for_child(const char *argv0, pid_t pid) -{ - int r = 0; - - if (pid) { for (;;) { int status; pid_t pr = waitpid(pid, &status, 0); @@ -266,5 +122,7 @@ } } +out: + strarray_fini(&argv); return r; }
View file
cyrus-imapd-2.5.tar.gz/lib/command.h
Changed
@@ -49,18 +49,7 @@ #include <config.h> #include <sys/types.h> #include <stdarg.h> -#include "prot.h" - -struct command { - char *argv0; - pid_t pid; - struct protstream *stdin_prot; - struct protstream *stdout_prot; -}; extern int run_command(const char *argv0, ...); -extern int command_popen(struct command **cmdp, const char *mode, const char *argv0, ...); -extern int command_pclose(struct command **cmdp); -extern int command_done_stdin(struct command *); #endif /* INCLUDED_COMMAND_H */
View file
cyrus-imapd-2.5.tar.gz/lib/cyr_lock.h
Changed
@@ -58,15 +58,9 @@ extern int lock_reopen (int fd, const char *filename, struct stat *sbuf, const char **failaction); -extern int lock_setlock (int fd, int ex, int nb, const char *filename); +extern int lock_blocking (int fd, const char *filename); +extern int lock_shared (int fd, const char *filename); +extern int lock_nonblocking(int fd, const char *filename); extern int lock_unlock (int fd, const char *filename); -/* compatibility defines for the older API */ -#define lock_blocking(fd, fn) \ - lock_setlock((fd), /*exclusive*/1, /*blocking*/0, (fn)) -#define lock_nonblocking(fd, fn) \ - lock_setlock((fd), /*exclusive*/1, /*nonblocking*/1, (fn)) -#define lock_shared(fd, fn) \ - lock_setlock((fd), /*exclusive*/0, /*nonblocking*/0, (fn)) - #endif /* INCLUDED_LOCK_H */
View file
cyrus-imapd-2.5.tar.gz/lib/cyrusdb.c
Changed
@@ -316,7 +316,7 @@ } } -EXPORTED int cyrusdb_copyfile(const char *srcname, const char *dstname) +HIDDEN int cyrusdb_copyfile(const char *srcname, const char *dstname) { return cyrus_copyfile(srcname, dstname, COPYFILE_NOLINK); } @@ -348,16 +348,16 @@ } -EXPORTED int cyrusdb_dumpfile(struct db *db, - const char *prefix, size_t prefixlen, - FILE *f, - struct txn **tid) +int cyrusdb_dumpfile(struct db *db, + const char *prefix, size_t prefixlen, + FILE *f, + struct txn **tid) { return cyrusdb_foreach(db, prefix, prefixlen, NULL, print_cb, f, tid); } -EXPORTED int cyrusdb_truncate(struct db *db, - struct txn **tid) +int cyrusdb_truncate(struct db *db, + struct txn **tid) { struct db_rock tr; @@ -367,9 +367,9 @@ return cyrusdb_foreach(db, "", 0, NULL, delete_cb, &tr, tid); } -EXPORTED int cyrusdb_undumpfile(struct db *db, - FILE *f, - struct txn **tid) +int cyrusdb_undumpfile(struct db *db, + FILE *f, + struct txn **tid) { struct buf line = BUF_INITIALIZER; const char *tab;
View file
cyrus-imapd-2.5.tar.gz/lib/cyrusdb.h
Changed
@@ -217,15 +217,15 @@ extern int cyrusdb_convert(const char *fromfname, const char *tofname, const char *frombackend, const char *tobackend); -extern int cyrusdb_dumpfile(struct db *db, - const char *prefix, size_t prefixlen, - FILE *f, - struct txn **tid); -extern int cyrusdb_truncate(struct db *db, - struct txn **tid); -extern int cyrusdb_undumpfile(struct db *db, - FILE *f, - struct txn **tid); +int cyrusdb_dumpfile(struct db *db, + const char *prefix, size_t prefixlen, + FILE *f, + struct txn **tid); +int cyrusdb_truncate(struct db *db, + struct txn **tid); +int cyrusdb_undumpfile(struct db *db, + FILE *f, + struct txn **tid); extern const char *cyrusdb_detect(const char *fname);
View file
cyrus-imapd-2.5.tar.gz/lib/cyrusdb_flat.c
Changed
@@ -475,7 +475,7 @@ static int foreach(struct dbengine *db, const char *prefix, size_t prefixlen, foreach_p *goodp, - foreach_cb *cb, void *rock, + foreach_cb *cb, void *rock, struct txn **mytid) { int r = CYRUSDB_OK; @@ -502,20 +502,19 @@ r = starttxn_or_refetch(db, mytid); if (r) return r; - if (!mytid) { + if(!mytid) { /* No transaction, use the fast method to avoid stomping on our * memory map if changes happen */ dbfd = dup(db->fd); if(dbfd == -1) return CYRUSDB_IOERROR; - + map_refresh(dbfd, 1, &dbbase, &dblen, db->size, db->fname, 0); /* drop our read lock on the file, since we don't really care * if it gets replaced out from under us, our mmap stays on the * old version */ lock_unlock(db->fd, db->fname); - } - else { + } else { /* use the same variables as in the no transaction case, just to * get things set up */ dbbase = db->base; @@ -525,16 +524,15 @@ if (prefix) { encode(prefix, prefixlen, &prefixbuf); offset = bsearch_mem_mbox(prefixbuf.s, dbbase, db->size, 0, &len); - } - else { + } else { offset = 0; } - + p = dbbase + offset; pend = dbbase + db->size; while (p < pend) { - if (!dontmove) { + if(!dontmove) { GETENTRY(p) } else dontmove = 0; @@ -545,7 +543,7 @@ if (!goodp || goodp(rock, keybuf.s, keybuf.len, DATA(db), DATALEN(db))) { unsigned long ino = db->ino; - unsigned long sz = db->size; + unsigned long sz = db->size; if(mytid) { /* transaction present, this means we do the slow way */ @@ -556,7 +554,7 @@ r = cb(rock, keybuf.s, keybuf.len, DATA(db), DATALEN(db)); if (r) break; - if (mytid) { + if(mytid) { /* reposition? (we made a change) */ if (!(ino == db->ino && sz == db->size)) { /* something changed in the file; reseek */ @@ -564,28 +562,27 @@ offset = bsearch_mem_mbox(savebuf.s, db->base, db->size, 0, &len); p = db->base + offset; - + GETENTRY(p); - + /* 'key' might not equal 'savebuf'. if it's different, we want to stay where we are. if it's the same, we should move on to the next one */ if (!buf_cmp(&savebuf, &keybuf)) { p = dataend + 1; - } - else { + } else { /* 'savebuf' got deleted, so we're now pointing at the right thing */ dontmove = 1; } - } + } } } p = dataend + 1; } - if (!mytid) { + if(!mytid) { /* cleanup the fast method */ map_free(&dbbase, &dblen); close(dbfd);
View file
cyrus-imapd-2.5.tar.gz/lib/cyrusdb_twoskip.c
Changed
@@ -1846,7 +1846,6 @@ static int dump(struct dbengine *db, int detail __attribute__((unused))) { struct skiprecord record; - struct buf scratch = BUF_INITIALIZER; size_t offset = DUMMY_OFFSET; int r = 0; int i; @@ -1879,12 +1878,10 @@ case RECORD: case DUMMY: - buf_setmap(&scratch, KEY(db, &record), record.keylen); - buf_replace_char(&scratch, '\0', '-'); - printf("%s kl=%llu dl=%llu lvl=%d (%s)\n", + printf("%s kl=%llu dl=%llu lvl=%d (%.*s)\n", (record.type == RECORD ? "RECORD" : "DUMMY"), (LLU)record.keylen, (LLU)record.vallen, - record.level, buf_cstring(&scratch)); + record.level, (int)record.keylen, KEY(db, &record)); printf("\t"); for (i = 0; i <= record.level; i++) { printf("%08llX ", (LLU)record.nextloci); @@ -1898,8 +1895,6 @@ offset += record.len; } - buf_free(&scratch); - return r; }
View file
cyrus-imapd-2.5.tar.gz/lib/hash.c
Changed
@@ -313,19 +313,3 @@ } } -EXPORTED int hash_numrecords(hash_table *table) -{ - unsigned i; - bucket *temp; - int count = 0; - - for (i = 0; i < table->size; i++) { - temp = (table->table)i; - while (temp) { - count++; - temp = temp->next; - } - } - - return count; -}
View file
cyrus-imapd-2.5.tar.gz/lib/hash.h
Changed
@@ -77,10 +77,6 @@ void hash_enumerate(hash_table *table,void (*func)(const char *,void *,void *), void *rock); -/* counts the number of nodes in the hash table */ - -int hash_numrecords(hash_table *table); - /* ** Frees a hash table. For each node that was inserted in the table, ** it calls the function whose address it was passed, with a pointer
View file
cyrus-imapd-2.5.tar.gz/lib/hashu64.c
Changed
@@ -42,7 +42,7 @@ return (a < b ? -1 : (a > b ? 1 : 0)); } -EXPORTED hashu64_table *construct_hashu64_table(hashu64_table *table, size_t size, int use_mpool) +hashu64_table *construct_hashu64_table(hashu64_table *table, size_t size, int use_mpool) { assert(table); assert(size); @@ -74,7 +74,7 @@ ** or, if there was already an entry for @key, the old data pointer. */ -EXPORTED void *hashu64_insert(uint64_t key, void *data, hashu64_table *table) +void *hashu64_insert(uint64_t key, void *data, hashu64_table *table) { unsigned val = key % table->size; bucketu64 *ptr, *newptr; @@ -155,7 +155,7 @@ ** the key is not in the table. */ -EXPORTED void *hashu64_lookup(uint64_t key, hashu64_table *table) +void *hashu64_lookup(uint64_t key, hashu64_table *table) { unsigned val = key % table->size; bucketu64 *ptr; @@ -180,7 +180,7 @@ */ /* Warning: use this function judiciously if you are using memory pools, * since it will leak memory until you get rid of the entire hash table */ -EXPORTED void *hashu64_del(uint64_t key, hashu64_table *table) +void *hashu64_del(uint64_t key, hashu64_table *table) { unsigned val = key % table->size; void *data; @@ -252,7 +252,7 @@ ** it. */ -EXPORTED void free_hashu64_table(hashu64_table *table, void (*func)(void *)) +void free_hashu64_table(hashu64_table *table, void (*func)(void *)) { unsigned i; bucketu64 *ptr, *temp; @@ -293,9 +293,8 @@ ** node in the table, passing it the key, the associated data and 'rock'. */ -EXPORTED void hashu64_enumerate(hashu64_table *table, - void (*func)(uint64_t, void *, void *), - void *rock) +void hashu64_enumerate(hashu64_table *table, void (*func)(uint64_t, void *, void *), + void *rock) { unsigned i; bucketu64 *temp, *temp_next;
View file
cyrus-imapd-2.5.tar.gz/lib/htmlchar.c
Deleted
@@ -1,5097 +0,0 @@ -/* ANSI-C code produced by gperf version 3.0.3 */ -/* Command-line: gperf /tmp/compile_st_9xQvtm.gperf */ -/* Computed positions: -k'1-7,10,12,$' */ - -#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ - && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ - && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ - && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ - && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ - && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ - && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ - && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ - && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ - && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ - && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ - && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ - && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ - && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ - && ('Z' == 90) && ('' == 91) && ('\\' == 92) && ('' == 93) \ - && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ - && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ - && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ - && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ - && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ - && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ - && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ - && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) -/* The character set is not based on ISO-646. */ -#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>." -#endif - - -/* Automatically generated by compile_st.pl, do not edit */ -#include "htmlchar.h" -struct htmlchar_desc { const char *name; int value; }; -#include <string.h> - -#define TOTAL_KEYWORDS 2125 -#define MIN_WORD_LENGTH 2 -#define MAX_WORD_LENGTH 31 -#define MIN_HASH_VALUE 64 -#define MAX_HASH_VALUE 18060 -/* maximum key range = 17997, duplicates = 0 */ - -#ifdef __GNUC__ -__inline -#else -#ifdef __cplusplus -inline -#endif -#endif -static unsigned int -hash (register const char *str, register unsigned int len) -{ - static const unsigned short asso_values = - { - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 5, - 5, 20, 0, 10, 45, 18061, 40, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 1595, 575, 4030, 2480, 1300, - 1045, 624, 1335, 1355, 75, 210, 165, 600, 800, 120, - 1430, 60, 140, 1065, 395, 290, 135, 25, 30, 390, - 90, 18061, 0, 18061, 0, 18061, 18061, 3316, 45, 2743, - 675, 15, 250, 2210, 3065, 310, 1755, 4806, 5, 970, - 40, 1375, 130, 265, 0, 10, 20, 35, 175, 1225, - 3598, 4421, 115, 420, 695, 0, 0, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, - 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061, 18061 - }; - register int hval = len; - - switch (hval) - { - default: - hval += asso_values(unsigned char)str11; - /*FALLTHROUGH*/ - case 11: - case 10: - hval += asso_values(unsigned char)str9; - /*FALLTHROUGH*/ - case 9: - case 8: - case 7: - hval += asso_values(unsigned char)str6; - /*FALLTHROUGH*/ - case 6: - hval += asso_values(unsigned char)str5; - /*FALLTHROUGH*/ - case 5: - hval += asso_values(unsigned char)str4+1; - /*FALLTHROUGH*/ - case 4: - hval += asso_values(unsigned char)str3+3; - /*FALLTHROUGH*/ - case 3: - hval += asso_values(unsigned char)str2+1; - /*FALLTHROUGH*/ - case 2: - hval += asso_values(unsigned char)str1+4; - /*FALLTHROUGH*/ - case 1: - hval += asso_values(unsigned char)str0; - break; - } - return hval + asso_values(unsigned char)strlen - 1; -} - -#ifdef __GNUC__ -__inline -#ifdef __GNUC_STDC_INLINE__ -__attribute__ ((__gnu_inline__)) -#endif -#endif -const struct htmlchar_desc * -__htmlchar_lookup (register const char *str, register unsigned int len) -{ - static const struct htmlchar_desc wordlist = - { - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"rarr", 0x2192}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"larr", 0x2190}, - {"", 0}, {"", 0}, {"", 0}, - {"npr", 0x2280}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lat", 0x2AAB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"uarr", 0x2191}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rpar", 0x0029}, - {"roarr", 0x21FE}, - {"", 0}, {"", 0}, - {"not", 0x00AC}, - {"lpar", 0x0028}, - {"loarr", 0x21FD}, - {"rarrtl", 0x21A3}, - {"", 0}, - {"bot", 0x22A5}, - {"spar", 0x2225}, - {"", 0}, - {"larrtl", 0x21A2}, - {"", 0}, {"", 0}, - {"epar", 0x22D5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"tprime", 0x2034}, - {"rotimes", 0x2A35}, - {"", 0}, {"", 0}, - {"uharr", 0x21BE}, - {"", 0}, - {"lotimes", 0x2A34}, - {"", 0}, {"", 0}, - {"nharr", 0x21AE}, - {"", 0}, - {"ll", 0x226A}, - {"", 0}, - {"npar", 0x2226}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"eparsl", 0x29E3}, - {"el", 0x2A99}, - {"", 0}, {"", 0}, {"", 0}, - {"bprime", 0x2035}, - {"", 0}, - {"par", 0x2225}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nparsl", 0x2AFD20E5}, - {"", 0}, - {"els", 0x2A95}, - {"ensp", 0x2002}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lnap", 0x2A89}, - {"blk14", 0x2591}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"npart", 0x22020338}, - {"", 0}, {"", 0}, {"", 0}, - {"Rarr", 0x21A0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nparallel", 0x2226}, - {"blk12", 0x2592}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nldr", 0x2025}, - {"rlarr", 0x21C4}, - {"", 0}, {"", 0}, - {"nlt", 0x226E}, - {"Larr", 0x219E}, - {"llarr", 0x21C7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"slarr", 0x2190}, - {"", 0}, {"", 0}, {"", 0}, - {"varr", 0x2195}, - {"blk34", 0x2593}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"tau", 0x03C4}, - {"rHar", 0x2964}, - {"", 0}, - {"Rarrtl", 0x2916}, - {"", 0}, {"", 0}, - {"lHar", 0x2962}, - {"", 0}, {"", 0}, {"", 0}, - {"squ", 0x25A1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nlarr", 0x219A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rrarr", 0x21C9}, - {"", 0}, {"", 0}, - {"lne", 0x2A87}, - {"", 0}, - {"lrarr", 0x21C6}, - {"rharul", 0x296C}, - {"", 0}, {"", 0}, {"", 0}, - {"srarr", 0x2192}, - {"lharul", 0x296A}, - {"", 0}, {"", 0}, - {"uHar", 0x2963}, - {"erarr", 0x2971}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"Or", 0x2A54}, - {"", 0}, {"", 0}, - {"rharu", 0x21C0}, - {"", 0}, - {"Ll", 0x22D8}, - {"", 0}, {"", 0}, - {"lharu", 0x21BC}, - {"", 0}, - {"pr", 0x227A}, - {"nGt", 0x226B20D2}, - {"", 0}, - {"nrarr", 0x219B}, - {"", 0}, {"", 0}, - {"bne", 0x003D20E5}, - {"", 0}, - {"upuparrows", 0x21C8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"emsp14", 0x2005}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"le", 0x2264}, - {"smt", 0x2AAA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rarrpl", 0x2945}, - {"ee", 0x2147}, - {"", 0}, - {"nvap", 0x224D20D2}, - {"rbarr", 0x290D}, - {"larrpl", 0x2939}, - {"", 0}, - {"les", 0x2A7D}, - {"", 0}, - {"lbarr", 0x290C}, - {"", 0}, - {"in", 0x2208}, - {"", 0}, - {"Uarr", 0x219F}, - {"napos", 0x0149}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"emsp13", 0x2004}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ne", 0x2260}, - {"int", 0x222B}, - {"", 0}, {"", 0}, - {"rarrap", 0x2975}, - {"", 0}, {"", 0}, - {"epsi", 0x03B5}, - {"", 0}, - {"qprime", 0x2057}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"upsi", 0x03C5}, - {"", 0}, - {"phmmat", 0x2133}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rbrkslu", 0x2990}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lbrkslu", 0x298D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"searr", 0x2198}, - {"", 0}, {"", 0}, - {"lap", 0x2A85}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nedot", 0x22500338}, - {"", 0}, {"", 0}, - {"top", 0x22A4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nle", 0x2270}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"emsp", 0x2003}, - {"nearr", 0x2197}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nap", 0x2249}, - {"nbsp", 0x00A0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"Re", 0x211C}, - {"", 0}, - {"Vbar", 0x2AEB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"topbot", 0x2336}, - {"", 0}, - {"looparrowleft", 0x21AB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"prap", 0x2AB7}, - {"sharp", 0x266F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"seswar", 0x2929}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Verbar", 0x2016}, - {"", 0}, {"", 0}, {"", 0}, - {"swarr", 0x2199}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nprcue", 0x22E0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"lneq", 0x2A87}, - {"lneqq", 0x2268}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"loz", 0x25CA}, - {"", 0}, - {"nwarr", 0x2196}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"verbar", 0x007C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"there4", 0x2234}, - {"", 0}, - {"blacktriangle", 0x25B4}, - {"", 0}, - {"rbrke", 0x298C}, - {"", 0}, {"", 0}, - {"pre", 0x2AAF}, - {"", 0}, - {"lbrke", 0x298B}, - {"", 0}, - {"blacktriangleleft", 0x25C2}, - {"blacktriangleright", 0x25B8}, - {"prop", 0x221D}, - {"", 0}, {"", 0}, {"", 0}, - {"leq", 0x2264}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"emacr", 0x0113}, - {"", 0}, - {"LT", 0x003C}, - {"", 0}, - {"perp", 0x22A5}, - {"", 0}, {"", 0}, - {"blacktriangledown", 0x25BE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"smeparsl", 0x29E4}, - {"leqq", 0x2266}, - {"vprop", 0x221D}, - {"", 0}, {"", 0}, {"", 0}, - {"nles", 0x2A7D0338}, - {"umacr", 0x016B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"Tau", 0x03A4}, - {"plus", 0x002B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nless", 0x226E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Upsi", 0x03D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"intcal", 0x22BA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nhArr", 0x21CE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"semi", 0x003B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Omacr", 0x014C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"plusdu", 0x2A25}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nleq", 0x2270}, - {"nleqq", 0x22660338}, - {"", 0}, {"", 0}, - {"Vee", 0x22C1}, - {"", 0}, - {"fltns", 0x25B1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"frac34", 0x00BE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"frac14", 0x00BC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"plusmn", 0x00B1}, - {"", 0}, {"", 0}, - {"darr", 0x2193}, - {"rnmid", 0x2AEE}, - {"frac12", 0x00BD}, - {"", 0}, - {"dot", 0x02D9}, - {"", 0}, {"", 0}, - {"frac35", 0x2157}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"frac15", 0x2155}, - {"", 0}, - {"vee", 0x2228}, - {"", 0}, {"", 0}, - {"frac45", 0x2158}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"fpartint", 0x2A0D}, - {"", 0}, {"", 0}, - {"frac25", 0x2156}, - {"", 0}, {"", 0}, - {"lvertneqq", 0x2268FE00}, - {"", 0}, - {"frac13", 0x2153}, - {"", 0}, {"", 0}, {"", 0}, - {"dharr", 0x21C2}, - {"blacksquare", 0x25AA}, - {"", 0}, {"", 0}, {"", 0}, - {"rhard", 0x21C1}, - {"prurel", 0x22B0}, - {"", 0}, {"", 0}, - {"squf", 0x25AA}, - {"lhard", 0x21BD}, - {"frac23", 0x2154}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nlArr", 0x21CD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"phone", 0x260E}, - {"veebar", 0x22BB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"frac38", 0x215C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"frac18", 0x215B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"frac16", 0x2159}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bernou", 0x212C}, - {"", 0}, {"", 0}, - {"ropf", 0xD563}, - {"nrArr", 0x21CF}, - {"", 0}, {"", 0}, {"", 0}, - {"lopf", 0xD55D}, - {"", 0}, - {"frac78", 0x215E}, - {"", 0}, {"", 0}, - {"sopf", 0xD564}, - {"", 0}, - {"frac58", 0x215D}, - {"", 0}, {"", 0}, - {"eopf", 0xD556}, - {"", 0}, {"", 0}, - {"between", 0x226C}, - {"", 0}, - {"topf", 0xD565}, - {"", 0}, - {"frac56", 0x215A}, - {"", 0}, {"", 0}, - {"Wopf", 0xD54E}, - {"Umacr", 0x016A}, - {"", 0}, {"", 0}, - {"Not", 0x2AEC}, - {"Xopf", 0xD54F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"uopf", 0xD566}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nopf", 0xD55F}, - {"pluse", 0x2A72}, - {"", 0}, {"", 0}, - {"llcorner", 0x231E}, - {"bopf", 0xD553}, - {"imacr", 0x012B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"nbumpe", 0x224F0338}, - {"", 0}, {"", 0}, - {"Qopf", 0x211A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ulcorner", 0x231C}, - {"Jopf", 0xD541}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"dHar", 0x2965}, - {"", 0}, {"", 0}, {"", 0}, - {"lrcorner", 0x231F}, - {"Zopf", 0x2124}, - {"", 0}, - {"plankv", 0x210F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ulcorn", 0x231C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"square", 0x25A1}, - {"", 0}, {"", 0}, - {"fnof", 0x0192}, - {"seArr", 0x21D8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"zopf", 0xD56B}, - {"", 0}, {"", 0}, {"", 0}, - {"urcorner", 0x231D}, - {"Oopf", 0xD546}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ropar", 0x2986}, - {"", 0}, {"", 0}, {"", 0}, - {"popf", 0xD561}, - {"lopar", 0x2985}, - {"", 0}, {"", 0}, {"", 0}, - {"Vopf", 0xD54D}, - {"neArr", 0x21D7}, - {"", 0}, {"", 0}, {"", 0}, - {"Ropf", 0x211D}, - {"", 0}, - {"urcorn", 0x231D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nbump", 0x224E0338}, - {"", 0}, {"", 0}, - {"sol", 0x002F}, - {"lozf", 0x29EB}, - {"nhpar", 0x2AF2}, - {"", 0}, {"", 0}, {"", 0}, - {"Lopf", 0xD543}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Map", 0x2905}, - {"vopf", 0xD567}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"napid", 0x224B0338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"iprod", 0x2A3C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nis", 0x22FC}, - {"", 0}, - {"lnsim", 0x22E6}, - {"", 0}, {"", 0}, {"", 0}, - {"Kopf", 0xD542}, - {"", 0}, - {"GT", 0x003E}, - {"", 0}, {"", 0}, - {"solb", 0x29C4}, - {"swArr", 0x21D9}, - {"solbar", 0x233F}, - {"", 0}, - {"profsurf", 0x2313}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"smashp", 0x2A33}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nwArr", 0x21D6}, - {"", 0}, {"", 0}, {"", 0}, - {"fopf", 0xD557}, - {"eqsim", 0x2242}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"uharl", 0x21BF}, - {"", 0}, {"", 0}, {"", 0}, - {"qopf", 0xD562}, - {"thkap", 0x2248}, - {"", 0}, - {"simrarr", 0x2972}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lparlt", 0x2993}, - {"mp", 0x2213}, - {"ell", 0x2113}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Uopf", 0xD54C}, - {"rlhar", 0x21CC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rBarr", 0x290F}, - {"", 0}, {"", 0}, - {"rlm", 0x200F}, - {"", 0}, - {"lBarr", 0x290E}, - {"varphi", 0x03D5}, - {"", 0}, {"", 0}, - {"iopf", 0xD55A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"mldr", 0x2026}, - {"", 0}, {"", 0}, - {"bnequiv", 0x226120E5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lrhar", 0x21CB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"varsubsetneq", 0x228AFE00}, - {"varsubsetneqq", 0x2ACBFE00}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lrm", 0x200E}, - {"", 0}, - {"nvsim", 0x223C20D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nlsim", 0x2274}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Yopf", 0xD550}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Topf", 0xD54B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotSubset", 0x228220D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"NotLess", 0x226E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotLessLess", 0x226A0338}, - {"", 0}, {"", 0}, - {"imof", 0x22B7}, - {"", 0}, - {"ulcrop", 0x230F}, - {"varsupsetneq", 0x228BFE00}, - {"varsupsetneqq", 0x2ACCFE00}, - {"", 0}, - {"pound", 0x00A3}, - {"", 0}, {"", 0}, - {"uml", 0x00A8}, - {"", 0}, - {"RBarr", 0x2910}, - {"", 0}, - {"NotLessTilde", 0x2274}, - {"", 0}, - {"NotLessGreater", 0x2278}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rsqb", 0x005D}, - {"", 0}, - {"marker", 0x25AE}, - {"NotLessEqual", 0x2270}, - {"", 0}, - {"lsqb", 0x005B}, - {"", 0}, - {"rarrlp", 0x21AC}, - {"", 0}, {"", 0}, - {"LessTilde", 0x2272}, - {"lesseqqgtr", 0x2A8B}, - {"larrlp", 0x21AB}, - {"", 0}, {"", 0}, - {"vBar", 0x2AE8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"parsl", 0x2AFD}, - {"", 0}, - {"Xi", 0x039E}, - {"LessLess", 0x2AA1}, - {"", 0}, - {"esdot", 0x2250}, - {"urcrop", 0x230E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ni", 0x220B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"prsim", 0x227E}, - {"NotSuperset", 0x228320D2}, - {"", 0}, {"", 0}, - {"rArr", 0x21D2}, - {"", 0}, {"", 0}, {"", 0}, - {"eqvparsl", 0x29E5}, - {"lArr", 0x21D0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Bernoullis", 0x212C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"pm", 0x00B1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"backprime", 0x2035}, - {"", 0}, {"", 0}, - {"wp", 0x2118}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"map", 0x21A6}, - {"uArr", 0x21D1}, - {"", 0}, {"", 0}, - {"NotNestedLessLess", 0x2AA10338}, - {"", 0}, {"", 0}, - {"nesim", 0x22420338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rAarr", 0x21DB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lAarr", 0x21DA}, - {"", 0}, - {"wr", 0x2240}, - {"", 0}, - {"part", 0x2202}, - {"", 0}, {"", 0}, {"", 0}, - {"Hat", 0x005E}, - {"", 0}, {"", 0}, - {"squarf", 0x25AA}, - {"pi", 0x03C0}, - {"Int", 0x222C}, - {"Bopf", 0xD539}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bnot", 0x2310}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Mopf", 0xD544}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Gopf", 0xD53E}, - {"", 0}, - {"epsiv", 0x03F5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"opar", 0x29B7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nsub", 0x2284}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"notin", 0x2209}, - {"brvbar", 0x00A6}, - {"", 0}, - {"rho", 0x03C1}, - {"dopf", 0xD555}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"vArr", 0x21D5}, - {"", 0}, {"", 0}, {"", 0}, - {"nLt", 0x226A20D2}, - {"", 0}, {"", 0}, - {"easter", 0x2A6E}, - {"nsupset", 0x228320D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nsubset", 0x228220D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"erDot", 0x2253}, - {"", 0}, - {"or", 0x2228}, - {"", 0}, {"", 0}, - {"simne", 0x2246}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"eqslantgtr", 0x2A96}, - {"", 0}, {"", 0}, - {"olt", 0x29C0}, - {"bNot", 0x2AED}, - {"ratio", 0x2236}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"dlcorn", 0x231E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"eqslantless", 0x2A95}, - {"", 0}, {"", 0}, - {"nsup", 0x2285}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"preceq", 0x2AAF}, - {"ii", 0x2148}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"olarr", 0x21BA}, - {"", 0}, - {"pluscir", 0x2A22}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Pr", 0x2ABB}, - {"", 0}, {"", 0}, - {"thorn", 0x00FE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"wedbar", 0x2A5F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"drcorn", 0x231F}, - {"", 0}, {"", 0}, {"", 0}, - {"frasl", 0x2044}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotReverseElement", 0x220C}, - {"rppolint", 0x2A12}, - {"", 0}, - {"equiv", 0x2261}, - {"", 0}, {"", 0}, {"", 0}, - {"Nopf", 0x2115}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"orarr", 0x21BB}, - {"rbrace", 0x007D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lbrace", 0x007B}, - {"", 0}, - {"Rho", 0x03A1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"flat", 0x266D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rbrksld", 0x298E}, - {"", 0}, {"", 0}, {"", 0}, - {"sqcaps", 0x2293FE00}, - {"lbrksld", 0x298F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nsqsube", 0x22E2}, - {"", 0}, - {"macr", 0x00AF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Vert", 0x2016}, - {"", 0}, - {"incare", 0x2105}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"forkv", 0x2AD9}, - {"", 0}, {"", 0}, {"", 0}, - {"oror", 0x2A56}, - {"nsupe", 0x2289}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nsube", 0x2288}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"notinvb", 0x22F7}, - {"", 0}, - {"real", 0x211C}, - {"dharl", 0x21C3}, - {"", 0}, {"", 0}, {"", 0}, - {"vert", 0x007C}, - {"", 0}, {"", 0}, - {"nvrtrie", 0x22B520D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"dotplus", 0x2214}, - {"measuredangle", 0x2221}, - {"", 0}, - {"reals", 0x211D}, - {"", 0}, {"", 0}, - {"rfr", 0xD52F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lfr", 0xD529}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sfr", 0xD530}, - {"nsupseteq", 0x2289}, - {"", 0}, {"", 0}, - {"nsqsupe", 0x22E3}, - {"efr", 0xD522}, - {"nsubseteq", 0x2288}, - {"", 0}, {"", 0}, {"", 0}, - {"tfr", 0xD531}, - {"", 0}, {"", 0}, - {"barwed", 0x2305}, - {"", 0}, - {"Wfr", 0xD51A}, - {"", 0}, - {"sqcap", 0x2293}, - {"", 0}, {"", 0}, - {"Xfr", 0xD51B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ufr", 0xD532}, - {"", 0}, - {"GreaterLess", 0x2277}, - {"preccurlyeq", 0x227C}, - {"", 0}, - {"nfr", 0xD52B}, - {"mopf", 0xD55E}, - {"", 0}, {"", 0}, {"", 0}, - {"bfr", 0xD51F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"GreaterTilde", 0x2273}, - {"", 0}, - {"GreaterGreater", 0x2AA2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Qfr", 0xD514}, - {"", 0}, {"", 0}, - {"GreaterEqual", 0x2265}, - {"", 0}, - {"parallel", 0x2225}, - {"NotDoubleVerticalBar", 0x2226}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"GreaterEqualLess", 0x22DB}, - {"", 0}, {"", 0}, - {"Jfr", 0xD50D}, - {"", 0}, {"", 0}, {"", 0}, - {"NotNestedGreaterGreater", 0x2AA20338}, - {"", 0}, {"", 0}, {"", 0}, - {"roplus", 0x2A2E}, - {"", 0}, - {"NotLeftTriangleBar", 0x29CF0338}, - {"", 0}, - {"diams", 0x2666}, - {"loplus", 0x2A2D}, - {"", 0}, - {"Zfr", 0x2128}, - {"", 0}, - {"NotLeftTriangleEqual", 0x22EC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotLeftTriangle", 0x22EA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Emacr", 0x0112}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"zfr", 0xD537}, - {"Fopf", 0xD53D}, - {"", 0}, {"", 0}, {"", 0}, - {"Ofr", 0xD512}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"equals", 0x003D}, - {"", 0}, - {"pfr", 0xD52D}, - {"", 0}, {"", 0}, - {"thetav", 0x03D1}, - {"", 0}, - {"Vfr", 0xD519}, - {"Sopf", 0xD54A}, - {"setmn", 0x2216}, - {"becaus", 0x2235}, - {"", 0}, - {"Rfr", 0x211C}, - {"", 0}, {"", 0}, - {"dlcrop", 0x230D}, - {"", 0}, - {"die", 0x00A8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"mlcp", 0x2ADB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"because", 0x2235}, - {"", 0}, {"", 0}, - {"Imacr", 0x012A}, - {"forall", 0x2200}, - {"", 0}, - {"Lfr", 0xD50F}, - {"", 0}, {"", 0}, {"", 0}, - {"blacklozenge", 0x29EB}, - {"emptyset", 0x2205}, - {"rscr", 0xD4C7}, - {"", 0}, - {"period", 0x002E}, - {"", 0}, - {"vfr", 0xD533}, - {"lscr", 0xD4C1}, - {"", 0}, {"", 0}, - {"lessdot", 0x22D6}, - {"", 0}, - {"sscr", 0xD4C8}, - {"omacr", 0x014D}, - {"", 0}, {"", 0}, {"", 0}, - {"escr", 0x212F}, - {"", 0}, - {"drcrop", 0x230C}, - {"Proportional", 0x221D}, - {"", 0}, - {"tscr", 0xD4C9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Wscr", 0xD4B2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Xscr", 0xD4B3}, - {"", 0}, - {"swnwar", 0x292A}, - {"", 0}, {"", 0}, - {"uscr", 0xD4CA}, - {"", 0}, {"", 0}, {"", 0}, - {"Kfr", 0xD50E}, - {"nscr", 0xD4C3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bscr", 0xD4B7}, - {"Proportion", 0x2237}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sim", 0x223C}, - {"", 0}, {"", 0}, - {"Square", 0x25A1}, - {"", 0}, {"", 0}, - {"Qscr", 0xD4AC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nsmid", 0x2224}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Jscr", 0xD4A5}, - {"", 0}, {"", 0}, {"", 0}, - {"ffr", 0xD523}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"dArr", 0x21D3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Zscr", 0xD4B5}, - {"", 0}, - {"nltrie", 0x22EC}, - {"", 0}, - {"qfr", 0xD52E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"nsupseteqq", 0x2AC60338}, - {"LessGreater", 0x2276}, - {"", 0}, {"", 0}, {"", 0}, - {"nsubseteqq", 0x2AC50338}, - {"", 0}, {"", 0}, {"", 0}, - {"zscr", 0xD4CF}, - {"", 0}, {"", 0}, {"", 0}, - {"Ufr", 0xD518}, - {"Oscr", 0xD4AA}, - {"", 0}, {"", 0}, - {"simplus", 0x2A24}, - {"", 0}, - {"wopf", 0xD568}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"pscr", 0xD4C5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Vscr", 0xD4B1}, - {"", 0}, - {"nrtrie", 0x22ED}, - {"", 0}, - {"ifr", 0xD526}, - {"Rscr", 0x211B}, - {"operp", 0x29B9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"profline", 0x2312}, - {"", 0}, - {"frown", 0x2322}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"target", 0x2316}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Lscr", 0x2112}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"vscr", 0xD4CB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"percnt", 0x0025}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ordf", 0x00AA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Eopf", 0xD53C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Element", 0x2208}, - {"", 0}, - {"Kscr", 0xD4A6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Yfr", 0xD51C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Tfr", 0xD517}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Amacr", 0x0100}, - {"", 0}, {"", 0}, {"", 0}, - {"Hopf", 0x210D}, - {"", 0}, {"", 0}, {"", 0}, - {"shortmid", 0x2223}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"fscr", 0xD4BB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Iopf", 0xD540}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"olcir", 0x29BE}, - {"nvHarr", 0x2904}, - {"", 0}, - {"phi", 0x03C6}, - {"qscr", 0xD4C6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"therefore", 0x2234}, - {"Gammad", 0x03DC}, - {"", 0}, {"", 0}, {"", 0}, - {"oopf", 0xD560}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nspar", 0x2226}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Uscr", 0xD4B0}, - {"", 0}, {"", 0}, {"", 0}, - {"elinters", 0x23E7}, - {"rect", 0x25AD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"emptyv", 0x2205}, - {"", 0}, - {"ord", 0x2A5D}, - {"sect", 0x00A7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"iscr", 0xD4BE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Popf", 0x2119}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lltri", 0x25FA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"varpi", 0x03D6}, - {"female", 0x2640}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ssmile", 0x2323}, - {"", 0}, - {"And", 0x2A53}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rarrfs", 0x291E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"larrfs", 0x291D}, - {"", 0}, {"", 0}, - {"QUOT", 0x0022}, - {"ultri", 0x25F8}, - {"topcir", 0x2AF1}, - {"", 0}, {"", 0}, {"", 0}, - {"nltri", 0x22EA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lrtri", 0x22BF}, - {"Barwed", 0x2306}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Yscr", 0xD4B4}, - {"", 0}, - {"thinsp", 0x2009}, - {"", 0}, - {"setminus", 0x2216}, - {"Tscr", 0xD4AF}, - {"nleftrightarrow", 0x21AE}, - {"", 0}, - {"rarrsim", 0x2974}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"larrsim", 0x2973}, - {"Bfr", 0xD505}, - {"", 0}, {"", 0}, {"", 0}, - {"gl", 0x2277}, - {"", 0}, - {"backsimeq", 0x22CD}, - {"urtri", 0x25F9}, - {"permil", 0x2030}, - {"", 0}, - {"lnE", 0x2268}, - {"", 0}, - {"nrtri", 0x22EB}, - {"", 0}, {"", 0}, {"", 0}, - {"Sqrt", 0x221A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lesges", 0x2A93}, - {"", 0}, - {"Mfr", 0xD510}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nvlt", 0x003C20D2}, - {"Wedge", 0x22C0}, - {"", 0}, - {"notnivb", 0x22FE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"olcross", 0x29BB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotHumpEqual", 0x224F0338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Gfr", 0xD50A}, - {"ohm", 0x03A9}, - {"", 0}, {"", 0}, - {"mstpos", 0x223E}, - {"", 0}, {"", 0}, - {"gnap", 0x2A8A}, - {"", 0}, - {"nsccue", 0x22E1}, - {"", 0}, {"", 0}, - {"siml", 0x2A9D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"scap", 0x2AB8}, - {"", 0}, {"", 0}, {"", 0}, - {"niv", 0x220B}, - {"", 0}, {"", 0}, - {"zeetrf", 0x2128}, - {"", 0}, {"", 0}, {"", 0}, - {"notni", 0x220C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Aopf", 0xD538}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ncap", 0x2A43}, - {"", 0}, {"", 0}, {"", 0}, - {"dfr", 0xD521}, - {"looparrowright", 0x21AC}, - {"vltri", 0x22B2}, - {"", 0}, {"", 0}, - {"nLl", 0x22D80338}, - {"rcub", 0x007D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lcub", 0x007B}, - {"", 0}, {"", 0}, - {"Because", 0x2235}, - {"", 0}, {"", 0}, - {"prime", 0x2032}, - {"sstarf", 0x22C6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"primes", 0x2119}, - {"oS", 0x24C8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"gne", 0x2A88}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"mho", 0x2127}, - {"", 0}, {"", 0}, - {"llhard", 0x296B}, - {"", 0}, - {"sce", 0x2AB0}, - {"", 0}, - {"vrtri", 0x22B3}, - {"", 0}, - {"epsilon", 0x03B5}, - {"", 0}, {"", 0}, {"", 0}, - {"ratail", 0x291A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"latail", 0x2919}, - {"", 0}, - {"piv", 0x03D6}, - {"", 0}, {"", 0}, - {"rcaron", 0x0159}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lcaron", 0x013E}, - {"upsilon", 0x03C5}, - {"", 0}, - {"Bscr", 0x212C}, - {"", 0}, - {"scaron", 0x0161}, - {"", 0}, - {"nlE", 0x22660338}, - {"", 0}, {"", 0}, - {"ecaron", 0x011B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"tcaron", 0x0165}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lrhard", 0x296D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ge", 0x2265}, - {"", 0}, - {"Mscr", 0x2133}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Darr", 0x21A1}, - {"", 0}, - {"ncaron", 0x0148}, - {"", 0}, - {"Dot", 0x00A8}, - {"", 0}, {"", 0}, - {"elsdot", 0x2A97}, - {"", 0}, - {"ges", 0x2A7E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"spades", 0x2660}, - {"", 0}, - {"Gscr", 0xD4A2}, - {"NotSubsetEqual", 0x2288}, - {"", 0}, {"", 0}, {"", 0}, - {"Nfr", 0xD511}, - {"ncup", 0x2A42}, - {"", 0}, {"", 0}, {"", 0}, - {"pointint", 0x2A15}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Therefore", 0x2234}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"backepsilon", 0x03F6}, - {"Im", 0x2111}, - {"", 0}, - {"jopf", 0xD55B}, - {"", 0}, - {"Zcaron", 0x017D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"spadesuit", 0x2660}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"dscr", 0xD4B9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"zcaron", 0x017E}, - {"", 0}, - {"gap", 0x2A86}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"mid", 0x2223}, - {"", 0}, {"", 0}, - {"NotSupersetEqual", 0x2289}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"image", 0x2111}, - {"Rcaron", 0x0158}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"prE", 0x2AB3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"oast", 0x229B}, - {"", 0}, - {"nvrArr", 0x2903}, - {"lE", 0x2266}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ordm", 0x00BA}, - {"LessEqualGreater", 0x22DA}, - {"Lcaron", 0x013D}, - {"", 0}, {"", 0}, {"", 0}, - {"twixt", 0x226C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"midast", 0x002A}, - {"", 0}, - {"dotminus", 0x2238}, - {"", 0}, - {"exist", 0x2203}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"vBarv", 0x2AE9}, - {"", 0}, - {"nvltrie", 0x22B420D2}, - {"", 0}, {"", 0}, - {"eDDot", 0x2A77}, - {"", 0}, - {"Pi", 0x03A0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"trade", 0x2122}, - {"lesdot", 0x2A7F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"eDot", 0x2251}, - {"", 0}, {"", 0}, {"", 0}, - {"precneqq", 0x2AB5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"mfr", 0xD52A}, - {"Nscr", 0xD4A9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gneq", 0x2A88}, - {"gneqq", 0x2269}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"prnap", 0x2AB9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nrarrw", 0x219D0338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Upsilon", 0x03A5}, - {"", 0}, - {"excl", 0x0021}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bsol", 0x005C}, - {"", 0}, {"", 0}, - {"lesssim", 0x2272}, - {"geq", 0x2265}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"diam", 0x22C4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"geqq", 0x2267}, - {"", 0}, - {"mnplus", 0x2213}, - {"", 0}, - {"Ffr", 0xD509}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"inodot", 0x0131}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Sfr", 0xD516}, - {"", 0}, {"", 0}, - {"ssetmn", 0x2216}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"upharpoonleft", 0x21BF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rarrb", 0x21E5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"larrb", 0x21E4}, - {"", 0}, {"", 0}, - {"rfloor", 0x230B}, - {"", 0}, {"", 0}, {"", 0}, - {"bepsi", 0x03F6}, - {"lfloor", 0x230A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"UpEquilibrium", 0x296E}, - {"", 0}, {"", 0}, {"", 0}, - {"plussim", 0x2A26}, - {"", 0}, {"", 0}, - {"mapstoleft", 0x21A4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Tcaron", 0x0164}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"mscr", 0xD4C2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nsucceq", 0x2AB00338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"divide", 0x00F7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"comp", 0x2201}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"orv", 0x2A5B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"napE", 0x2A700338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gvertneqq", 0x2269FE00}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ShortRightArrow", 0x2192}, - {"", 0}, {"", 0}, {"", 0}, - {"Fscr", 0x2131}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"triplus", 0x2A39}, - {"wfr", 0xD534}, - {"", 0}, {"", 0}, - {"vsupne", 0x228BFE00}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"vsubne", 0x228AFE00}, - {"", 0}, {"", 0}, - {"Sscr", 0xD4AE}, - {"", 0}, - {"diamondsuit", 0x2666}, - {"", 0}, - {"mapstoup", 0x21A5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"crarr", 0x21B5}, - {"commat", 0x0040}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"div", 0x00F7}, - {"gopf", 0xD558}, - {"", 0}, {"", 0}, - {"backsim", 0x223D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"smallsetminus", 0x2216}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Precedes", 0x227A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Efr", 0xD508}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"PlusMinus", 0x00B1}, - {"rarrbfs", 0x2920}, - {"dollar", 0x0024}, - {"precsim", 0x227E}, - {"", 0}, {"", 0}, - {"larrbfs", 0x291F}, - {"", 0}, {"", 0}, {"", 0}, - {"SquareSupersetEqual", 0x2292}, - {"disin", 0x22F2}, - {"", 0}, - {"SquareSubsetEqual", 0x2291}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"intprod", 0x2A3C}, - {"clubs", 0x2663}, - {"SquareSuperset", 0x2290}, - {"GreaterFullEqual", 0x2267}, - {"", 0}, - {"SquareSubset", 0x228F}, - {"Hfr", 0x210C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"varpropto", 0x221D}, - {"", 0}, {"", 0}, {"", 0}, - {"PrecedesSlantEqual", 0x227C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Ifr", 0x2111}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"harr", 0x2194}, - {"efDot", 0x2252}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"late", 0x2AAD}, - {"", 0}, {"", 0}, {"", 0}, - {"ofr", 0xD52C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"AMP", 0x0026}, - {"", 0}, {"", 0}, - {"horbar", 0x2015}, - {"", 0}, {"", 0}, - {"npre", 0x2AAF0338}, - {"lates", 0x2AADFE00}, - {"cap", 0x2229}, - {"eqcolon", 0x2255}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"wscr", 0xD4CC}, - {"Equal", 0x2A75}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"hoarr", 0x21FF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"dcaron", 0x010F}, - {"", 0}, - {"bsolhsub", 0x27C8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"udarr", 0x21C5}, - {"", 0}, {"", 0}, - {"Pfr", 0xD513}, - {"", 0}, {"", 0}, - {"Tab", 0x0009}, - {"", 0}, {"", 0}, - {"malt", 0x2720}, - {"THORN", 0x00DE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rpargt", 0x2994}, - {"caps", 0x2229FE00}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"propto", 0x221D}, - {"", 0}, {"", 0}, - {"Escr", 0x2130}, - {"", 0}, {"", 0}, - {"orslope", 0x2A57}, - {"", 0}, - {"lesseqgtr", 0x22DA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gnsim", 0x22E7}, - {"lagran", 0x2112}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cross", 0x2717}, - {"nshortmid", 0x2224}, - {"scsim", 0x227F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"RoundImplies", 0x2970}, - {"", 0}, - {"Hscr", 0x210B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Iscr", 0x2110}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"varrho", 0x03F1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Ncaron", 0x0147}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"oscr", 0x2134}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Dopf", 0xD53B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"prod", 0x220F}, - {"DotDot", 0x20DC}, - {"", 0}, {"", 0}, - {"NotTilde", 0x2241}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"thicksim", 0x223C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"barwedge", 0x2305}, - {"", 0}, {"", 0}, {"", 0}, - {"diamond", 0x22C4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"equest", 0x225F}, - {"", 0}, {"", 0}, {"", 0}, - {"iiint", 0x222D}, - {"", 0}, {"", 0}, - {"Afr", 0xD504}, - {"", 0}, {"", 0}, {"", 0}, - {"NotTildeFullEqual", 0x2247}, - {"", 0}, - {"Pscr", 0xD4AB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"vangrt", 0x299C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"plusdo", 0x2214}, - {"", 0}, {"", 0}, {"", 0}, - {"lsime", 0x2A8D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bottom", 0x22A5}, - {"", 0}, {"", 0}, - {"smte", 0x2AAC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"hbar", 0x210F}, - {"", 0}, {"", 0}, - {"ENG", 0x014A}, - {"plusb", 0x229E}, - {"", 0}, {"", 0}, - {"simdot", 0x2A6A}, - {"npolint", 0x2A14}, - {"", 0}, {"", 0}, - {"smtes", 0x2AACFE00}, - {"", 0}, {"", 0}, - {"SquareIntersection", 0x2293}, - {"dsol", 0x29F6}, - {"range", 0x29A5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nsime", 0x2244}, - {"", 0}, {"", 0}, - {"psi", 0x03C8}, - {"", 0}, - {"bsime", 0x22CD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"rceil", 0x2309}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lceil", 0x2308}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"clubsuit", 0x2663}, - {"REG", 0x00AE}, - {"radic", 0x221A}, - {"", 0}, - {"upsih", 0x03D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Union", 0x22C3}, - {"", 0}, {"", 0}, - {"ap", 0x2248}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"maltese", 0x2720}, - {"precnsim", 0x22E8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rarrc", 0x2933}, - {"", 0}, {"", 0}, - {"nesear", 0x2928}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NestedLessLess", 0x226A}, - {"", 0}, {"", 0}, {"", 0}, - {"gel", 0x22DB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Phi", 0x03A6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"hearts", 0x2665}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"jfr", 0xD527}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Ascr", 0xD49C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"HorizontalLine", 0x2500}, - {"UpArrowBar", 0x2912}, - {"Uarrocir", 0x2949}, - {"", 0}, - {"notindot", 0x22F50338}, - {"", 0}, - {"Fouriertrf", 0x2131}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"prec", 0x227A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rationals", 0x211A}, - {"", 0}, - {"realine", 0x211B}, - {"", 0}, - {"heartsuit", 0x2665}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Scaron", 0x0160}, - {"", 0}, {"", 0}, {"", 0}, - {"wedge", 0x2227}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"copf", 0xD554}, - {"", 0}, - {"capcap", 0x2A4B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"isins", 0x22F4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"isin", 0x2208}, - {"mapstodown", 0x21A7}, - {"", 0}, - {"lessgtr", 0x2276}, - {"", 0}, - {"ape", 0x224A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"tint", 0x222D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nvlArr", 0x2902}, - {"", 0}, {"", 0}, {"", 0}, - {"lt", 0x003C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nLeftrightarrow", 0x21CE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ThickSpace", 0x205F200A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"iquest", 0x00BF}, - {"", 0}, {"", 0}, {"", 0}, - {"apos", 0x0027}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"mDDot", 0x223A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"smile", 0x2323}, - {"", 0}, {"", 0}, - {"ltdot", 0x22D6}, - {"lowbar", 0x005F}, - {"", 0}, {"", 0}, {"", 0}, - {"xodot", 0x2A00}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"SquareUnion", 0x2294}, - {"star", 0x2606}, - {"", 0}, - {"jscr", 0xD4BF}, - {"", 0}, {"", 0}, {"", 0}, - {"xharr", 0x27F7}, - {"shortparallel", 0x2225}, - {"", 0}, {"", 0}, {"", 0}, - {"natur", 0x266E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"utdot", 0x22F0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"capcup", 0x2A47}, - {"", 0}, - {"plusacir", 0x2A23}, - {"", 0}, - {"imagline", 0x2110}, - {"", 0}, - {"bsemi", 0x204F}, - {"", 0}, {"", 0}, {"", 0}, - {"xotime", 0x2A02}, - {"", 0}, - {"cir", 0x25CB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"copysr", 0x2117}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Vdashl", 0x2AE6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Prime", 0x2033}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Del", 0x2207}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"race", 0x223D0331}, - {"", 0}, {"", 0}, - {"Epsilon", 0x0395}, - {"", 0}, {"", 0}, {"", 0}, - {"rAtail", 0x291C}, - {"", 0}, {"", 0}, {"", 0}, - {"Lt", 0x226A}, - {"lAtail", 0x291B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"oline", 0x203E}, - {"gammad", 0x03DD}, - {"", 0}, {"", 0}, {"", 0}, - {"nsupE", 0x2AC60338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nsubE", 0x2AC50338}, - {"Ecaron", 0x011A}, - {"", 0}, {"", 0}, {"", 0}, - {"nleqslant", 0x2A7D0338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"xlarr", 0x27F5}, - {"", 0}, - {"ofcir", 0x29BF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"scpolint", 0x2A13}, - {"doublebarwedge", 0x2306}, - {"ddarr", 0x21CA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rangd", 0x2992}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"langd", 0x2991}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"hercon", 0x22B9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Equilibrium", 0x21CC}, - {"", 0}, {"", 0}, - {"qint", 0x2A0C}, - {"", 0}, - {"ominus", 0x2296}, - {"Omicron", 0x039F}, - {"", 0}, {"", 0}, {"", 0}, - {"Kappa", 0x039A}, - {"", 0}, - {"xrarr", 0x27F6}, - {"varkappa", 0x03F0}, - {"", 0}, {"", 0}, - {"uwangle", 0x29A7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"rtrie", 0x22B5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ltrie", 0x22B4}, - {"", 0}, {"", 0}, - {"amacr", 0x0101}, - {"", 0}, - {"DoubleLeftTee", 0x2AE4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"EqualTilde", 0x2242}, - {"", 0}, {"", 0}, {"", 0}, - {"hopf", 0xD559}, - {"", 0}, - {"thksim", 0x223C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"strns", 0x00AF}, - {"nisd", 0x22FA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gesl", 0x22DBFE00}, - {"", 0}, {"", 0}, - {"rtri", 0x25B9}, - {"triminus", 0x2A3A}, - {"", 0}, - {"it", 0x2062}, - {"isinsv", 0x22F3}, - {"ltri", 0x25C3}, - {"", 0}, {"", 0}, - {"UpTee", 0x22A5}, - {"nsimeq", 0x2244}, - {"", 0}, {"", 0}, {"", 0}, - {"varepsilon", 0x03F5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"infin", 0x221E}, - {"ecolon", 0x2255}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"OverParenthesis", 0x23DC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"utri", 0x25B5}, - {"", 0}, - {"sqsupe", 0x2292}, - {"", 0}, - {"midcir", 0x2AF0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"uparrow", 0x2191}, - {"gfr", 0xD524}, - {"amp", 0x0026}, - {"", 0}, - {"sqsupset", 0x2290}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Exists", 0x2203}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"UnderBrace", 0x23DF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"lescc", 0x2AA8}, - {"and", 0x2227}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ecir", 0x2256}, - {"", 0}, {"", 0}, {"", 0}, - {"ETH", 0x00D0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"parsim", 0x2AF3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rfisht", 0x297D}, - {"", 0}, {"", 0}, {"", 0}, - {"tosa", 0x2929}, - {"lfisht", 0x297C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"xmap", 0x27FC}, - {"cedil", 0x00B8}, - {"", 0}, - {"doteq", 0x2250}, - {"", 0}, {"", 0}, - {"PrecedesTilde", 0x227E}, - {"rmoust", 0x23B1}, - {"", 0}, {"", 0}, {"", 0}, - {"sqsup", 0x2290}, - {"lmoust", 0x23B0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"veeeq", 0x225A}, - {"notinE", 0x22F90338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ufisht", 0x297E}, - {"", 0}, - {"lesdotor", 0x2A83}, - {"TRADE", 0x2122}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bkarow", 0x290D}, - {"", 0}, {"", 0}, - {"lsim", 0x2272}, - {"", 0}, - {"wedgeq", 0x2259}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"esim", 0x2242}, - {"udhar", 0x296E}, - {"", 0}, {"", 0}, {"", 0}, - {"sime", 0x2243}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"osol", 0x2298}, - {"", 0}, {"", 0}, - {"harrcir", 0x2948}, - {"", 0}, - {"profalar", 0x232E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nsim", 0x2241}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bsim", 0x223D}, - {"times", 0x00D7}, - {"", 0}, {"", 0}, {"", 0}, - {"lvnE", 0x2268FE00}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"hamilt", 0x210B}, - {"", 0}, - {"leftarrowtail", 0x21A2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gscr", 0x210A}, - {"para", 0x00B6}, - {"rangle", 0x27E9}, - {"ShortUpArrow", 0x2191}, - {"", 0}, {"", 0}, - {"aopf", 0xD552}, - {"langle", 0x27E8}, - {"", 0}, {"", 0}, {"", 0}, - {"micro", 0x00B5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Gamma", 0x0393}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotElement", 0x2209}, - {"", 0}, {"", 0}, - {"caron", 0x02C7}, - {"", 0}, {"", 0}, - {"gesles", 0x2A94}, - {"", 0}, - {"vnsup", 0x228320D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"csub", 0x2ACF}, - {"", 0}, - {"nvle", 0x226420D2}, - {"", 0}, - {"timesb", 0x22A0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"mapsto", 0x21A6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rsaquo", 0x203A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lsaquo", 0x2039}, - {"", 0}, - {"xhArr", 0x27FA}, - {"rthree", 0x22CC}, - {"", 0}, {"", 0}, - {"Uparrow", 0x21D1}, - {"", 0}, - {"lthree", 0x22CB}, - {"", 0}, {"", 0}, - {"zigrarr", 0x21DD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"searrow", 0x2198}, - {"", 0}, - {"Gt", 0x226B}, - {"", 0}, {"", 0}, {"", 0}, - {"Dfr", 0xD507}, - {"", 0}, {"", 0}, {"", 0}, - {"LeftTee", 0x22A3}, - {"infintie", 0x29DD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"dotsquare", 0x22A1}, - {"NotGreater", 0x226F}, - {"nearrow", 0x2197}, - {"", 0}, {"", 0}, - {"toea", 0x2928}, - {"", 0}, {"", 0}, - {"NotGreaterGreater", 0x226B0338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ShortLeftArrow", 0x2190}, - {"", 0}, {"", 0}, - {"csup", 0x2AD0}, - {"", 0}, {"", 0}, {"", 0}, - {"NotGreaterSlantEqual", 0x2A7E0338}, - {"", 0}, {"", 0}, {"", 0}, - {"rx", 0x211E}, - {"", 0}, {"", 0}, - {"ltcir", 0x2A79}, - {"", 0}, - {"NotGreaterLess", 0x2279}, - {"", 0}, {"", 0}, {"", 0}, - {"lesg", 0x22DAFE00}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotTildeTilde", 0x2249}, - {"prnE", 0x2AB5}, - {"NotGreaterFullEqual", 0x22670338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"tridot", 0x25EC}, - {"NotCongruent", 0x2262}, - {"", 0}, {"", 0}, {"", 0}, - {"middot", 0x00B7}, - {"LeftUpVector", 0x21BF}, - {"", 0}, {"", 0}, - {"LeftUpVectorBar", 0x2958}, - {"", 0}, {"", 0}, - {"dtdot", 0x22F1}, - {"InvisibleTimes", 0x2062}, - {"iota", 0x03B9}, - {"", 0}, - {"swarrow", 0x2199}, - {"xlArr", 0x27F8}, - {"lowast", 0x2217}, - {"simeq", 0x2243}, - {"", 0}, {"", 0}, {"", 0}, - {"racute", 0x0155}, - {"", 0}, {"", 0}, - {"npreceq", 0x2AAF0338}, - {"", 0}, - {"lacute", 0x013A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sacute", 0x015B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"eacute", 0x00E9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nwarrow", 0x2196}, - {"", 0}, {"", 0}, - {"beta", 0x03B2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"uacute", 0x00FA}, - {"", 0}, {"", 0}, {"", 0}, - {"xrArr", 0x27F9}, - {"nacute", 0x0144}, - {"", 0}, {"", 0}, {"", 0}, - {"bigoplus", 0x2A01}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lmidot", 0x0140}, - {"", 0}, {"", 0}, - {"hArr", 0x21D4}, - {"", 0}, {"", 0}, - {"dd", 0x2146}, - {"", 0}, - {"Dscr", 0xD49F}, - {"", 0}, {"", 0}, {"", 0}, - {"csupe", 0x2AD2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"csube", 0x2AD1}, - {"", 0}, - {"Zeta", 0x0396}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"xopf", 0xD569}, - {"", 0}, {"", 0}, - {"tilde", 0x02DC}, - {"", 0}, {"", 0}, - {"Cap", 0x22D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Zacute", 0x0179}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sdot", 0x22C5}, - {"zeta", 0x03B6}, - {"", 0}, {"", 0}, {"", 0}, - {"edot", 0x0117}, - {"", 0}, - {"iiiint", 0x2A0C}, - {"", 0}, {"", 0}, - {"tdot", 0x20DB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nvgt", 0x003E20D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"zacute", 0x017A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Oacute", 0x00D3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sqsupseteq", 0x2292}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nu", 0x03BD}, - {"Racute", 0x0154}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"eogon", 0x0119}, - {"cfr", 0xD520}, - {"dwangle", 0x29A6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxUr", 0x2559}, - {"", 0}, {"", 0}, - {"Lacute", 0x0139}, - {"", 0}, {"", 0}, {"", 0}, - {"iff", 0x21D4}, - {"", 0}, - {"uogon", 0x0173}, - {"ForAll", 0x2200}, - {"", 0}, {"", 0}, - {"Zdot", 0x017B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"coprod", 0x2210}, - {"", 0}, {"", 0}, {"", 0}, - {"gEl", 0x2A8C}, - {"", 0}, - {"Cross", 0x2A2F}, - {"NotGreaterEqual", 0x2271}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"uuarr", 0x21C8}, - {"", 0}, {"", 0}, - {"shy", 0x00AD}, - {"", 0}, {"", 0}, {"", 0}, - {"gnE", 0x2269}, - {"zdot", 0x017C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"scE", 0x2AB4}, - {"", 0}, {"", 0}, - {"Lmidot", 0x013F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sigmav", 0x03C2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotLessSlantEqual", 0x2A7D0338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ast", 0x002A}, - {"imped", 0x01B5}, - {"NotGreaterTilde", 0x2275}, - {"dtri", 0x25BF}, - {"rsh", 0x21B1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lsh", 0x21B0}, - {"sqcups", 0x2294FE00}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"half", 0x00BD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ubreve", 0x016D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"plustwo", 0x2A27}, - {"divideontimes", 0x22C7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"OpenCurlyQuote", 0x2018}, - {"", 0}, {"", 0}, {"", 0}, - {"eplus", 0x2A71}, - {"zwnj", 0x200C}, - {"", 0}, - {"bigcap", 0x22C2}, - {"", 0}, {"", 0}, {"", 0}, - {"order", 0x2134}, - {"", 0}, {"", 0}, {"", 0}, - {"Uacute", 0x00DA}, - {"bump", 0x224E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"uplus", 0x228E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"iacute", 0x00ED}, - {"scnap", 0x2ABA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"cscr", 0xD4B8}, - {"", 0}, {"", 0}, {"", 0}, - {"nsc", 0x2281}, - {"", 0}, - {"glE", 0x2A92}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"homtht", 0x223B}, - {"", 0}, {"", 0}, {"", 0}, - {"sdote", 0x2A66}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sup1", 0x00B9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotPrecedes", 0x2280}, - {"sup2", 0x00B2}, - {"dfisht", 0x297F}, - {"", 0}, - {"sqcup", 0x2294}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Psi", 0x03A8}, - {"", 0}, {"", 0}, - {"sigmaf", 0x03C2}, - {"", 0}, {"", 0}, - {"ltrPar", 0x2996}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rhov", 0x03F1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Rsh", 0x21B1}, - {"", 0}, - {"simlE", 0x2A9F}, - {"", 0}, - {"Star", 0x22C6}, - {"", 0}, - {"Yacute", 0x00DD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"circeq", 0x2257}, - {"", 0}, {"", 0}, - {"UpArrow", 0x2191}, - {"", 0}, {"", 0}, - {"sup3", 0x00B3}, - {"", 0}, {"", 0}, - {"Lsh", 0x21B0}, - {"", 0}, {"", 0}, - {"bigcup", 0x22C3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"barvee", 0x22BD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nvinfin", 0x29DE}, - {"", 0}, {"", 0}, - {"Uogon", 0x0172}, - {"bumpe", 0x224F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"upharpoonright", 0x21BE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"iogon", 0x012F}, - {"iinfin", 0x29DC}, - {"", 0}, - {"complement", 0x2201}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ohbar", 0x29B5}, - {"", 0}, - {"Tilde", 0x223C}, - {"chi", 0x03C7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"sup", 0x2283}, - {"", 0}, {"", 0}, {"", 0}, - {"hfr", 0xD525}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ReverseElement", 0x220B}, - {"", 0}, {"", 0}, - {"nexist", 0x2204}, - {"nexists", 0x2204}, - {"leqslant", 0x2A7D}, - {"", 0}, {"", 0}, {"", 0}, - {"prcue", 0x227C}, - {"", 0}, {"", 0}, - {"vellip", 0x22EE}, - {"", 0}, {"", 0}, - {"egs", 0x2A96}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"theta", 0x03B8}, - {"gE", 0x2267}, - {"", 0}, - {"Copf", 0x2102}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"UnionPlus", 0x228E}, - {"", 0}, - {"Ubreve", 0x016C}, - {"", 0}, {"", 0}, {"", 0}, - {"xi", 0x03BE}, - {"boxVr", 0x255F}, - {"", 0}, {"", 0}, {"", 0}, - {"nGtv", 0x226B0338}, - {"", 0}, {"", 0}, - {"rarrw", 0x219D}, - {"", 0}, - {"cirscir", 0x29C2}, - {"", 0}, - {"Intersection", 0x22C2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxHu", 0x2567}, - {"", 0}, {"", 0}, - {"ngt", 0x226F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Beta", 0x0392}, - {"", 0}, {"", 0}, - {"trisb", 0x29CD}, - {"smid", 0x2223}, - {"ngtr", 0x226F}, - {"rtriltri", 0x29CE}, - {"", 0}, - {"ovbar", 0x233D}, - {"", 0}, {"", 0}, - {"gesdot", 0x2A80}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LessFullEqual", 0x2266}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nmid", 0x2224}, - {"", 0}, - {"nwnear", 0x2927}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rsquor", 0x2019}, - {"isinv", 0x2208}, - {"", 0}, {"", 0}, {"", 0}, - {"lsquor", 0x201A}, - {"sc", 0x227B}, - {"", 0}, {"", 0}, {"", 0}, - {"oint", 0x222E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Dcaron", 0x010E}, - {"", 0}, {"", 0}, - {"simg", 0x2A9E}, - {"", 0}, {"", 0}, - {"isindot", 0x22F5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"hscr", 0xD4BD}, - {"boxV", 0x2551}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nsce", 0x2AB00338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"leftarrow", 0x2190}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"trie", 0x225C}, - {"NotHumpDownHump", 0x224E0338}, - {"", 0}, {"", 0}, {"", 0}, - {"male", 0x2642}, - {"", 0}, {"", 0}, {"", 0}, - {"ccaps", 0x2A4D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"supmult", 0x2AC2}, - {"", 0}, {"", 0}, - {"models", 0x22A7}, - {"Implies", 0x21D2}, - {"Mu", 0x039C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Gdot", 0x0120}, - {"", 0}, - {"VDash", 0x22AB}, - {"", 0}, - {"LeftArrowBar", 0x21E4}, - {"", 0}, {"", 0}, - {"nshortparallel", 0x2226}, - {"numsp", 0x2007}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"backcong", 0x224C}, - {"afr", 0xD51E}, - {"", 0}, - {"ubrcy", 0x045E}, - {"", 0}, {"", 0}, {"", 0}, - {"DoubleLeftArrow", 0x21D0}, - {"", 0}, {"", 0}, {"", 0}, - {"vartheta", 0x03D1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"compfn", 0x2218}, - {"", 0}, {"", 0}, - {"vartriangleright", 0x22B3}, - {"", 0}, {"", 0}, {"", 0}, - {"vartriangleleft", 0x22B2}, - {"", 0}, {"", 0}, {"", 0}, - {"vDash", 0x22A8}, - {"", 0}, {"", 0}, - {"nsucc", 0x2281}, - {"nge", 0x2271}, - {"boxDr", 0x2553}, - {"boxHU", 0x2569}, - {"omicron", 0x03BF}, - {"", 0}, {"", 0}, - {"fork", 0x22D4}, - {"", 0}, {"", 0}, - {"raquo", 0x00BB}, - {"", 0}, - {"simgE", 0x2AA0}, - {"", 0}, {"", 0}, - {"laquo", 0x00AB}, - {"", 0}, - {"tbrk", 0x23B4}, - {"", 0}, - {"GreaterSlantEqual", 0x2A7E}, - {"Laplacetrf", 0x2112}, - {"colone", 0x2254}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"supsub", 0x2AD4}, - {"colon", 0x003A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Diamond", 0x22C4}, - {"", 0}, {"", 0}, - {"bbrk", 0x23B5}, - {"hairsp", 0x200A}, - {"", 0}, {"", 0}, - {"Nacute", 0x0143}, - {"", 0}, - {"breve", 0x02D8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"supset", 0x2283}, - {"", 0}, {"", 0}, {"", 0}, - {"quest", 0x003F}, - {"", 0}, - {"ccups", 0x2A4C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"asymp", 0x2248}, - {"", 0}, {"", 0}, - {"Leftarrow", 0x21D0}, - {"", 0}, - {"duarr", 0x21F5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Barv", 0x2AE7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Coproduct", 0x2210}, - {"Gbreve", 0x011E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bemptyv", 0x29B0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"imath", 0x0131}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"supne", 0x228B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bbrktbrk", 0x23B6}, - {"", 0}, - {"Theta", 0x0398}, - {"", 0}, - {"cwint", 0x2231}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ccaron", 0x010D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ascr", 0xD4B6}, - {"", 0}, {"", 0}, - {"Nu", 0x039D}, - {"", 0}, - {"yopf", 0xD56A}, - {"", 0}, {"", 0}, {"", 0}, - {"ltlarr", 0x2976}, - {"ic", 0x2063}, - {"", 0}, {"", 0}, - {"trpezium", 0x23E2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxUL", 0x255D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"raemptyv", 0x29B3}, - {"nges", 0x2A7E0338}, - {"", 0}, {"", 0}, {"", 0}, - {"laemptyv", 0x29B4}, - {"", 0}, {"", 0}, {"", 0}, - {"eng", 0x014B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"blank", 0x2423}, - {"bumpeq", 0x224F}, - {"", 0}, {"", 0}, {"", 0}, - {"lnapprox", 0x2A89}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"supsup", 0x2AD6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"af", 0x2061}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Mellintrf", 0x2133}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"realpart", 0x211C}, - {"", 0}, - {"Ubrcy", 0x040E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rang", 0x27E9}, - {"UpperRightArrow", 0x2197}, - {"", 0}, {"", 0}, {"", 0}, - {"lang", 0x27E8}, - {"", 0}, - {"xfr", 0xD535}, - {"", 0}, - {"sbquo", 0x201A}, - {"", 0}, {"", 0}, - {"block", 0x2588}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ocir", 0x229A}, - {"", 0}, - {"boxHd", 0x2564}, - {"", 0}, {"", 0}, - {"Esim", 0x2A73}, - {"ngeq", 0x2271}, - {"ngeqq", 0x22670338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"apacir", 0x2A6F}, - {"", 0}, {"", 0}, - {"Iota", 0x0399}, - {"", 0}, {"", 0}, {"", 0}, - {"nang", 0x222020D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DoubleLeftRightArrow", 0x21D4}, - {"", 0}, {"", 0}, {"", 0}, - {"capdot", 0x2A40}, - {"NotVerticalBar", 0x2224}, - {"", 0}, {"", 0}, {"", 0}, - {"bigotimes", 0x2A02}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Sacute", 0x015A}, - {"", 0}, {"", 0}, {"", 0}, - {"mu", 0x03BC}, - {"supseteq", 0x2287}, - {"supseteqq", 0x2AC6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sum", 0x2211}, - {"", 0}, {"", 0}, - {"dbkarow", 0x290F}, - {"", 0}, - {"xoplus", 0x2A01}, - {"", 0}, {"", 0}, - {"lesdoto", 0x2A81}, - {"", 0}, {"", 0}, - {"supsetneq", 0x228B}, - {"", 0}, {"", 0}, {"", 0}, - {"sfrown", 0x2322}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotSquareSubsetEqual", 0x22E2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"timesd", 0x2A30}, - {"", 0}, {"", 0}, - {"num", 0x0023}, - {"", 0}, - {"NotSquareSubset", 0x228F0338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxUl", 0x255C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"kappav", 0x03F0}, - {"", 0}, - {"Rang", 0x27EB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"EmptySmallSquare", 0x25FB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"xsqcup", 0x2A06}, - {"", 0}, {"", 0}, - {"bigodot", 0x2A00}, - {"boxplus", 0x229E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Lang", 0x27EA}, - {"", 0}, {"", 0}, {"", 0}, - {"nGg", 0x22D90338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"xscr", 0xD4CD}, - {"", 0}, {"", 0}, - {"TildeTilde", 0x2248}, - {"", 0}, - {"xnis", 0x22FB}, - {"", 0}, - {"NotEqual", 0x2260}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"COPY", 0x00A9}, - {"", 0}, {"", 0}, {"", 0}, - {"NotSquareSupersetEqual", 0x22E3}, - {"conint", 0x222E}, - {"", 0}, - {"prnsim", 0x22E8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotSquareSuperset", 0x22900338}, - {"", 0}, {"", 0}, - {"vsupnE", 0x2ACCFE00}, - {"", 0}, - {"bsolb", 0x29C5}, - {"", 0}, {"", 0}, - {"vsubnE", 0x2ACBFE00}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"reg", 0x00AE}, - {"", 0}, - {"gsime", 0x2A8E}, - {"cwconint", 0x2232}, - {"", 0}, - {"leg", 0x22DA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"roang", 0x27ED}, - {"", 0}, {"", 0}, - {"asympeq", 0x224D}, - {"", 0}, - {"loang", 0x27EC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"boxVL", 0x2563}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"kopf", 0xD55C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"coloneq", 0x2254}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Eacute", 0x00C9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ddotseq", 0x2A77}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cent", 0x00A2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"circledast", 0x229B}, - {"apE", 0x2A70}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Breve", 0x02D8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"origof", 0x22B6}, - {"", 0}, - {"questeq", 0x225F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gla", 0x2AA5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Iacute", 0x00CD}, - {"", 0}, - {"boxUR", 0x255A}, - {"", 0}, {"", 0}, - {"LeftArrow", 0x2190}, - {"OpenCurlyDoubleQuote", 0x201C}, - {"supsetneqq", 0x2ACC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NegativeMediumSpace", 0x200B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"oacute", 0x00F3}, - {"", 0}, {"", 0}, {"", 0}, - {"supplus", 0x2AC0}, - {"", 0}, - {"LessSlantEqual", 0x2A7D}, - {"", 0}, {"", 0}, {"", 0}, - {"Edot", 0x0116}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"Omega", 0x03A9}, - {"", 0}, {"", 0}, - {"rtimes", 0x22CA}, - {"", 0}, - {"gamma", 0x03B3}, - {"", 0}, - {"exponentiale", 0x2147}, - {"ltimes", 0x22C9}, - {"", 0}, - {"dagger", 0x2020}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"circledR", 0x00AE}, - {"", 0}, {"", 0}, - {"NotPrecedesSlantEqual", 0x22E0}, - {"", 0}, - {"cirfnint", 0x2A10}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Eogon", 0x0118}, - {"", 0}, - {"nrarrc", 0x29330338}, - {"Cfr", 0x212D}, - {"Idot", 0x0130}, - {"", 0}, {"", 0}, {"", 0}, - {"ReverseEquilibrium", 0x21CB}, - {"", 0}, {"", 0}, {"", 0}, - {"risingdotseq", 0x2253}, - {"", 0}, {"", 0}, - {"shcy", 0x0448}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"awint", 0x2A11}, - {"", 0}, {"", 0}, - {"odot", 0x2299}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"euro", 0x20AC}, - {"", 0}, - {"downarrow", 0x2193}, - {"", 0}, - {"TripleDot", 0x20DB}, - {"gt", 0x003E}, - {"", 0}, - {"Bumpeq", 0x224E}, - {"", 0}, {"", 0}, - {"ZeroWidthSpace", 0x200B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ljcy", 0x0459}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxVl", 0x2562}, - {"", 0}, - {"demptyv", 0x29B1}, - {"", 0}, - {"Iogon", 0x012E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"euml", 0x00EB}, - {"", 0}, {"", 0}, - {"doteqdot", 0x2251}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxDL", 0x2557}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"njcy", 0x045A}, - {"", 0}, {"", 0}, {"", 0}, - {"Otimes", 0x2A37}, - {"uuml", 0x00FC}, - {"", 0}, {"", 0}, - {"gtdot", 0x22D7}, - {"Sup", 0x22D1}, - {"", 0}, - {"ngsim", 0x2275}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"caret", 0x2041}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ApplyFunction", 0x2061}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"isinE", 0x22F9}, - {"", 0}, {"", 0}, {"", 0}, - {"MediumSpace", 0x205F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"zhcy", 0x0436}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LeftFloor", 0x230A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"tritime", 0x2A3B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Aacute", 0x00C1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"capbrcup", 0x2A49}, - {"Ouml", 0x00D6}, - {"", 0}, {"", 0}, {"", 0}, - {"Cscr", 0xD49E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"imagpart", 0x2111}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sext", 0x2736}, - {"", 0}, {"", 0}, - {"dblac", 0x02DD}, - {"", 0}, {"", 0}, - {"Assign", 0x2254}, - {"xcap", 0x22C2}, - {"oplus", 0x2295}, - {"", 0}, {"", 0}, - {"iiota", 0x2129}, - {"", 0}, {"", 0}, - {"andslope", 0x2A58}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotPrecedesEqual", 0x2AAF0338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Sc", 0x2ABC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxH", 0x2550}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ZHcy", 0x0416}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nequiv", 0x2262}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"quot", 0x0022}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"boxVR", 0x2560}, - {"", 0}, - {"rtrif", 0x25B8}, - {"", 0}, - {"boxDl", 0x2556}, - {"", 0}, {"", 0}, - {"ltrif", 0x25C2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Aogon", 0x0104}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"duhar", 0x296F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Chi", 0x03A7}, - {"", 0}, - {"nLtv", 0x226A0338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"utrif", 0x25B4}, - {"", 0}, - {"iocy", 0x0451}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"suplarr", 0x297B}, - {"", 0}, {"", 0}, {"", 0}, - {"xcup", 0x22C3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Uuml", 0x00DC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"succeq", 0x2AB0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"yen", 0x00A5}, - {"", 0}, {"", 0}, {"", 0}, - {"starf", 0x2605}, - {"", 0}, - {"iuml", 0x00EF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"awconint", 0x2233}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"KHcy", 0x0425}, - {"", 0}, {"", 0}, {"", 0}, - {"DoubleDot", 0x00A8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Abreve", 0x0102}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"yfr", 0xD536}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gescc", 0x2AA9}, - {"", 0}, - {"TScy", 0x0426}, - {"", 0}, {"", 0}, {"", 0}, - {"acd", 0x223F}, - {"andd", 0x2A5C}, - {"", 0}, - {"Supset", 0x22D1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"eqcirc", 0x2256}, - {"", 0}, - {"scnE", 0x2AB6}, - {"Yuml", 0x0178}, - {"ntriangleleft", 0x22EA}, - {"", 0}, - {"triangle", 0x25B5}, - {"ogt", 0x29C1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"deg", 0x00B0}, - {"", 0}, - {"szlig", 0x00DF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"iexcl", 0x00A1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"triangleleft", 0x25C3}, - {"", 0}, - {"omid", 0x29B6}, - {"ThinSpace", 0x2009}, - {"drbkarow", 0x2910}, - {"", 0}, - {"gesdotol", 0x2A84}, - {"comma", 0x002C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gsim", 0x2273}, - {"", 0}, {"", 0}, - {"DD", 0x2145}, - {"", 0}, {"", 0}, - {"gsiml", 0x2A90}, - {"", 0}, {"", 0}, {"", 0}, - {"nvge", 0x226520D2}, - {"", 0}, {"", 0}, - {"quaternions", 0x210D}, - {"", 0}, {"", 0}, - {"boxDR", 0x2554}, - {"empty", 0x2205}, - {"", 0}, {"", 0}, - {"ring", 0x02DA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rsquo", 0x2019}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lsquo", 0x2018}, - {"gvnE", 0x2269FE00}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"tshcy", 0x045B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"succcurlyeq", 0x227D}, - {"", 0}, {"", 0}, - {"yscr", 0xD4CE}, - {"", 0}, - {"cirE", 0x29C3}, - {"", 0}, - {"NotCupCap", 0x226D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"TildeEqual", 0x2243}, - {"", 0}, {"", 0}, {"", 0}, - {"leftleftarrows", 0x21C7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"iecy", 0x0435}, - {"ctdot", 0x22EF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Colone", 0x2A74}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"capand", 0x2A44}, - {"Colon", 0x2237}, - {"", 0}, {"", 0}, - {"angzarr", 0x237C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rcedil", 0x0157}, - {"sqsube", 0x2291}, - {"", 0}, {"", 0}, {"", 0}, - {"lcedil", 0x013C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"scedil", 0x015F}, - {"", 0}, {"", 0}, - {"boxminus", 0x229F}, - {"", 0}, - {"sqsub", 0x228F}, - {"", 0}, {"", 0}, - {"sqsubset", 0x228F}, - {"", 0}, - {"tcedil", 0x0163}, - {"puncsp", 0x2008}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"dzcy", 0x045F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angrt", 0x221F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ncedil", 0x0146}, - {"", 0}, - {"ntrianglelefteq", 0x22EC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"triangleq", 0x225C}, - {"phiv", 0x03D5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Sum", 0x2211}, - {"", 0}, {"", 0}, {"", 0}, - {"gtcir", 0x2A7A}, - {"", 0}, {"", 0}, - {"mumap", 0x22B8}, - {"", 0}, - {"integers", 0x2124}, - {"gtrarr", 0x2978}, - {"djcy", 0x0452}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"orderof", 0x2134}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"trianglelefteq", 0x22B4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Ccaron", 0x010C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nleftarrow", 0x219A}, - {"", 0}, {"", 0}, - {"NotEqualTilde", 0x22420338}, - {"fjlig", 0x0066006A}, - {"delta", 0x03B4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"nprec", 0x2280}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"TSHcy", 0x040B}, - {"", 0}, - {"DoubleUpArrow", 0x21D1}, - {"gacute", 0x01F5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"kfr", 0xD528}, - {"YUcy", 0x042E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DoubleVerticalBar", 0x2225}, - {"Rcedil", 0x0156}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"vnsub", 0x228220D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ijlig", 0x0133}, - {"Lcedil", 0x013B}, - {"centerdot", 0x00B7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DoubleUpDownArrow", 0x21D5}, - {"", 0}, {"", 0}, {"", 0}, - {"angst", 0x00C5}, - {"", 0}, {"", 0}, {"", 0}, - {"ogon", 0x02DB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"weierp", 0x2118}, - {"", 0}, {"", 0}, {"", 0}, - {"fllig", 0xFB02}, - {"", 0}, {"", 0}, - {"lEg", 0x2A8B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"PrecedesEqual", 0x2AAF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Kcedil", 0x0136}, - {"", 0}, {"", 0}, - {"gdot", 0x0121}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NewLine", 0x000A}, - {"", 0}, {"", 0}, {"", 0}, - {"UpperLeftArrow", 0x2196}, - {"", 0}, - {"Lleftarrow", 0x21DA}, - {"", 0}, {"", 0}, - {"bowtie", 0x22C8}, - {"jmath", 0x0237}, - {"", 0}, - {"LeftDoubleBracket", 0x27E6}, - {"", 0}, {"", 0}, {"", 0}, - {"CircleTimes", 0x2297}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LeftTeeArrow", 0x21A4}, - {"", 0}, {"", 0}, - {"DownTee", 0x22A4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxdr", 0x250C}, - {"", 0}, - {"LeftVector", 0x21BC}, - {"", 0}, - {"ndash", 0x2013}, - {"circledS", 0x24C8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"circ", 0x02C6}, - {"", 0}, {"", 0}, - {"angrtvb", 0x22BE}, - {"", 0}, {"", 0}, - {"LeftUpDownVector", 0x2951}, - {"", 0}, - {"lambda", 0x03BB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"andand", 0x2A55}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"kscr", 0xD4C0}, - {"", 0}, {"", 0}, {"", 0}, - {"rcy", 0x0440}, - {"LeftTriangleBar", 0x29CF}, - {"", 0}, {"", 0}, - {"dtrif", 0x25BE}, - {"lcy", 0x043B}, - {"", 0}, {"", 0}, - {"LeftTriangleEqual", 0x22B4}, - {"", 0}, - {"scy", 0x0441}, - {"LJcy", 0x0409}, - {"", 0}, - {"LeftTriangle", 0x22B2}, - {"sccue", 0x227D}, - {"ecy", 0x044D}, - {"", 0}, {"", 0}, - {"egrave", 0x00E8}, - {"", 0}, - {"tcy", 0x0442}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LongLeftArrow", 0x27F5}, - {"ucy", 0x0443}, - {"YIcy", 0x0407}, - {"odsold", 0x29BC}, - {"ugrave", 0x00F9}, - {"", 0}, - {"ncy", 0x043D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bcy", 0x0431}, - {"", 0}, - {"gbreve", 0x011F}, - {"", 0}, {"", 0}, - {"rdquor", 0x201D}, - {"Vdash", 0x22A9}, - {"", 0}, {"", 0}, {"", 0}, - {"ldquor", 0x201E}, - {"KJcy", 0x040C}, - {"", 0}, - {"cire", 0x2257}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"utilde", 0x0169}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ntilde", 0x00F1}, - {"bull", 0x2022}, - {"", 0}, {"", 0}, {"", 0}, - {"Jcy", 0x0419}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"Tcedil", 0x0162}, - {"", 0}, {"", 0}, - {"Zcy", 0x0417}, - {"vdash", 0x22A2}, - {"Conint", 0x222F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"zcy", 0x0437}, - {"", 0}, - {"bumpE", 0x2AAE}, - {"", 0}, {"", 0}, - {"Ocy", 0x041E}, - {"", 0}, {"", 0}, - {"Ograve", 0x00D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"pcy", 0x043F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Vcy", 0x0412}, - {"", 0}, {"", 0}, {"", 0}, - {"Lambda", 0x039B}, - {"Rcy", 0x0420}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Otilde", 0x00D5}, - {"", 0}, - {"CircleMinus", 0x2296}, - {"", 0}, {"", 0}, - {"NotTildeEqual", 0x2244}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Product", 0x220F}, - {"", 0}, {"", 0}, {"", 0}, - {"Lcy", 0x041B}, - {"", 0}, {"", 0}, {"", 0}, - {"HilbertSpace", 0x210B}, - {"", 0}, {"", 0}, - {"dashv", 0x22A3}, - {"", 0}, {"", 0}, - {"vcy", 0x0432}, - {"", 0}, {"", 0}, {"", 0}, - {"fallingdotseq", 0x2252}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"NotExists", 0x2204}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"minus", 0x2212}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"thetasym", 0x03D1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Kcy", 0x041A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cirmid", 0x2AEF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sqsubseteq", 0x2291}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nVdash", 0x22AE}, - {"eta", 0x03B7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"eth", 0x00F0}, - {"", 0}, - {"longleftarrow", 0x27F5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"fcy", 0x0444}, - {"", 0}, - {"nvdash", 0x22AC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"omega", 0x03C9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"lg", 0x2276}, - {"", 0}, - {"SHcy", 0x0428}, - {"", 0}, {"", 0}, {"", 0}, - {"minusb", 0x229F}, - {"", 0}, - {"natural", 0x266E}, - {"", 0}, - {"eg", 0x2A9A}, - {"", 0}, {"", 0}, {"", 0}, - {"naturals", 0x2115}, - {"", 0}, - {"Ucy", 0x0423}, - {"tscy", 0x0446}, - {"", 0}, - {"Ugrave", 0x00D9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"xvee", 0x22C1}, - {"", 0}, - {"geqslant", 0x2A7E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"supdsub", 0x2AD8}, - {"icy", 0x0438}, - {"", 0}, {"", 0}, - {"igrave", 0x00EC}, - {"", 0}, {"", 0}, - {"Gcedil", 0x0122}, - {"", 0}, {"", 0}, {"", 0}, - {"Utilde", 0x0168}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"telrec", 0x2315}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"supdot", 0x2ABE}, - {"", 0}, {"", 0}, {"", 0}, - {"aleph", 0x2135}, - {"", 0}, {"", 0}, - {"itilde", 0x0129}, - {"", 0}, {"", 0}, {"", 0}, - {"hookleftarrow", 0x21A9}, - {"", 0}, {"", 0}, - {"Vvdash", 0x22AA}, - {"", 0}, {"", 0}, - {"zwj", 0x200D}, - {"ReverseUpEquilibrium", 0x296F}, - {"", 0}, - {"cacute", 0x0107}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bullet", 0x2022}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ccupssm", 0x2A50}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"grave", 0x0060}, - {"lozenge", 0x25CA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"notinvc", 0x22F6}, - {"otimes", 0x2297}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Ycy", 0x042B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Tcy", 0x0422}, - {"Euml", 0x00CB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Longleftarrow", 0x27F8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lgE", 0x2A91}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"HumpEqual", 0x224F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bigsqcup", 0x2A06}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cdot", 0x010B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ngE", 0x22670338}, - {"", 0}, - {"acute", 0x00B4}, - {"", 0}, {"", 0}, {"", 0}, - {"Iuml", 0x00CF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"GJcy", 0x0403}, - {"ouml", 0x00F6}, - {"", 0}, - {"sub", 0x2282}, - {"sdotb", 0x22A1}, - {"DotEqual", 0x2250}, - {"", 0}, {"", 0}, - {"egsdot", 0x2A98}, - {"hkswarow", 0x2926}, - {"TildeFullEqual", 0x2245}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"Ncedil", 0x0145}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NestedGreaterGreater", 0x226B}, - {"", 0}, {"", 0}, {"", 0}, - {"Congruent", 0x2261}, - {"", 0}, - {"subrarr", 0x2979}, - {"", 0}, {"", 0}, {"", 0}, - {"gimel", 0x2137}, - {"", 0}, - {"DownLeftVector", 0x21BD}, - {"", 0}, {"", 0}, - {"DownLeftVectorBar", 0x2956}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ltcc", 0x2AA6}, - {"", 0}, {"", 0}, {"", 0}, - {"UnderBar", 0x005F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"succneqq", 0x2AB6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DoubleRightTee", 0x22A8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DoubleLongLeftArrow", 0x27F8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DoubleLongLeftRightArrow", 0x27FA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"supnE", 0x2ACC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"congdot", 0x2A6D}, - {"", 0}, {"", 0}, {"", 0}, - {"Bcy", 0x0411}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"supE", 0x2AC6}, - {"", 0}, - {"NotSucceeds", 0x2281}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"Mcy", 0x041C}, - {"", 0}, {"", 0}, {"", 0}, - {"bigwedge", 0x22C0}, - {"angrtvbd", 0x299D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Succeeds", 0x227B}, - {"", 0}, {"", 0}, {"", 0}, - {"Gcy", 0x0413}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"DownLeftTeeVector", 0x295E}, - {"", 0}, {"", 0}, {"", 0}, - {"rmoustache", 0x23B1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lmoustache", 0x23B0}, - {"NJcy", 0x040A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"complexes", 0x2102}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"SucceedsSlantEqual", 0x227D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"dcy", 0x0434}, - {"DoubleDownArrow", 0x21D3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"scirc", 0x015D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ecirc", 0x00EA}, - {"cuepr", 0x22DE}, - {"Auml", 0x00C4}, - {"", 0}, {"", 0}, {"", 0}, - {"cupor", 0x2A45}, - {"", 0}, {"", 0}, {"", 0}, - {"Wcirc", 0x0174}, - {"", 0}, {"", 0}, {"", 0}, - {"FilledSmallSquare", 0x25FC}, - {"", 0}, {"", 0}, {"", 0}, - {"boxdL", 0x2555}, - {"", 0}, - {"ucirc", 0x00FB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LeftUpTeeVector", 0x2960}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"scnsim", 0x22E9}, - {"", 0}, {"", 0}, {"", 0}, - {"filig", 0xFB01}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gtrless", 0x2277}, - {"", 0}, {"", 0}, {"", 0}, - {"timesbar", 0x2A31}, - {"YAcy", 0x042F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"Jcirc", 0x0134}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Scedil", 0x015E}, - {"", 0}, - {"ltquest", 0x2A7B}, - {"", 0}, - {"uring", 0x016F}, - {"gnapprox", 0x2A8A}, - {"", 0}, {"", 0}, {"", 0}, - {"DownArrowBar", 0x2913}, - {"", 0}, {"", 0}, {"", 0}, - {"dzigrarr", 0x27FF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Poincareplane", 0x210C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"submult", 0x2AC1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nabla", 0x2207}, - {"", 0}, - {"sigma", 0x03C3}, - {"", 0}, - {"Ocirc", 0x00D4}, - {"", 0}, - {"curren", 0x00A4}, - {"", 0}, {"", 0}, {"", 0}, - {"Ncy", 0x041D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"boxVH", 0x256C}, - {"cup", 0x222A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"leftrightsquigarrow", 0x21AD}, - {"", 0}, - {"Dagger", 0x2021}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Ntilde", 0x00D1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"UpArrowDownArrow", 0x21C5}, - {"", 0}, {"", 0}, {"", 0}, - {"DoubleLongRightArrow", 0x27F9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"robrk", 0x27E7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lobrk", 0x27E6}, - {"", 0}, {"", 0}, {"", 0}, - {"bigstar", 0x2605}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cups", 0x222AFE00}, - {"", 0}, - {"subsub", 0x2AD5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"Downarrow", 0x21D3}, - {"succsim", 0x227F}, - {"", 0}, - {"mdash", 0x2014}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Gg", 0x22D9}, - {"", 0}, {"", 0}, - {"subset", 0x2282}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"approxeq", 0x224A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gesdoto", 0x2A82}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxdl", 0x2510}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"aacute", 0x00E1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"triangledown", 0x25BF}, - {"", 0}, {"", 0}, - {"minusdu", 0x2A2A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"dscy", 0x0455}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"CirclePlus", 0x2295}, - {"", 0}, {"", 0}, {"", 0}, - {"subne", 0x228A}, - {"", 0}, {"", 0}, {"", 0}, - {"Ucirc", 0x00DB}, - {"", 0}, {"", 0}, - {"VeryThinSpace", 0x200A}, - {"", 0}, {"", 0}, - {"mcy", 0x043C}, - {"Backslash", 0x2216}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"OElig", 0x0152}, - {"", 0}, {"", 0}, - {"icirc", 0x00EE}, - {"", 0}, - {"rdca", 0x2937}, - {"EmptyVerySmallSquare", 0x25AB}, - {"", 0}, {"", 0}, {"", 0}, - {"ldca", 0x2936}, - {"hellip", 0x2026}, - {"NotSucceedsTilde", 0x227F0338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Uring", 0x016E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"notnivc", 0x22FD}, - {"", 0}, {"", 0}, - {"DoubleContourIntegral", 0x222F}, - {"", 0}, {"", 0}, {"", 0}, - {"supe", 0x2287}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ncongdot", 0x2A6D0338}, - {"", 0}, - {"subsup", 0x2AD3}, - {"", 0}, {"", 0}, {"", 0}, - {"Fcy", 0x0424}, - {"", 0}, {"", 0}, {"", 0}, - {"quatint", 0x2A16}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"aogon", 0x0105}, - {"", 0}, - {"Ycirc", 0x0176}, - {"Scy", 0x0421}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"DiacriticalTilde", 0x02DC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nLeftarrow", 0x21CD}, - {"", 0}, {"", 0}, - {"rbbrk", 0x2773}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lbbrk", 0x2772}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LeftDownVector", 0x21C3}, - {"", 0}, {"", 0}, - {"LeftDownVectorBar", 0x2959}, - {"", 0}, - {"leftthreetimes", 0x22CB}, - {"curarr", 0x21B7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cudarrr", 0x2935}, - {"", 0}, - {"OverBar", 0x203E}, - {"acE", 0x223E0333}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cudarrl", 0x2938}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angmsd", 0x2221}, - {"", 0}, {"", 0}, - {"OverBrace", 0x23DE}, - {"", 0}, {"", 0}, {"", 0}, - {"gtlPar", 0x2995}, - {"", 0}, - {"boxdR", 0x2552}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"abreve", 0x0103}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"subseteq", 0x2286}, - {"subseteqq", 0x2AC5}, - {"", 0}, {"", 0}, {"", 0}, - {"OverBracket", 0x23B4}, - {"", 0}, {"", 0}, {"", 0}, - {"cemptyv", 0x29B2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ncong", 0x2247}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bcong", 0x224C}, - {"andv", 0x2A5A}, - {"", 0}, - {"subsetneq", 0x228A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"oelig", 0x0153}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"harrw", 0x21AD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ShortDownArrow", 0x2193}, - {"", 0}, {"", 0}, - {"NonBreakingSpace", 0x00A0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"UpTeeArrow", 0x21A5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LeftDownTeeVector", 0x2961}, - {"", 0}, {"", 0}, - {"apid", 0x224B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cupcap", 0x2A46}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"biguplus", 0x2A04}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"MinusPlus", 0x2213}, - {"", 0}, {"", 0}, {"", 0}, - {"lessapprox", 0x2A85}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angle", 0x2220}, - {"", 0}, {"", 0}, - {"succnsim", 0x22E9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"odash", 0x229D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gjcy", 0x0453}, - {"", 0}, - {"Gcirc", 0x011C}, - {"", 0}, - {"Ecy", 0x042D}, - {"", 0}, {"", 0}, - {"Egrave", 0x00C8}, - {"", 0}, {"", 0}, {"", 0}, - {"DiacriticalGrave", 0x0060}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"check", 0x2713}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LeftArrowRightArrow", 0x21C6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ntgl", 0x2279}, - {"", 0}, - {"boxur", 0x2514}, - {"", 0}, - {"SmallCircle", 0x2218}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cong", 0x2245}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"succ", 0x227B}, - {"Icy", 0x0418}, - {"", 0}, {"", 0}, - {"Igrave", 0x00CC}, - {"", 0}, {"", 0}, - {"varnothing", 0x2205}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cupcup", 0x2A4A}, - {"", 0}, {"", 0}, {"", 0}, - {"ocy", 0x043E}, - {"", 0}, {"", 0}, - {"ograve", 0x00F2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Itilde", 0x0128}, - {"pitchfork", 0x22D4}, - {"", 0}, {"", 0}, - {"copy", 0x00A9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"otilde", 0x00F5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DownArrow", 0x2193}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"rdquo", 0x201D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ldquo", 0x201C}, - {"Pcy", 0x041F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"ntriangleright", 0x22EB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"subsetneqq", 0x2ACB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gtrdot", 0x22D7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"IOcy", 0x0401}, - {"subplus", 0x2ABF}, - {"", 0}, - {"bdquo", 0x201E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxVh", 0x256B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"fflig", 0xFB00}, - {"nVDash", 0x22AF}, - {"Eta", 0x0397}, - {"", 0}, {"", 0}, - {"beth", 0x2136}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ac", 0x223E}, - {"", 0}, {"", 0}, {"", 0}, - {"DZcy", 0x040F}, - {"", 0}, - {"Sub", 0x22D0}, - {"", 0}, - {"notinva", 0x2209}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nvDash", 0x22AD}, - {"", 0}, {"", 0}, - {"minusd", 0x2238}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"DScy", 0x0405}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"precapprox", 0x2AB7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Acy", 0x0410}, - {"", 0}, {"", 0}, - {"Agrave", 0x00C0}, - {"", 0}, {"", 0}, {"", 0}, - {"Delta", 0x0394}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LeftTeeVector", 0x295A}, - {"", 0}, {"", 0}, - {"SucceedsTilde", 0x227F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Atilde", 0x00C3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"circledcirc", 0x229A}, - {"", 0}, - {"Cacute", 0x0106}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lsimg", 0x2A8F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"NotSucceedsSlantEqual", 0x22E1}, - {"", 0}, {"", 0}, - {"IJlig", 0x0132}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"ntrianglerighteq", 0x22ED}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"boxtimes", 0x22A0}, - {"", 0}, {"", 0}, - {"kappa", 0x03BA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxHD", 0x2566}, - {"hyphen", 0x2010}, - {"", 0}, - {"Cdot", 0x010A}, - {"", 0}, {"", 0}, - {"Cconint", 0x2230}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ngeqslant", 0x2A7E0338}, - {"", 0}, - {"Scirc", 0x015C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"wreath", 0x2240}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"jcy", 0x0439}, - {"IEcy", 0x0415}, - {"", 0}, - {"NegativeThinSpace", 0x200B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"chcy", 0x0447}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LowerRightArrow", 0x2198}, - {"", 0}, {"", 0}, {"", 0}, - {"downdownarrows", 0x21CA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"supsim", 0x2AC8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ntlg", 0x2278}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"dash", 0x2010}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ang", 0x2220}, - {"", 0}, - {"Sigma", 0x03A3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"ddagger", 0x2021}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"varsigma", 0x03C2}, - {"", 0}, {"", 0}, - {"NotSucceedsEqual", 0x2AB00338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"wcirc", 0x0175}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Subset", 0x22D0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"circleddash", 0x229D}, - {"", 0}, {"", 0}, {"", 0}, - {"Ecirc", 0x00CA}, - {"", 0}, {"", 0}, - {"Dashv", 0x2AE4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"CircleDot", 0x2299}, - {"", 0}, {"", 0}, {"", 0}, - {"Hcirc", 0x0124}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Icirc", 0x00CE}, - {"", 0}, - {"yacute", 0x00FD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bigvee", 0x22C1}, - {"LongLeftRightArrow", 0x27F7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ocirc", 0x00F4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"xdtri", 0x25BD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"UnderBracket", 0x23B5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"cularr", 0x21B6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxuL", 0x255B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Cup", 0x22D3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"curlyvee", 0x22CE}, - {"", 0}, - {"Superset", 0x2283}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"equivDD", 0x2A78}, - {"", 0}, - {"glj", 0x2AA4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxvr", 0x251C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"napprox", 0x2249}, - {"notniva", 0x220C}, - {"", 0}, {"", 0}, - {"gtreqless", 0x22DB}, - {"ldrdhar", 0x2967}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"leftharpoonup", 0x21BC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lhblk", 0x2584}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gcy", 0x0433}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"uhblk", 0x2580}, - {"", 0}, - {"LeftCeiling", 0x2308}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"longleftrightarrow", 0x27F7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Acirc", 0x00C2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DiacriticalAcute", 0x00B4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DifferentialD", 0x2146}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"boxul", 0x2518}, - {"ContourIntegral", 0x222E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Aring", 0x00C5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DJcy", 0x0402}, - {"boxv", 0x2502}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cularrp", 0x293D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"bigtriangleup", 0x25B3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"DownTeeArrow", 0x21A7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Oslash", 0x00D8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"Longleftrightarrow", 0x27FA}, - {"", 0}, {"", 0}, - {"auml", 0x00E4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"jcirc", 0x0135}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gtreqqless", 0x2A8C}, - {"ccedil", 0x00E7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"supedot", 0x2AC4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"AElig", 0x00C6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gg", 0x226B}, - {"Dcy", 0x0414}, - {"", 0}, {"", 0}, - {"rightrightarrows", 0x21C9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxuR", 0x2558}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Jukcy", 0x0404}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"leftrightharpoons", 0x21CB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"longmapsto", 0x27FC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"RightTee", 0x22A2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"UpDownArrow", 0x2195}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"ange", 0x29A4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RightTriangleBar", 0x29D0}, - {"", 0}, {"", 0}, - {"RightTeeVector", 0x295B}, - {"", 0}, - {"expectation", 0x2130}, - {"", 0}, - {"RightTriangleEqual", 0x22B5}, - {"gtcc", 0x2AA7}, - {"leftharpoondown", 0x21BD}, - {"RightUpTeeVector", 0x295C}, - {"", 0}, - {"RightTriangle", 0x22B3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"intercal", 0x22BA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxhu", 0x2534}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"iukcy", 0x0456}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"subdot", 0x2ABD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"xwedge", 0x22C0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"downharpoonright", 0x21C2}, - {"", 0}, {"", 0}, {"", 0}, - {"downharpoonleft", 0x21C3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"ExponentialE", 0x2147}, - {"", 0}, {"", 0}, - {"leftrightarrows", 0x21C6}, - {"", 0}, - {"triangleright", 0x25B9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gcirc", 0x011D}, - {"", 0}, - {"updownarrow", 0x2195}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"intlarhk", 0x2A17}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"suphsub", 0x2AD7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"boxvL", 0x2561}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bigtriangledown", 0x25BD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"circlearrowright", 0x21BB}, - {"", 0}, {"", 0}, {"", 0}, - {"circlearrowleft", 0x21BA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gtrsim", 0x2273}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"CenterDot", 0x00B7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DoubleRightArrow", 0x21D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxhU", 0x2568}, - {"gtquest", 0x2A7C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"LongRightArrow", 0x27F6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sung", 0x266A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"subnE", 0x2ACB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"curarrm", 0x293C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"subE", 0x2AC5}, - {"", 0}, - {"cupdot", 0x228D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NegativeVeryThinSpace", 0x200B}, - {"", 0}, {"", 0}, {"", 0}, - {"VerticalLine", 0x007C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"xuplus", 0x2A04}, - {"", 0}, - {"LowerLeftArrow", 0x2199}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"trianglerighteq", 0x22B5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxvl", 0x2524}, - {"rdldhar", 0x2969}, - {"", 0}, {"", 0}, - {"aelig", 0x00E6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Updownarrow", 0x21D5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Hacek", 0x02C7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DiacriticalDot", 0x02D9}, - {"", 0}, {"", 0}, - {"DiacriticalDoubleAcute", 0x02DD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nRightarrow", 0x21CF}, - {"", 0}, - {"curlywedge", 0x22CF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxhd", 0x252C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"nrightarrow", 0x219B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rarrhk", 0x21AA}, - {"", 0}, - {"CHcy", 0x0427}, - {"", 0}, {"", 0}, - {"larrhk", 0x21A9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"HumpDownHump", 0x224E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"LeftVectorBar", 0x2952}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DownArrowUpArrow", 0x21F5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"acy", 0x0430}, - {"", 0}, {"", 0}, - {"agrave", 0x00E0}, - {"", 0}, {"", 0}, {"", 0}, - {"CupCap", 0x224D}, - {"", 0}, {"", 0}, {"", 0}, - {"RightFloor", 0x230B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Rrightarrow", 0x21DB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"atilde", 0x00E3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"boxvR", 0x255E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"mcomma", 0x2A29}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"xutri", 0x25B3}, - {"", 0}, {"", 0}, - {"Alpha", 0x0391}, - {"", 0}, {"", 0}, {"", 0}, - {"ccirc", 0x0109}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"hksearow", 0x2925}, - {"ClockwiseContourIntegral", 0x2232}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"longrightarrow", 0x27F6}, - {"yacy", 0x044F}, - {"", 0}, - {"Cayleys", 0x212D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"PartialD", 0x2202}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"sube", 0x2286}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"yuml", 0x00FF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"amalg", 0x2A3F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"searhk", 0x2925}, - {"LeftRightArrow", 0x2194}, - {"hookrightarrow", 0x21AA}, - {"", 0}, - {"numero", 0x2116}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"topfork", 0x2ADA}, - {"", 0}, {"", 0}, {"", 0}, - {"nearhk", 0x2924}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DownRightTeeVector", 0x295F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"LeftRightVector", 0x294E}, - {"", 0}, - {"Longrightarrow", 0x27F9}, - {"", 0}, - {"hybull", 0x2043}, - {"", 0}, - {"alefsym", 0x2135}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"oslash", 0x00F8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"DownBreve", 0x0311}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"CapitalDifferentialD", 0x2145}, - {"", 0}, - {"swarhk", 0x2926}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"otimesas", 0x2A36}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"thickapprox", 0x2248}, - {"", 0}, {"", 0}, - {"nwarhk", 0x2923}, - {"", 0}, {"", 0}, - {"Ccedil", 0x00C7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"hcirc", 0x0125}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"gtrapprox", 0x2A86}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"VerticalTilde", 0x2240}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ffllig", 0xFB04}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angmsdae", 0x29AC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RightVector", 0x21C0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Iukcy", 0x0406}, - {"", 0}, {"", 0}, - {"angmsdab", 0x29A9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"khcy", 0x0445}, - {"", 0}, {"", 0}, {"", 0}, - {"precnapprox", 0x2AB9}, - {"", 0}, - {"DownLeftRightVector", 0x2950}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RuleDelayed", 0x29F4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"kjcy", 0x045C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"LeftAngleBracket", 0x27E8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RightAngleBracket", 0x27E9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"acirc", 0x00E2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"CounterClockwiseContourIntegral", 0x2233}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angmsdaf", 0x29AD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"aring", 0x00E5}, - {"", 0}, {"", 0}, {"", 0}, - {"odiv", 0x2A38}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RightUpDownVector", 0x294F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"leftrightarrow", 0x2194}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"FilledVerySmallSquare", 0x25AA}, - {"", 0}, - {"Integral", 0x222B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"planck", 0x210F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"daleth", 0x2138}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"suphsol", 0x27C9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"SucceedsEqual", 0x2AB0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rdsh", 0x21B3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ldsh", 0x21B2}, - {"", 0}, {"", 0}, - {"approx", 0x2248}, - {"", 0}, {"", 0}, {"", 0}, - {"jukcy", 0x0454}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Leftrightarrow", 0x21D4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cuesc", 0x22DF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"xcirc", 0x25EF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lstrok", 0x0142}, - {"", 0}, - {"subsim", 0x2AC7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"tstrok", 0x0167}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Jsercy", 0x0408}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"RightVectorBar", 0x2953}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"ycy", 0x044B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"yicy", 0x0457}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angmsdad", 0x29AB}, - {"", 0}, {"", 0}, - {"Lstrok", 0x0141}, - {"", 0}, {"", 0}, - {"NegativeThickSpace", 0x200B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rbrack", 0x005D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"lbrack", 0x005B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"kcedil", 0x0137}, - {"", 0}, {"", 0}, {"", 0}, - {"ImaginaryI", 0x2148}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"UnderParenthesis", 0x23DD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"InvisibleComma", 0x2063}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ffilig", 0xFB03}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Tstrok", 0x0166}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"divonx", 0x22C7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Ccirc", 0x0108}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"NotRightTriangleBar", 0x29D00338}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotRightTriangleEqual", 0x22ED}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NotRightTriangle", 0x22EB}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"kcy", 0x043A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"bigcirc", 0x25EF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"pertenk", 0x2031}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxvH", 0x256A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"luruhar", 0x2966}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"subedot", 0x2AC3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"curlyeqprec", 0x22DE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"dstrok", 0x0111}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cuvee", 0x22CE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"straightepsilon", 0x03F5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxbox", 0x29C9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"ycirc", 0x0177}, - {"", 0}, {"", 0}, {"", 0}, - {"vzigzag", 0x299A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"alpha", 0x03B1}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Cedilla", 0x00B8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rightleftharpoons", 0x21CC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RightTeeArrow", 0x21A6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"hslash", 0x210F}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"planckh", 0x210E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"rightleftarrows", 0x21C4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DDotrahd", 0x2911}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"boxvh", 0x253C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ggg", 0x22D9}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"SOFTcy", 0x042C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rightthreetimes", 0x22CC}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"boxh", 0x2500}, - {"", 0}, {"", 0}, - {"checkmark", 0x2713}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Hstrok", 0x0126}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"SupersetEqual", 0x2287}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rightsquigarrow", 0x219D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"ruluhar", 0x2968}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angmsdag", 0x29AE}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"udblac", 0x0171}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"jsercy", 0x0458}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"VerticalBar", 0x2223}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"DownRightVector", 0x21C1}, - {"", 0}, {"", 0}, - {"DownRightVectorBar", 0x2957}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Odblac", 0x0150}, - {"", 0}, - {"VerticalSeparator", 0x2758}, - {"", 0}, {"", 0}, {"", 0}, - {"cylcty", 0x232D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"succapprox", 0x2AB8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angsph", 0x2222}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"softcy", 0x044C}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Udblac", 0x0170}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"kgreen", 0x0138}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"digamma", 0x03DD}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"lurdshar", 0x294A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"boxhD", 0x2565}, - {"", 0}, {"", 0}, {"", 0}, - {"RightArrow", 0x2192}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"ldrushar", 0x294B}, - {"", 0}, - {"twoheadrightarrow", 0x21A0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RightArrowLeftArrow", 0x21C4}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"CloseCurlyQuote", 0x2019}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angmsdac", 0x29AA}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RightDownTeeVector", 0x295D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"SubsetEqual", 0x2286}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"SHCHcy", 0x0429}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RightDoubleBracket", 0x27E7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"Dstrok", 0x0110}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RightUpVector", 0x21BE}, - {"", 0}, {"", 0}, - {"RightUpVectorBar", 0x2954}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angmsdah", 0x29AF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"cupbrcap", 0x2A48}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"multimap", 0x22B8}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"angmsdaa", 0x29A8}, - {"HARDcy", 0x042A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"odblac", 0x0151}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"hstrok", 0x0127}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"twoheadleftarrow", 0x219E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"curlyeqsucc", 0x22DF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"cuwed", 0x22CF}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"CloseCurlyDoubleQuote", 0x201D}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"yucy", 0x044E}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rightarrow", 0x2192}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"straightphi", 0x03D5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"Rightarrow", 0x21D2}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"hardcy", 0x044A}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"shchcy", 0x0449}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"succnapprox", 0x2ABA}, - {"", 0}, {"", 0}, {"", 0}, - {"RightArrowBar", 0x21E5}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"RightDownVector", 0x21C2}, - {"", 0}, {"", 0}, - {"RightDownVectorBar", 0x2955}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"curvearrowleft", 0x21B6}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rightharpoondown", 0x21C1}, - {"", 0}, {"", 0}, - {"curvearrowright", 0x21B7}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, - {"rightarrowtail", 0x21A3}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"rightharpoonup", 0x21C0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, - {"SuchThat", 0x220B}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, - {"RightCeiling", 0x2309}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"", 0}, {"", 0}, {"", 0}, {"", 0}, - {"NoBreak", 0x2060} - }; - - if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) - { - register int key = hash (str, len); - - if (key <= MAX_HASH_VALUE && key >= 0) - { - register const char *s = wordlistkey.name; - - if (*str == *s && !strncmp (str + 1, s + 1, len - 1) && slen == '\0') - return &wordlistkey; - } - } - return 0; -} - -int htmlchar_from_string(const char *s) -{ - const struct htmlchar_desc *d = __htmlchar_lookup(s, strlen(s)); - return (d == NULL ? -1 : d->value); -} - -int htmlchar_from_string_len(const char *s, size_t len) -{ - const struct htmlchar_desc *d = __htmlchar_lookup(s, len); - return (d == NULL ? -1 : d->value); -} -
View file
cyrus-imapd-2.5.tar.gz/lib/htmlchar.h
Deleted
@@ -1,10 +0,0 @@ -/* Automatically generated by compile_st.pl, do not edit */ -#ifndef __STRING_TABLE_htmlchar_H_ -#define __STRING_TABLE_htmlchar_H_ 1 - -#include <string.h> - -extern int htmlchar_from_string(const char *s); -extern int htmlchar_from_string_len(const char *s, size_t len); - -#endif /* __STRING_TABLE_htmlchar_H_ */
View file
cyrus-imapd-2.5.tar.gz/lib/htmlchar.st
Deleted
@@ -1,2171 +0,0 @@ -# htmlchars.st - string table data for HTML5 named characters -# -# Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# -# 3. The name "Carnegie Mellon University" must not be used to -# endorse or promote products derived from this software without -# prior written permission. For permission or any legal -# details, please contact -# Carnegie Mellon University -# Center for Technology Transfer and Enterprise Creation -# 4615 Forbes Avenue -# Suite 302 -# Pittsburgh, PA 15213 -# (412) 268-7393, fax: (412) 268-7395 -# innovation@andrew.cmu.edu -# -# 4. Redistributions of any form whatsoever must retain the following -# acknowledgment: -# "This product includes software developed by Computing Services -# at Carnegie Mellon University (http://www.cmu.edu/computing/)." -# -# CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO -# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE -# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN -# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING -# OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -# Derived from -# http://dev.w3.org/html5/spec/named-character-references.html#named-character-references - -table htmlchar -ent 0x00C1 "Aacute" -ent 0x00E1 "aacute" -ent 0x0102 "Abreve" -ent 0x0103 "abreve" -ent 0x223F "acd" -ent 0x223E0333 "acE" -ent 0x00C2 "Acirc" -ent 0x00E2 "acirc" -ent 0x223E "ac" -ent 0x00B4 "acute" -ent 0x0410 "Acy" -ent 0x0430 "acy" -ent 0x00C6 "AElig" -ent 0x00E6 "aelig" -ent 0xD504 "Afr" -ent 0xD51E "afr" -ent 0x2061 "af" -ent 0x00C0 "Agrave" -ent 0x00E0 "agrave" -ent 0x2135 "alefsym" -ent 0x2135 "aleph" -ent 0x0391 "Alpha" -ent 0x03B1 "alpha" -ent 0x0100 "Amacr" -ent 0x0101 "amacr" -ent 0x2A3F "amalg" -ent 0x0026 "amp" -ent 0x0026 "AMP" -ent 0x2A55 "andand" -ent 0x2A5C "andd" -ent 0x2A58 "andslope" -ent 0x2227 "and" -ent 0x2A53 "And" -ent 0x2A5A "andv" -ent 0x29A4 "ange" -ent 0x2220 "angle" -ent 0x29A8 "angmsdaa" -ent 0x29A9 "angmsdab" -ent 0x29AA "angmsdac" -ent 0x29AB "angmsdad" -ent 0x29AC "angmsdae" -ent 0x29AD "angmsdaf" -ent 0x29AE "angmsdag" -ent 0x29AF "angmsdah" -ent 0x2221 "angmsd" -ent 0x221F "angrt" -ent 0x299D "angrtvbd" -ent 0x22BE "angrtvb" -ent 0x2222 "angsph" -ent 0x00C5 "angst" -ent 0x2220 "ang" -ent 0x237C "angzarr" -ent 0x0104 "Aogon" -ent 0x0105 "aogon" -ent 0xD538 "Aopf" -ent 0xD552 "aopf" -ent 0x2A6F "apacir" -ent 0x224A "ape" -ent 0x2A70 "apE" -ent 0x224B "apid" -ent 0x0027 "apos" -ent 0x2061 "ApplyFunction" -ent 0x224A "approxeq" -ent 0x2248 "approx" -ent 0x2248 "ap" -ent 0x00C5 "Aring" -ent 0x00E5 "aring" -ent 0xD49C "Ascr" -ent 0xD4B6 "ascr" -ent 0x2254 "Assign" -ent 0x002A "ast" -ent 0x224D "asympeq" -ent 0x2248 "asymp" -ent 0x00C3 "Atilde" -ent 0x00E3 "atilde" -ent 0x00C4 "Auml" -ent 0x00E4 "auml" -ent 0x2233 "awconint" -ent 0x2A11 "awint" -ent 0x224C "backcong" -ent 0x03F6 "backepsilon" -ent 0x2035 "backprime" -ent 0x22CD "backsimeq" -ent 0x223D "backsim" -ent 0x2216 "Backslash" -ent 0x22BD "barvee" -ent 0x2AE7 "Barv" -ent 0x2305 "barwedge" -ent 0x2305 "barwed" -ent 0x2306 "Barwed" -ent 0x23B6 "bbrktbrk" -ent 0x23B5 "bbrk" -ent 0x224C "bcong" -ent 0x0411 "Bcy" -ent 0x0431 "bcy" -ent 0x201E "bdquo" -ent 0x2235 "because" -ent 0x2235 "Because" -ent 0x2235 "becaus" -ent 0x29B0 "bemptyv" -ent 0x03F6 "bepsi" -ent 0x212C "Bernoullis" -ent 0x212C "bernou" -ent 0x0392 "Beta" -ent 0x03B2 "beta" -ent 0x2136 "beth" -ent 0x226C "between" -ent 0xD505 "Bfr" -ent 0xD51F "bfr" -ent 0x22C2 "bigcap" -ent 0x25EF "bigcirc" -ent 0x22C3 "bigcup" -ent 0x2A00 "bigodot" -ent 0x2A01 "bigoplus" -ent 0x2A02 "bigotimes" -ent 0x2A06 "bigsqcup" -ent 0x2605 "bigstar" -ent 0x25BD "bigtriangledown" -ent 0x25B3 "bigtriangleup" -ent 0x2A04 "biguplus" -ent 0x22C1 "bigvee" -ent 0x22C0 "bigwedge" -ent 0x290D "bkarow" -ent 0x29EB "blacklozenge" -ent 0x25AA "blacksquare" -ent 0x25BE "blacktriangledown" -ent 0x25C2 "blacktriangleleft" -ent 0x25B8 "blacktriangleright" -ent 0x25B4 "blacktriangle" -ent 0x2423 "blank" -ent 0x2592 "blk12" -ent 0x2591 "blk14" -ent 0x2593 "blk34" -ent 0x2588 "block" -ent 0x226120E5 "bnequiv" -ent 0x003D20E5 "bne" -ent 0x2310 "bnot" -ent 0x2AED "bNot" -ent 0xD539 "Bopf" -ent 0xD553 "bopf" -ent 0x22A5 "bottom" -ent 0x22A5 "bot" -ent 0x22C8 "bowtie" -ent 0x29C9 "boxbox" -ent 0x2510 "boxdl" -ent 0x2555 "boxdL" -ent 0x2556 "boxDl" -ent 0x2557 "boxDL" -ent 0x250C "boxdr" -ent 0x2552 "boxdR" -ent 0x2553 "boxDr" -ent 0x2554 "boxDR" -ent 0x252C "boxhd" -ent 0x2564 "boxHd" -ent 0x2565 "boxhD" -ent 0x2566 "boxHD" -ent 0x2500 "boxh" -ent 0x2550 "boxH" -ent 0x2534 "boxhu" -ent 0x2567 "boxHu" -ent 0x2568 "boxhU" -ent 0x2569 "boxHU" -ent 0x229F "boxminus" -ent 0x229E "boxplus" -ent 0x22A0 "boxtimes" -ent 0x2518 "boxul" -ent 0x255B "boxuL" -ent 0x255C "boxUl" -ent 0x255D "boxUL" -ent 0x2514 "boxur" -ent 0x2558 "boxuR" -ent 0x2559 "boxUr" -ent 0x255A "boxUR" -ent 0x253C "boxvh" -ent 0x256A "boxvH" -ent 0x256B "boxVh" -ent 0x256C "boxVH" -ent 0x2524 "boxvl" -ent 0x2561 "boxvL" -ent 0x2562 "boxVl" -ent 0x2563 "boxVL" -ent 0x251C "boxvr" -ent 0x255E "boxvR" -ent 0x255F "boxVr" -ent 0x2560 "boxVR" -ent 0x2502 "boxv" -ent 0x2551 "boxV" -ent 0x2035 "bprime" -ent 0x02D8 "breve" -ent 0x02D8 "Breve" -ent 0x00A6 "brvbar" -ent 0x212C "Bscr" -ent 0xD4B7 "bscr" -ent 0x204F "bsemi" -ent 0x22CD "bsime" -ent 0x223D "bsim" -ent 0x29C5 "bsolb" -ent 0x27C8 "bsolhsub" -ent 0x005C "bsol" -ent 0x2022 "bullet" -ent 0x2022 "bull" -ent 0x224E "Bumpeq" -ent 0x224F "bumpeq" -ent 0x224F "bumpe" -ent 0x2AAE "bumpE" -ent 0x224E "bump" -ent 0x0106 "Cacute" -ent 0x0107 "cacute" -ent 0x2A44 "capand" -ent 0x2A49 "capbrcup" -ent 0x2A4B "capcap" -ent 0x2A47 "capcup" -ent 0x2A40 "capdot" -ent 0x2145 "CapitalDifferentialD" -ent 0x2229FE00 "caps" -ent 0x2229 "cap" -ent 0x22D2 "Cap" -ent 0x2041 "caret" -ent 0x02C7 "caron" -ent 0x212D "Cayleys" -ent 0x2A4D "ccaps" -ent 0x010C "Ccaron" -ent 0x010D "ccaron" -ent 0x00C7 "Ccedil" -ent 0x00E7 "ccedil" -ent 0x0108 "Ccirc" -ent 0x0109 "ccirc" -ent 0x2230 "Cconint" -ent 0x2A50 "ccupssm" -ent 0x2A4C "ccups" -ent 0x010A "Cdot" -ent 0x010B "cdot" -ent 0x00B8 "Cedilla" -ent 0x00B8 "cedil" -ent 0x29B2 "cemptyv" -ent 0x00B7 "centerdot" -ent 0x00B7 "CenterDot" -ent 0x00A2 "cent" -ent 0x212D "Cfr" -ent 0xD520 "cfr" -ent 0x0427 "CHcy" -ent 0x0447 "chcy" -ent 0x2713 "checkmark" -ent 0x2713 "check" -ent 0x03A7 "Chi" -ent 0x03C7 "chi" -ent 0x2257 "circeq" -ent 0x21BA "circlearrowleft" -ent 0x21BB "circlearrowright" -ent 0x229B "circledast" -ent 0x229A "circledcirc" -ent 0x229D "circleddash" -ent 0x2299 "CircleDot" -ent 0x00AE "circledR" -ent 0x24C8 "circledS" -ent 0x2296 "CircleMinus" -ent 0x2295 "CirclePlus" -ent 0x2297 "CircleTimes" -ent 0x02C6 "circ" -ent 0x2257 "cire" -ent 0x29C3 "cirE" -ent 0x2A10 "cirfnint" -ent 0x2AEF "cirmid" -ent 0x29C2 "cirscir" -ent 0x25CB "cir" -ent 0x2232 "ClockwiseContourIntegral" -ent 0x201D "CloseCurlyDoubleQuote" -ent 0x2019 "CloseCurlyQuote" -ent 0x2663 "clubs" -ent 0x2663 "clubsuit" -ent 0x2254 "coloneq" -ent 0x2254 "colone" -ent 0x2A74 "Colone" -ent 0x003A "colon" -ent 0x2237 "Colon" -ent 0x0040 "commat" -ent 0x002C "comma" -ent 0x2218 "compfn" -ent 0x2201 "complement" -ent 0x2102 "complexes" -ent 0x2201 "comp" -ent 0x2A6D "congdot" -ent 0x2261 "Congruent" -ent 0x2245 "cong" -ent 0x222E "conint" -ent 0x222F "Conint" -ent 0x222E "ContourIntegral" -ent 0x2102 "Copf" -ent 0xD554 "copf" -ent 0x2210 "coprod" -ent 0x2210 "Coproduct" -ent 0x2117 "copysr" -ent 0x00A9 "copy" -ent 0x00A9 "COPY" -ent 0x2233 "CounterClockwiseContourIntegral" -ent 0x21B5 "crarr" -ent 0x2717 "cross" -ent 0x2A2F "Cross" -ent 0xD49E "Cscr" -ent 0xD4B8 "cscr" -ent 0x2AD1 "csube" -ent 0x2ACF "csub" -ent 0x2AD2 "csupe" -ent 0x2AD0 "csup" -ent 0x22EF "ctdot" -ent 0x2938 "cudarrl" -ent 0x2935 "cudarrr" -ent 0x22DE "cuepr" -ent 0x22DF "cuesc" -ent 0x293D "cularrp" -ent 0x21B6 "cularr" -ent 0x2A48 "cupbrcap" -ent 0x224D "CupCap" -ent 0x2A46 "cupcap" -ent 0x2A4A "cupcup" -ent 0x228D "cupdot" -ent 0x2A45 "cupor" -ent 0x222AFE00 "cups" -ent 0x222A "cup" -ent 0x22D3 "Cup" -ent 0x293C "curarrm" -ent 0x21B7 "curarr" -ent 0x22DE "curlyeqprec" -ent 0x22DF "curlyeqsucc" -ent 0x22CE "curlyvee" -ent 0x22CF "curlywedge" -ent 0x00A4 "curren" -ent 0x21B6 "curvearrowleft" -ent 0x21B7 "curvearrowright" -ent 0x22CE "cuvee" -ent 0x22CF "cuwed" -ent 0x2232 "cwconint" -ent 0x2231 "cwint" -ent 0x232D "cylcty" -ent 0x2020 "dagger" -ent 0x2021 "Dagger" -ent 0x2138 "daleth" -ent 0x2193 "darr" -ent 0x21A1 "Darr" -ent 0x21D3 "dArr" -ent 0x2010 "dash" -ent 0x22A3 "dashv" -ent 0x2AE4 "Dashv" -ent 0x290F "dbkarow" -ent 0x02DD "dblac" -ent 0x010E "Dcaron" -ent 0x010F "dcaron" -ent 0x0414 "Dcy" -ent 0x0434 "dcy" -ent 0x2021 "ddagger" -ent 0x21CA "ddarr" -ent 0x2911 "DDotrahd" -ent 0x2A77 "ddotseq" -ent 0x2145 "DD" -ent 0x2146 "dd" -ent 0x00B0 "deg" -ent 0x0394 "Delta" -ent 0x03B4 "delta" -ent 0x2207 "Del" -ent 0x29B1 "demptyv" -ent 0x297F "dfisht" -ent 0xD507 "Dfr" -ent 0xD521 "dfr" -ent 0x21C3 "dharl" -ent 0x21C2 "dharr" -ent 0x2965 "dHar" -ent 0x00B4 "DiacriticalAcute" -ent 0x02D9 "DiacriticalDot" -ent 0x02DD "DiacriticalDoubleAcute" -ent 0x0060 "DiacriticalGrave" -ent 0x02DC "DiacriticalTilde" -ent 0x2666 "diamondsuit" -ent 0x22C4 "diamond" -ent 0x22C4 "Diamond" -ent 0x2666 "diams" -ent 0x22C4 "diam" -ent 0x00A8 "die" -ent 0x2146 "DifferentialD" -ent 0x03DD "digamma" -ent 0x22F2 "disin" -ent 0x22C7 "divideontimes" -ent 0x00F7 "divide" -ent 0x22C7 "divonx" -ent 0x00F7 "div" -ent 0x0402 "DJcy" -ent 0x0452 "djcy" -ent 0x231E "dlcorn" -ent 0x230D "dlcrop" -ent 0x0024 "dollar" -ent 0xD53B "Dopf" -ent 0xD555 "dopf" -ent 0x20DC "DotDot" -ent 0x2251 "doteqdot" -ent 0x2250 "doteq" -ent 0x2250 "DotEqual" -ent 0x2238 "dotminus" -ent 0x2214 "dotplus" -ent 0x22A1 "dotsquare" -ent 0x00A8 "Dot" -ent 0x02D9 "dot" -ent 0x2306 "doublebarwedge" -ent 0x222F "DoubleContourIntegral" -ent 0x00A8 "DoubleDot" -ent 0x21D3 "DoubleDownArrow" -ent 0x21D0 "DoubleLeftArrow" -ent 0x21D4 "DoubleLeftRightArrow" -ent 0x2AE4 "DoubleLeftTee" -ent 0x27F8 "DoubleLongLeftArrow" -ent 0x27FA "DoubleLongLeftRightArrow" -ent 0x27F9 "DoubleLongRightArrow" -ent 0x21D2 "DoubleRightArrow" -ent 0x22A8 "DoubleRightTee" -ent 0x21D1 "DoubleUpArrow" -ent 0x21D5 "DoubleUpDownArrow" -ent 0x2225 "DoubleVerticalBar" -ent 0x2913 "DownArrowBar" -ent 0x2193 "downarrow" -ent 0x2193 "DownArrow" -ent 0x21D3 "Downarrow" -ent 0x21F5 "DownArrowUpArrow" -ent 0x0311 "DownBreve" -ent 0x21CA "downdownarrows" -ent 0x21C3 "downharpoonleft" -ent 0x21C2 "downharpoonright" -ent 0x2950 "DownLeftRightVector" -ent 0x295E "DownLeftTeeVector" -ent 0x2956 "DownLeftVectorBar" -ent 0x21BD "DownLeftVector" -ent 0x295F "DownRightTeeVector" -ent 0x2957 "DownRightVectorBar" -ent 0x21C1 "DownRightVector" -ent 0x21A7 "DownTeeArrow" -ent 0x22A4 "DownTee" -ent 0x2910 "drbkarow" -ent 0x231F "drcorn" -ent 0x230C "drcrop" -ent 0xD49F "Dscr" -ent 0xD4B9 "dscr" -ent 0x0405 "DScy" -ent 0x0455 "dscy" -ent 0x29F6 "dsol" -ent 0x0110 "Dstrok" -ent 0x0111 "dstrok" -ent 0x22F1 "dtdot" -ent 0x25BE "dtrif" -ent 0x25BF "dtri" -ent 0x21F5 "duarr" -ent 0x296F "duhar" -ent 0x29A6 "dwangle" -ent 0x040F "DZcy" -ent 0x045F "dzcy" -ent 0x27FF "dzigrarr" -ent 0x00C9 "Eacute" -ent 0x00E9 "eacute" -ent 0x2A6E "easter" -ent 0x011A "Ecaron" -ent 0x011B "ecaron" -ent 0x00CA "Ecirc" -ent 0x00EA "ecirc" -ent 0x2256 "ecir" -ent 0x2255 "ecolon" -ent 0x042D "Ecy" -ent 0x044D "ecy" -ent 0x2A77 "eDDot" -ent 0x0116 "Edot" -ent 0x0117 "edot" -ent 0x2251 "eDot" -ent 0x2147 "ee" -ent 0x2252 "efDot" -ent 0xD508 "Efr" -ent 0xD522 "efr" -ent 0x00C8 "Egrave" -ent 0x00E8 "egrave" -ent 0x2A98 "egsdot" -ent 0x2A96 "egs" -ent 0x2A9A "eg" -ent 0x2208 "Element" -ent 0x23E7 "elinters" -ent 0x2113 "ell" -ent 0x2A97 "elsdot" -ent 0x2A95 "els" -ent 0x2A99 "el" -ent 0x0112 "Emacr" -ent 0x0113 "emacr" -ent 0x2205 "emptyset" -ent 0x25FB "EmptySmallSquare" -ent 0x2205 "empty" -ent 0x25AB "EmptyVerySmallSquare" -ent 0x2205 "emptyv" -ent 0x2004 "emsp13" -ent 0x2005 "emsp14" -ent 0x2003 "emsp" -ent 0x014A "ENG" -ent 0x014B "eng" -ent 0x2002 "ensp" -ent 0x0118 "Eogon" -ent 0x0119 "eogon" -ent 0xD53C "Eopf" -ent 0xD556 "eopf" -ent 0x29E3 "eparsl" -ent 0x22D5 "epar" -ent 0x2A71 "eplus" -ent 0x0395 "Epsilon" -ent 0x03B5 "epsilon" -ent 0x03B5 "epsi" -ent 0x03F5 "epsiv" -ent 0x2256 "eqcirc" -ent 0x2255 "eqcolon" -ent 0x2242 "eqsim" -ent 0x2A96 "eqslantgtr" -ent 0x2A95 "eqslantless" -ent 0x003D "equals" -ent 0x2242 "EqualTilde" -ent 0x2A75 "Equal" -ent 0x225F "equest" -ent 0x21CC "Equilibrium" -ent 0x2A78 "equivDD" -ent 0x2261 "equiv" -ent 0x29E5 "eqvparsl" -ent 0x2971 "erarr" -ent 0x2253 "erDot" -ent 0x212F "escr" -ent 0x2130 "Escr" -ent 0x2250 "esdot" -ent 0x2242 "esim" -ent 0x2A73 "Esim" -ent 0x0397 "Eta" -ent 0x03B7 "eta" -ent 0x00D0 "ETH" -ent 0x00F0 "eth" -ent 0x00CB "Euml" -ent 0x00EB "euml" -ent 0x20AC "euro" -ent 0x0021 "excl" -ent 0x2203 "Exists" -ent 0x2203 "exist" -ent 0x2130 "expectation" -ent 0x2147 "exponentiale" -ent 0x2147 "ExponentialE" -ent 0x2252 "fallingdotseq" -ent 0x0424 "Fcy" -ent 0x0444 "fcy" -ent 0x2640 "female" -ent 0xFB03 "ffilig" -ent 0xFB00 "fflig" -ent 0xFB04 "ffllig" -ent 0xD509 "Ffr" -ent 0xD523 "ffr" -ent 0xFB01 "filig" -ent 0x25FC "FilledSmallSquare" -ent 0x25AA "FilledVerySmallSquare" -ent 0x0066006A "fjlig" -ent 0x266D "flat" -ent 0xFB02 "fllig" -ent 0x25B1 "fltns" -ent 0x0192 "fnof" -ent 0xD53D "Fopf" -ent 0xD557 "fopf" -ent 0x2200 "forall" -ent 0x2200 "ForAll" -ent 0x22D4 "fork" -ent 0x2AD9 "forkv" -ent 0x2131 "Fouriertrf" -ent 0x2A0D "fpartint" -ent 0x00BD "frac12" -ent 0x2153 "frac13" -ent 0x00BC "frac14" -ent 0x2155 "frac15" -ent 0x2159 "frac16" -ent 0x215B "frac18" -ent 0x2154 "frac23" -ent 0x2156 "frac25" -ent 0x00BE "frac34" -ent 0x2157 "frac35" -ent 0x215C "frac38" -ent 0x2158 "frac45" -ent 0x215A "frac56" -ent 0x215D "frac58" -ent 0x215E "frac78" -ent 0x2044 "frasl" -ent 0x2322 "frown" -ent 0x2131 "Fscr" -ent 0xD4BB "fscr" -ent 0x01F5 "gacute" -ent 0x03DC "Gammad" -ent 0x03DD "gammad" -ent 0x0393 "Gamma" -ent 0x03B3 "gamma" -ent 0x2A86 "gap" -ent 0x011E "Gbreve" -ent 0x011F "gbreve" -ent 0x0122 "Gcedil" -ent 0x011C "Gcirc" -ent 0x011D "gcirc" -ent 0x0413 "Gcy" -ent 0x0433 "gcy" -ent 0x0120 "Gdot" -ent 0x0121 "gdot" -ent 0x22DB "gel" -ent 0x2A8C "gEl" -ent 0x2267 "geqq" -ent 0x2A7E "geqslant" -ent 0x2265 "geq" -ent 0x2AA9 "gescc" -ent 0x2A84 "gesdotol" -ent 0x2A82 "gesdoto" -ent 0x2A80 "gesdot" -ent 0x2A94 "gesles" -ent 0x22DBFE00 "gesl" -ent 0x2A7E "ges" -ent 0x2265 "ge" -ent 0x2267 "gE" -ent 0xD50A "Gfr" -ent 0xD524 "gfr" -ent 0x22D9 "ggg" -ent 0x226B "gg" -ent 0x22D9 "Gg" -ent 0x2137 "gimel" -ent 0x0403 "GJcy" -ent 0x0453 "gjcy" -ent 0x2AA5 "gla" -ent 0x2A92 "glE" -ent 0x2AA4 "glj" -ent 0x2277 "gl" -ent 0x2A8A "gnapprox" -ent 0x2A8A "gnap" -ent 0x2269 "gneqq" -ent 0x2A88 "gneq" -ent 0x2269 "gnE" -ent 0x2A88 "gne" -ent 0x22E7 "gnsim" -ent 0xD53E "Gopf" -ent 0xD558 "gopf" -ent 0x0060 "grave" -ent 0x22DB "GreaterEqualLess" -ent 0x2265 "GreaterEqual" -ent 0x2267 "GreaterFullEqual" -ent 0x2AA2 "GreaterGreater" -ent 0x2277 "GreaterLess" -ent 0x2A7E "GreaterSlantEqual" -ent 0x2273 "GreaterTilde" -ent 0x210A "gscr" -ent 0xD4A2 "Gscr" -ent 0x2A8E "gsime" -ent 0x2A90 "gsiml" -ent 0x2273 "gsim" -ent 0x2AA7 "gtcc" -ent 0x2A7A "gtcir" -ent 0x22D7 "gtdot" -ent 0x2995 "gtlPar" -ent 0x2A7C "gtquest" -ent 0x2A86 "gtrapprox" -ent 0x2978 "gtrarr" -ent 0x22D7 "gtrdot" -ent 0x22DB "gtreqless" -ent 0x2A8C "gtreqqless" -ent 0x2277 "gtrless" -ent 0x2273 "gtrsim" -ent 0x003E "gt" -ent 0x003E "GT" -ent 0x226B "Gt" -ent 0x2269FE00 "gvertneqq" -ent 0x2269FE00 "gvnE" -ent 0x02C7 "Hacek" -ent 0x200A "hairsp" -ent 0x00BD "half" -ent 0x210B "hamilt" -ent 0x042A "HARDcy" -ent 0x044A "hardcy" -ent 0x2948 "harrcir" -ent 0x2194 "harr" -ent 0x21D4 "hArr" -ent 0x21AD "harrw" -ent 0x005E "Hat" -ent 0x210F "hbar" -ent 0x0124 "Hcirc" -ent 0x0125 "hcirc" -ent 0x2665 "hearts" -ent 0x2665 "heartsuit" -ent 0x2026 "hellip" -ent 0x22B9 "hercon" -ent 0x210C "Hfr" -ent 0xD525 "hfr" -ent 0x210B "HilbertSpace" -ent 0x2925 "hksearow" -ent 0x2926 "hkswarow" -ent 0x21FF "hoarr" -ent 0x223B "homtht" -ent 0x21A9 "hookleftarrow" -ent 0x21AA "hookrightarrow" -ent 0x210D "Hopf" -ent 0xD559 "hopf" -ent 0x2015 "horbar" -ent 0x2500 "HorizontalLine" -ent 0x210B "Hscr" -ent 0xD4BD "hscr" -ent 0x210F "hslash" -ent 0x0126 "Hstrok" -ent 0x0127 "hstrok" -ent 0x224E "HumpDownHump" -ent 0x224F "HumpEqual" -ent 0x2043 "hybull" -ent 0x2010 "hyphen" -ent 0x00CD "Iacute" -ent 0x00ED "iacute" -ent 0x00CE "Icirc" -ent 0x00EE "icirc" -ent 0x2063 "ic" -ent 0x0418 "Icy" -ent 0x0438 "icy" -ent 0x0130 "Idot" -ent 0x0415 "IEcy" -ent 0x0435 "iecy" -ent 0x00A1 "iexcl" -ent 0x21D4 "iff" -ent 0x2111 "Ifr" -ent 0xD526 "ifr" -ent 0x00CC "Igrave" -ent 0x00EC "igrave" -ent 0x2A0C "iiiint" -ent 0x222D "iiint" -ent 0x29DC "iinfin" -ent 0x2129 "iiota" -ent 0x2148 "ii" -ent 0x0132 "IJlig" -ent 0x0133 "ijlig" -ent 0x012A "Imacr" -ent 0x012B "imacr" -ent 0x2111 "image" -ent 0x2148 "ImaginaryI" -ent 0x2110 "imagline" -ent 0x2111 "imagpart" -ent 0x0131 "imath" -ent 0x22B7 "imof" -ent 0x01B5 "imped" -ent 0x21D2 "Implies" -ent 0x2111 "Im" -ent 0x2105 "incare" -ent 0x29DD "infintie" -ent 0x221E "infin" -ent 0x0131 "inodot" -ent 0x22BA "intcal" -ent 0x2124 "integers" -ent 0x222B "Integral" -ent 0x22BA "intercal" -ent 0x22C2 "Intersection" -ent 0x2A17 "intlarhk" -ent 0x2A3C "intprod" -ent 0x222B "int" -ent 0x222C "Int" -ent 0x2208 "in" -ent 0x2063 "InvisibleComma" -ent 0x2062 "InvisibleTimes" -ent 0x0401 "IOcy" -ent 0x0451 "iocy" -ent 0x012E "Iogon" -ent 0x012F "iogon" -ent 0xD540 "Iopf" -ent 0xD55A "iopf" -ent 0x0399 "Iota" -ent 0x03B9 "iota" -ent 0x2A3C "iprod" -ent 0x00BF "iquest" -ent 0x2110 "Iscr" -ent 0xD4BE "iscr" -ent 0x22F5 "isindot" -ent 0x22F9 "isinE" -ent 0x22F4 "isins" -ent 0x22F3 "isinsv" -ent 0x2208 "isin" -ent 0x2208 "isinv" -ent 0x0128 "Itilde" -ent 0x0129 "itilde" -ent 0x2062 "it" -ent 0x0406 "Iukcy" -ent 0x0456 "iukcy" -ent 0x00CF "Iuml" -ent 0x00EF "iuml" -ent 0x0134 "Jcirc" -ent 0x0135 "jcirc" -ent 0x0419 "Jcy" -ent 0x0439 "jcy" -ent 0xD50D "Jfr" -ent 0xD527 "jfr" -ent 0x0237 "jmath" -ent 0xD541 "Jopf" -ent 0xD55B "jopf" -ent 0xD4A5 "Jscr" -ent 0xD4BF "jscr" -ent 0x0408 "Jsercy" -ent 0x0458 "jsercy" -ent 0x0404 "Jukcy" -ent 0x0454 "jukcy" -ent 0x039A "Kappa" -ent 0x03BA "kappa" -ent 0x03F0 "kappav" -ent 0x0136 "Kcedil" -ent 0x0137 "kcedil" -ent 0x041A "Kcy" -ent 0x043A "kcy" -ent 0xD50E "Kfr" -ent 0xD528 "kfr" -ent 0x0138 "kgreen" -ent 0x0425 "KHcy" -ent 0x0445 "khcy" -ent 0x040C "KJcy" -ent 0x045C "kjcy" -ent 0xD542 "Kopf" -ent 0xD55C "kopf" -ent 0xD4A6 "Kscr" -ent 0xD4C0 "kscr" -ent 0x21DA "lAarr" -ent 0x0139 "Lacute" -ent 0x013A "lacute" -ent 0x29B4 "laemptyv" -ent 0x2112 "lagran" -ent 0x039B "Lambda" -ent 0x03BB "lambda" -ent 0x2991 "langd" -ent 0x27E8 "langle" -ent 0x27E8 "lang" -ent 0x27EA "Lang" -ent 0x2112 "Laplacetrf" -ent 0x2A85 "lap" -ent 0x00AB "laquo" -ent 0x291F "larrbfs" -ent 0x21E4 "larrb" -ent 0x291D "larrfs" -ent 0x21A9 "larrhk" -ent 0x21AB "larrlp" -ent 0x2939 "larrpl" -ent 0x2973 "larrsim" -ent 0x21A2 "larrtl" -ent 0x2190 "larr" -ent 0x219E "Larr" -ent 0x21D0 "lArr" -ent 0x2919 "latail" -ent 0x291B "lAtail" -ent 0x2AADFE00 "lates" -ent 0x2AAD "late" -ent 0x2AAB "lat" -ent 0x290C "lbarr" -ent 0x290E "lBarr" -ent 0x2772 "lbbrk" -ent 0x007B "lbrace" -ent 0x005B "lbrack" -ent 0x298B "lbrke" -ent 0x298F "lbrksld" -ent 0x298D "lbrkslu" -ent 0x013D "Lcaron" -ent 0x013E "lcaron" -ent 0x013B "Lcedil" -ent 0x013C "lcedil" -ent 0x2308 "lceil" -ent 0x007B "lcub" -ent 0x041B "Lcy" -ent 0x043B "lcy" -ent 0x2936 "ldca" -ent 0x201E "ldquor" -ent 0x201C "ldquo" -ent 0x2967 "ldrdhar" -ent 0x294B "ldrushar" -ent 0x21B2 "ldsh" -ent 0x27E8 "LeftAngleBracket" -ent 0x21E4 "LeftArrowBar" -ent 0x21C6 "LeftArrowRightArrow" -ent 0x21A2 "leftarrowtail" -ent 0x2190 "leftarrow" -ent 0x2190 "LeftArrow" -ent 0x21D0 "Leftarrow" -ent 0x2308 "LeftCeiling" -ent 0x27E6 "LeftDoubleBracket" -ent 0x2961 "LeftDownTeeVector" -ent 0x2959 "LeftDownVectorBar" -ent 0x21C3 "LeftDownVector" -ent 0x230A "LeftFloor" -ent 0x21BD "leftharpoondown" -ent 0x21BC "leftharpoonup" -ent 0x21C7 "leftleftarrows" -ent 0x21C6 "leftrightarrows" -ent 0x2194 "leftrightarrow" -ent 0x2194 "LeftRightArrow" -ent 0x21D4 "Leftrightarrow" -ent 0x21CB "leftrightharpoons" -ent 0x21AD "leftrightsquigarrow" -ent 0x294E "LeftRightVector" -ent 0x21A4 "LeftTeeArrow" -ent 0x22A3 "LeftTee" -ent 0x295A "LeftTeeVector" -ent 0x22CB "leftthreetimes" -ent 0x29CF "LeftTriangleBar" -ent 0x22B4 "LeftTriangleEqual" -ent 0x22B2 "LeftTriangle" -ent 0x2951 "LeftUpDownVector" -ent 0x2960 "LeftUpTeeVector" -ent 0x2958 "LeftUpVectorBar" -ent 0x21BF "LeftUpVector" -ent 0x2952 "LeftVectorBar" -ent 0x21BC "LeftVector" -ent 0x22DA "leg" -ent 0x2A8B "lEg" -ent 0x2266 "leqq" -ent 0x2A7D "leqslant" -ent 0x2264 "leq" -ent 0x2AA8 "lescc" -ent 0x2A83 "lesdotor" -ent 0x2A81 "lesdoto" -ent 0x2A7F "lesdot" -ent 0x2A93 "lesges" -ent 0x22DAFE00 "lesg" -ent 0x2A85 "lessapprox" -ent 0x22D6 "lessdot" -ent 0x22DA "lesseqgtr" -ent 0x2A8B "lesseqqgtr" -ent 0x22DA "LessEqualGreater" -ent 0x2266 "LessFullEqual" -ent 0x2276 "LessGreater" -ent 0x2276 "lessgtr" -ent 0x2AA1 "LessLess" -ent 0x2272 "lesssim" -ent 0x2A7D "LessSlantEqual" -ent 0x2272 "LessTilde" -ent 0x2A7D "les" -ent 0x2264 "le" -ent 0x2266 "lE" -ent 0x297C "lfisht" -ent 0x230A "lfloor" -ent 0xD50F "Lfr" -ent 0xD529 "lfr" -ent 0x2A91 "lgE" -ent 0x2276 "lg" -ent 0x21BD "lhard" -ent 0x2962 "lHar" -ent 0x296A "lharul" -ent 0x21BC "lharu" -ent 0x2584 "lhblk" -ent 0x0409 "LJcy" -ent 0x0459 "ljcy" -ent 0x21C7 "llarr" -ent 0x231E "llcorner" -ent 0x21DA "Lleftarrow" -ent 0x296B "llhard" -ent 0x25FA "lltri" -ent 0x226A "ll" -ent 0x22D8 "Ll" -ent 0x013F "Lmidot" -ent 0x0140 "lmidot" -ent 0x23B0 "lmoustache" -ent 0x23B0 "lmoust" -ent 0x2A89 "lnapprox" -ent 0x2A89 "lnap" -ent 0x2268 "lneqq" -ent 0x2A87 "lneq" -ent 0x2268 "lnE" -ent 0x2A87 "lne" -ent 0x22E6 "lnsim" -ent 0x27EC "loang" -ent 0x21FD "loarr" -ent 0x27E6 "lobrk" -ent 0x27F5 "longleftarrow" -ent 0x27F5 "LongLeftArrow" -ent 0x27F8 "Longleftarrow" -ent 0x27F7 "longleftrightarrow" -ent 0x27F7 "LongLeftRightArrow" -ent 0x27FA "Longleftrightarrow" -ent 0x27FC "longmapsto" -ent 0x27F6 "longrightarrow" -ent 0x27F6 "LongRightArrow" -ent 0x27F9 "Longrightarrow" -ent 0x21AB "looparrowleft" -ent 0x21AC "looparrowright" -ent 0x2985 "lopar" -ent 0xD543 "Lopf" -ent 0xD55D "lopf" -ent 0x2A2D "loplus" -ent 0x2A34 "lotimes" -ent 0x2217 "lowast" -ent 0x005F "lowbar" -ent 0x2199 "LowerLeftArrow" -ent 0x2198 "LowerRightArrow" -ent 0x25CA "lozenge" -ent 0x29EB "lozf" -ent 0x25CA "loz" -ent 0x2993 "lparlt" -ent 0x0028 "lpar" -ent 0x21C6 "lrarr" -ent 0x231F "lrcorner" -ent 0x296D "lrhard" -ent 0x21CB "lrhar" -ent 0x200E "lrm" -ent 0x22BF "lrtri" -ent 0x2039 "lsaquo" -ent 0x2112 "Lscr" -ent 0xD4C1 "lscr" -ent 0x21B0 "lsh" -ent 0x21B0 "Lsh" -ent 0x2A8D "lsime" -ent 0x2A8F "lsimg" -ent 0x2272 "lsim" -ent 0x005B "lsqb" -ent 0x201A "lsquor" -ent 0x2018 "lsquo" -ent 0x0141 "Lstrok" -ent 0x0142 "lstrok" -ent 0x2AA6 "ltcc" -ent 0x2A79 "ltcir" -ent 0x22D6 "ltdot" -ent 0x22CB "lthree" -ent 0x22C9 "ltimes" -ent 0x2976 "ltlarr" -ent 0x2A7B "ltquest" -ent 0x22B4 "ltrie" -ent 0x25C2 "ltrif" -ent 0x25C3 "ltri" -ent 0x2996 "ltrPar" -ent 0x003C "lt" -ent 0x003C "LT" -ent 0x226A "Lt" -ent 0x294A "lurdshar" -ent 0x2966 "luruhar" -ent 0x2268FE00 "lvertneqq" -ent 0x2268FE00 "lvnE" -ent 0x00AF "macr" -ent 0x2642 "male" -ent 0x2720 "maltese" -ent 0x2720 "malt" -ent 0x21A7 "mapstodown" -ent 0x21A4 "mapstoleft" -ent 0x21A6 "mapsto" -ent 0x21A5 "mapstoup" -ent 0x21A6 "map" -ent 0x2905 "Map" -ent 0x25AE "marker" -ent 0x2A29 "mcomma" -ent 0x041C "Mcy" -ent 0x043C "mcy" -ent 0x2014 "mdash" -ent 0x223A "mDDot" -ent 0x2221 "measuredangle" -ent 0x205F "MediumSpace" -ent 0x2133 "Mellintrf" -ent 0xD510 "Mfr" -ent 0xD52A "mfr" -ent 0x2127 "mho" -ent 0x00B5 "micro" -ent 0x002A "midast" -ent 0x2AF0 "midcir" -ent 0x00B7 "middot" -ent 0x2223 "mid" -ent 0x229F "minusb" -ent 0x2238 "minusd" -ent 0x2A2A "minusdu" -ent 0x2213 "MinusPlus" -ent 0x2212 "minus" -ent 0x2ADB "mlcp" -ent 0x2026 "mldr" -ent 0x2213 "mnplus" -ent 0x22A7 "models" -ent 0xD544 "Mopf" -ent 0xD55E "mopf" -ent 0x2213 "mp" -ent 0x2133 "Mscr" -ent 0xD4C2 "mscr" -ent 0x223E "mstpos" -ent 0x22B8 "multimap" -ent 0x22B8 "mumap" -ent 0x039C "Mu" -ent 0x03BC "mu" -ent 0x2207 "nabla" -ent 0x0143 "Nacute" -ent 0x0144 "nacute" -ent 0x222020D2 "nang" -ent 0x2A700338 "napE" -ent 0x224B0338 "napid" -ent 0x0149 "napos" -ent 0x2249 "napprox" -ent 0x2249 "nap" -ent 0x2115 "naturals" -ent 0x266E "natural" -ent 0x266E "natur" -ent 0x00A0 "nbsp" -ent 0x224F0338 "nbumpe" -ent 0x224E0338 "nbump" -ent 0x2A43 "ncap" -ent 0x0147 "Ncaron" -ent 0x0148 "ncaron" -ent 0x0145 "Ncedil" -ent 0x0146 "ncedil" -ent 0x2A6D0338 "ncongdot" -ent 0x2247 "ncong" -ent 0x2A42 "ncup" -ent 0x041D "Ncy" -ent 0x043D "ncy" -ent 0x2013 "ndash" -ent 0x2924 "nearhk" -ent 0x2197 "nearrow" -ent 0x2197 "nearr" -ent 0x21D7 "neArr" -ent 0x22500338 "nedot" -ent 0x200B "NegativeMediumSpace" -ent 0x200B "NegativeThickSpace" -ent 0x200B "NegativeThinSpace" -ent 0x200B "NegativeVeryThinSpace" -ent 0x2262 "nequiv" -ent 0x2928 "nesear" -ent 0x22420338 "nesim" -ent 0x226B "NestedGreaterGreater" -ent 0x226A "NestedLessLess" -ent 0x2260 "ne" -ent 0x000A "NewLine" -ent 0x2204 "nexists" -ent 0x2204 "nexist" -ent 0xD511 "Nfr" -ent 0xD52B "nfr" -ent 0x22670338 "ngeqq" -ent 0x2A7E0338 "ngeqslant" -ent 0x2271 "ngeq" -ent 0x2A7E0338 "nges" -ent 0x22670338 "ngE" -ent 0x2271 "nge" -ent 0x22D90338 "nGg" -ent 0x2275 "ngsim" -ent 0x226F "ngtr" -ent 0x226B20D2 "nGt" -ent 0x226F "ngt" -ent 0x226B0338 "nGtv" -ent 0x21AE "nharr" -ent 0x21CE "nhArr" -ent 0x2AF2 "nhpar" -ent 0x22FA "nisd" -ent 0x22FC "nis" -ent 0x220B "ni" -ent 0x220B "niv" -ent 0x040A "NJcy" -ent 0x045A "njcy" -ent 0x219A "nlarr" -ent 0x21CD "nlArr" -ent 0x2025 "nldr" -ent 0x219A "nleftarrow" -ent 0x21CD "nLeftarrow" -ent 0x21AE "nleftrightarrow" -ent 0x21CE "nLeftrightarrow" -ent 0x22660338 "nleqq" -ent 0x2A7D0338 "nleqslant" -ent 0x2270 "nleq" -ent 0x226E "nless" -ent 0x2A7D0338 "nles" -ent 0x22660338 "nlE" -ent 0x2270 "nle" -ent 0x22D80338 "nLl" -ent 0x2274 "nlsim" -ent 0x22EC "nltrie" -ent 0x22EA "nltri" -ent 0x226A20D2 "nLt" -ent 0x226E "nlt" -ent 0x226A0338 "nLtv" -ent 0x2224 "nmid" -ent 0x2060 "NoBreak" -ent 0x00A0 "NonBreakingSpace" -ent 0x2115 "Nopf" -ent 0xD55F "nopf" -ent 0x2262 "NotCongruent" -ent 0x226D "NotCupCap" -ent 0x2226 "NotDoubleVerticalBar" -ent 0x2209 "NotElement" -ent 0x22420338 "NotEqualTilde" -ent 0x2260 "NotEqual" -ent 0x2204 "NotExists" -ent 0x2271 "NotGreaterEqual" -ent 0x22670338 "NotGreaterFullEqual" -ent 0x226B0338 "NotGreaterGreater" -ent 0x2279 "NotGreaterLess" -ent 0x2A7E0338 "NotGreaterSlantEqual" -ent 0x2275 "NotGreaterTilde" -ent 0x226F "NotGreater" -ent 0x224E0338 "NotHumpDownHump" -ent 0x224F0338 "NotHumpEqual" -ent 0x22F50338 "notindot" -ent 0x22F90338 "notinE" -ent 0x2209 "notin" -ent 0x2209 "notinva" -ent 0x22F7 "notinvb" -ent 0x22F6 "notinvc" -ent 0x29CF0338 "NotLeftTriangleBar" -ent 0x22EC "NotLeftTriangleEqual" -ent 0x22EA "NotLeftTriangle" -ent 0x2270 "NotLessEqual" -ent 0x2278 "NotLessGreater" -ent 0x226A0338 "NotLessLess" -ent 0x2A7D0338 "NotLessSlantEqual" -ent 0x2274 "NotLessTilde" -ent 0x226E "NotLess" -ent 0x2AA20338 "NotNestedGreaterGreater" -ent 0x2AA10338 "NotNestedLessLess" -ent 0x220C "notni" -ent 0x220C "notniva" -ent 0x22FE "notnivb" -ent 0x22FD "notnivc" -ent 0x2AAF0338 "NotPrecedesEqual" -ent 0x22E0 "NotPrecedesSlantEqual" -ent 0x2280 "NotPrecedes" -ent 0x220C "NotReverseElement" -ent 0x29D00338 "NotRightTriangleBar" -ent 0x22ED "NotRightTriangleEqual" -ent 0x22EB "NotRightTriangle" -ent 0x22E2 "NotSquareSubsetEqual" -ent 0x228F0338 "NotSquareSubset" -ent 0x22E3 "NotSquareSupersetEqual" -ent 0x22900338 "NotSquareSuperset" -ent 0x2288 "NotSubsetEqual" -ent 0x228220D2 "NotSubset" -ent 0x2AB00338 "NotSucceedsEqual" -ent 0x22E1 "NotSucceedsSlantEqual" -ent 0x227F0338 "NotSucceedsTilde" -ent 0x2281 "NotSucceeds" -ent 0x2289 "NotSupersetEqual" -ent 0x228320D2 "NotSuperset" -ent 0x2244 "NotTildeEqual" -ent 0x2247 "NotTildeFullEqual" -ent 0x2249 "NotTildeTilde" -ent 0x2241 "NotTilde" -ent 0x00AC "not" -ent 0x2AEC "Not" -ent 0x2224 "NotVerticalBar" -ent 0x2226 "nparallel" -ent 0x2AFD20E5 "nparsl" -ent 0x22020338 "npart" -ent 0x2226 "npar" -ent 0x2A14 "npolint" -ent 0x22E0 "nprcue" -ent 0x2AAF0338 "npreceq" -ent 0x2280 "nprec" -ent 0x2AAF0338 "npre" -ent 0x2280 "npr" -ent 0x29330338 "nrarrc" -ent 0x219B "nrarr" -ent 0x21CF "nrArr" -ent 0x219D0338 "nrarrw" -ent 0x219B "nrightarrow" -ent 0x21CF "nRightarrow" -ent 0x22ED "nrtrie" -ent 0x22EB "nrtri" -ent 0x22E1 "nsccue" -ent 0x2AB00338 "nsce" -ent 0xD4A9 "Nscr" -ent 0xD4C3 "nscr" -ent 0x2281 "nsc" -ent 0x2224 "nshortmid" -ent 0x2226 "nshortparallel" -ent 0x2244 "nsimeq" -ent 0x2244 "nsime" -ent 0x2241 "nsim" -ent 0x2224 "nsmid" -ent 0x2226 "nspar" -ent 0x22E2 "nsqsube" -ent 0x22E3 "nsqsupe" -ent 0x2288 "nsube" -ent 0x2AC50338 "nsubE" -ent 0x2AC50338 "nsubseteqq" -ent 0x2288 "nsubseteq" -ent 0x228220D2 "nsubset" -ent 0x2284 "nsub" -ent 0x2AB00338 "nsucceq" -ent 0x2281 "nsucc" -ent 0x2289 "nsupe" -ent 0x2AC60338 "nsupE" -ent 0x2AC60338 "nsupseteqq" -ent 0x2289 "nsupseteq" -ent 0x228320D2 "nsupset" -ent 0x2285 "nsup" -ent 0x2279 "ntgl" -ent 0x00D1 "Ntilde" -ent 0x00F1 "ntilde" -ent 0x2278 "ntlg" -ent 0x22EC "ntrianglelefteq" -ent 0x22EA "ntriangleleft" -ent 0x22ED "ntrianglerighteq" -ent 0x22EB "ntriangleright" -ent 0x2116 "numero" -ent 0x2007 "numsp" -ent 0x0023 "num" -ent 0x039D "Nu" -ent 0x03BD "nu" -ent 0x224D20D2 "nvap" -ent 0x22AC "nvdash" -ent 0x22AD "nvDash" -ent 0x22AE "nVdash" -ent 0x22AF "nVDash" -ent 0x226520D2 "nvge" -ent 0x003E20D2 "nvgt" -ent 0x2904 "nvHarr" -ent 0x29DE "nvinfin" -ent 0x2902 "nvlArr" -ent 0x226420D2 "nvle" -ent 0x22B420D2 "nvltrie" -ent 0x003C20D2 "nvlt" -ent 0x2903 "nvrArr" -ent 0x22B520D2 "nvrtrie" -ent 0x223C20D2 "nvsim" -ent 0x2923 "nwarhk" -ent 0x2196 "nwarrow" -ent 0x2196 "nwarr" -ent 0x21D6 "nwArr" -ent 0x2927 "nwnear" -ent 0x00D3 "Oacute" -ent 0x00F3 "oacute" -ent 0x229B "oast" -ent 0x00D4 "Ocirc" -ent 0x00F4 "ocirc" -ent 0x229A "ocir" -ent 0x041E "Ocy" -ent 0x043E "ocy" -ent 0x229D "odash" -ent 0x0150 "Odblac" -ent 0x0151 "odblac" -ent 0x2A38 "odiv" -ent 0x2299 "odot" -ent 0x29BC "odsold" -ent 0x0152 "OElig" -ent 0x0153 "oelig" -ent 0x29BF "ofcir" -ent 0xD512 "Ofr" -ent 0xD52C "ofr" -ent 0x02DB "ogon" -ent 0x00D2 "Ograve" -ent 0x00F2 "ograve" -ent 0x29C1 "ogt" -ent 0x29B5 "ohbar" -ent 0x03A9 "ohm" -ent 0x222E "oint" -ent 0x21BA "olarr" -ent 0x29BE "olcir" -ent 0x29BB "olcross" -ent 0x203E "oline" -ent 0x29C0 "olt" -ent 0x014C "Omacr" -ent 0x014D "omacr" -ent 0x03A9 "Omega" -ent 0x03C9 "omega" -ent 0x039F "Omicron" -ent 0x03BF "omicron" -ent 0x29B6 "omid" -ent 0x2296 "ominus" -ent 0xD546 "Oopf" -ent 0xD560 "oopf" -ent 0x29B7 "opar" -ent 0x201C "OpenCurlyDoubleQuote" -ent 0x2018 "OpenCurlyQuote" -ent 0x29B9 "operp" -ent 0x2295 "oplus" -ent 0x21BB "orarr" -ent 0x2134 "orderof" -ent 0x2134 "order" -ent 0x00AA "ordf" -ent 0x00BA "ordm" -ent 0x2A5D "ord" -ent 0x22B6 "origof" -ent 0x2A56 "oror" -ent 0x2A57 "orslope" -ent 0x2228 "or" -ent 0x2A54 "Or" -ent 0x2A5B "orv" -ent 0x2134 "oscr" -ent 0xD4AA "Oscr" -ent 0x00D8 "Oslash" -ent 0x00F8 "oslash" -ent 0x2298 "osol" -ent 0x24C8 "oS" -ent 0x00D5 "Otilde" -ent 0x00F5 "otilde" -ent 0x2A36 "otimesas" -ent 0x2297 "otimes" -ent 0x2A37 "Otimes" -ent 0x00D6 "Ouml" -ent 0x00F6 "ouml" -ent 0x233D "ovbar" -ent 0x203E "OverBar" -ent 0x23DE "OverBrace" -ent 0x23B4 "OverBracket" -ent 0x23DC "OverParenthesis" -ent 0x2225 "parallel" -ent 0x00B6 "para" -ent 0x2AF3 "parsim" -ent 0x2AFD "parsl" -ent 0x2202 "PartialD" -ent 0x2202 "part" -ent 0x2225 "par" -ent 0x041F "Pcy" -ent 0x043F "pcy" -ent 0x0025 "percnt" -ent 0x002E "period" -ent 0x2030 "permil" -ent 0x22A5 "perp" -ent 0x2031 "pertenk" -ent 0xD513 "Pfr" -ent 0xD52D "pfr" -ent 0x03A6 "Phi" -ent 0x03C6 "phi" -ent 0x03D5 "phiv" -ent 0x2133 "phmmat" -ent 0x260E "phone" -ent 0x22D4 "pitchfork" -ent 0x03A0 "Pi" -ent 0x03C0 "pi" -ent 0x03D6 "piv" -ent 0x210E "planckh" -ent 0x210F "planck" -ent 0x210F "plankv" -ent 0x2A23 "plusacir" -ent 0x229E "plusb" -ent 0x2A22 "pluscir" -ent 0x2214 "plusdo" -ent 0x2A25 "plusdu" -ent 0x2A72 "pluse" -ent 0x00B1 "PlusMinus" -ent 0x00B1 "plusmn" -ent 0x2A26 "plussim" -ent 0x2A27 "plustwo" -ent 0x002B "plus" -ent 0x00B1 "pm" -ent 0x210C "Poincareplane" -ent 0x2A15 "pointint" -ent 0x2119 "Popf" -ent 0xD561 "popf" -ent 0x00A3 "pound" -ent 0x2AB7 "prap" -ent 0x227C "prcue" -ent 0x2AB7 "precapprox" -ent 0x227C "preccurlyeq" -ent 0x2AAF "PrecedesEqual" -ent 0x227C "PrecedesSlantEqual" -ent 0x227E "PrecedesTilde" -ent 0x227A "Precedes" -ent 0x2AAF "preceq" -ent 0x2AB9 "precnapprox" -ent 0x2AB5 "precneqq" -ent 0x22E8 "precnsim" -ent 0x227E "precsim" -ent 0x227A "prec" -ent 0x2AAF "pre" -ent 0x2AB3 "prE" -ent 0x2119 "primes" -ent 0x2032 "prime" -ent 0x2033 "Prime" -ent 0x2AB9 "prnap" -ent 0x2AB5 "prnE" -ent 0x22E8 "prnsim" -ent 0x220F "prod" -ent 0x220F "Product" -ent 0x232E "profalar" -ent 0x2312 "profline" -ent 0x2313 "profsurf" -ent 0x221D "Proportional" -ent 0x2237 "Proportion" -ent 0x221D "propto" -ent 0x221D "prop" -ent 0x227E "prsim" -ent 0x227A "pr" -ent 0x2ABB "Pr" -ent 0x22B0 "prurel" -ent 0xD4AB "Pscr" -ent 0xD4C5 "pscr" -ent 0x03A8 "Psi" -ent 0x03C8 "psi" -ent 0x2008 "puncsp" -ent 0xD514 "Qfr" -ent 0xD52E "qfr" -ent 0x2A0C "qint" -ent 0x211A "Qopf" -ent 0xD562 "qopf" -ent 0x2057 "qprime" -ent 0xD4AC "Qscr" -ent 0xD4C6 "qscr" -ent 0x210D "quaternions" -ent 0x2A16 "quatint" -ent 0x225F "questeq" -ent 0x003F "quest" -ent 0x0022 "quot" -ent 0x0022 "QUOT" -ent 0x21DB "rAarr" -ent 0x223D0331 "race" -ent 0x0154 "Racute" -ent 0x0155 "racute" -ent 0x221A "radic" -ent 0x29B3 "raemptyv" -ent 0x2992 "rangd" -ent 0x29A5 "range" -ent 0x27E9 "rangle" -ent 0x27E9 "rang" -ent 0x27EB "Rang" -ent 0x00BB "raquo" -ent 0x2975 "rarrap" -ent 0x2920 "rarrbfs" -ent 0x21E5 "rarrb" -ent 0x2933 "rarrc" -ent 0x291E "rarrfs" -ent 0x21AA "rarrhk" -ent 0x21AC "rarrlp" -ent 0x2945 "rarrpl" -ent 0x2974 "rarrsim" -ent 0x21A3 "rarrtl" -ent 0x2916 "Rarrtl" -ent 0x2192 "rarr" -ent 0x21A0 "Rarr" -ent 0x21D2 "rArr" -ent 0x219D "rarrw" -ent 0x291A "ratail" -ent 0x291C "rAtail" -ent 0x211A "rationals" -ent 0x2236 "ratio" -ent 0x290D "rbarr" -ent 0x290F "rBarr" -ent 0x2910 "RBarr" -ent 0x2773 "rbbrk" -ent 0x007D "rbrace" -ent 0x005D "rbrack" -ent 0x298C "rbrke" -ent 0x298E "rbrksld" -ent 0x2990 "rbrkslu" -ent 0x0158 "Rcaron" -ent 0x0159 "rcaron" -ent 0x0156 "Rcedil" -ent 0x0157 "rcedil" -ent 0x2309 "rceil" -ent 0x007D "rcub" -ent 0x0420 "Rcy" -ent 0x0440 "rcy" -ent 0x2937 "rdca" -ent 0x2969 "rdldhar" -ent 0x201D "rdquor" -ent 0x201D "rdquo" -ent 0x21B3 "rdsh" -ent 0x211B "realine" -ent 0x211C "realpart" -ent 0x211D "reals" -ent 0x211C "real" -ent 0x25AD "rect" -ent 0x00AE "reg" -ent 0x00AE "REG" -ent 0x211C "Re" -ent 0x220B "ReverseElement" -ent 0x21CB "ReverseEquilibrium" -ent 0x296F "ReverseUpEquilibrium" -ent 0x297D "rfisht" -ent 0x230B "rfloor" -ent 0x211C "Rfr" -ent 0xD52F "rfr" -ent 0x21C1 "rhard" -ent 0x2964 "rHar" -ent 0x296C "rharul" -ent 0x21C0 "rharu" -ent 0x03A1 "Rho" -ent 0x03C1 "rho" -ent 0x03F1 "rhov" -ent 0x27E9 "RightAngleBracket" -ent 0x21E5 "RightArrowBar" -ent 0x21C4 "RightArrowLeftArrow" -ent 0x21A3 "rightarrowtail" -ent 0x2192 "rightarrow" -ent 0x2192 "RightArrow" -ent 0x21D2 "Rightarrow" -ent 0x2309 "RightCeiling" -ent 0x27E7 "RightDoubleBracket" -ent 0x295D "RightDownTeeVector" -ent 0x2955 "RightDownVectorBar" -ent 0x21C2 "RightDownVector" -ent 0x230B "RightFloor" -ent 0x21C1 "rightharpoondown" -ent 0x21C0 "rightharpoonup" -ent 0x21C4 "rightleftarrows" -ent 0x21CC "rightleftharpoons" -ent 0x21C9 "rightrightarrows" -ent 0x219D "rightsquigarrow" -ent 0x21A6 "RightTeeArrow" -ent 0x22A2 "RightTee" -ent 0x295B "RightTeeVector" -ent 0x22CC "rightthreetimes" -ent 0x29D0 "RightTriangleBar" -ent 0x22B5 "RightTriangleEqual" -ent 0x22B3 "RightTriangle" -ent 0x294F "RightUpDownVector" -ent 0x295C "RightUpTeeVector" -ent 0x2954 "RightUpVectorBar" -ent 0x21BE "RightUpVector" -ent 0x2953 "RightVectorBar" -ent 0x21C0 "RightVector" -ent 0x02DA "ring" -ent 0x2253 "risingdotseq" -ent 0x21C4 "rlarr" -ent 0x21CC "rlhar" -ent 0x200F "rlm" -ent 0x23B1 "rmoustache" -ent 0x23B1 "rmoust" -ent 0x2AEE "rnmid" -ent 0x27ED "roang" -ent 0x21FE "roarr" -ent 0x27E7 "robrk" -ent 0x2986 "ropar" -ent 0x211D "Ropf" -ent 0xD563 "ropf" -ent 0x2A2E "roplus" -ent 0x2A35 "rotimes" -ent 0x2970 "RoundImplies" -ent 0x2994 "rpargt" -ent 0x0029 "rpar" -ent 0x2A12 "rppolint" -ent 0x21C9 "rrarr" -ent 0x21DB "Rrightarrow" -ent 0x203A "rsaquo" -ent 0x211B "Rscr" -ent 0xD4C7 "rscr" -ent 0x21B1 "rsh" -ent 0x21B1 "Rsh" -ent 0x005D "rsqb" -ent 0x2019 "rsquor" -ent 0x2019 "rsquo" -ent 0x22CC "rthree" -ent 0x22CA "rtimes" -ent 0x22B5 "rtrie" -ent 0x25B8 "rtrif" -ent 0x29CE "rtriltri" -ent 0x25B9 "rtri" -ent 0x29F4 "RuleDelayed" -ent 0x2968 "ruluhar" -ent 0x211E "rx" -ent 0x015A "Sacute" -ent 0x015B "sacute" -ent 0x201A "sbquo" -ent 0x2AB8 "scap" -ent 0x0160 "Scaron" -ent 0x0161 "scaron" -ent 0x227D "sccue" -ent 0x015E "Scedil" -ent 0x015F "scedil" -ent 0x2AB0 "sce" -ent 0x2AB4 "scE" -ent 0x015C "Scirc" -ent 0x015D "scirc" -ent 0x2ABA "scnap" -ent 0x2AB6 "scnE" -ent 0x22E9 "scnsim" -ent 0x2A13 "scpolint" -ent 0x227F "scsim" -ent 0x227B "sc" -ent 0x2ABC "Sc" -ent 0x0421 "Scy" -ent 0x0441 "scy" -ent 0x22A1 "sdotb" -ent 0x2A66 "sdote" -ent 0x22C5 "sdot" -ent 0x2925 "searhk" -ent 0x2198 "searrow" -ent 0x2198 "searr" -ent 0x21D8 "seArr" -ent 0x00A7 "sect" -ent 0x003B "semi" -ent 0x2929 "seswar" -ent 0x2216 "setminus" -ent 0x2216 "setmn" -ent 0x2736 "sext" -ent 0x2322 "sfrown" -ent 0xD516 "Sfr" -ent 0xD530 "sfr" -ent 0x266F "sharp" -ent 0x0429 "SHCHcy" -ent 0x0449 "shchcy" -ent 0x0428 "SHcy" -ent 0x0448 "shcy" -ent 0x2193 "ShortDownArrow" -ent 0x2190 "ShortLeftArrow" -ent 0x2223 "shortmid" -ent 0x2225 "shortparallel" -ent 0x2192 "ShortRightArrow" -ent 0x2191 "ShortUpArrow" -ent 0x00AD "shy" -ent 0x03C2 "sigmaf" -ent 0x03A3 "Sigma" -ent 0x03C3 "sigma" -ent 0x03C2 "sigmav" -ent 0x2A6A "simdot" -ent 0x2243 "simeq" -ent 0x2243 "sime" -ent 0x2AA0 "simgE" -ent 0x2A9E "simg" -ent 0x2A9F "simlE" -ent 0x2A9D "siml" -ent 0x2246 "simne" -ent 0x2A24 "simplus" -ent 0x2972 "simrarr" -ent 0x223C "sim" -ent 0x2190 "slarr" -ent 0x2218 "SmallCircle" -ent 0x2216 "smallsetminus" -ent 0x2A33 "smashp" -ent 0x29E4 "smeparsl" -ent 0x2223 "smid" -ent 0x2323 "smile" -ent 0x2AACFE00 "smtes" -ent 0x2AAC "smte" -ent 0x2AAA "smt" -ent 0x042C "SOFTcy" -ent 0x044C "softcy" -ent 0x233F "solbar" -ent 0x29C4 "solb" -ent 0x002F "sol" -ent 0xD54A "Sopf" -ent 0xD564 "sopf" -ent 0x2660 "spades" -ent 0x2660 "spadesuit" -ent 0x2225 "spar" -ent 0x2293FE00 "sqcaps" -ent 0x2293 "sqcap" -ent 0x2294FE00 "sqcups" -ent 0x2294 "sqcup" -ent 0x221A "Sqrt" -ent 0x2291 "sqsube" -ent 0x2291 "sqsubseteq" -ent 0x228F "sqsubset" -ent 0x228F "sqsub" -ent 0x2292 "sqsupe" -ent 0x2292 "sqsupseteq" -ent 0x2290 "sqsupset" -ent 0x2290 "sqsup" -ent 0x2293 "SquareIntersection" -ent 0x2291 "SquareSubsetEqual" -ent 0x228F "SquareSubset" -ent 0x2292 "SquareSupersetEqual" -ent 0x2290 "SquareSuperset" -ent 0x25A1 "square" -ent 0x25A1 "Square" -ent 0x2294 "SquareUnion" -ent 0x25AA "squarf" -ent 0x25AA "squf" -ent 0x25A1 "squ" -ent 0x2192 "srarr" -ent 0xD4AE "Sscr" -ent 0xD4C8 "sscr" -ent 0x2216 "ssetmn" -ent 0x2323 "ssmile" -ent 0x22C6 "sstarf" -ent 0x2605 "starf" -ent 0x22C6 "Star" -ent 0x2606 "star" -ent 0x03F5 "straightepsilon" -ent 0x03D5 "straightphi" -ent 0x00AF "strns" -ent 0x2ABD "subdot" -ent 0x2AC3 "subedot" -ent 0x2286 "sube" -ent 0x2AC5 "subE" -ent 0x2AC1 "submult" -ent 0x228A "subne" -ent 0x2ACB "subnE" -ent 0x2ABF "subplus" -ent 0x2979 "subrarr" -ent 0x2AC5 "subseteqq" -ent 0x2286 "subseteq" -ent 0x2286 "SubsetEqual" -ent 0x2ACB "subsetneqq" -ent 0x228A "subsetneq" -ent 0x2282 "subset" -ent 0x22D0 "Subset" -ent 0x2AC7 "subsim" -ent 0x2AD5 "subsub" -ent 0x2AD3 "subsup" -ent 0x2282 "sub" -ent 0x22D0 "Sub" -ent 0x2AB8 "succapprox" -ent 0x227D "succcurlyeq" -ent 0x2AB0 "SucceedsEqual" -ent 0x227D "SucceedsSlantEqual" -ent 0x227F "SucceedsTilde" -ent 0x227B "Succeeds" -ent 0x2AB0 "succeq" -ent 0x2ABA "succnapprox" -ent 0x2AB6 "succneqq" -ent 0x22E9 "succnsim" -ent 0x227F "succsim" -ent 0x227B "succ" -ent 0x220B "SuchThat" -ent 0x2211 "sum" -ent 0x2211 "Sum" -ent 0x266A "sung" -ent 0x00B9 "sup1" -ent 0x00B2 "sup2" -ent 0x00B3 "sup3" -ent 0x2ABE "supdot" -ent 0x2AD8 "supdsub" -ent 0x2AC4 "supedot" -ent 0x2287 "SupersetEqual" -ent 0x2283 "Superset" -ent 0x2287 "supe" -ent 0x2AC6 "supE" -ent 0x27C9 "suphsol" -ent 0x2AD7 "suphsub" -ent 0x297B "suplarr" -ent 0x2AC2 "supmult" -ent 0x228B "supne" -ent 0x2ACC "supnE" -ent 0x2AC0 "supplus" -ent 0x2AC6 "supseteqq" -ent 0x2287 "supseteq" -ent 0x2ACC "supsetneqq" -ent 0x228B "supsetneq" -ent 0x2283 "supset" -ent 0x22D1 "Supset" -ent 0x2AC8 "supsim" -ent 0x2AD4 "supsub" -ent 0x2AD6 "supsup" -ent 0x2283 "sup" -ent 0x22D1 "Sup" -ent 0x2926 "swarhk" -ent 0x2199 "swarrow" -ent 0x2199 "swarr" -ent 0x21D9 "swArr" -ent 0x292A "swnwar" -ent 0x00DF "szlig" -ent 0x0009 "Tab" -ent 0x2316 "target" -ent 0x03A4 "Tau" -ent 0x03C4 "tau" -ent 0x23B4 "tbrk" -ent 0x0164 "Tcaron" -ent 0x0165 "tcaron" -ent 0x0162 "Tcedil" -ent 0x0163 "tcedil" -ent 0x0422 "Tcy" -ent 0x0442 "tcy" -ent 0x20DB "tdot" -ent 0x2315 "telrec" -ent 0xD517 "Tfr" -ent 0xD531 "tfr" -ent 0x2234 "there4" -ent 0x2234 "therefore" -ent 0x2234 "Therefore" -ent 0x03D1 "thetasym" -ent 0x0398 "Theta" -ent 0x03B8 "theta" -ent 0x03D1 "thetav" -ent 0x2248 "thickapprox" -ent 0x223C "thicksim" -ent 0x205F200A "ThickSpace" -ent 0x2009 "ThinSpace" -ent 0x2009 "thinsp" -ent 0x2248 "thkap" -ent 0x223C "thksim" -ent 0x00DE "THORN" -ent 0x00FE "thorn" -ent 0x2243 "TildeEqual" -ent 0x2245 "TildeFullEqual" -ent 0x2248 "TildeTilde" -ent 0x02DC "tilde" -ent 0x223C "Tilde" -ent 0x2A31 "timesbar" -ent 0x22A0 "timesb" -ent 0x2A30 "timesd" -ent 0x00D7 "times" -ent 0x222D "tint" -ent 0x2928 "toea" -ent 0x2336 "topbot" -ent 0x2AF1 "topcir" -ent 0x2ADA "topfork" -ent 0xD54B "Topf" -ent 0xD565 "topf" -ent 0x22A4 "top" -ent 0x2929 "tosa" -ent 0x2034 "tprime" -ent 0x2122 "trade" -ent 0x2122 "TRADE" -ent 0x25BF "triangledown" -ent 0x22B4 "trianglelefteq" -ent 0x25C3 "triangleleft" -ent 0x225C "triangleq" -ent 0x22B5 "trianglerighteq" -ent 0x25B9 "triangleright" -ent 0x25B5 "triangle" -ent 0x25EC "tridot" -ent 0x225C "trie" -ent 0x2A3A "triminus" -ent 0x20DB "TripleDot" -ent 0x2A39 "triplus" -ent 0x29CD "trisb" -ent 0x2A3B "tritime" -ent 0x23E2 "trpezium" -ent 0xD4AF "Tscr" -ent 0xD4C9 "tscr" -ent 0x0426 "TScy" -ent 0x0446 "tscy" -ent 0x040B "TSHcy" -ent 0x045B "tshcy" -ent 0x0166 "Tstrok" -ent 0x0167 "tstrok" -ent 0x226C "twixt" -ent 0x219E "twoheadleftarrow" -ent 0x21A0 "twoheadrightarrow" -ent 0x00DA "Uacute" -ent 0x00FA "uacute" -ent 0x2949 "Uarrocir" -ent 0x2191 "uarr" -ent 0x219F "Uarr" -ent 0x21D1 "uArr" -ent 0x040E "Ubrcy" -ent 0x045E "ubrcy" -ent 0x016C "Ubreve" -ent 0x016D "ubreve" -ent 0x00DB "Ucirc" -ent 0x00FB "ucirc" -ent 0x0423 "Ucy" -ent 0x0443 "ucy" -ent 0x21C5 "udarr" -ent 0x0170 "Udblac" -ent 0x0171 "udblac" -ent 0x296E "udhar" -ent 0x297E "ufisht" -ent 0xD518 "Ufr" -ent 0xD532 "ufr" -ent 0x00D9 "Ugrave" -ent 0x00F9 "ugrave" -ent 0x21BF "uharl" -ent 0x21BE "uharr" -ent 0x2963 "uHar" -ent 0x2580 "uhblk" -ent 0x231C "ulcorner" -ent 0x231C "ulcorn" -ent 0x230F "ulcrop" -ent 0x25F8 "ultri" -ent 0x016A "Umacr" -ent 0x016B "umacr" -ent 0x00A8 "uml" -ent 0x005F "UnderBar" -ent 0x23DF "UnderBrace" -ent 0x23B5 "UnderBracket" -ent 0x23DD "UnderParenthesis" -ent 0x228E "UnionPlus" -ent 0x22C3 "Union" -ent 0x0172 "Uogon" -ent 0x0173 "uogon" -ent 0xD54C "Uopf" -ent 0xD566 "uopf" -ent 0x2912 "UpArrowBar" -ent 0x21C5 "UpArrowDownArrow" -ent 0x2191 "uparrow" -ent 0x2191 "UpArrow" -ent 0x21D1 "Uparrow" -ent 0x2195 "updownarrow" -ent 0x2195 "UpDownArrow" -ent 0x21D5 "Updownarrow" -ent 0x296E "UpEquilibrium" -ent 0x21BF "upharpoonleft" -ent 0x21BE "upharpoonright" -ent 0x228E "uplus" -ent 0x2196 "UpperLeftArrow" -ent 0x2197 "UpperRightArrow" -ent 0x03D2 "upsih" -ent 0x03A5 "Upsilon" -ent 0x03C5 "upsilon" -ent 0x03C5 "upsi" -ent 0x03D2 "Upsi" -ent 0x21A5 "UpTeeArrow" -ent 0x22A5 "UpTee" -ent 0x21C8 "upuparrows" -ent 0x231D "urcorner" -ent 0x231D "urcorn" -ent 0x230E "urcrop" -ent 0x016E "Uring" -ent 0x016F "uring" -ent 0x25F9 "urtri" -ent 0xD4B0 "Uscr" -ent 0xD4CA "uscr" -ent 0x22F0 "utdot" -ent 0x0168 "Utilde" -ent 0x0169 "utilde" -ent 0x25B4 "utrif" -ent 0x25B5 "utri" -ent 0x21C8 "uuarr" -ent 0x00DC "Uuml" -ent 0x00FC "uuml" -ent 0x29A7 "uwangle" -ent 0x299C "vangrt" -ent 0x03F5 "varepsilon" -ent 0x03F0 "varkappa" -ent 0x2205 "varnothing" -ent 0x03D5 "varphi" -ent 0x03D6 "varpi" -ent 0x221D "varpropto" -ent 0x03F1 "varrho" -ent 0x2195 "varr" -ent 0x21D5 "vArr" -ent 0x03C2 "varsigma" -ent 0x2ACBFE00 "varsubsetneqq" -ent 0x228AFE00 "varsubsetneq" -ent 0x2ACCFE00 "varsupsetneqq" -ent 0x228BFE00 "varsupsetneq" -ent 0x03D1 "vartheta" -ent 0x22B2 "vartriangleleft" -ent 0x22B3 "vartriangleright" -ent 0x2AE8 "vBar" -ent 0x2AEB "Vbar" -ent 0x2AE9 "vBarv" -ent 0x0412 "Vcy" -ent 0x0432 "vcy" -ent 0x2AE6 "Vdashl" -ent 0x22A2 "vdash" -ent 0x22A8 "vDash" -ent 0x22A9 "Vdash" -ent 0x22AB "VDash" -ent 0x22BB "veebar" -ent 0x225A "veeeq" -ent 0x2228 "vee" -ent 0x22C1 "Vee" -ent 0x22EE "vellip" -ent 0x007C "verbar" -ent 0x2016 "Verbar" -ent 0x2223 "VerticalBar" -ent 0x007C "VerticalLine" -ent 0x2758 "VerticalSeparator" -ent 0x2240 "VerticalTilde" -ent 0x007C "vert" -ent 0x2016 "Vert" -ent 0x200A "VeryThinSpace" -ent 0xD519 "Vfr" -ent 0xD533 "vfr" -ent 0x22B2 "vltri" -ent 0x228220D2 "vnsub" -ent 0x228320D2 "vnsup" -ent 0xD54D "Vopf" -ent 0xD567 "vopf" -ent 0x221D "vprop" -ent 0x22B3 "vrtri" -ent 0xD4B1 "Vscr" -ent 0xD4CB "vscr" -ent 0x228AFE00 "vsubne" -ent 0x2ACBFE00 "vsubnE" -ent 0x228BFE00 "vsupne" -ent 0x2ACCFE00 "vsupnE" -ent 0x22AA "Vvdash" -ent 0x299A "vzigzag" -ent 0x0174 "Wcirc" -ent 0x0175 "wcirc" -ent 0x2A5F "wedbar" -ent 0x2259 "wedgeq" -ent 0x2227 "wedge" -ent 0x22C0 "Wedge" -ent 0x2118 "weierp" -ent 0xD51A "Wfr" -ent 0xD534 "wfr" -ent 0xD54E "Wopf" -ent 0xD568 "wopf" -ent 0x2118 "wp" -ent 0x2240 "wreath" -ent 0x2240 "wr" -ent 0xD4B2 "Wscr" -ent 0xD4CC "wscr" -ent 0x22C2 "xcap" -ent 0x25EF "xcirc" -ent 0x22C3 "xcup" -ent 0x25BD "xdtri" -ent 0xD51B "Xfr" -ent 0xD535 "xfr" -ent 0x27F7 "xharr" -ent 0x27FA "xhArr" -ent 0x039E "Xi" -ent 0x03BE "xi" -ent 0x27F5 "xlarr" -ent 0x27F8 "xlArr" -ent 0x27FC "xmap" -ent 0x22FB "xnis" -ent 0x2A00 "xodot" -ent 0xD54F "Xopf" -ent 0xD569 "xopf" -ent 0x2A01 "xoplus" -ent 0x2A02 "xotime" -ent 0x27F6 "xrarr" -ent 0x27F9 "xrArr" -ent 0xD4B3 "Xscr" -ent 0xD4CD "xscr" -ent 0x2A06 "xsqcup" -ent 0x2A04 "xuplus" -ent 0x25B3 "xutri" -ent 0x22C1 "xvee" -ent 0x22C0 "xwedge" -ent 0x00DD "Yacute" -ent 0x00FD "yacute" -ent 0x042F "YAcy" -ent 0x044F "yacy" -ent 0x0176 "Ycirc" -ent 0x0177 "ycirc" -ent 0x042B "Ycy" -ent 0x044B "ycy" -ent 0x00A5 "yen" -ent 0xD51C "Yfr" -ent 0xD536 "yfr" -ent 0x0407 "YIcy" -ent 0x0457 "yicy" -ent 0xD550 "Yopf" -ent 0xD56A "yopf" -ent 0xD4B4 "Yscr" -ent 0xD4CE "yscr" -ent 0x042E "YUcy" -ent 0x044E "yucy" -ent 0x00FF "yuml" -ent 0x0178 "Yuml" -ent 0x0179 "Zacute" -ent 0x017A "zacute" -ent 0x017D "Zcaron" -ent 0x017E "zcaron" -ent 0x0417 "Zcy" -ent 0x0437 "zcy" -ent 0x017B "Zdot" -ent 0x017C "zdot" -ent 0x2128 "zeetrf" -ent 0x200B "ZeroWidthSpace" -ent 0x0396 "Zeta" -ent 0x03B6 "zeta" -ent 0x2128 "Zfr" -ent 0xD537 "zfr" -ent 0x0416 "ZHcy" -ent 0x0436 "zhcy" -ent 0x21DD "zigrarr" -ent 0x2124 "Zopf" -ent 0xD56B "zopf" -ent 0xD4B5 "Zscr" -ent 0xD4CF "zscr" -ent 0x200D "zwj" -ent 0x200C "zwnj"
View file
cyrus-imapd-2.5.tar.gz/lib/imapoptions
Changed
@@ -93,12 +93,6 @@ # OPTIONS -{ "addressbookprefix", "#addressbooks", STRING } -/* The prefix for the addressbook mailboxes hierarchies. The hierarchy - delimiter will be automatically appended. The public addressbook - hierarchy will be at the toplevel of the shared namespace. A - user's personal calendar hierarchy will be a child of their Inbox. */ - { "admins", "", STRING } /* The list of userids with administrative rights. Separate each userid with a space. Sites using Kerberos authentication may use @@ -297,33 +291,6 @@ layers of MIME structure. The default of 1000 is much higher than any sane message should have. */ -{ "caldav_allowscheduling", "on", ENUM("off", "on", "apple") } -/* Enable calendar scheduling operations. If set to "apple", the - server will emulate Apple CalendarServer behavior as closely as - possible. */ - -{ "caldav_realm", NULL, STRING } -/* The realm to present for HTTP authentication of CalDAV resources. - If not set (the default), the value of the "servername" option will - be used.*/ - -{ "calendarprefix", "#calendars", STRING } -/* The prefix for the calendar mailboxes hierarchies. The hierarchy - delimiter will be automatically appended. The public calendar - hierarchy will be at the toplevel of the shared namespace. A - user's personal calendar hierarchy will be a child of their Inbox. */ - -{ "calendar_greedymatch", 1, SWITCH } -/* If yes, return recurrence rules that span over the timeframe without - checking that there is definitely a recurrence in the range. This - is more efficient and true for sensible clients, and hopefully the - others will just ignore the bogons */ - -{ "carddav_realm", NULL, STRING } -/* The realm to present for HTTP authentication of CardDAV resources. - If not set (the default), the value of the "servername" option will - be used.*/ - { "chatty", 0, SWITCH } /* If yes, syslog tags and commands for every IMAP command, mailboxes for every lmtp connection, every POP3 command, etc */ @@ -389,30 +356,6 @@ /* The pathname of the IMAP configuration directory. This field is required. */ -{ "conversations", 0, SWITCH } -/* Enable the XCONVERSATIONS extensions. Extract conversation - tracking information from incoming messages and track them - in per-user databases. */ - -{ "conversations_counted_flags", NULL, STRING } -/* space-separated list of flags for which per-conversation counts - will be kept. Note that you need to reconstruct the conversations - database with ctl_conversationsdb if you change this option on a - running server, or the counts will be wrong. */ - -{ "conversations_db", "skiplist", STRINGLIST("berkeley", "berkeley-nosync", "berkeley-hash", "berkeley-hash-nosync", "skiplist", "sql", "twoskip")} -/* The cyrusdb backend to use for the per-user conversations database. */ - -{ "conversations_expire_days", 90, INT } -/* How long the conversations database keeps the message tracking - information needed for receiving new messages in existing - conversations, in days. */ - -{ "dav_realm", NULL, STRING } -/* The realm to present for HTTP authentication of generic DAV - resources (principals). If not set (the default), the value of the - "servername" option will be used.*/ - { "debug_command", NULL, STRING } /* Debug command to be used by processes started with -D option. The string is a C format string that gets 3 options: the first is the name of the @@ -470,13 +413,6 @@ session. Otherwise, the missing mailbox is treated as empty while in use by the client.*/ -{ "dkim_domain", NULL, STRING } -/* The domain to be reported as doing DKIM signing. */ - -{ "dkim_selector", NULL, STRING } -/* Name of the selector subdividing the domain namespace. This - specifies the actual key used for DKIM signing within the domain. */ - { "duplicate_db", "skiplist", STRINGLIST("berkeley", "berkeley-nosync", "berkeley-hash", "berkeley-hash-nosync", "skiplist", "sql", "twoskip")} /* The cyrusdb backend to use for the duplicate delivery suppression and sieve. */ @@ -511,10 +447,10 @@ /* Don't send event notification for folder with given special-use attributes. Set ALL for any folder */ -{ "event_extra_params", "timestamp", BITFIELD("bodyStructure", "clientAddress", "diskUsed", "flagNames", "messageContent", "messageSize", "messages", "modseq", "service", "timestamp", "uidnext", "vnd.cmu.midset", "vnd.cmu.unseenMessages", "vnd.cmu.envelope", "vnd.cmu.mbtype", "vnd.cmu.mailboxACL", "vnd.cmu.davFilename", "vnd.cmu.davUid", "vnd.fastmail.clientId", "vnd.fastmail.sessionId", "vnd.fastmail.convExists", "vnd.fastmail.convUnseen", "vnd.fastmail.cid") } -/* Space-separated list of extra parameters to add to any appropriated event. */ +{ "event_extra_params", "timestamp", BITFIELD("bodyStructure", "clientAddress", "diskUsed", "flagNames", "messageContent", "messageSize", "messages", "modseq", "service", "timestamp", "uidnext", "vnd.cmu.midset", "vnd.cmu.unseenMessages", "vnd.cmu.envelope") } +/* Space-separated list of extra parameters to add to any appropriated event. */ -{ "event_groups", "message mailbox", BITFIELD("message", "quota", "flags", "access", "mailbox", "subscription", "calendar") } +{ "event_groups", "message mailbox", BITFIELD("message", "quota", "flags", "access", "mailbox", "subscription") } /* Space-separated list of groups of related events to turn on notification */ { "event_notifier", NULL, STRING } @@ -596,75 +532,6 @@ /* The password to use for authentication to the backend server hostname (where hostname is the short hostname of the server) - Cyrus Murder */ -{ "httpallowcompress", 1, SWITCH } -/* If enabled, the server will compress response payloads if the client - indicates that it can accept them. Note that the compressed data - will appear in telemetry logs, leaving only the response headers as - human-readable.*/ - -{ "httpallowcors", NULL, STRING } -/* A wildmat pattern specifying a list of origin URIs ( scheme "://" - host ":" port ) that are allowed to make Cross-Origin Resource - Sharing (CORS) requests on the server. By default, CORS requests - are disabled. -.PP - Note that the scheme and host should both be lowercase, the port - should be omitted if using the default for the scheme (80 for http, - 443 for https), and there should be no trailing '/' (e.g.: - "http://www.example.com:8080", "https://example.org"). */ - -{ "httpallowtrace", 0, SWITCH } -/* Allow use of the TRACE method. -.PP - Note that sensitive data might be disclosed by the response. */ - -{ "httpallowedurls", NULL, STRING } -/* Space-separated list of relative URLs (paths) rooted at - "httpdocroot" (see below) to be served by httpd. If set, this - option will limit served static content to only those paths specified - (returning "404 Not Found" to any other client requested URLs). - Otherwise, httpd will serve any content found in "httpdocroot". -.PP - Note that any path specified by "rss_feedlist_template" is an - exception to this rule.*/ - -{ "httpcontentmd5", 0, SWITCH } -/* If enabled, HTTP responses will include a Content-MD5 header for - the purpose of providing an end-to-end message integrity check - (MIC) of the payload body. Note that enabling this option will - use additional CPU to generate the MD5 digest, which may be ignored - by clients anyways. */ - -{ "httpdocroot", NULL, STRING } -/* If set, http will serve the static content (html/text/jpeg/gif - files, etc) rooted at this directory. Otherwise, httpd will not - serve any static content. */ - -{ "httpkeepalive", 20, INT } -/* Set the length of the HTTP server's keepalive heartbeat in seconds. - The default is 20. The minimum value is 0, which will disable the - keepalive heartbeat. When enabled, if a request takes longer than - \fIhttpkeepalive\fR seconds to process, the server will send the client - provisional responses every \fIhttpkeepalive\fR seconds until the - final response can be sent */ - -{ "httpmodules", "", BITFIELD("caldav", "carddav", "domainkey", "ischedule", "rss", "timezone") } -/* Space-separated list of HTTP modules that will be enabled in - httpd(8). This option has no effect on modules that are disabled - at compile time due to missing dependencies (e.g. libical). */ - -{ "httpprettytelemetry", 0, SWITCH } -/* If enabled, HTTP response payloads including server-generated - markup languages (HTML, XML) will utilize line breaks and - indentation to promote better human-readability in telemetry logs. - Note that enabling this option will increase the amount of data - sent across the wire. */ - -{ "httptimeout", 5, INT } -/* Set the length of the HTTP server's inactivity autologout timer, - in minutes. The default is 5. The minimum value is 0, which will - disable persistent connections. */ - { "idlesocket", "{configdirectory}/socket/idle", STRING } /* Unix domain socket that idled listens on. */ @@ -987,14 +854,11 @@ { "mboxname_lockpath", NULL, STRING } /* Path to mailbox name lock files (default $conf/lock) */ -{ "metapartition_files", "", BITFIELD("header", "index", "cache", "expunge", "squat", "annotations", "lock", "dav", "archivecache") } +{ "metapartition_files", "", BITFIELD("header", "index", "cache", "expunge", "squat", "annotations") } /* Space-separated list of metadata files to be stored on a \fImetapartition\fR rather than in the mailbox directory on a spool partition. */ -{ "me_secret", NULL, STRING } -/* messagingengine.com x-sasl-enc secret key */ - # Commented out - there's no such thing as "metapartition-name", # but we need this for the man page # { "metapartition-name", NULL, STRING } @@ -1366,45 +1230,6 @@ allowed in envelope tests. When disabled, ANY grammatically correct header will be allowed. */ -{ "rss_feedlist_template", NULL, STRING } -/* File containing HTML that will be used as a template for displaying - the list of available RSS feeds. A single instance of the variable - %RSS_FEEDLIST% should appear in the file, which will be replaced by - a nested unordered list of feeds. The toplevel unordered list will - be tagged with an id of "feed" (<ul id='feed'>) which can be used - by stylesheet(s) in your template. The dynamically created list of - feeds based on the HTML template will be accessible at the "/rss" - URL on the server. */ - -{ "rss_feeds", "*", STRING } -/* A wildmat pattern specifying which mailbox hierarchies should be - treated as RSS feeds. Only mailboxes matching the wildmat will - have their messages available via RSS. If not set, a default - wildmat of "*" (ALL mailboxes) will be used. */ - -{ "rss_maxage", 0, INT } -/* Maximum age (in days) of items to display in an RSS channel. If - non-zero, httpd will only display items received within the last - \fIrss_maxage\fR days. If set to 0, all available items will be - displayed (the default). */ - -{ "rss_maxitems", 0, INT } -/* Maximum number of items to display in an RSS channel. If non-zero, - httpd will display no more than the \fIrss_maxitems\fR most recent - items. If set to 0, all available items will be displayed (the - default). */ - -{ "rss_maxsynopsis", 0, INT } -/* Maximum RSS item synopsis length. If non-zero, httpd will display - no more than the first \fIrss_maxsynopsis\fR characters of an - item's synopsis. If set to 0, the entire synopsis will be - displayed (the default). */ - -{ "rss_realm", NULL, STRING } -/* The realm to present for HTTP authentication of RSS feeds. If not - set (the default), the value of the "servername" option will be - used.*/ - # Commented out - used by libsasl # { "sasl_auto_transition", 0, SWITCH } /* If enabled, the SASL library will automatically create authentication @@ -1429,40 +1254,12 @@ /* The mechanism used by the server to verify plaintext passwords. Possible values include "auxprop", "saslauthd", and "pwcheck". */ -{ "search_batchsize", 20, INT } -/* The number of messages to be indexed in one batch (default 20). - Note that long batches may delay user commands or mail delivery. */ - -{ "search_normalisation_max", 1000, INT } -/* A resource bound for the combinatorial explosion of search expression - * tree complexity caused by normalising expressions with many OR nodes. - * These can use more CPU time to optimise than they save IO time in scanning - * folders. */ - -{ "search_engine", "none", ENUM("none", "squat", "sphinx", "xapian") } -/* The indexing engine used to speed up searching. */ - -{ "search_index_headers", 1, SWITCH } -/* Whether to index headers other than From, To, Cc, Bcc, and Subject. - * Experiment shows that some headers such as Received and DKIM-Signature - * can contribute up to 2/3rds of the index size but almost nothing to - * the utility of searching. Note that is header indexing is disabled, - * headers can still be searched, the searches will just be slower. - */ - -{ "search_indexed_db", "twoskip", STRINGLIST("flat", "berkeley", "berkeley-hash", "skiplist", "twoskip")} -/* The cyrusdb backend to use for the search latest indexed uid state. */ - { "search_skipdiacrit", 1, SWITCH } /* When searching, should diacriticals be stripped from the search terms. The default is "true", a search for "hav" will match "Håvard". This is not RFC5051 complient, but it backwards compatible, and may be preferred by some sites. */ -{ "search_skiphtml", 0, SWITCH } -/* If enabled, HTML parts of messages are skipped, i.e. not indexed and - not searchable. Otherwise, they're indexed. */ - { "search_whitespace", "merge", ENUM("skip", "merge", "keep") } /* When searching, how whitespace should be handled. Options are: "skip" (default in 2.3 and earlier series) - where a search for @@ -1590,10 +1387,6 @@ successfully authenticate. Otherwise lmtpd returns permanent failures (causing the mail to bounce immediately). */ -{ "sortcache_db", "twoskip", STRINGLIST("berkeley", "berkeley-hash", "skiplist", "twoskip")} -/* The cyrusdb backend to use for caching sort results (currently only - used for xconvmultisort) */ - { "specialuse_extra", NULL, STRING } /* Whitespace separated list of extra special-use attributes that can be set on a mailbox. RFC 6154 currently lists @@ -1605,19 +1398,6 @@ /* If enabled, this option causes LIST and LSUB output to always include the XLIST "special-use" flags */ -{ "sphinx_text_excludes_odd_headers", 0, SWITCH } -/* If enabled, Sphinx will perform a TEXT search as if it matches - FROM, TO, CC, BCC or SUBJECT but not any other headers. This - is contrary to the RFC but a more useful behaviour for most - users. Default: disabled. */ - -{ "sphinx_socket", "{configdirectory}/socket/sphinx", STRING } -/* Unix domain socket that the Sphinx searchd - daemons listens on. */ - -{ "sphinx_pidfile", "/var/run/sphinx.pid", STRING } -/* File where the Sphinx searchd daemon writes its pid. */ - { "sql_database", NULL, STRING } /* Name of the database which contains the cyrusdb table(s). */ @@ -1836,20 +1616,10 @@ /* Send mail to mailboxes, which do not exists, to this user. NOTE: This must be an existing local mailbox name. NOT an email address! */ -{ "defaultsearchtier", "", STRING } -/* Name of the default tier that messages will be indexed to */ - -{ "zoneinfo_db", "skiplist", STRINGLIST("flat", "berkeley", "berkeley-hash", "skiplist")} -/* The cyrusdb backend to use for zoneinfo. */ - -{ "zoneinfo_db_path", NULL, STRING } -/* The absolute path to the zoneinfo db file. If not specified, - will be confdir/zoneinfo.db */ - /* .SH SEE ALSO .PP \fBimapd(8)\fR, \fBpop3d(8)\fR, \fBnntpd(8)\fR, \fBlmtpd(8)\fR, -\fBhttpd(8)\fR, \fBtimsieved(8)\fR, \fBidled(8)\fR, \fBnotifyd(8)\fR, +\fBtimsieved(8)\fR, \fBidled(8)\fR, \fBnotifyd(8)\fR, \fBdeliver(8)\fR, \fBmaster(8)\fR, \fBciphers(1)\fR */
View file
cyrus-imapd-2.5.tar.gz/lib/imclient.c
Changed
@@ -66,6 +66,8 @@ #include <openssl/pem.h> #include <openssl/x509.h> #include <openssl/ssl.h> +#else +#include <errno.h> #endif /* HAVE_SSL */ #include "assert.h"
View file
cyrus-imapd-2.5.tar.gz/lib/libconfig.c
Changed
@@ -203,18 +203,6 @@ return config_getoverflowstring(buf, NULL); } -EXPORTED const char *config_archivepartitiondir(const char *partition) -{ - char buf80; - - if(strlcpy(buf, "archivepartition-", sizeof(buf)) >= sizeof(buf)) - return 0; - if(strlcat(buf, partition, sizeof(buf)) >= sizeof(buf)) - return 0; - - return config_getoverflowstring(buf, NULL); -} - static void config_ispartition(const char *key, const char *val __attribute__((unused)), void *rock)
View file
cyrus-imapd-2.5.tar.gz/lib/libconfig.h
Changed
@@ -62,7 +62,6 @@ void (*func)(const char *, const char *, void *), void *rock); extern const char *config_partitiondir(const char *partition); extern const char *config_metapartitiondir(const char *partition); -extern const char *config_archivepartitiondir(const char *partition); /* cached configuration variables accessable to external world */ extern const char *config_filename;
View file
cyrus-imapd-2.5.tar.gz/lib/lock_fcntl.c
Changed
@@ -117,28 +117,65 @@ } /* - * Obtain a lock on 'fd'. The lock is exclusive if 'exclusive' - * is true, otherwise shared. Normally blocks until a lock is - * obtained, but if 'nonblock' is true does not block and instead - * fails with errno=EWOUDBLOCK if the lock cannot be obtained. - * + * Obtain an exclusive lock on 'fd'. + * Returns 0 for success, -1 for failure, with errno set to an + * appropriate error code. + */ +EXPORTED int lock_blocking(int fd, const char *filename __attribute__((unused))) +{ + int r; + struct flock fl; + + for (;;) { + fl.l_type= F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + r = fcntl(fd, F_SETLKW, &fl); + if (r != -1) return 0; + if (errno == EINTR) continue; + return -1; + } +} + +/* + * Obtain a shared lock on 'fd'. * Returns 0 for success, -1 for failure, with errno set to an * appropriate error code. */ -EXPORTED int lock_setlock(int fd, int exclusive, int nonblock, - const char *filename __attribute__((unused))) +EXPORTED int lock_shared(int fd, const char *filename __attribute__((unused))) { int r; struct flock fl; - int type = (exclusive ? F_WRLCK : F_RDLCK); - int cmd = (nonblock ? F_SETLK : F_SETLKW); for (;;) { - fl.l_type= type; + fl.l_type= F_RDLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + r = fcntl(fd, F_SETLKW, &fl); + if (r != -1) return 0; + if (errno == EINTR) continue; + return -1; + } +} + +/* + * Attempt to get an exclusive lock on 'fd' without blocking. + * Returns 0 for success, -1 for failure, with errno set to an + * appropriate error code. + */ +EXPORTED int lock_nonblocking(int fd, const char *filename __attribute__((unused))) +{ + int r; + struct flock fl; + + for (;;) { + fl.l_type= F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; - r = fcntl(fd, cmd, &fl); + r = fcntl(fd, F_SETLK, &fl); if (r != -1) return 0; if (errno == EINTR) continue; return -1;
View file
cyrus-imapd-2.5.tar.gz/lib/lock_flock.c
Changed
@@ -106,23 +106,50 @@ } /* - * Obtain a lock on 'fd'. The lock is exclusive if 'exclusive' - * is true, otherwise shared. Normally blocks until a lock is - * obtained, but if 'nonblock' is true does not block and instead - * fails with errno=EWOUDBLOCK if the lock cannot be obtained. - * + * Obtain an exclusive lock on 'fd'. + * Returns 0 for success, -1 for failure, with errno set to an + * appropriate error code. + */ +EXPORTED int lock_blocking(int fd, const char *filename __attribute__((unused))) +{ + int r; + + for (;;) { + r = flock(fd, LOCK_EX); + if (r != -1) return 0; + if (errno == EINTR) continue; + return -1; + } +} + +/* + * Obtain a shared lock on 'fd'. + * Returns 0 for success, -1 for failure, with errno set to an + * appropriate error code. + */ +EXPORTED int lock_shared(int fd, const char *filename __attribute__((unused))) +{ + int r; + + for (;;) { + r = flock(fd, LOCK_SH); + if (r != -1) return 0; + if (errno == EINTR) continue; + return -1; + } +} + +/* + * Attempt to get an exclusive lock on 'fd' without blocking. * Returns 0 for success, -1 for failure, with errno set to an * appropriate error code. */ -EXPORTED int lock_setlock(int fd, int exclusive, int nonblock, - const char *filename __attribute__((unused))) +EXPORTED int lock_nonblocking(int fd, const char *filename __attribute__((unused))) { int r; - int op = (exclusive ? LOCK_EX : LOCK_SH); - if (nonblock) op |= LOCK_NB; for (;;) { - r = flock(fd, op); + r = flock(fd, LOCK_EX|LOCK_NB); if (r != -1) return 0; if (errno == EINTR) continue; return -1;
View file
cyrus-imapd-2.5.tar.gz/lib/mappedfile.c
Changed
@@ -100,20 +100,21 @@ int is_rw; }; -static void _ensure_mapped(struct mappedfile *mf, size_t offset, int update) +static void _ensure_mapped(struct mappedfile *mf, size_t offset) { + const char *base = NULL; + size_t len = 0; + /* we may be rewriting inside a file, so don't shrink, only extend */ - if (update) { - if (offset > mf->map_size) - mf->was_resized = 1; - else - offset = mf->map_size; - } + if (offset > mf->map_size) + mf->was_resized = 1; + else + offset = mf->map_size; /* always give refresh another go, we may be map_nommap */ - buf_init_mmap(&mf->map_buf, /*onceonly*/0, mf->fd, mf->fname, - offset, /*mboxname*/NULL); + map_refresh(mf->fd, 0, &base, &len, offset, mf->fname, 0); + buf_init_mmap(&mf->map_buf, base, len); mf->map_size = offset; } @@ -165,7 +166,8 @@ goto err; } - _ensure_mapped(mf, sbuf.st_size, /*update*/0); + _ensure_mapped(mf, sbuf.st_size); + mf->was_resized = 0; /* not actually resized */ *mfp = mf; @@ -245,7 +247,7 @@ buf_free(&mf->map_buf); } - _ensure_mapped(mf, sbuf.st_size, /*update*/0); + _ensure_mapped(mf, sbuf.st_size); return 0; } @@ -269,11 +271,11 @@ mf->lock_status = MF_WRITELOCKED; /* XXX - can we guarantee the fd isn't reused? */ - if (mf->map_ino != sbuf.st_ino) { + if (mf->map_ino != sbuf.st_ino) buf_free(&mf->map_buf); - } - _ensure_mapped(mf, sbuf.st_size, /*update*/0); + _ensure_mapped(mf, sbuf.st_size); + mf->was_resized = 0; return 0; } @@ -362,7 +364,7 @@ return -1; } - _ensure_mapped(mf, pos+written, /*update*/1); + _ensure_mapped(mf, pos+written); return written; } @@ -413,7 +415,7 @@ return -1; } - _ensure_mapped(mf, pos+written, /*update*/1); + _ensure_mapped(mf, pos+written); return written; } @@ -427,14 +429,18 @@ mf->dirty++; + /* make sure we don't think the future is valid any more */ + if (offset < (off_t)mf->map_size) + mf->map_size = offset; + r = ftruncate(mf->fd, offset); if (r < 0) { syslog(LOG_ERR, "IOERROR: ftruncate %s: %m", mf->fname); return r; } - _ensure_mapped(mf, offset, /*update*/0); - mf->was_resized = 1; /* force the fsync */ + _ensure_mapped(mf, offset); + mf->was_resized = 1; return 0; }
View file
cyrus-imapd-2.5.tar.gz/lib/me.c
Deleted
@@ -1,98 +0,0 @@ -/* Messagingengine.com utility functions */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <sys/socket.h> -#include <sys/un.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <unistd.h> -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <time.h> -#include "me.h" -#include "libconfig.h" - -static void rc4_encode(int key_len, unsigned char * key_buf, int data_len, unsigned char * data_buf); -static char * base64_encode (int data_len, unsigned char * data); - -EXPORTED const char *me_create_sasl_enc(const char *username) -{ - static char padded_sasl256, junk_buf256, key_buf256; - int i, junk_len, epoch, key_len, data_len; - char *encoded_base64; - const char * format; - - epoch = (int)time(0); - junk_len = 31 - strlen(username); - if (junk_len < 0) junk_len = 0; - - for (i = 0; i < junk_len; i++) - junk_bufi = 'A' + (rand() % 26); - junk_bufi = '\0'; - - padded_sasl255 = '\0'; - snprintf(padded_sasl, 255, "%02d%s%s", junk_len, junk_buf, username); - - key_buf255 = '\0'; - format = config_getstring(IMAPOPT_ME_SECRET); - snprintf(key_buf, 255, format, epoch, epoch); - - key_len = strlen(key_buf); - data_len = strlen(padded_sasl); - rc4_encode(key_len, (unsigned char *)key_buf, data_len, (unsigned char *)padded_sasl); - encoded_base64 = base64_encode(data_len, (unsigned char *)padded_sasl); - - padded_sasl255 = '\0'; - snprintf(padded_sasl, 255, "%s %d", encoded_base64, epoch); - - return padded_sasl; -} - -static void rc4_encode(int key_len, unsigned char * key_buf, int data_len, unsigned char * data_buf) { - static unsigned char S256; - unsigned char tmp; - int i = 0, j = 0, l; - - for (i = 0; i < 256; i++) Si = i; - for (i = 0; i < 256; i++) { - j = (j + Si + key_bufi % key_len) % 256; - tmp = Si; Si = Sj; Sj = tmp; - } - i = 0; - for (l = 0; l < data_len; l++) { - i = (i + 1) % 256; - j = (j + Si) % 256; - tmp = Si; Si = Sj; Sj = tmp; - data_bufl ^= S(Si + Sj) % 256; - } -} - -static char * base64_encode (int data_len, unsigned char * data) -{ - static const char base64 = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - static char tobuf512; - unsigned char * end = data + (data_len < 500 ? data_len: 500); - char *d = tobuf; - unsigned char c1, c2, c3; - - while (1) { - c1 = *data++; - *d++ = base64c1>>2; - c2 = *data++; - *d++ = base64((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4); - if (data > end) break; - c3 = *data++; - *d++ = base64((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6); - if (data > end) break; - *d++ = base64c3 & 0x3F; - if (data == end) break; - } - *d = '\0'; - return tobuf; -} -
View file
cyrus-imapd-2.5.tar.gz/lib/me.h
Deleted
@@ -1,10 +0,0 @@ -/* Messagingengine.com generic functions */ - -#ifndef ME_H -#define ME_H - -/* sasl_enc support */ - -const char *me_create_sasl_enc(const char *username); - -#endif
View file
cyrus-imapd-2.5.tar.gz/lib/prot.c
Changed
@@ -820,7 +820,7 @@ int n; time_t newtime; char timebuf20; - + time(&newtime); snprintf(timebuf, sizeof(timebuf), ">%ld>", newtime); n = write(s->logfd, timebuf, strlen(timebuf)); @@ -836,9 +836,7 @@ } } while (left); - /* we don't care THAT much about logs - * (void)fsync(s->logfd); - */ + (void)fsync(s->logfd); } }
View file
cyrus-imapd-2.5.tar.gz/lib/ptrarray.c
Changed
@@ -204,10 +204,3 @@ return i; return -1; } - -EXPORTED void ptrarray_sort(ptrarray_t *pa, - int (*compare)(const void **, const void **)) -{ - qsort(pa->data, pa->count, sizeof(void*), - (int (*)(const void *, const void *))compare); -}
View file
cyrus-imapd-2.5.tar.gz/lib/ptrarray.h
Changed
@@ -77,14 +77,9 @@ #define ptrarray_pop(pa) ptrarray_remove((pa), -1) #define ptrarray_push(pa, s) ptrarray_append((pa), (s)) -#define ptrarray_tail(pa) ptrarray_nth((pa), -1) -#define ptrarray_head(pa) ptrarray_nth((pa), 0) - void **ptrarray_takevf(ptrarray_t *pa); int ptrarray_find(const ptrarray_t *pa, void *match, int starting); -void ptrarray_sort(ptrarray_t *pa, int (*compare)(const void **, const void **)); - #endif /* __CYRUS_PTRARRAY_H__ */
View file
cyrus-imapd-2.5.tar.gz/lib/retry.c
Changed
@@ -48,7 +48,6 @@ #include <unistd.h> #endif -#include "exitcodes.h" #include "retry.h" #include "xmalloc.h" @@ -115,8 +114,7 @@ int i; ssize_t n; size_t written = 0; - size_t len = 0; - struct iovec *iov, *baseiov = NULL; + struct iovec *iov, *baseiov; static int iov_max = #ifdef MAXIOV MAXIOV @@ -132,17 +130,6 @@ if (!iovcnt) return 0; - for (i = 0; i < iovcnt; i++) { - len += srciovi.iov_len; - } - - n = written = writev(fd, srciov, iovcnt > iov_max ? iov_max : iovcnt); - - /* did we get lucky and write it all? */ - if (written == len) - return written; - - /* oh well, welcome to the slow path - we have copies */ baseiov = iov = (struct iovec *)xmalloc(iovcnt * sizeof(struct iovec)); for (i = 0; i < iovcnt; i++) { iovi.iov_base = srciovi.iov_base; @@ -150,18 +137,13 @@ } for (;;) { - for (i = 0; i < iovcnt; i++) { - if (iovi.iov_len > (size_t)n) { - iovi.iov_base += n; - iovi.iov_len -= n; - break; - } - n -= iovi.iov_len; + while (iovcnt && iov0.iov_len == 0) { iov++; iovcnt--; - if (!iovcnt) fatal("ran out of iov", EC_SOFTWARE); } + if (!iovcnt) break; + n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt); if (n == -1) { if (errno == EINVAL && iov_max > 10) { @@ -175,7 +157,17 @@ written += n; - if (written == len) break; + for (i = 0; i < iovcnt; i++) { + if (iovi.iov_len > (size_t)n) { + iovi.iov_base = (char *)iovi.iov_base + n; + iovi.iov_len -= n; + break; + } + n -= iovi.iov_len; + iovi.iov_len = 0; + } + + if (i == iovcnt) break; } free(baseiov);
View file
cyrus-imapd-2.5.tar.gz/lib/signals.c
Changed
@@ -101,8 +101,6 @@ fatal("unable to install signal handler for SIGINT", EC_TEMPFAIL); if (sigaction(SIGTERM, &action, NULL) < 0) fatal("unable to install signal handler for SIGTERM", EC_TEMPFAIL); - if (sigaction(SIGUSR2, &action, NULL) < 0) - fatal("unable to install signal handler for SIGTERM", EC_TEMPFAIL); signals_reset_sighup_handler(1); } @@ -203,7 +201,6 @@ else exit(EC_TEMPFAIL); } for (sig = 1 ; sig < _NSIG ; sig++) { - if (sig == SIGUSR2) continue; /* only ever polled explicitly */ if (gotsignalsig) return sig; } @@ -275,19 +272,3 @@ return r; #endif } - -EXPORTED void signals_clear(int sig) -{ - if (sig >= 0 && sig < _NSIG) - gotsignalsig = 0; -} - -EXPORTED int signals_cancelled() -{ - if (gotsignalSIGUSR2) { - gotsignalSIGUSR2 = 0; - return 1; - } - - return 0; -}
View file
cyrus-imapd-2.5.tar.gz/lib/signals.h
Changed
@@ -54,7 +54,5 @@ int signals_poll(void); int signals_select(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tout); -void signals_clear(int sig); -int signals_cancelled(); #endif /* INCLUDED_SIGNALS_H */
View file
cyrus-imapd-2.5.tar.gz/lib/strarray.c
Changed
@@ -410,20 +410,3 @@ { return sa->count; } - -EXPORTED int strarray_cmp(const strarray_t *a, const strarray_t *b) -{ - int as = strarray_size(a); - int bs = strarray_size(b); - int i; - - /* test size first */ - if (as != bs) return as - bs; - - for (i = 0; i < as; i++) { - int res = strcmpsafe(strarray_nth(a, i), strarray_nth(b, i)); - if (res) return res; - } - - return 0; -}
View file
cyrus-imapd-2.5.tar.gz/lib/strarray.h
Changed
@@ -105,6 +105,4 @@ int strarray_size(const strarray_t *sa); -int strarray_cmp(const strarray_t *a, const strarray_t *b); - #endif /* __CYRUS_STRARRAY_H__ */
View file
cyrus-imapd-2.5.tar.gz/lib/util.c
Changed
@@ -448,6 +448,14 @@ } } + destfd = open(to, O_RDWR|O_TRUNC|O_CREAT, 0666); + if (destfd == -1) { + if (!(flags & COPYFILE_MKDIR)) + syslog(LOG_ERR, "IOERROR: creating %s: %m", to); + r = -1; + goto done; + } + srcfd = open(from, O_RDONLY, 0666); if (srcfd == -1) { syslog(LOG_ERR, "IOERROR: opening %s: %m", from); @@ -461,20 +469,6 @@ goto done; } - if (!sbuf.st_size) { - syslog(LOG_ERR, "IOERROR: zero byte file %s: %m", from); - r = -1; - goto done; - } - - destfd = open(to, O_RDWR|O_TRUNC|O_CREAT, 0666); - if (destfd == -1) { - if (!(flags & COPYFILE_MKDIR)) - syslog(LOG_ERR, "IOERROR: creating %s: %m", to); - r = -1; - goto done; - } - map_refresh(srcfd, 1, &src_base, &src_size, sbuf.st_size, from, 0); n = retry_write(destfd, src_base, src_size); @@ -1296,12 +1290,12 @@ * This buf is CoW, and if written to the data will be freed * using map_free(). */ -EXPORTED void buf_init_mmap(struct buf *buf, int onceonly, int fd, - const char *fname, size_t size, const char *mboxname) +EXPORTED void buf_init_mmap(struct buf *buf, const char *base, int len) { + buf->alloc = 0; + buf->len = len; buf->flags = BUF_MMAP; - map_refresh(fd, onceonly, (const char **)&buf->s, &buf->len, - size, fname, mboxname); + buf->s = (char *)base; } static void _buf_free_data(struct buf *buf)
View file
cyrus-imapd-2.5.tar.gz/lib/util.h
Changed
@@ -280,8 +280,7 @@ void buf_init_ro(struct buf *buf, const char *base, size_t len); void buf_initm(struct buf *buf, char *base, int len); void buf_init_ro_cstr(struct buf *buf, const char *str); -void buf_init_mmap(struct buf *buf, int onceonly, int fd, - const char *fname, size_t size, const char *mboxname); +void buf_init_mmap(struct buf *buf, const char *base, int len); void buf_free(struct buf *buf); void buf_move(struct buf *dst, struct buf *src);
View file
cyrus-imapd-2.5.tar.gz/man/ctl_conversationsdb.8
Deleted
@@ -1,134 +0,0 @@ -.\" -*- nroff -*- -.TH CTL_CONVERSATIONSDB 8 "Project Cyrus" CMU -.\" -.\" Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. -.\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" -.\" 1. Redistributions of source code must retain the above copyright -.\" notice, this list of conditions and the following disclaimer. -.\" -.\" 2. Redistributions in binary form must reproduce the above copyright -.\" notice, this list of conditions and the following disclaimer in -.\" the documentation and/or other materials provided with the -.\" distribution. -.\" -.\" 3. The name "Carnegie Mellon University" must not be used to -.\" endorse or promote products derived from this software without -.\" prior written permission. For permission or any legal -.\" details, please contact -.\" Carnegie Mellon University -.\" Center for Technology Transfer and Enterprise Creation -.\" 4615 Forbes Avenue -.\" Suite 302 -.\" Pittsburgh, PA 15213 -.\" (412) 268-7393, fax: (412) 268-7395 -.\" innovation@andrew.cmu.edu -.\" 4. Redistributions of any form whatsoever must retain the following -.\" acknowledgment: -.\" "This product includes software developed by Computing Services -.\" at Carnegie Mellon University (http://www.cmu.edu/computing/)." -.\" -.\" CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO -.\" THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -.\" AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE -.\" FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN -.\" AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING -.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.SH NAME -ctl_conversationsdb \- perform operations on the conversations databases -.SH SYNOPSIS -\fBctl_conversationsdb\fP \fB-C\fP \fIconfig-file\fP \fB-d\fR -\fIuserid\fR > \fItext\fP -.br -\fBctl_conversationsdb\fP \fB-C\fP \fIconfig-file\fP \fB-u\fR -\fIuserid\fR < \fItext\fP -.br -\fBctl_conversationsdb\fP \fB-C\fP \fIconfig-file\fP \fB-v\fR - \fB-z\fR | \fB-b\fR | \fB-R\fR \fIuserid\fR -.br -\fBctl_conversationsdb\fP \fB-C\fP \fIconfig-file\fP \fB-v\fR - \fB-z\fR | \fB-b\fR | \fB-R\fR \fB-r\fR -.SH DESCRIPTION -.B Ctl_conversationsdb -is used to perform various administrative operations on a -conversations database and associated information in \fIcyrus.index\fR -files. -.PP -.B Ctl_conversationsdb -reads its configuration options out of the -.IR imapd.conf (5) -file unless specified otherwise by \fB-C\fR. -The -.I configdirectory -option is used to determine the default location of the conversations database. -.PP -In the first synopsis, the \fB-d\fP option dumps the contents of a -conversations database to standard output in an ASCII format. In the -second synopsis, the resulting \fIfile\fP is fed back in, using the -\fB-u\fP option to "undump" from standard input. This pair of commands -is useful for disaster recovery, or for changing the backend used to -store the conversations database. -.PP -The third synopsis is used to reconstruct conversations information -in various ways for a specific user, and the fourth to reconstruct -conversations information for all users. See -.SM OPTIONS -below for details. -.SH OPTIONS -.TP -.BI \-C\ "config-file" -Read configuration options from \fIconfig-file\fR. -.TP -.B \-d -Dump the conversations database which corresponds to the user \fIuserid\fR -to standard output in an ASCII format. The resulting file can be used -to recreate a database using the \fB-u\fR option. -.TP -.B \-u -"Undumps" the conversations database corresponding to the user \fIuserid\fR, -i.e. replaces all the entries with data from ASCII records parsed from -standard input. The output from the \fB-d\fR option can be used as -input. -.TP -.B \-v -Be more verbose when running. -.TP -.B \-r -Be recursive; apply the main operation to every user. Warning: do not -combine with \fB-u\fP, it will not do what you expect. -.TP -.B \-z -Remove all conversation information from the conversations database -for user \fIuserid\fP, and from all the user's mailboxes. The -information can all be recalculated (eventually) from message headers, -using the \fB-b\fP option. -.TP -.B \-b -Rebuild all conversation information in the conversations database -for user \fIuserid\fP, and in all the user's mailboxes, from the header -information in messages. Does not affect messages which already have -conversation information. -.IP -This is a bulk mode version of what happens -to each message when it arrives, and can be used to add missing -conversation information for past messages, e.g. after using \fB-z\fP or -after upgrading Cyrus from older versions. Note: this operation uses -information from \fIcyrus.cache\fP files so it does not need to read -every single message file. -.TP -.B \-R -Recalculate counts of messages stored in existing conversations in the -conversations database for user \fIuserid\fP. This is a limited subset -of \fB-b\fP; in particular it does not create conversations or assign -messages to conversations. -.SH FILES -.TP -.B /etc/imapd.conf -.SH SEE ALSO -.PP -\fBimapd.conf(5)\fR, \fBmaster(8)\fR
View file
cyrus-imapd-2.5.tar.gz/man/ctl_zoneinfo.8
Deleted
@@ -1,86 +0,0 @@ -.\" -*- nroff -*- -.TH CTL_ZONEINFO 8 "Project Cyrus" CMU -.\" -.\" Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved. -.\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" -.\" 1. Redistributions of source code must retain the above copyright -.\" notice, this list of conditions and the following disclaimer. -.\" -.\" 2. Redistributions in binary form must reproduce the above copyright -.\" notice, this list of conditions and the following disclaimer in -.\" the documentation and/or other materials provided with the -.\" distribution. -.\" -.\" 3. The name "Carnegie Mellon University" must not be used to -.\" endorse or promote products derived from this software without -.\" prior written permission. For permission or any legal -.\" details, please contact -.\" Carnegie Mellon University -.\" Center for Technology Transfer and Enterprise Creation -.\" 4615 Forbes Avenue -.\" Suite 302 -.\" Pittsburgh, PA 15213 -.\" (412) 268-7393, fax: (412) 268-7395 -.\" innovation@andrew.cmu.edu - * -.\" 4. Redistributions of any form whatsoever must retain the following -.\" acknowledgment: -.\" "This product includes software developed by Computing Services -.\" at Carnegie Mellon University (http://www.cmu.edu/computing/)." -.\" -.\" CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO -.\" THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -.\" AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE -.\" FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN -.\" AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING -.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.SH NAME -ctl_zoneinfo \- perform operations on the zoneinfo database -.SH SYNOPSIS -.B ctl_zoneinfo - -.B \-C -.I config-file - - -.B \-v - -.B \-r -.I version-string -.SH DESCRIPTION -.I Ctl_zoneinfo -is used to perform various administrative operations on the zoneinfo -database. -.PP -.I Ctl_zoneinfo -reads its configuration options out of the -.IR imapd.conf (5) -file unless specified otherwise by \fB-C\fR. -.SH OPTIONS -.TP -.BI \-C " config-file" -Read configuration options from \fIconfig-file\fR. -.TP -.B \-v -Enable verbose output. -.TP -.BI \-r " version-string" -Rebuild the zoneinfo database based on the directory structure of -\fIconfigdirectory\fB/zoneinfo\fR. The database to be rebuilt will be -in the default location of \fIconfigdirectory\fB/zoneinfo.db\fR unless -otherwise specified by the \fIzoneinfo_db_path\fR option in -\fBimapd.conf\fR. The \fIversion-string\fR should describe the source -of the timezone data (e.g. "Olson 2013h") and will be used by the -\fItimezone\fR module of \fBhttpd\fR. -.SH FILES -.TP -.B /etc/imapd.conf -.SH SEE ALSO -.PP -\fBimapd.conf(5)\fR, \fBmaster(8)\fR, \fBhttpd(8)\fR
View file
cyrus-imapd-2.5.tar.gz/man/cyr_dbtool.8
Changed
@@ -50,12 +50,6 @@ .B \-n - -.B \-o - - -.B \-T - <db file> <db backend> <action> <key> @@ -104,14 +98,6 @@ .TP .BI \-n Create the database file if it doesn't already exist. -.TP -.BI \-o -Store all the output in memory and only print it once the transaction -is completed. -.TP -.BI \-T -Use a transaction to do the action (most especially for 'show') - the -default used to be transactions. .SH FILES .TP .B /etc/imapd.conf
View file
cyrus-imapd-2.5.tar.gz/man/cyr_expire.8
Changed
@@ -48,9 +48,6 @@ .I config-file -.BI \-A " archive-duration" - - .BI \-D " delete-duration" @@ -80,9 +77,7 @@ cleanse mailboxes of partially expunged messages (when using the "delayed" expunge mode), and .IP \(bu 2m -remove deleted mailboxes (when using the "delayed" delete mode), and -.IP \(bu 2m -expire entries from conversations databases. +remove deleted mailboxes (when using the "delayed" delete mode). .PP The expiration of messages is controlled by the \fB/vendor/cmu/cyrus-imapd/expire\fR mailbox annotation which @@ -107,12 +102,6 @@ annotation applies to the mailbox then duplicate database entries are expired using the value given to the \fB-E\fR option. .PP -Expiration of conversations database entries occurs if the -\fBconversations\fP option is present in \fIimapd.conf\fP. Expiration -can be disabled using the \fB\-c\fP option. The period used to -expire entries is controlled by the \fBconversations_expire_days\fP -option in \fIimapd.conf\fP. -.PP \fBCyr_expire\fR reads its configuration options out of the \fIimapd.conf\fR(5) file unless specified otherwise by \fB-C\fR. .PP @@ -123,11 +112,6 @@ \fB\-C\fI config-file\fR Read configuration options from \fIconfig-file\fR. .TP -\fB\-A \fIarchive-duration\fR -Archive non-flagged messages older than \fIarchive-duration\fR to the -archive partition, allowing mailbox messages to be split between fast -storage and slow large storage. Only does anything if archivepartition-* -has been set in your config. \fB\-D \fIdelete-duration\fR Remove previously deleted mailboxes older than \fIdelete-duration\fR (when using the "delayed" delete mode). @@ -147,11 +131,7 @@ (when using the "delayed" expunge mode). Format is the same as delete-duration. .TP -\fB-c\fR -Do not expire conversation database entries, even if the conversations -feature is enabled. -.TP -\fB\-x\fR +\fB\-x Do not expunge messages even if using delayed expunge mode. This reduces IO traffic considerably, allowing \fBcyr_expire\fR to be run frequently to clean up the duplicate database without overloading the machine.
View file
cyrus-imapd-2.5.tar.gz/man/httpd.8
Deleted
@@ -1,129 +0,0 @@ -.\" -*- nroff -*- -.TH HTTPD 8 "Project Cyrus" CMU -.\" -.\" Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved. -.\" -.\" Redistribution and use in source and binary forms, with or without -.\" modification, are permitted provided that the following conditions -.\" are met: -.\" -.\" 1. Redistributions of source code must retain the above copyright -.\" notice, this list of conditions and the following disclaimer. -.\" -.\" 2. Redistributions in binary form must reproduce the above copyright -.\" notice, this list of conditions and the following disclaimer in -.\" the documentation and/or other materials provided with the -.\" distribution. -.\" -.\" 3. The name "Carnegie Mellon University" must not be used to -.\" endorse or promote products derived from this software without -.\" prior written permission. For permission or any legal -.\" details, please contact -.\" Carnegie Mellon University -.\" Center for Technology Transfer and Enterprise Creation -.\" 4615 Forbes Avenue -.\" Suite 302 -.\" Pittsburgh, PA 15213 -.\" (412) 268-7393, fax: (412) 268-7395 -.\" innovation@andrew.cmu.edu - * -.\" 4. Redistributions of any form whatsoever must retain the following -.\" acknowledgment: -.\" "This product includes software developed by Computing Services -.\" at Carnegie Mellon University (http://www.cmu.edu/computing/)." -.\" -.\" CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO -.\" THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -.\" AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE -.\" FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN -.\" AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING -.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.SH NAME -httpd \- HTTP server process -.SH SYNOPSIS -.B httpd - -.B \-C -.I config-file - - -.B \-U -.I uses - - -.B \-T -.I timeout - - -.B \-D - -.br - -.B \-s - - -.B \-p -.I ssf - -.SH DESCRIPTION -.I Httpd -is a HTTP server. -It accepts commands on its standard input and responds on its standard output. -It MUST invoked by -.IR master (8) -with those descriptors attached to a remote client connection. -.PP -.I Httpd -reads its configuration options out of the -.IR imapd.conf (5) -file unless specified otherwise by \fB-C\fR. -.PP -If the directory -.RI log/ user -exists under the directory specified in the -.I configdirectory -configuration option, then -.I httpd -will create protocol telemetry logs for sessions authenticating as -.IR user . -The telemetry logs will be stored in the -.RI log/ user -directory with a filename of the -.I httpd -process-id. -.SH OPTIONS -.TP -.BI \-C " config-file" -Read configuration options from \fIconfig-file\fR. -.TP -.BI \-U " uses" -The maximum number of times that the process should be used for new -connections before shutting down. The default is 250. -.TP -.BI \-T " timeout" -The number of seconds that the process will wait for a new connection -before shutting down. Note that a value of 0 (zero) will disable the -timeout. The default is 60. -.TP -.BI \-D -Run external debugger specified in debug_command. -.TP -.BI \-s -Serve HTTP over SSL (https). All data to and from -.I httpd -is encrypted using the Secure Sockets Layer. -.TP -.BI \-p " ssf" -Tell -.I httpd -that an external layer exists. An SSF (security strength factor) of 1 -means an integrity protection layer exists. Any higher SSF implies -some form of privacy protection. -.SH FILES -.TP -.B /etc/imapd.conf -.SH SEE ALSO -.PP -\fBimapd.conf(5)\fR, \fBmaster(8)\fR
View file
cyrus-imapd-2.5.tar.gz/man/squatter.8
Changed
@@ -63,114 +63,33 @@ .B \-v .IR mailbox ... -.br -.B squatter - -.B \-C -.I config-file - - -.B \-r - - -.B \-s - - -.B \-i - - -.B \-a - - -.B \-v - -.B -u -.IR user ... -.br -.B squatter - -.B \-C -.I config-file - - -.B -v - - -.B -s - - -.B -d - - -.B -n -.I channel - -.B -R -.br -.B squatter - -.B \-C -.I config-file - - -.B -v - - -.B -s - -.B -f -.I synclogfile .SH DESCRIPTION -.B Squatter -creates a new text index for one or more IMAP mailboxes. The +.I Squatter +creates a new SQUAT index for one or more IMAP mailboxes. The SQUAT index is a unified index of all of the header and body text of each -message in a given mailbox. This index is used to significantly reduce +message a given mailbox. This index is used to significantly reduce IMAP SEARCH times on a mailbox. .PP -The name \fBsquatter\fP is a historical relic from the days when the -only indexing engine supported by Cyrus was SQUAT. Today the -\fIsearch_engine\fP setting in \fIimapd.conf\fP determines which -search engine is used. -.PP -In the first synposis -.B squatter +.I Squatter creates an index of ALL messages in the mailbox, not just those since -the last time that it was run. The \fB-i\fP option is used to select -incremental updates. -Any messages appended to the mailbox after -.B squatter +the last time that it was run (i.e., it does NOT do incremental +updates). Any messages appended to the mailbox after +.I squatter is run, will NOT be included in the index. To include new messages in the index, -.B squatter +.I squatter must be run again. -.PP -In the second synopsis, \fBsquatter\fP runs in rolling mode. In this mode -\fBsquatter\fP backgrounds itself and runs as a daemon, listening to a sync -log channel (chosen using \fB-n\fP option, and set up using the -\fIsync_log_channels\fP setting in \fIimapd.conf\fP). Very soon after -messages are delivered or uploaded to mailboxes \fBsquatter\fP will -incrementally index the affected mailbox. -.PP -Note that incremental updates are very inefficient with the SQUAT -search engine. -If using SQUAT for large and active mailboxes, you should run -.B squatter +For large and active mailboxes, it is recommended to run +.I squatter periodically as an EVENT in -.IR cyrus.conf (5). -Incremental updates are much more efficient with Sphinx, so if using -Sphinx you should run \fBsquatter -R\fP as a START in \fIcyrus.conf\fP(5). -.PP -In the third synopsis, \fBsquatter\fP reads a single sync log file -and performs incremental indexing on the mailboxes listed therein. This is -sometimes useful for cleaning up after problems with rolling mode. +.IR cyrus.conf (5) +. .PP .B NOTE: -Messages and mailboxes that have not been indexed can still be -SEARCHed, just not as quickly as those with an index. Also, some -advanced features of Sphinx like stemming will not work unless -messages have been indexed. +Messages and mailboxes that have not been indexed CAN still be +SEARCHed, just not as quickly as those with a SQUAT index. .PP -.B Squatter +.I Squatter reads its configuration options out of the .IR imapd.conf (5) file unless specified otherwise by \fB-C\fR. @@ -179,49 +98,6 @@ .BI \-C " config-file" Read configuration options from \fIconfig-file\fR. .TP -.B \-R -Run in rolling mode; \fBsquatter\fP runs as a daemon listening to -a sync log channel and continuously incrementally indexing mailboxes. -See also \fB-d\fP and \fB-n\fP. -.TP -.BI \-S " seconds" -After processing each mailbox, sleep for "seconds" before continuing. -Can be used to provide some load balancing. Accepts fractional amounts. -.TP -.BI \-T " directory" -When indexing, work on a temporary copy of the search engine databases -in \fIdirectory\fP. That directory would typically be on some very -fast filesystem, like an SSD or tmpfs. This option may not work with all -search engines, but it's only effect is to speed up initial indexing. -.TP -.B \-u -Extra options refer to usernames (e.g. foo@bar.com) rather than mailbox names. -.TP -.B \-d -In rolling mode, don't background and do emit log messages on standard -error. Useful for debugging. -.TP -.BI \-f " synclogfile" -Read the \fIsynclogfile\fP and incrementally index all the mailboxes -listed therein, then exit. -.TP -.BI \-n " channel" -In rolling mode, specify the name of the sync log channel that -\fBsquatter\fP will listen to. The default is "squatter". -.TP -.B \-o -In compact mode, if only one source database is selected, just copy it -to the destination rather than compacting. -.TP -.B \-F -In compact mode, filter the resulting database to only include messages -which are not expunged in mailboxes with existing name/uidvalidity. -.TP -.B \-A -In compact mode, audit the resulting database to ensure that every -non-expunged message in all the user's mailboxes which is specified -by cyrus.indexed.db is present in the xapian database. -.TP .B \-r Recursively create indexes for all sub-mailboxes of the mailboxes or mailbox prefixes given as arguments. @@ -231,7 +107,7 @@ (within a small time delta). .TP .B \-i -Incremental updates where indexes already exist. +Incremental updates where squat indexes already exist. .TP .B \-a Only create indexes for mailboxes which have the shared
View file
cyrus-imapd-2.5.tar.gz/man/sync_client.8
Changed
@@ -91,9 +91,6 @@ .B \-m - -.B \-A - .br .B \-s @@ -188,12 +185,6 @@ User mode. Remaining arguments are list of users who should be replicated. .TP -.BI \-A -All users mode. -Sync every user on the server to the replica (doesn't do non-user mailboxes -at all... this could be considered a bug and maybe it should do those mailboxes -independently) -.TP .BI \-m Mailbox mode. Remaining arguments are list of mailboxes which should be replicated.
View file
cyrus-imapd-2.5.tar.gz/man/unexpunge.8
Changed
@@ -62,10 +62,6 @@ .B \-v - -.B \-f -.I flagname - .I mailbox .br .B unexpunge @@ -80,10 +76,6 @@ .B \-v - -.B \-f -.I flagname - .I mailbox .br .B unexpunge @@ -98,10 +90,6 @@ .B \-v - -.B \-f -.I flagname - .IR "mailbox uid" ... .SH DESCRIPTION .I Unexpunge @@ -136,12 +124,6 @@ .B \-d Unset the \fI\\Deleted\fR flag on any restored messages. .TP -.BI \-f " flagname" -Set the user flag -.I flagname -on any restored messages. This can make it easy to batch operate on -just the restored messages afterwards. -.TP .B \-v Enable verbose output/logging. .SH FILES
View file
cyrus-imapd-2.5.tar.gz/master/conf/normal.conf
Changed
@@ -21,10 +21,6 @@ # nntp cmd="nntpd" listen="nntp" prefork=0 # nntps cmd="nntpd -s" listen="nntps" prefork=0 - # these are only necessary if using HTTP for CalDAV, CardDAV, or RSS -# http cmd="httpd" listen="http" prefork=0 -# https cmd="httpd -s" listen="https" prefork=0 - # at least one LMTP is required for delivery # lmtp cmd="lmtpd" listen="lmtp" prefork=0 lmtpunix cmd="lmtpd" listen="/var/imap/socket/lmtp" prefork=0
View file
cyrus-imapd-2.5.tar.gz/master/conf/prefork.conf
Changed
@@ -21,10 +21,6 @@ # nntp cmd="nntpd" listen="nntp" prefork=3 # nntps cmd="nntpd -s" listen="nntps" prefork=1 - # these are only necessary if using HTTP for CalDAV, CardDAV, or RSS -# http cmd="httpd" listen="http" prefork=3 -# https cmd="httpd -s" listen="https" prefork=1 - # at least one LMTP is required for delivery # lmtp cmd="lmtpd" listen="lmtp" prefork=0 lmtpunix cmd="lmtpd" listen="/var/imap/socket/lmtp" prefork=1
View file
cyrus-imapd-2.5.tar.gz/master/master.c
Changed
@@ -829,6 +829,13 @@ fcntl_unset(STATUS_FD, FD_CLOEXEC); fcntl_unset(LISTEN_FD, FD_CLOEXEC); + + /* close all listeners */ + for (i = 0; i < nservices; i++) { + xclose(Servicesi.socket); + xclose(Servicesi.stat0); + xclose(Servicesi.stat1); + } } else { snprintf(name_env3, sizeof(name_env3), "CYRUS_ISDAEMON=1"); @@ -836,13 +843,6 @@ } limit_fds(s->maxfds); - /* close all listeners */ - for (i = 0; i < nservices; i++) { - xclose(Servicesi.socket); - xclose(Servicesi.stat0); - xclose(Servicesi.stat1); - } - syslog(LOG_DEBUG, "about to exec %s", path); /* add service name to environment */
View file
cyrus-imapd-2.5.tar.gz/mkdebian.pl
Deleted
@@ -1,123 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use warnings; - -my $num = `git log --pretty=oneline | wc -l`; -chomp($num); - -my $branch = `git branch | grep \\\* | cut -c 3-`; -chomp($branch); - -my $date = `date -R`; - -my $basename = "cyrus-$branch"; -my $basedir = $branch eq 'fastmail' ? 'usr/cyrus' : "usr/$basename"; - -mkdir("debian"); -open(FH, ">debian/changelog"); -print FH <<EOF; -cyrus-$branch ($num-1) experimental; urgency=low - - * basic package set up - - -- Bron Gondwana <brong\@fastmail.fm> $date -EOF -close(FH); - -open(FH, ">debian/control"); -print FH <<EOF; -Source: $basename -Section: mail -Priority: extra -Maintainer: Bron Gondwana <brong\@fastmail.fm> -Build-Depends: libssl-dev, zlib1g-dev, comerr-dev, libsasl2-dev, - libzephyr-dev, libpcre3-dev, autoconf, libxapian-dev, - libxml2-dev, libical-dev, libsqlite3-dev, - flex, bison, debhelper, libsnmp-dev - -Package: $basename -Architecture: all -Depends: \${shlibs:Depends} -Description: Cyrus package for branch $branch at FastMail.FM - -Package: $basename-build -Architecture: all -Depends: \${shlibs:Depends} -Description: Cyrus package for branch $branch at FastMail.FM - build files -EOF -close(FH); - -open(FH, ">debian/copyright"); -print FH "See the upstream files at CMU\n"; -close(FH); - -open(FH, ">debian/rules"); -print FH <<EOF; -#!/usr/bin/make -f -# debian/rules for alien - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -# Use v4 compatability mode, so ldconfig gets added to maint scripts. -export DH_COMPAT=4 - -PACKAGE=\$(shell dh_listpackages) - -build: - dh_testdir - autoreconf -v -i - ./configure --without-krb --with-perl=/usr/bin/perl --enable-http --enable-calalarmd --enable-idled --with-extraident=git-$branch-$num --prefix=/$basedir -with-cyrus-prefix=/$basedir --with-zlib --without-snmp --enable-replication --without-bdb --enable-xapian - make -j 8 all CFLAGS="-g -fPIC -W -Wall -Werror -fstack-protector-all" - make sieve/test - touch build - -clean: - dh_testdir - dh_testroot - dh_clean -d - rm -f build - -binary-indep: build - -binary-arch: build - dh_testdir - dh_testroot - dh_clean -k -d - dh_installdirs - - dh_installdocs - dh_installchangelogs - - make install DESTDIR=\$(CURDIR)/debian/$basename - /bin/bash ./libtool --mode=install install -o root -m 755 sieve/test \$(PWD)/debian/$basename/$basedir/bin/sieve-test - install -o root -m 755 tools/rehash debian/$basename/$basedir/bin/rehash - install -o root -m 755 tools/mkimap debian/$basename/$basedir/bin/mkimap - install -o root -m 755 tools/translatesieve debian/$basename/$basedir/bin/translatesieve - install -o root -m 755 tools/upgradesieve debian/$basename/$basedir/bin/upgradesieve - - # set up source package - # no need to actually install the built object files! It's just the source we want - mkdir -p debian/$basename-build/usr/src/$basename-build/cyrus - # but keep the git data so we can build again! - find . -maxdepth 1 -mindepth 1 -not -name debian -print0 | \\ - xargs -0 -r -i cp -a {} debian/$basename-build/usr/src/$basename-build/cyrus/ - - dh_compress - dh_makeshlibs - dh_installdeb - #-dh_shlibdeps - dh_gencontrol - dh_md5sums - dh_builddeb -- -z3 - -binary: binary-arch -EOF -close(FH); - -chmod(0755, "debian/rules"); - -print "Debian build environment for branch \"$branch\" set up - - - run dpkg-buildpackage to build\n";
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_external.c
Changed
@@ -61,25 +61,20 @@ const char *user, const char *mailbox, int nopt __attribute__((unused)), char **options __attribute__((unused)), - const char *message, const char *fname) + const char *message) { const char *notify; - const char *buf12; + const char *buf10; int fds2, status; pid_t child_pid; FILE *stream; /* check/parse options */ if (!(notify = config_getstring(IMAPOPT_NOTIFY_EXTERNAL))) { - syslog(LOG_ERR, "ERROR: external recipient (program) not specified"); - return strdup("NO external recipient (program) not specified"); + syslog(LOG_ERR, "ERROR: no external recipient (program) specified"); + return strdup("NO Recipient unspecified"); } -/* if (!*user) { - syslog(LOG_ERR, "ERROR: external recipient user not specified"); - return strdup("NO external recipient user not specified"); - } -*/ buf0 = notify; buf1 = "-c"; buf2 = class; @@ -89,9 +84,7 @@ buf6 = user; buf7 = "-m"; buf8 = mailbox; - buf9 = "-f"; - buf10 = fname; - buf11 = NULL; + buf9 = NULL; if (pipe(fds) < 0) { syslog(LOG_ERR,
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_external.h
Changed
@@ -49,8 +49,7 @@ char* notify_external(const char *class, const char *priority, const char *user, const char *mailbox, - int nopt, char **options, - const char *message, const char *fname); + int nopt, char **options, const char *message); #endif /* _NOTIFY_EXTERNAL_H */
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_log.c
Changed
@@ -51,8 +51,7 @@ char* notify_log(const char *class, const char *priority, const char *user, const char *mailbox, int nopt, char **options, - const char *message, - const char *fname __attribute__((unused))) + const char *message) { char opt_str1024 = ""; char *sep = "";
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_log.h
Changed
@@ -48,6 +48,6 @@ char* notify_log(const char *class, const char *priority, const char *user, const char *mailbox, int nopt, char **options, - const char *message, const char *fname); + const char *message); #endif /* _NOTIFY_LOG_H_ */
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_mailto.c
Changed
@@ -65,8 +65,7 @@ const char *user __attribute__((unused)), const char *mailbox __attribute__((unused)), int nopt, char **options, - const char *message, - const char *fname __attribute__((unused))) + const char *message) { FILE *sm; const char *smbuf7; @@ -110,14 +109,14 @@ return strdup("NO mailto could not spawn sendmail process"); t = time(NULL); - snprintf(outmsgid, sizeof(outmsgid), "<cmu-sieve-%d-%lu-%d@%s>", + snprintf(outmsgid, sizeof(outmsgid), "<cmu-sieve-%d-%lu-%d@%s>", (int) sm_pid, t, global_outgoing_count++, config_servername); - + fprintf(sm, "Message-ID: %s\r\n", outmsgid); time_to_rfc822(t, datestr, sizeof(datestr)); fprintf(sm, "Date: %s\r\n", datestr); - + fprintf(sm, "X-Sieve: %s\r\n", SIEVE_VERSION); fprintf(sm, "From: Mail Sieve Subsystem <%s>\r\n", config_getstring(IMAPOPT_POSTMASTER)); fprintf(sm, "To: <%s>\r\n", options0); @@ -147,7 +146,7 @@ if (msg) { const unsigned char *s = (const unsigned char *)msg; - + while (*s) { if (0 != (*s & 0x80)) { result = 1;
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_mailto.h
Changed
@@ -46,9 +46,11 @@ #include <config.h> /* the only option should be a mailto URI */ -char* notify_mailto(const char *class, const char *priority, - const char *user, const char *mailbox, +char* notify_mailto(const char *class __attribute__((unused)), + const char *priority __attribute__((unused)), + const char *user __attribute__((unused)), + const char *mailbox __attribute__((unused)), int nopt, char **options, - const char *message, const char *fname); + const char *message); #endif /* _NOTIFY_MAILTO_H_ */
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_null.c
Changed
@@ -52,8 +52,7 @@ const char *mailbox __attribute__((unused)), int nopt __attribute__((unused)), char **options __attribute__((unused)), - const char *message __attribute__((unused)), - const char *fname __attribute__((unused))) + const char *message __attribute__((unused))) { return strdup("OK null notification successful"); }
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_null.h
Changed
@@ -45,9 +45,12 @@ #include <config.h> -char* notify_null(const char *class, const char *priority, - const char *user, const char *mailbox, - int nopt, char **options, - const char *message, const char *fname); +char* notify_null(const char *class __attribute__((unused)), + const char *priority __attribute__((unused)), + const char *user __attribute__((unused)), + const char *mailbox __attribute__((unused)), + int nopt __attribute__((unused)), + char **options __attribute__((unused)), + const char *message __attribute__((unused))); #endif /* _NOTIFY_NULL_H_ */
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_zephyr.c
Changed
@@ -73,8 +73,7 @@ char* notify_zephyr(const char *class, const char *priority, const char *user, const char *mailbox, int nopt, char **options, - const char *message, - const char *fname __attribute__((unused))) + const char *message) { ZNotice_t notice; int retval;
View file
cyrus-imapd-2.5.tar.gz/notifyd/notify_zephyr.h
Changed
@@ -49,6 +49,6 @@ char* notify_zephyr(const char *class, const char *priority, const char *user, const char *mailbox, int nopt, char **options, - const char *message, const char *fname); + const char *message); #endif /* _NOTIFY_ZEPHYR_H_ */
View file
cyrus-imapd-2.5.tar.gz/notifyd/notifyd.c
Changed
@@ -107,7 +107,6 @@ strarray_t options = STRARRAY_INITIALIZER; long nopt; char *reply; - char *fname; notifymethod_t *nmethod; while (1) { @@ -148,7 +147,6 @@ strarray_appendm(&options, cp = fetch_arg(cp, tail)); if (cp) message = (cp = fetch_arg(cp, tail)); - if (cp) fname = (cp = fetch_arg(cp, tail)); if (!message) { syslog(LOG_ERR, "malformed notify request"); @@ -171,7 +169,7 @@ if (nmethod->name) { reply = nmethod->notify(class, priority, user, mailbox, - nopt, options.data, message, fname); + nopt, options.data, message); } #if 0 /* we don't care about responses right now */ else {
View file
cyrus-imapd-2.5.tar.gz/notifyd/notifyd.h
Changed
@@ -54,7 +54,7 @@ char *(*notify)(const char *class, const char *priority, const char *user, const char *mailbox, int nopt, char **options, - const char *message, const char *fname); /* notification function */ + const char *message); /* notification function */ } notifymethod_t; /* array of supported notification methods */
View file
cyrus-imapd-2.5.tar.gz/perl/sieve/lib/lex.c
Changed
@@ -84,7 +84,7 @@ if (strcmp(str,"quota")==0) return RESP_CODE_QUOTA; if (strcmp(str,"transition-needed")==0) return RESP_CODE_TRANSITION_NEEDED; if (strcmp(str,"trylater")==0) return RESP_CODE_TRYLATER; - if (strcmp(str,"nonexistant")==0) return RESP_CODE_NONEXISTANT; + if (strcmp(str,"nonexistent")==0) return RESP_CODE_NONEXISTENT; if (strcmp(str,"alreadyexists")==0) return RESP_CODE_ALREADYEXISTS; if (strcmp(str,"warning")==0) return RESP_CODE_WARNINGS; if (strcmp(str,"tag")==0) return RESP_CODE_TAG;
View file
cyrus-imapd-2.5.tar.gz/perl/sieve/lib/lex.h
Changed
@@ -75,7 +75,7 @@ RESP_CODE_QUOTA_MAXSIZE = 305, RESP_CODE_TRANSITION_NEEDED = 306, RESP_CODE_TRYLATER = 307, - RESP_CODE_NONEXISTANT = 308, + RESP_CODE_NONEXISTENT = 308, RESP_CODE_ALREADYEXISTS = 309, RESP_CODE_WARNINGS = 310, RESP_CODE_TAG = 311
View file
cyrus-imapd-2.5.tar.gz/ptclient/ldap.c
Changed
@@ -946,7 +946,7 @@ if (rc != PTSM_OK) return rc; - if (ptsm->domain_base_dn && (strrchr(canon_id, '@') != NULL)) { + if (ptsm->domain_base_dn && ptsm->domain_base_dn0 != '\0' && (strrchr(canon_id, '@') != NULL)) { syslog(LOG_DEBUG, "Attempting to get domain for %s from %s", canon_id, ptsm->domain_base_dn); /* Get the base dn to search from domain_base_dn searched on domain_scope with
View file
cyrus-imapd-2.5.tar.gz/sieve/interp.c
Changed
@@ -213,11 +213,6 @@ interp->getheader = f; } -EXPORTED void sieve_register_fname(sieve_interp_t *interp, sieve_get_fname *f) -{ - interp->getfname = f; -} - EXPORTED void sieve_register_envelope(sieve_interp_t *interp, sieve_get_envelope *f) { interp->getenvelope = f;
View file
cyrus-imapd-2.5.tar.gz/sieve/interp.h
Changed
@@ -57,7 +57,6 @@ sieve_get_envelope *getenvelope; sieve_get_body *getbody; sieve_get_include *getinclude; - sieve_get_fname *getfname; sieve_parse_error *err;
View file
cyrus-imapd-2.5.tar.gz/sieve/script.c
Changed
@@ -350,10 +350,9 @@ } static int send_notify_callback(sieve_interp_t *interp, - void *message_context, - void *script_context, notify_list_t *notify, - char *actions_string __attribute__((unused)), - const char **errmsg) + void *message_context, + void * script_context, notify_list_t *notify, + char *actions_string, const char **errmsg) { sieve_notify_context_t nc; struct buf out = BUF_INITIALIZER; @@ -377,18 +376,15 @@ build_notify_message(interp, notify->message, message_context, &out); buf_appendcstr(&out, "\n\n"); - /* buf_appendcstr(&out, actions_string); */ + buf_appendcstr(&out, actions_string); nc.message = buf_cstring(&out); - nc.fname = NULL; - if (interp->getfname) - interp->getfname(message_context, &nc.fname); ret = interp->notify(&nc, interp->interp_context, script_context, message_context, - errmsg); + errmsg); buf_free(&out);
View file
cyrus-imapd-2.5.tar.gz/sieve/sieve_interface.h
Changed
@@ -66,7 +66,6 @@ typedef int sieve_get_header(void *message_context, const char *header, const char ***contents); -typedef int sieve_get_fname(void *message_context, const char **fname); typedef int sieve_get_envelope(void *message_context, const char *field, const char ***contents); @@ -120,7 +119,6 @@ const char **options; const char *priority; const char *message; - const char *fname; } sieve_notify_context_t; #define SIEVE_HASHLEN 16 @@ -158,7 +156,6 @@ sieve_script_parse */ void sieve_register_size(sieve_interp_t *interp, sieve_get_size *f); void sieve_register_header(sieve_interp_t *interp, sieve_get_header *f); -void sieve_register_fname(sieve_interp_t *interp, sieve_get_fname *f); void sieve_register_envelope(sieve_interp_t *interp, sieve_get_envelope *f); void sieve_register_body(sieve_interp_t *interp, sieve_get_body *f);
View file
cyrus-imapd-2.5.tar.gz/tools/compile_st.pl
Changed
@@ -102,9 +102,6 @@ my $next_unknown = -1; my $next_known = 0; my @entries; -my $nliterals = 0; -my $nenums = 0; -my $type; # # Slurp the .st stringtab file into variables. @@ -136,21 +133,13 @@ elsif ($a0 eq "ent") { my $enum; - my $literal; my $string; my $value; - ($enum) = ($a1 =~ m/^(A-Za-z_A-Za-z_0-9*)$/); + ($enum) = m/^\s*ent\s+(A-Za-z_A-Za-z_0-9*)\s*$/; if (!defined $enum) { - ($literal) = ($a1 =~ m/^(0-9+|0x0-9a-fA-F+)$/); - } - die "Bad syntax for \"ent\" at or near \"$_\"" - unless (defined $enum || defined $literal); - - if (defined $a2) - { - ($string) = m/^\s*ent\s+\S+\s+"(^"+)"\s*$/; + ($enum, $string) = m/^\s*ent\s+(A-Za-z_A-Za-z_0-9*)\s+"(^"+)"\s*$/; die "Bad syntax for \"ent\" at or near \"$_\"" unless defined $string; } @@ -168,14 +157,7 @@ $next_known++; } - push(@entries, { - enum => $enum, - literal => $literal, - value => $value, - string => $string - }); - $nenums++ if defined $enum; - $nliterals++ if defined $literal; + push(@entries, { enum => $enum, value => $value, string => $string }); } else { @@ -190,7 +172,6 @@ unless scalar(@entries) > 0; $unknown = "-1" unless defined $unknown; -$type = ($nenums ? "enum $name" : "int"); # Emit the C header file is requested if ($h_flag) @@ -202,26 +183,19 @@ printf "#include <string.h>\n"; printf "\n"; - if ($nenums) + printf "enum %s {\n", $name; + foreach my $e (@entries) { - printf "enum %s {\n", $name; - foreach my $e (@entries) - { - next if !defined $e->{enum}; - printf " %s", $e->{enum}; - printf "=%d", $e->{value} - if defined $e->{value}; - printf ",\n" - } - printf "};\n"; + printf " %s", $e->{enum}; + printf "=%d", $e->{value} + if defined $e->{value}; + printf ",\n" } + printf "};\n"; - printf "extern %s %s_from_string(const char *s);\n", $type, $name; - printf "extern %s %s_from_string_len(const char *s, size_t len);\n", $type, $name; - if (!$nliterals) - { - printf "extern const char *%s_to_string(%s v);\n", $name, $type; - } + printf "extern enum %s %s_from_string(const char *s);\n", $name, $name; + printf "extern enum %s %s_from_string_len(const char *s, size_t len);\n", $name, $name; + printf "extern const char *%s_to_string(enum %s v);\n", $name, $name; printf "\n"; printf "#endif /* __STRING_TABLE_%s_H_ */\n", $name; @@ -243,50 +217,40 @@ printf $fh "#include \"%s.h\"\n", $name; printf $fh "%%}\n"; - printf $fh "struct %s_desc { const char *name; %s value; };\n", $name, $type; + printf $fh "struct %s_desc { const char *name; enum %s value; };\n", $name, $name; printf $fh "%%%%\n"; foreach my $e (@entries) { next unless defined $e->{string}; - if (defined $e->{enum}) - { - printf $fh "%s, %s\n", $e->{string}, $e->{enum}; - } - elsif (defined $e->{literal}) - { - printf $fh "%s, %s\n", $e->{string}, $e->{literal}; - } + printf $fh "%s, %s\n", $e->{string}, $e->{enum}; } printf $fh "%%%%\n"; - printf $fh "%s %s_from_string(const char *s)\n", $type, $name; + printf $fh "enum %s %s_from_string(const char *s)\n", $name, $name; printf $fh "{\n"; printf $fh " const struct %s_desc *d = __%s_lookup(s, strlen(s));\n", $name, $name; printf $fh " return (d == NULL ? %s : d->value);\n", $unknown; printf $fh "}\n"; printf $fh "\n"; - printf $fh "%s %s_from_string_len(const char *s, size_t len)\n", $type, $name; + printf $fh "enum %s %s_from_string_len(const char *s, size_t len)\n", $name, $name; printf $fh "{\n"; printf $fh " const struct %s_desc *d = __%s_lookup(s, len);\n", $name, $name; printf $fh " return (d == NULL ? %s : d->value);\n", $unknown; printf $fh "}\n"; printf $fh "\n"; - if (!$nliterals) + printf $fh "const char *%s_to_string(enum %s v)\n", $name, $name; + printf $fh "{\n"; + printf $fh " static const char * const strs = {\n"; + foreach my $e (@entries) { - printf $fh "const char *%s_to_string(%s v)\n", $name, $type; - printf $fh "{\n"; - printf $fh " static const char * const strs = {\n"; - foreach my $e (@entries) - { - next unless defined $e->{string}; - printf $fh "\t\"%s\", /* %s */\n", $e->{string}, $e->{enum}; - } - printf $fh " };\n"; - printf $fh " return (v >= 0 && v < (int)(sizeof(strs)/sizeof(strs0)) ? strsv : NULL);\n"; - printf $fh "}\n"; + next unless defined $e->{string}; + printf $fh "\t\"%s\", /* %s */\n", $e->{string}, $e->{enum}; } + printf $fh " };\n"; + printf $fh " return (v >= 0 && v < (int)(sizeof(strs)/sizeof(strs0)) ? strsv : NULL);\n"; + printf $fh "}\n"; close $fh;
View file
cyrus-imapd-2.5.tar.gz/tools/vzic
Deleted
-(directory)
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/ChangeLog
Deleted
@@ -1,57 +0,0 @@ -2006-03-18 Damon Chaplin <damon@gnome.org> - - * Released Vzic 1.3 - -2006-03-18 Damon Chaplin <damon@gnome.org> - - * vzic-output.c (expand_tzname): added special case for America/Nome. - (output_rrule): made hacks a bit more general, to handle Asia/Gaza - which now has a day=4 rule. At some point we should check what newer - versions of Outlook can handle so we can be more accurate. - - * vzic-dump.c (dump_time_zone_names): try looking for timezone info - using original and linked name. - - * README, *.c: fixed spelling 'compatable' -> 'compatible'. - - * vzic.c: patch from Jonathan Guthrie to support a --olson-dir option. - -2003-10-25 Damon Chaplin <damon@gnome.org> - - * Released Vzic 1.2 - -2003-10-25 Damon Chaplin <damon@gnome.org> - - * vzic-output.c: - * Makefile: moved the PRODUCT_ID and TZID_PREFIX settings to the - Makefile and changed the default so people don't accidentally use - the same IDs as Evolution. - - * vzic-parse.c (parse_time): substitute 23:59:59 when we read a time - of 24:00:00. This is a bit of a kludge to avoid problems, since - 24:00:00 is not a valid iCalendar time. Since 24:00:00 is only used - for a few timezones in the 1930s it doesn't matter too much. - - To write a correct fix we'd need to review all the code that deals - with times to see if it would be affected, e.g. a time of 24:00 on - one day should be considered equal to 0:00 the next day. - - We'd also need to adjust the output times to use 0:00 the next day - rather than 24:00. If we need to output recurrence rules that would - be a problem, since 'last saturday at 24:00' can't be easily - converted to another rule that uses 0:00 instead. - -2003-10-22 Damon Chaplin <damon@gnome.org> - - * Released Vzic 1.1 - -2003-10-22 Damon Chaplin <damon@gnome.org> - - * vzic-parse.c (parse_time): allow a time of 24:00, as used in - the America/Montreal and America/Toronto rules in the 1930s! - I'm not 100% sure the rest of the code will handle this OK, but - it only affects the 'pure' output. - -2003-09-01 Damon Chaplin <damon@gnome.org> - - * Released Vzic 1.0
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/Makefile
Deleted
@@ -1,90 +0,0 @@ - -# -# You will need to set this to the directory that the Olson timezone data -# files are in. -# -OLSON_DIR = . - - -# This is used as the PRODID property on the iCalendar files output. -# It identifies the product which created the iCalendar objects. -# So you need to substitute your own organization name and product. -PRODUCT_ID = -//CyrusIMAP.org//Cyrus %s//EN - -# This is what libical-evolution uses. -#PRODUCT_ID = -//Ximian//NONSGML Evolution Olson-VTIMEZONE Converter//EN - - -# This is used to create unique IDs for each VTIMEZONE component. -# The prefix is put before each timezone city name. It should start and end -# with a '/'. The first part, i.e. 'myorganization.org' below, should be -# a unique vendor ID, e.g. use a hostname. The part after that can be -# anything you want. We use a date and version number for libical. The %D -# gets expanded to today's date. There is also a vzic-merge.pl which can be -# used to merge changes into a master set of VTIMEZONEs. If a VTIMEZONE has -# changed, it bumps the version number on the end of this prefix. */ -#TZID_PREFIX = /myorganization.org/%D_1/ -TZID_PREFIX = - -# This is what libical-evolution uses. -#TZID_PREFIX = /softwarestudio.org/Olson_%D_1/ - - -# Set any -I include directories to find the libical header files, and the -# libical library to link with. You only need these if you want to run the -# tests. You may need to change the '#include <ical.h>' line at the top of -# test-vzic.c as well. -LIBICAL_CFLAGS = -LIBICAL_LDADD = -lical - - -# -# You shouldn't need to change the rest of the file. -# - -GLIB_CFLAGS = `pkg-config --cflags glib-2.0` -GLIB_LDADD = `pkg-config --libs glib-2.0` - -CFLAGS = -g -I../.. -DOLSON_DIR=\"$(OLSON_DIR)\" -DPRODUCT_ID='"$(PRODUCT_ID)"' -DTZID_PREFIX='"$(TZID_PREFIX)"' $(GLIB_CFLAGS) $(LIBICAL_CFLAGS) - -OBJECTS = vzic.o vzic-parse.o vzic-dump.o vzic-output.o - -all: vzic - -vzic: $(OBJECTS) - $(CC) $(OBJECTS) $(GLIB_LDADD) -o vzic - -test-vzic: test-vzic.o - $(CC) test-vzic.o $(LIBICAL_LDADD) -o test-vzic - -# Dependencies. -$(OBJECTS): vzic.h -vzic.o vzic-parse.o: vzic-parse.h -vzic.o vzic-dump.o: vzic-dump.h -vzic.o vzic-output.o: vzic-output.h - -test-parse: vzic - ./vzic-dump.pl $(OLSON_DIR) - ./vzic --dump --pure - @echo - @echo "#" - @echo "# If either of these diff commands outputs anything there may be a problem." - @echo "#" - diff -ru zoneinfo/ZonesPerl zoneinfo/ZonesVzic - diff -ru zoneinfo/RulesPerl zoneinfo/RulesVzic - -test-changes: vzic test-vzic - ./test-vzic --dump-changes - ./vzic --dump-changes --pure - @echo - @echo "#" - @echo "# If this diff command outputs anything there may be a problem." - @echo "#" - diff -ru zoneinfo/ChangesVzic test-output - -clean: - -rm -rf vzic $(OBJECTS) *~ ChangesVzic RulesVzic ZonesVzic RulesPerl ZonesPerl test-vzic test-vzic.o - -.PHONY: clean perl-dump test-parse - -
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/README
Deleted
@@ -1,202 +0,0 @@ - - -VZIC README -=========== - -This is 'vzic', a program to convert the Olson timezone database files into -VTIMEZONE files compatible with the iCalendar specification (RFC2445). - -(The name is based on the 'zic' program which converts the Olson files into -time zone information files used by several Unix C libraries, including -glibc. See zic(8) and tzfile(5).) - - - -REQUIREMENTS -============ - -You need the Olson timezone database files, which can be found at: - - ftp://elsie.nci.nih.gov/pub/ - - (Old versions can be found at ftp://munnari.oz.au/pub/oldtz/) - - -Vzic also uses the GLib library (for hash tables, dynamic arrays, and date -calculations). You need version 2.0 or higher. You can get this from: - - http://www.gtk.org - - - -BUILDING -======== - -Edit the Makefile to set the OLSON_DIR, PRODUCT_ID and TZID_PREFIX variables. - -Then run 'make'. - - - -RUNNING -======= - -Run 'vzic'. - -The output is placed in the zoneinfo subdirectory by default, -but you can use the --output-dir options to set another toplevel output -directory. - -By default it outputs VTIMEZONEs that try to be compatible with Outlook -(2000, at least). Outlook can't handle certain iCalendar constructs in -VTIMEZONEs, such as RRULEs using BYMONTHDAY, so it has to adjust the RRULEs -slightly to get Outlook to parse them. Unfortunately this means they are -slightly wrong. If given the --pure option, vzic outputs the exact data, -without worrying about compatability. - -NOTE: We don't convert all the Olson files. We skip 'backward', 'etcetera', -'leapseconds', 'pacificnew', 'solar87', 'solar88' and 'solar89', 'factory' -and 'systemv', since these don't really provide any useful timezones. -See vzic.c. - - - -MERGING CHANGES INTO A MASTER SET OF VTIMEZONES -=============================================== - -The Olson timezone files are updated fairly often, so we need to build new -sets of VTIMEZONE files. Though we have to be careful to ensure that the TZID -of updated timezones is also updated, since it must remain unique. - -We use a version number on the end of the TZID prefix (see the TZIDPrefix -variable in vzic-output.c) to ensure this uniqueness. - -But we don't want to update the version numbers of VTIMEZONEs which have not -changed. So we use the vzic-merge.pl Perl script. This merges in the new set -of VTIMEZONEs with a 'master' set. It compares each new VTIMEZONE file with -the one in the master set (ignoring changes to the TZID). If the new -VTIMEZONE file is different, it copies it to the master set and sets the -version number to the old VTIMEZONE's version number + 1. - -To use vzic-merge.pl you must change the $MASTER_ZONEINFO_DIR and -$NEW_ZONEINFO_DIR variables at the top of the file to point to your 2 sets of -VTIMEZONEs. You then just run the script. (I recommend you keep a backup of -the old master VTIMEZONE files, and use diff to compare the new master set -with the old one, in case anything goes wrong.) - -You must merge in changes to the zones.tab file by hand. - -Note that some timezones are renamed or removed occasionally, so applications -should be able to cope with this. - - - -COMPATABILITY NOTES -=================== - -It seems that Microsoft Outlook is very picky about the iCalendar files it -will accept. (I've been testing with Outlook 2000. I hope the other versions -are no worse.) Here's a few problems we've had with the VTIMEZONEs: - - o Outlook doesn't like any years before 1600. We were using '1st Jan 0001' - in all VTIMEZONEs to specify the first UTC offset known for the timezone. - (The Olson data does not give a start date for this.) - - Now we just skip this first component for most timezones. The UTC offset - can still be found from the TZOFFSETFROM property of the first component. - - Though some timezones only specify one UTC offset that applies forever, - so in these cases we output '1st Jan 1970' (Indian/Cocos, - Pacific/Johnston). - - o Outlook doesn't like the BYMONTHDAY specifier in RRULEs. - - We have changed most of the VTIMEZONEs to use things like 'BYDAY=2SU' - rather than 'BYMONTHDAY=8,9,10,11,12,13,14;BYDAY=SU', though some of - them were impossible to convert correctly so they are not always correct. - - o Outlook doesn't like TZOFFSETFROM/TZOFFSETTO properties which include a - seconds component, e.g. 'TZOFFSETFROM:+110628'. - Quite a lot of the Olson timezones include seconds in their UTC offsets, - though no timezones currently have a UTC offset that uses the seconds - value. - - We've rounded all UTC offsets to the nearest minute. Since all timezone - offsets currently used have '00' as the seconds offset, this doesn't lose - us much. - - o Outlook doesn't like lines being split in certain places, even though - the iCalendar spec says they can be split anywhere. - - o Outlook can only handle one RDATE or a pair of RRULEs. So we had to remove - all historical data. - - -TESTING -======= - -Do a 'make test-vic', then run ./test-vic. - -The test-vzic program compares our libical code and VTIMEZONE data against -the Unix functions like mktime(). It steps over a period of time (1970-2037) -converting from UTC to a given timezone and back again every 15 minutes. -Any differences are output into the test-output directory. - -The output matches for all of the timezones, except in a few places where the -result can't be determined. So I think we can be fairly confident that the -VTIMEZONEs are correct. - -Note that you must use the same Olson data in libical that the OS is using -for mktime() etc. For example, I am using RedHat 9 which uses tzdata2002d, -so I converted this to VTIMEZONE files and installed it into the libical -timezone data directory before testing. (You need to use '--pure' when -creating the VTIMEZONE files as well.) - - -Testing the Parsing Code ------------------------- - -Run 'make test-parse'. - -This runs 'vzic --dump' and 'perl-dump' and compares the output. The diff -commands should not produce any output. - -'vzic --dump' dumps all the parsed data out in the original Olson format, -but without comments. The files are written into the ZonesVzic and RulesVzic -subdirectories of the zoneinfo directory. - -'make perl-dump' runs the vzic-dump.pl perl script which outputs the files -in the same format as 'vzic --dump' in the ZonesPerl and RulesPerl -subdirectories. The perl script doesn't actually parse the fields; it only -strips comments and massages the fields so we have the same output format. - -Currently they both produce exactly the same output so we know the parsing -code is OK. - - -Testing the VTIMEZONE Files ---------------------------- - -Run 'make test-changes'. - -This runs 'vzic --dump-changes' and 'test-vzic --dump-changes' and compares -the output. The diff command should not produce any output. - -Both commands output timezone changes for each zone up to a specific year -(2030) into files for each timezone. It outputs the timezone changes in a -list in this format: - - Timezone Name Date and Time of Change in UTC New Offset from UTC - - America/Dawson 26 Oct 1986 2:00:00 -0800 - -Unfortunately there are some differences here, but they all happen before -1970 so it doesn't matter too much. It looks like the libical code has -problems determining things like 'last Sunday of the month' before 1970. -This is because it uses mktime() etc. which can't really handle dates -before 1970. - - - -Damon Chaplin <damon@gnome.org>, 25 Oct 2003. -
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/test-vzic.c
Deleted
@@ -1,422 +0,0 @@ -/* - * Vzic - a program to convert Olson timezone database files into VZTIMEZONE - * files compatible with the iCalendar specification (RFC2445). - * - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2003 Damon Chaplin. - * - * Author: Damon Chaplin <damon@gnome.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - */ - -/* - * test-vzic.c - test vzic + libical against mktime() and friends. - * - * Note that when we output VCALENDAR data compatible with Outlook the - * results aren't all correct. - * - * We have to modify some RRULEs which makes these timezones incorrect: - * - * Africa/Cairo - * America/Godthab - * America/Santiago - * Antarctica/Palmer - * Asia/Baghdad - * Asia/Damascus - * Asia/Jerusalem - * - * Also, we can only output one RDATE or a pair of RRULEs which may make some - * other timezones incorrect sometimes (e.g. if they change). - */ - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> - -#include <ical.h> -/*#include <evolution/ical.h>*/ - -#define CHANGES_MAX_YEAR 2030 - -/* These are the years between which we test against the Unix timezone - functions, inclusive. When using 'vzic --pure' you can test the full - range from 1970 to 2037 and it should match against mktime() etc. - (assuming you are using the same Olson timezone data for both). - - But when using VTIMEZONE's that are compatible with Outlook, it is only - worth testing times in the future. There will be lots of differences in - the past, since we can't include any historical changes in the files. */ -#if 1 -#define DUMP_START_YEAR 2003 -#define DUMP_END_YEAR 2038 -#else -#define DUMP_START_YEAR 1970 -#define DUMP_END_YEAR 2038 -#endif - -/* The maximum size of any complete pathname. */ -#define PATHNAME_BUFFER_SIZE 1024 - -#ifndef FALSE -#define FALSE (0) -#endif - -#ifndef TRUE -#define TRUE (!FALSE) -#endif - -int VzicDumpChanges = FALSE; - -/* We output beneath the current directory for now. */ -char *directory = "test-output"; - -static void usage (void); -static int parse_zone_name (char *name, - char **directory, - char **subdirectory, - char **filename); -static void ensure_directory_exists (char *directory); -static void dump_local_times (icaltimezone *zone, - FILE *fp); - - -int main(int argc, char* argv) -{ - icalarray *zones; - icaltimezone *zone; - char *zone_directory, *zone_subdirectory, *zone_filename, *location; - char output_directoryPATHNAME_BUFFER_SIZE; - char filenamePATHNAME_BUFFER_SIZE; - FILE *fp; - int i; - int skipping = TRUE; - - /* - * Command-Line Option Parsing. - */ - for (i = 1; i < argc; i++) { - /* --dump-changes: Dumps a list of times when each timezone changed, - and the new local time offset from UTC. */ - if (!strcmp (argvi, "--dump-changes")) - VzicDumpChanges = TRUE; - - else - usage (); - } - - - zones = icaltimezone_get_builtin_timezones (); - - ensure_directory_exists (directory); - - for (i = 0; i < zones->num_elements; i++) { - zone = icalarray_element_at (zones, i); - - location = icaltimezone_get_location (zone); - -#if 0 - /* Use this to start at a certain zone. */ - if (skipping && strcmp (location, "America/Boise")) - continue; -#endif - - skipping = FALSE; - - /* Use this to only output data for certain timezones. */ -#if 0 - if (strcmp (location, "America/Cancun") - && strcmp (location, "Asia/Baku") - && strcmp (location, "Asia/Nicosia") - && strcmp (location, "Asia/Novosibirsk") - && strcmp (location, "Asia/Samarkand") - && strcmp (location, "Asia/Tashkent") - && strcmp (location, "Asia/Tbilisi") - && strcmp (location, "Asia/Yerevan") - && strcmp (location, "Australia/Broken_Hill") - && strcmp (location, "Europe/Simferopol") - && strcmp (location, "Europe/Tallinn") - && strcmp (location, "Europe/Zaporozhye") - ) - continue; -#endif - -#if 0 - printf ("%s\n", location); -#endif - - parse_zone_name (location, &zone_directory, &zone_subdirectory, - &zone_filename); - - sprintf (output_directory, "%s/%s", directory, zone_directory); - ensure_directory_exists (output_directory); - sprintf (filename, "%s/%s", output_directory, zone_filename); - - if (zone_subdirectory) { - sprintf (output_directory, "%s/%s/%s", directory, zone_directory, - zone_subdirectory); - ensure_directory_exists (output_directory); - sprintf (filename, "%s/%s", output_directory, zone_filename); - } - - fp = fopen (filename, "w"); - if (!fp) { - fprintf (stderr, "Couldn't create file: %s\n", filename); - exit (1); - } - - /* We can run 2 different tests - output all changes for each zone, or - test against mktime()/localtime(). Should have a command-line option - or something. */ - if (VzicDumpChanges) - icaltimezone_dump_changes (zone, CHANGES_MAX_YEAR, fp); - else - dump_local_times (zone, fp); - - if (ferror (fp)) { - fprintf (stderr, "Error writing file: %s\n", filename); - exit (1); - } - - fclose (fp); - } - - return 0; -} - - -static void -usage (void) -{ - fprintf (stderr, "Usage: test-vzic --dump-changes\n"); - - exit (1); -} - - -/* This checks that the Zone name only uses the characters in -+_/a-zA-Z0-9, - and outputs a warning if it isn't. */ -static int -parse_zone_name (char *name, - char **directory, - char **subdirectory, - char **filename) -{ - static int invalid_zone_num = 1; - - char *p, ch, *first_slash_pos = NULL, *second_slash_pos = NULL; - int invalid = FALSE; - - for (p = name; (ch = *p) != 0; p++) { - if ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') - && (ch < '0' || ch > '9') && ch != '/' && ch != '_' - && ch != '-' && ch != '+') { - fprintf (stderr, "Warning: Unusual Zone name: %s\n", name); - invalid = TRUE; - break; - } - - if (ch == '/') { - if (!first_slash_pos) { - first_slash_pos = p; - } else if (!second_slash_pos) { - second_slash_pos = p; - } else { - fprintf (stderr, "Warning: More than 2 '/' characters in Zone name: %s\n", name); - invalid = TRUE; - break; - } - } - } - - if (!first_slash_pos) { - fprintf (stderr, "No '/' character in Zone name: %s. Skipping.\n", name); - return FALSE; - } - - if (invalid) { - fprintf (stderr, "Invalid zone name: %s\n", name); - exit (0); - } else { - *first_slash_pos = '\0'; - *directory = icalmemory_strdup (name); - *first_slash_pos = '/'; - - if (second_slash_pos) { - *second_slash_pos = '\0'; - *subdirectory = icalmemory_strdup (first_slash_pos + 1); - *second_slash_pos = '/'; - - *filename = icalmemory_strdup (second_slash_pos + 1); - } else { - *subdirectory = NULL; - *filename = icalmemory_strdup (first_slash_pos + 1); - } - } -} - - -static void -ensure_directory_exists (char *directory) -{ - struct stat filestat; - - if (stat (directory, &filestat) != 0) { - /* If the directory doesn't exist, try to create it. */ - if (errno == ENOENT) { - if (mkdir (directory, 0777) != 0) { - fprintf (stderr, "Can't create directory: %s\n", directory); - exit (1); - } - } else { - fprintf (stderr, "Error calling stat() on directory: %s\n", directory); - exit (1); - } - } else if (!S_ISDIR (filestat.st_mode)) { - fprintf (stderr, "Can't create directory, already exists: %s\n", - directory); - exit (1); - } -} - - -static void -dump_local_times (icaltimezone *zone, FILE *fp) -{ - static char *months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - icaltimezone *utc_timezone; - struct icaltimetype tt, tt_copy; - struct tm tm, local_tm; - time_t t; - char tzstring256, *location; - int last_year_output = 0; - int total_error = 0, total_error2 = 0; - - utc_timezone = icaltimezone_get_utc_timezone (); - - /* This is our UTC time that we will use to iterate over the period. */ - tt.year = DUMP_START_YEAR; - tt.month = 1; - tt.day = 1; - tt.hour = 0; - tt.minute = 0; - tt.second = 0; - tt.is_utc = 0; - tt.is_date = 0; - tt.zone = ""; - - tm.tm_year = tt.year - 1900; - tm.tm_mon = tt.month - 1; - tm.tm_mday = tt.day; - tm.tm_hour = tt.hour; - tm.tm_min = tt.minute; - tm.tm_sec = tt.second; - tm.tm_isdst = -1; - - /* Convert it to a time_t by saying it is in UTC. */ - putenv ("TZ=UTC"); - t = mktime (&tm); - - location = icaltimezone_get_location (zone); - sprintf (tzstring, "TZ=%s", location); - - /*printf ("Zone: %s\n", location);*/ - putenv (tzstring); - - /* Loop around converting the UTC time to local time, outputting it, and - then adding on 15 minutes to the UTC time. */ - while (tt.year <= DUMP_END_YEAR) { - if (tt.year > last_year_output) { - last_year_output = tt.year; -#if 0 - printf (" %i\n", last_year_output); - fprintf (fp, " %i\n", last_year_output); -#endif - } - -#if 1 - /* First use the Unix functions. */ - /* Now convert it to a local time in the given timezone. */ - local_tm = *localtime (&t); -#endif - -#if 1 - /* Now use libical. */ - tt_copy = tt; - icaltimezone_convert_time (&tt_copy, utc_timezone, zone); -#endif - -#if 1 - if (local_tm.tm_year + 1900 != tt_copy.year - || local_tm.tm_mon + 1 != tt_copy.month - || local_tm.tm_mday != tt_copy.day - || local_tm.tm_hour != tt_copy.hour - || local_tm.tm_min != tt_copy.minute - || local_tm.tm_sec != tt_copy.second) { - - /* The error format is: - - ERROR: Original-UTC-Time Local-Time-From-mktime Local-Time-From-Libical - - */ - - total_error++; - - fprintf (fp, "ERROR:%2i %s %04i %2i:%02i:%02i UTC", - tt.day, monthstt.month - 1, tt.year, - tt.hour, tt.minute, tt.second); - fprintf (fp, " ->%2i %s %04i %2i:%02i:%02i", - local_tm.tm_mday, monthslocal_tm.tm_mon, - local_tm.tm_year + 1900, - local_tm.tm_hour, local_tm.tm_min, local_tm.tm_sec); - fprintf (fp, " Us:%2i %s %04i %2i:%02i:%02i\n", - tt_copy.day, monthstt_copy.month - 1, tt_copy.year, - tt_copy.hour, tt_copy.minute, tt_copy.second); - } -#endif - - /* Now convert it back, and check we get the original time. */ - icaltimezone_convert_time (&tt_copy, zone, utc_timezone); - if (tt.year != tt_copy.year - || tt.month != tt_copy.month - || tt.day != tt_copy.day - || tt.hour != tt_copy.hour - || tt.minute != tt_copy.minute - || tt.second != tt_copy.second) { - - total_error2++; - - fprintf (fp, "ERROR 2: %2i %s %04i %2i:%02i:%02i UTC", - tt.day, monthstt.month - 1, tt.year, - tt.hour, tt.minute, tt.second); - fprintf (fp, " Us:%2i %s %04i %2i:%02i:%02i UTC\n", - tt_copy.day, monthstt_copy.month - 1, tt_copy.year, - tt_copy.hour, tt_copy.minute, tt_copy.second); - } - - - /* Increment the time. */ - icaltime_adjust (&tt, 0, 0, 15, 0); - - /* We assume leap seconds are not included in time_t values, which should - be true on POSIX systems. */ - t += 15 * 60; - } - - printf ("Zone: %40s Errors: %i (%i)\n", icaltimezone_get_location (zone), - total_error, total_error2); -}
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-dump.c
Deleted
@@ -1,409 +0,0 @@ -/* - * Vzic - a program to convert Olson timezone database files into VZTIMEZONE - * files compatible with the iCalendar specification (RFC2445). - * - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2003 Damon Chaplin. - * - * Author: Damon Chaplin <damon@gnome.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - */ - -/* - * These functions are for dumping all the parsed Zones and Rules to - * files, to be compared with the output of vzic-dump.pl to check our parsing - * code is OK. Some of the functions are also used for producing debugging - * output. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "vzic.h" -#include "vzic-dump.h" - - -static void dump_add_rule (char *name, - GArray *rule_array, - GPtrArray *name_array); -static int dump_compare_strings (const void *arg1, - const void *arg2); - - -void -dump_zone_data (GArray *zone_data, - char *filename) -{ - static char *months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - FILE *fp; - ZoneData *zone; - ZoneLineData *zone_line; - int i, j; - gboolean output_month, output_day, output_time; - - fp = fopen (filename, "w"); - if (!fp) { - fprintf (stderr, "Couldn't create file: %s\n", filename); - exit (1); - } - - for (i = 0; i < zone_data->len; i++) { - zone = &g_array_index (zone_data, ZoneData, i); - - fprintf (fp, "Zone\t%s\t", zone->zone_name); - - for (j = 0; j < zone->zone_line_data->len; j++) { - zone_line = &g_array_index (zone->zone_line_data, ZoneLineData, j); - - if (j != 0) - fprintf (fp, "\t\t\t"); - - fprintf (fp, "%s\t", dump_time (zone_line->stdoff_seconds, TIME_WALL, - FALSE)); - - if (zone_line->rules) - fprintf (fp, "%s\t", zone_line->rules); - else if (zone_line->save_seconds != 0) - fprintf (fp, "%s\t", dump_time (zone_line->save_seconds, TIME_WALL, - FALSE)); - else - fprintf (fp, "-\t"); - - fprintf (fp, "%s\t", zone_line->format ? zone_line->format : "-"); - - if (zone_line->until_set) { - fprintf (fp, "%s\t", dump_year (zone_line->until_year)); - - output_month = output_day = output_time = FALSE; - - if (zone_line->until_time_code != TIME_WALL - || zone_line->until_time_seconds != 0) - output_month = output_day = output_time = TRUE; - else if (zone_line->until_day_code != DAY_SIMPLE - || zone_line->until_day_number != 1) - output_month = output_day = TRUE; - else if (zone_line->until_month != 0) - output_month = TRUE; - - if (output_month) - fprintf (fp, "%s", monthszone_line->until_month); - - fprintf (fp, "\t"); - - if (output_day) - fprintf (fp, "%s", dump_day_coded (zone_line->until_day_code, - zone_line->until_day_number, - zone_line->until_day_weekday)); - - fprintf (fp, "\t"); - - if (output_time) - fprintf (fp, "%s", dump_time (zone_line->until_time_seconds, - zone_line->until_time_code, FALSE)); - - } else { - fprintf (fp, "\t\t\t"); - } - - fprintf (fp, "\n"); - } - } - - fclose (fp); -} - - -void -dump_rule_data (GHashTable *rule_data, - char *filename) -{ - FILE *fp; - GPtrArray *name_array; - GArray *rule_array; - int i; - char *name; - - fp = fopen (filename, "w"); - if (!fp) { - fprintf (stderr, "Couldn't create file: %s\n", filename); - exit (1); - } - - /* We need to sort the rules by their names, so they are in the same order - as the Perl output. So we place all the names in a temporary GPtrArray, - sort it, then output them. */ - name_array = g_ptr_array_new (); - g_hash_table_foreach (rule_data, (GHFunc) dump_add_rule, name_array); - qsort (name_array->pdata, name_array->len, sizeof (char*), - dump_compare_strings); - - for (i = 0; i < name_array->len; i++) { - name = g_ptr_array_index (name_array, i); - rule_array = g_hash_table_lookup (rule_data, name); - if (!rule_array) { - fprintf (stderr, "Couldn't access rules: %s\n", name); - exit (1); - } - dump_rule_array (name, rule_array, fp); - } - - g_ptr_array_free (name_array, TRUE); - - fclose (fp); -} - - -static void -dump_add_rule (char *name, - GArray *rule_array, - GPtrArray *name_array) -{ - g_ptr_array_add (name_array, name); -} - - -static int -dump_compare_strings (const void *arg1, - const void *arg2) -{ - char **a, **b; - - a = (char**) arg1; - b = (char**) arg2; - - return strcmp (*a, *b); -} - - -void -dump_rule_array (char *name, - GArray *rule_array, - FILE *fp) -{ - static char *months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - - RuleData *rule; - int i; - -#if 0 - fprintf (fp, "\n# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S"); -#endif - - for (i = 0; i < rule_array->len; i++) { - rule = &g_array_index (rule_array, RuleData, i); - - fprintf (fp, "Rule\t%s\t%s\t", name, dump_year (rule->from_year)); - - if (rule->to_year == rule->from_year) - fprintf (fp, "only\t"); - else - fprintf (fp, "%s\t", dump_year (rule->to_year)); - - fprintf (fp, "%s\t", rule->type ? rule->type : "-"); - - fprintf (fp, "%s\t", monthsrule->in_month); - - fprintf (fp, "%s\t", - dump_day_coded (rule->on_day_code, rule->on_day_number, - rule->on_day_weekday)); - - fprintf (fp, "%s\t", dump_time (rule->at_time_seconds, rule->at_time_code, - FALSE)); - - fprintf (fp, "%s\t", dump_time (rule->save_seconds, TIME_WALL, TRUE)); - - fprintf (fp, "%s", rule->letter_s ? rule->letter_s : "-"); - - fprintf (fp, "\n"); - } -} - - -char* -dump_time (int seconds, - TimeCode time_code, - gboolean use_zero) -{ - static char buffer256, *sign; - int hours, minutes; - char *code; - - if (time_code == TIME_STANDARD) - code = "s"; - else if (time_code == TIME_UNIVERSAL) - code = "u"; - else - code = ""; - - if (seconds < 0) { - seconds = -seconds; - sign = "-"; - } else { - sign = ""; - } - - hours = seconds / 3600; - minutes = (seconds % 3600) / 60; - seconds = seconds % 60; - - if (use_zero && hours == 0 && minutes == 0 && seconds == 0) - return "0"; - else if (seconds == 0) - sprintf (buffer, "%s%i:%02i%s", sign, hours, minutes, code); - else - sprintf (buffer, "%s%i:%02i:%02i%s", sign, hours, minutes, seconds, code); - - return buffer; -} - - -char* -dump_day_coded (DayCode day_code, - int day_number, - int day_weekday) -{ - static char buffer256; - static char *weekdays = { "Sun", "Mon", "Tue", "Wed", - "Thu", "Fri", "Sat" }; - - switch (day_code) { - case DAY_SIMPLE: - sprintf (buffer, "%i", day_number); - break; - case DAY_WEEKDAY_ON_OR_AFTER: - sprintf (buffer, "%s>=%i", weekdaysday_weekday, day_number); - break; - case DAY_WEEKDAY_ON_OR_BEFORE: - sprintf (buffer, "%s<=%i", weekdaysday_weekday, day_number); - break; - case DAY_LAST_WEEKDAY: - sprintf (buffer, "last%s", weekdaysday_weekday); - break; - default: - fprintf (stderr, "Invalid day code: %i\n", day_code); - exit (1); - } - - return buffer; -} - - -char* -dump_year (int year) -{ - static char buffer256; - - if (year == YEAR_MINIMUM) - return "min"; - if (year == YEAR_MAXIMUM) - return "max"; - - sprintf (buffer, "%i", year); - return buffer; -} - - -void -dump_time_zone_names (GList *names, - char *output_dir, - GHashTable *zones_hash) -{ - char filenamePATHNAME_BUFFER_SIZE, *zone_name, *zone_name_in_hash = NULL; - char strings_filenamePATHNAME_BUFFER_SIZE; - FILE *fp, *strings_fp = NULL; - GList *elem; - ZoneDescription *zone_desc; - - sprintf (filename, "%s/zones.tab", output_dir); - sprintf (strings_filename, "%s/zones.h", output_dir); - - fp = fopen (filename, "w"); - if (!fp) { - fprintf (stderr, "Couldn't create file: %s\n", filename); - exit (1); - } - - if (VzicDumpZoneTranslatableStrings) { - strings_fp = fopen (strings_filename, "w"); - if (!strings_fp) { - fprintf (stderr, "Couldn't create file: %s\n", strings_filename); - exit (1); - } - } - - names = g_list_sort (names, (GCompareFunc) strcmp); - - elem = names; - while (elem) { - zone_name = (char*) elem->data; - - zone_desc = g_hash_table_lookup (zones_hash, zone_name); - - /* SPECIAL CASES: These timezones are links from other zones and are - almost exactly the same - they are basically there so users can find - them a bit easier. But they don't have entries in the zone.tab file, - so we use the entry from the timezone linked from. */ - if (!zone_desc) { - if (!strcmp (zone_name, "America/Indiana/Indianapolis")) - zone_name_in_hash = "America/Indianapolis"; - else if (!strcmp (zone_name, "America/Kentucky/Louisville")) - zone_name_in_hash = "America/Louisville"; - else if (!strcmp (zone_name, "Asia/Istanbul")) - zone_name_in_hash = "Europe/Istanbul"; - else if (!strcmp (zone_name, "Europe/Nicosia")) - zone_name_in_hash = "Asia/Nicosia"; - - if (zone_name_in_hash) - zone_desc = g_hash_table_lookup (zones_hash, zone_name_in_hash); - } - - if (zone_desc) { - fprintf (fp, "%+04i%02i%02i %+04i%02i%02i %s\n", - zone_desc->latitude0, zone_desc->latitude1, - zone_desc->latitude2, - zone_desc->longitude0, zone_desc->longitude1, - zone_desc->longitude2, - zone_name); - } else { - g_print ("Zone description not found for: %s\n", zone_name); - fprintf (fp, "%s\n", zone_name); - } - - - if (VzicDumpZoneTranslatableStrings) { -#if 0 - char zone_name_buffer1024, *src, *dest; - - for (src = zone_name, dest = zone_name_buffer; *src; src++, dest++) - *dest = (*src == '_') ? ' ' : *src; - *dest = '\0'; -#endif - - fprintf (strings_fp, "N_(\"%s\");\n", zone_name); - } - - elem = elem->next; - } - - fclose (fp); - - if (VzicDumpZoneTranslatableStrings) - fclose (strings_fp); -} -
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-dump.h
Deleted
@@ -1,58 +0,0 @@ -/* - * Vzic - a program to convert Olson timezone database files into VZTIMEZONE - * files compatible with the iCalendar specification (RFC2445). - * - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2003 Damon Chaplin. - * - * Author: Damon Chaplin <damon@gnome.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - */ - -/* - * These functions are for dumping all the parsed Zones and Rules to - * files, to be compared with the output of vzic-dump.pl to check our parsing - * code is OK. Some of the functions are also used for producing debugging - * output. - */ - -#ifndef _VZIC_DUMP_H_ -#define _VZIC_DUMP_H_ - -#include <glib.h> - -void dump_zone_data (GArray *zone_data, - char *filename); -void dump_rule_data (GHashTable *rule_data, - char *filename); - -void dump_rule_array (char *name, - GArray *rule_array, - FILE *fp); - -char* dump_year (int year); -char* dump_day_coded (DayCode day_code, - int day_number, - int day_weekday); -char* dump_time (int seconds, - TimeCode time_code, - gboolean use_zero); - -void dump_time_zone_names (GList *names, - char *output_dir, - GHashTable *zones_hash); - -#endif /* _VZIC_DUMP_H_ */
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-dump.pl
Deleted
@@ -1,222 +0,0 @@ -#!/usr/bin/perl -w - -# -# Vzic - a program to convert Olson timezone database files into VZTIMEZONE -# files compatible with the iCalendar specification (RFC2445). -# -# Copyright (C) 2000-2001 Ximian, Inc. -# Copyright (C) 2003 Damon Chaplin. -# -# Author: Damon Chaplin <damon@gnome.org> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. -# - -# -# This reads the Olson timezone files, strips any comments, and outputs them -# in a very simple format with tab-separated fields. It is used to compare -# with the output of dump_zone_data() and dump_rule_data() to double-check -# that we have parsed the files correctly. -# - -my $zones_fh = "zonesfile"; -my $rules_fh = "rulesfile"; - -my %Rules; - -if ($#ARGV != 0) { - die "Usage: $0 <OlsonDirectory>"; -} - -my $OLSON_DIR = $ARGV0; - -# We place output in subdirectories of the current directory. -my $OUTPUT_DIR = "zoneinfo"; - -if (! -d "$OUTPUT_DIR") { - mkdir ("$OUTPUT_DIR", 0777) - || die "Can't create directory: $OUTPUT_DIR"; -} -if (! -d "$OUTPUT_DIR/ZonesPerl") { - mkdir ("$OUTPUT_DIR/ZonesPerl", 0777) - || die "Can't create directory: $OUTPUT_DIR/ZonesPerl"; -} -if (! -d "$OUTPUT_DIR/RulesPerl") { - mkdir ("$OUTPUT_DIR/RulesPerl", 0777) - || die "Can't create directory: $OUTPUT_DIR/RulesPerl"; -} - - -&ReadOlsonFile ("africa"); -&ReadOlsonFile ("antarctica"); -&ReadOlsonFile ("asia"); -&ReadOlsonFile ("australasia"); -&ReadOlsonFile ("europe"); -&ReadOlsonFile ("northamerica"); -&ReadOlsonFile ("southamerica"); - -# These are backwards-compatability and weird stuff. -#&ReadOlsonFile ("backward"); -#&ReadOlsonFile ("etcetera"); -#&ReadOlsonFile ("leapseconds"); -#&ReadOlsonFile ("pacificnew"); -#&ReadOlsonFile ("solar87"); -#&ReadOlsonFile ("solar88"); -#&ReadOlsonFile ("solar89"); - -# We don't do this one since it is not useful and the use of '"' in the Zone -# line messes up our split() command. -#&ReadOlsonFile ("factory"); - -# We don't do this since the vzic program can't do it. -#&ReadOlsonFile ("systemv"); - - - - -1; - - -sub ReadOlsonFile { - my ($file) = @_; - -# print ("Reading olson file: $file\n"); - - open (OLSONFILE, "$OLSON_DIR/$file") - || die "Can't open file: $file"; - - open ($zones_fh, ">$OUTPUT_DIR/ZonesPerl/$file") - || die "Can't open file: $OUTPUT_DIR/ZonesPerl/$file"; - - open ($rules_fh, ">$OUTPUT_DIR/RulesPerl/$file") - || die "Can't open file: $OUTPUT_DIR/RulesPerl/$file"; - - %Rules = (); - - my $zone_continues = 0; - - while (<OLSONFILE>) { - next if (m/^#/); - - # '#' characters can appear in strings, but the Olson files don't use - # that feature at present so we treat all '#' as comments for now. - s/#.*//; - - next if (m/^\s*$/); - - if ($zone_continues) { - $zone_continues = &ReadZoneContinuationLine; - - } elsif (m/^Rule\s/) { - &ReadRuleLine; - - } elsif (m/^Zone\s/) { - $zone_continues = &ReadZoneLine; - - } elsif (m/^Link\s/) { -# print "Link: $link_from, $link_to\n"; - - } elsif (m/^Leap\s/) { -# print "Leap\n"; - - } else { - die "Invalid line: $_"; - } - } - -# print ("Read olson file: $file\n"); - - foreach $key (sort (keys (%Rules))) { - print $rules_fh "$Rules{$key}" - } - - close ($zones_fh); - close ($rules_fh); - close (OLSONFILE); -} - - -sub ReadZoneLine { - my ($zone, $name, $gmtoff, $rules_save, $format, - $until_year, $until_month, $until_day, $until_time, $remainder) - = split ' ', $_, 10; - - return &ReadZoneLineCommon ($zone, $name, $gmtoff, $rules_save, $format, - $until_year, $until_month, $until_day, - $until_time); -} - - -sub ReadZoneContinuationLine { - my ($gmtoff, $rules_save, $format, - $until_year, $until_month, $until_day, $until_time, $remainder) - = split ' ', $_, 8; - - return &ReadZoneLineCommon ("", "", $gmtoff, $rules_save, $format, - $until_year, $until_month, $until_day, - $until_time); -} - - -sub ReadZoneLineCommon { - my ($zone, $name, $gmtoff, $rules_save, $format, - $until_year, $until_month, $until_day, $until_time) = @_; - - if (!defined ($until_year)) { $until_year = ""; } - if (!defined ($until_month)) { $until_month = ""; } - if (!defined ($until_day)) { $until_day = ""; } - if (!defined ($until_time)) { $until_time = ""; } - - # A few of the gmtoffsets have an unnecessary :00 seconds. - $gmtoff =~ s/(\d+):(\d+):00/$1:$2/; - - # Make sure the gmtoff does have minutes. - $gmtoff =~ s/^(-?\d+)$/$1:00/; - - # Fix a few other bits so they all use the same format. - if ($gmtoff eq "0") { $gmtoff = "0:00"; } - $until_time =~ s/^0(\d):/$1:/; - if ($until_time eq "0:00") { $until_time = ""; } - if ($until_day eq "1" && $until_time eq "") { $until_day = ""; } - if ($until_month eq "Jan" && $until_day eq "" && $until_time eq "") { - $until_month = ""; - } - - # For Zone continuation lines we need to insert an extra TAB. - if (!$zone) { $zone = "\t" }; - - print $zones_fh "$zone\t$name\t$gmtoff\t$rules_save\t$format\t$until_year\t$until_month\t$until_day\t$until_time\n"; - - if (defined ($until_year) && $until_year) { - return 1; - } else { - return 0; - } -} - - -sub ReadRuleLine { - my ($rule, $name, $from, $to, $type, $in, $on, $at, $save, $letter_s, - $remainder) = split; - - $at =~ s/(\d+:\d+):00/$1/; - $save =~ s/(\d+:\d+):00/$1/; - if ($save eq "0:00") { $save = "0"; } - - $Rules{$name} .= "$rule\t$name\t$from\t$to\t$type\t$in\t$on\t$at\t$save\t$letter_s\n"; - -# print $rules_fh "$rule\t$name\t$from\t$to\t$type\t$in\t$on\t$at\t$save\t$letter_s\n"; -} -
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-merge.pl
Deleted
@@ -1,177 +0,0 @@ -#!/usr/bin/perl -w - -# -# Vzic - a program to convert Olson timezone database files into VZTIMEZONE -# files compatible with the iCalendar specification (RFC2445). -# -# Copyright (C) 2001 Ximian, Inc. -# Copyright (C) 2003 Damon Chaplin. -# -# Author: Damon Chaplin <damon@gnome.org> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. -# - -# -# This merges in a new set of VTIMEZONE files with the 'master' set. It only -# updates the files in the master set if the VTIMEZONE component has really -# been changes. Note that the TZID normally includes the date the VTIMEZONE -# file was generated on, so we have to ignore this when comparing the files. -# - -# Set these to the toplevel directories of the 2 sets of VTIMEZONE files. -$NEW_ZONEINFO_DIR = "zoneinfo"; - -# Set this to 1 if you have version numbers in the TZID like libical. -$LIBICAL_VERSIONING = 0; - -# Set this to 0 for dry-runs, and 1 to actually update. -$DO_UPDATES = 1; - -# Save this so we can restore it later. -$input_record_separator = $/; - -sub read_conf { - my $file = shift; - - open CONF, $file or die "can't open $file"; - while (<CONF>) { - if (/^#/) { - next; - } - if (/\@include:\s+(.*)$/) { - push @configs, $1; - } - if (/^configdirectory:\s+(.*)$/) { - $confdir = $1; - } - } - close CONF; -} - -$imapdconf = shift || "/etc/imapd.conf"; - -push @configs, $imapdconf; - -while ($conf = shift @configs) { - read_conf($conf); -} - -if (! $confdir) { $confdir = "/var/imap"; } - -$MASTER_ZONEINFO_DIR = $confdir . "/zoneinfo"; - - -chdir $NEW_ZONEINFO_DIR - || die "Can't cd to $NEW_ZONEINFO_DIR"; - -foreach $new_file (`find -name "*.ics"`) { - # Get rid of './' at start and whitespace at end. - $new_file =~ s/^\.\///; - $new_file =~ s/\s+$//; - -# print "File: $new_file\n"; - - open (NEWZONEFILE, "$new_file") - || die "Can't open file: $NEW_ZONEINFO_DIR/$new_file"; - undef $/; - $new_contents = <NEWZONEFILE>; - $/ = $input_record_separator; - close (NEWZONEFILE); - - $master_file = $MASTER_ZONEINFO_DIR . "/$new_file"; - -# print "Master File: $master_file\n"; - - $copy_to_master = 0; - - # If the ics file exists in the master copy we have to compare them, - # otherwise we can just copy the new file into the master directory. - if (-l $new_file) { - $link_to = readlink($new_file); - - if (! -e $master_file || ! -l $master_file || - (readlink($master_file) ne $link_to)) { - - print "Linking $new_file to $link_to...\n"; - - if ($DO_UPDATES) { - unlink($master_file); - symlink($link_to, $master_file); - } - } - } elsif (-e $master_file) { - open (MASTERZONEFILE, "$master_file") - || die "Can't open file: $master_file"; - undef $/; - $master_contents = <MASTERZONEFILE>; - $/ = $input_record_separator; - close (MASTERZONEFILE); - - $new_contents_copy = $new_contents; - - # Strip the TZID from both contents. -# $new_contents_copy =~ s/^TZID:\S+\r$//m; -# $new_tzid = $&; -# $master_contents =~ s/^TZID:\S+\r$//m; -# $master_tzid = $&; - - # Strip the PRODID from both contents. - $new_contents_copy =~ s/^PRODID:.*$//m; - $master_contents =~ s/^PRODID:.*$//m; - - # Strip the LAST-MODIFIED from both contents. - $new_contents_copy =~ s/^LAST-MODIFIED:(\S+)\r$//m; - $master_contents =~ s/^LAST-MODIFIED:(\S+)\r$//m; - -# print "Matched: $master_tzid\n"; - - - if ($new_contents_copy ne $master_contents) { - print "$new_file has changed. Updating...\n"; - $copy_to_master = 1; - - if ($LIBICAL_VERSIONING) { - # We bump the version number in the new file. -# $master_tzid =~ m%_(\d+)/%; - $version_num = $1; -# print "Version: $version_num\n"; - - $version_num++; - $new_tzid =~ s%_(\d+)/%_$version_num/%; - -# print "New TZID: $new_tzid\n"; - $new_contents =~ s/^TZID:\S+$/$new_tzid/m; - } - } - - } else { - print "$new_file doesn't exist in master directory. Copying...\n"; - $copy_to_master = 1; - } - - if ($copy_to_master) { -# print "Updating: $new_file\n"; - - if ($DO_UPDATES) { - open (MASTERZONEFILE, ">$master_file") - || die "Can't create file: $master_file"; - print MASTERZONEFILE $new_contents; - close (MASTERZONEFILE); - } - } - -} -
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-output.c
Deleted
@@ -1,2367 +0,0 @@ -/* - * Vzic - a program to convert Olson timezone database files into VZTIMEZONE - * files compatible with the iCalendar specification (RFC2445). - * - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2003 Damon Chaplin. - * - * Author: Damon Chaplin <damon@gnome.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - */ - -/* ALGORITHM: - * - * First we expand all the Rule arrays, so that each element only represents 1 - * year. If a Rule extends to infinity we expand it up to a few years past the - * maximum UNTIL year used in any of the timezones. We do this to make sure - * that the last of the expanded Rules (which may be infinite) is only used - * in the last of the time periods (i.e. the last Zone line). - * - * The Rule arrays are also sorted by the start time (FROM + IN + ON + AT). - * Doing all this makes it much easier to find which rules apply to which - * periods. - * - * For each timezone (i.e. ZoneData element), we step through each of the - * time periods, the ZoneLineData elements (which represent each Zone line - * from the Olson file.) - * - * We calculate the start & end time of the period. - * - For the first line the start time is -infinity. - * - For the last line the end time is +infinity. - * - The end time of each line is also the start time of the next. - * - * We create an array of time changes which occur in this period, including - * the one implied by the Zone line itself (though this is later taken out - * if it is found to be at exactly the same time as the first Rule). - * - * Now we iterate over the time changes, outputting them as STANDARD or - * DAYLIGHT components. We also try to merge them together into RRULEs or - * use RDATEs. - */ - - -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <sys/stat.h> -#include <unistd.h> - -#include "vzic.h" -#include "vzic-output.h" - -#include "vzic-dump.h" - -#include "xversion.h" - - -/* These come from the Makefile. See the comments there. */ -char *ProductID = PRODUCT_ID; -char *TZIDPrefix = TZID_PREFIX; - -/* We expand the TZIDPrefix, replacing %D with the date, in here. */ -char TZIDPrefixExpanded1024; - - -/* We only use RRULEs if there are at least MIN_RRULE_OCCURRENCES occurrences, - since otherwise RDATEs are more efficient. Actually, I've set this high - so we only use RRULEs for infinite recurrences. Since expanding RRULEs is - very time-consuming, this seems sensible. */ -#define MIN_RRULE_OCCURRENCES 1 - - -/* The year we go up to when dumping the list of timezone changes (used - for testing & debugging). */ -#define MAX_CHANGES_YEAR 2030 - -/* This is the maximum year that time_t value can typically hold on 32-bit - systems. */ -#define MAX_TIME_T_YEAR 2038 - - -/* The year we use to start RRULEs. */ -#define RRULE_START_YEAR 1970 - -/* The year we use for RDATEs. */ -#define RDATE_YEAR 1970 - - -static char *WeekDays = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" }; -static int DaysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - -char *CurrentZoneName; - - -typedef struct _VzicTime VzicTime; -struct _VzicTime -{ - /* Normal years, e.g. 2001. */ - int year; - - /* 0 (Jan) to 11 (Dec). */ - int month; - - /* The day, either a simple month day number, 1-31, or a rule such as - the last Sunday, or the first Monday on or after the 8th. */ - DayCode day_code; - int day_number; /* 1 to 31. */ - int day_weekday; /* 0 (Sun) to 6 (Sat). */ - - /* The time, in seconds from midnight. The code specifies whether the - time is a wall clock time, local standard time, or universal time. */ - int time_seconds; - TimeCode time_code; - - /* The offset from UTC for local standard time. */ - int stdoff; - - /* The offset from UTC for local wall clock time. If this is different to - stdoff then this is a DAYLIGHT component. This is TZOFFSETTO. */ - int walloff; - - /* TRUE if the time change recurs every year to infinity. */ - gboolean is_infinite; - - /* TRUE if the change has already been output. */ - gboolean output; - - /* These are the offsets of the previous VzicTime, and are used when - calculating the time of the change. We place them here in - output_zone_components() to simplify the output code. */ - int prev_stdoff; - int prev_walloff; - - /* The abbreviated form of the timezone name. Note that this may not be - unique. */ - char *tzname; -}; - - -static void expand_and_sort_rule_array (gpointer key, - gpointer value, - gpointer data); -static int rule_sort_func (const void *arg1, - const void *arg2); -static void output_zone (char *directory, - ZoneData *zone, - char *zone_name, - GHashTable *rule_data); -static gboolean parse_zone_name (char *name, - char **directory, - char **subdirectory, - char **filename); -static void output_zone_to_files (ZoneData *zone, - char *zone_name, - GHashTable *rule_data, - FILE *fp, - FILE *changes_fp); -static gboolean add_rule_changes (ZoneLineData *zone_line, - char *zone_name, - GArray *changes, - GHashTable *rule_data, - VzicTime *start, - VzicTime *end, - char **start_letter_s, - int *save_seconds); -static char* expand_tzname (char *zone_name, - char *format, - gboolean have_letter_s, - char *letter_s, - gboolean is_daylight); -static int compare_times (VzicTime *time1, - int stdoff1, - int walloff1, - VzicTime *time2, - int stdoff2, - int walloff2); -static gboolean times_match (VzicTime *time1, - int stdoff1, - int walloff1, - VzicTime *time2, - int stdoff2, - int walloff2); -static void output_zone_components (FILE *fp, - char *name, - GArray *changes); -static void set_previous_offsets (GArray *changes); -static gboolean check_for_recurrence (FILE *fp, - GArray *changes, - int idx); -static void check_for_rdates (FILE *fp, - GArray *changes, - int idx); -static gboolean timezones_match (char *tzname1, - char *tzname2); -static int output_component_start (char *buffer, - VzicTime *vzictime, - gboolean output_rdate, - gboolean use_same_tz_offset); -static void output_component_end (FILE *fp, - VzicTime *vzictime); - -static void vzictime_init (VzicTime *vzictime); -static int calculate_actual_time (VzicTime *vzictime, - TimeCode time_code, - int stdoff, - int walloff); -static int calculate_wall_time (int time, - TimeCode time_code, - int stdoff, - int walloff, - int *day_offset); -static int calculate_until_time (int time, - TimeCode time_code, - int stdoff, - int walloff, - int *year, - int *month, - int *day); -static void fix_time_overflow (int *year, - int *month, - int *day, - int day_offset); - -static char* format_time (int year, - int month, - int day, - int time); -static char* format_tz_offset (int tz_offset, - gboolean round_seconds); -static gboolean output_rrule (char *rrule_buffer, - int month, - DayCode day_code, - int day_number, - int day_weekday, - int day_offset, - char *until); -static gboolean output_rrule_2 (char *buffer, - int month, - int day_number, - int day_weekday); - -static char* format_vzictime (VzicTime *vzictime); - -static void dump_changes (FILE *fp, - char *zone_name, - GArray *changes); -static void dump_change (FILE *fp, - char *zone_name, - VzicTime *vzictime, - int year); - -static void expand_tzid_prefix (void); - - -void -output_vtimezone_files (char *directory, - GArray *zone_data, - GHashTable *rule_data, - GHashTable *link_data, - int max_until_year) -{ - ZoneData *zone; - GList *links; - char *link_to; - int i; - - /* Insert today's date into the TZIDs we output. */ - expand_tzid_prefix (); - - /* Expand the rule data so that each entry specifies only one year, and - sort it so we can easily find the rules applicable to each Zone span. */ - g_hash_table_foreach (rule_data, expand_and_sort_rule_array, - GINT_TO_POINTER (max_until_year)); - - /* Output each timezone. */ - for (i = 0; i < zone_data->len; i++) { - zone = &g_array_index (zone_data, ZoneData, i); - output_zone (directory, zone, zone->zone_name, rule_data); - - /* Look for any links from this zone. */ - links = g_hash_table_lookup (link_data, zone->zone_name); - - while (links) { - link_to = links->data; - - /* We ignore Links that don't have a '/' in them (things like 'EST5EDT'). - */ - if (strchr (link_to, '/')) { - output_zone (directory, zone, link_to, rule_data); - } - - links = links->next; - } - } -} - - -static void -expand_and_sort_rule_array (gpointer key, - gpointer value, - gpointer data) -{ - char *name = key; - GArray *rule_array = value; - RuleData *rule, tmp_rule; - int len, max_year, i, from, to, year; - gboolean is_infinite; - - /* We expand the rule data to a year greater than any year used in a Zone - UNTIL value. This is so that we can easily get parts of the array to - use for each Zone line. */ - max_year = GPOINTER_TO_INT (data) + 2; - - /* If any of the rules apply to several years, we turn it into a single rule - for each year. If the Rule is infinite we go up to max_year. - We change the FROM field in the copies of the Rule, setting it to each - of the years, and set TO to FROM, except if TO was YEAR_MAXIMUM we set - the last TO to YEAR_MAXIMUM, so we still know the Rule is infinite. */ - len = rule_array->len; - for (i = 0; i < len; i++) { - rule = &g_array_index (rule_array, RuleData, i); - - /* None of the Rules currently use the TYPE field, but we'd better check. - */ - if (rule->type) { - fprintf (stderr, "Rules %s has a TYPE: %s\n", name, rule->type); - exit (1); - } - - if (rule->from_year != rule->to_year) { - from = rule->from_year; - to = rule->to_year; - - tmp_rule = *rule; - - /* Flag that this is a shallow copy so we don't free anything twice. */ - tmp_rule.is_shallow_copy = TRUE; - - /* See if it is an infinite Rule. */ - if (to == YEAR_MAXIMUM) { - is_infinite = TRUE; - to = max_year; - if (from < to) - rule->to_year = rule->from_year; - } else { - is_infinite = FALSE; - } - - /* Create a copy of the Rule for each year. */ - for (year = from + 1; year <= to; year++) { - tmp_rule.from_year = year; - - /* If the Rule is infinite, mark the last copy as infinite. */ - if (year == to && is_infinite) - tmp_rule.to_year = YEAR_MAXIMUM; - else - tmp_rule.to_year = year; - - g_array_append_val (rule_array, tmp_rule); - } - } - } - - /* Now sort the rules. */ - qsort (rule_array->data, rule_array->len, sizeof (RuleData), rule_sort_func); - -#if 0 - dump_rule_array (name, rule_array, stdout); -#endif -} - - -/* This is used to sort the rules, after the rules have all been expanded so - that each one is only for one year. */ -static int -rule_sort_func (const void *arg1, - const void *arg2) -{ - RuleData *rule1, *rule2; - int time1_year, time1_month, time1_day; - int time2_year, time2_month, time2_day; - int month_diff, result; - VzicTime t1, t2; - - rule1 = (RuleData*) arg1; - rule2 = (RuleData*) arg2; - - time1_year = rule1->from_year; - time1_month = rule1->in_month; - time2_year = rule2->from_year; - time2_month = rule2->in_month; - - /* If there is more that one month difference we don't need to calculate - the day or time. */ - month_diff = (time1_year - time2_year) * 12 + time1_month - time2_month; - - if (month_diff > 1) - return 1; - if (month_diff < -1) - return -1; - - /* Now we have to calculate the day and time of the Rule start and the - VzicTime, using the given offsets. */ - t1.year = time1_year; - t1.month = time1_month; - t1.day_code = rule1->on_day_code; - t1.day_number = rule1->on_day_number; - t1.day_weekday = rule1->on_day_weekday; - t1.time_code = rule1->at_time_code; - t1.time_seconds = rule1->at_time_seconds; - - t2.year = time2_year; - t2.month = time2_month; - t2.day_code = rule2->on_day_code; - t2.day_number = rule2->on_day_number; - t2.day_weekday = rule2->on_day_weekday; - t2.time_code = rule2->at_time_code; - t2.time_seconds = rule2->at_time_seconds; - - /* FIXME: We don't know the offsets yet, but I don't think any Rules are - close enough together that the offsets can make a difference. Should - check this. */ - calculate_actual_time (&t1, TIME_WALL, 0, 0); - calculate_actual_time (&t2, TIME_WALL, 0, 0); - - /* Now we can compare the entire time. */ - if (t1.year > t2.year) - result = 1; - else if (t1.year < t2.year) - result = -1; - - else if (t1.month > t2.month) - result = 1; - else if (t1.month < t2.month) - result = -1; - - else if (t1.day_number > t2.day_number) - result = 1; - else if (t1.day_number < t2.day_number) - result = -1; - - else if (t1.time_seconds > t2.time_seconds) - result = 1; - else if (t1.time_seconds < t2.time_seconds) - result = -1; - - else { - printf ("WARNING: Rule dates matched.\n"); - result = 0; - } - - return result; -} - - -static void -output_zone (char *directory, - ZoneData *zone, - char *zone_name, - GHashTable *rule_data) -{ - FILE *fp, *changes_fp = NULL; - char output_directoryPATHNAME_BUFFER_SIZE; - char filenamePATHNAME_BUFFER_SIZE; - char changes_filenamePATHNAME_BUFFER_SIZE; - char *zone_directory, *zone_subdirectory, *zone_filename; - - /* Set a global for the zone_name, to be used only for debug messages. */ - CurrentZoneName = zone_name; - - /* Use this to only output a particular zone. */ -#if 0 - if (strcmp (zone_name, "Atlantic/Azores")) - return; -#endif - -#if 0 - printf ("Outputting Zone: %s\n", zone_name); -#endif - - if (!parse_zone_name (zone_name, &zone_directory, &zone_subdirectory, - &zone_filename)) - return; - - if (VzicDumpZoneNamesAndCoords) { - VzicTimeZoneNames = g_list_prepend (VzicTimeZoneNames, - g_strdup (zone_name)); - } - - sprintf (output_directory, "%s/%s", directory, zone_directory); - ensure_directory_exists (output_directory); - sprintf (filename, "%s/%s.ics", output_directory, zone_filename); - - if (VzicDumpChanges) { - sprintf (output_directory, "%s/ChangesVzic/%s", directory, zone_directory); - ensure_directory_exists (output_directory); - sprintf (changes_filename, "%s/%s", output_directory, zone_filename); - } - - if (zone_subdirectory) { - sprintf (output_directory, "%s/%s/%s", directory, zone_directory, - zone_subdirectory); - ensure_directory_exists (output_directory); - sprintf (filename, "%s/%s.ics", output_directory, zone_filename); - - if (VzicDumpChanges) { - sprintf (output_directory, "%s/ChangesVzic/%s/%s", directory, - zone_directory, zone_subdirectory); - ensure_directory_exists (output_directory); - sprintf (changes_filename, "%s/%s", output_directory, zone_filename); - } - } - - /* Create the files. */ - fp = fopen (filename, "w"); - if (!fp) { - fprintf (stderr, "Couldn't create file: %s\n", filename); - exit (1); - } - - if (VzicDumpChanges) { - changes_fp = fopen (changes_filename, "w"); - if (!changes_fp) { - fprintf (stderr, "Couldn't create file: %s\n", changes_filename); - exit (1); - } - } - - fprintf (fp, "BEGIN:VCALENDAR\r\nPRODID:"); - fprintf (fp, ProductID, _CYRUS_VERSION); - fprintf (fp, "\r\nVERSION:2.0\r\n"); - - - output_zone_to_files (zone, zone_name, rule_data, fp, changes_fp); - - if (ferror (fp)) { - fprintf (stderr, "Error writing file: %s\n", filename); - exit (1); - } - - fprintf (fp, "END:VCALENDAR\r\n"); - - fclose (fp); - - g_free (zone_directory); - g_free (zone_subdirectory); - g_free (zone_filename); -} - - -/* This checks that the Zone name only uses the characters in -+_/a-zA-Z0-9, - and outputs a warning if it isn't. */ -static gboolean -parse_zone_name (char *name, - char **directory, - char **subdirectory, - char **filename) -{ - static int invalid_zone_num = 1; - - char *p, ch, *first_slash_pos = NULL, *second_slash_pos = NULL; - gboolean invalid = FALSE; - - for (p = name; (ch = *p) != 0; p++) { - if ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') - && (ch < '0' || ch > '9') && ch != '/' && ch != '_' - && ch != '-' && ch != '+') { - fprintf (stderr, "WARNING: Unusual Zone name: %s\n", name); - invalid = TRUE; - break; - } - - if (ch == '/') { - if (!first_slash_pos) { - first_slash_pos = p; - } else if (!second_slash_pos) { - second_slash_pos = p; - } else { - fprintf (stderr, "WARNING: More than 2 '/' characters in Zone name: %s\n", name); - invalid = TRUE; - break; - } - } - } - - if (!first_slash_pos) { -#if 0 - fprintf (stderr, "No '/' character in Zone name: %s. Skipping.\n", name); -#endif - return FALSE; - } - - if (invalid) { - *directory = g_strdup ("Invalid"); - *filename = g_strdup_printf ("Zone%i", invalid_zone_num++); - } else { - *first_slash_pos = '\0'; - *directory = g_strdup (name); - *first_slash_pos = '/'; - - if (second_slash_pos) { - *second_slash_pos = '\0'; - *subdirectory = g_strdup (first_slash_pos + 1); - *second_slash_pos = '/'; - - *filename = g_strdup (second_slash_pos + 1); - } else { - *subdirectory = NULL; - *filename = g_strdup (first_slash_pos + 1); - } - } - - return invalid ? FALSE : TRUE; -} - - -static void -output_zone_to_files (ZoneData *zone, - char *zone_name, - GHashTable *rule_data, - FILE *fp, - FILE *changes_fp) -{ - ZoneLineData *zone_line; - GArray *changes; - int i, stdoff, walloff, start_index, save_seconds; - VzicTime start, end, *vzictime_start, *vzictime, *vzictime_first_rule_change; - gboolean is_daylight, found_letter_s; - char *start_letter_s; - - changes = g_array_new (FALSE, FALSE, sizeof (VzicTime)); - - vzictime_init (&start); - vzictime_init (&end); - - /* The first period starts at -infinity. */ - start.year = YEAR_MINIMUM; - - for (i = 0; i < zone->zone_line_data->len; i++) { - zone_line = &g_array_index (zone->zone_line_data, ZoneLineData, i); - - /* This is the local standard time offset from GMT for this period. */ - start.stdoff = stdoff = zone_line->stdoff_seconds; - start.walloff = walloff = stdoff + zone_line->save_seconds; - - if (zone_line->until_set) { - end.year = zone_line->until_year; - end.month = zone_line->until_month; - end.day_code = zone_line->until_day_code; - end.day_number = zone_line->until_day_number; - end.day_weekday = zone_line->until_day_weekday; - end.time_seconds = zone_line->until_time_seconds; - end.time_code = zone_line->until_time_code; - } else { - /* The last period ends at +infinity. */ - end.year = YEAR_MAXIMUM; - } - - /* Add a time change for the start of the period. This may be removed - later if one of the rules expands to exactly the same time. */ - start_index = changes->len; - g_array_append_val (changes, start); - - /* If there are Rules associated with this period, add all the relevant - time changes. */ - save_seconds = 0; - if (zone_line->rules) - found_letter_s = add_rule_changes (zone_line, zone_name, changes, - rule_data, &start, &end, - &start_letter_s, &save_seconds); - else - found_letter_s = FALSE; - - /* FIXME: I'm not really sure what to do about finding a LETTER_S for the - first part of the period (i.e. before the first Rule comes into effect). - Currently we try to use the same LETTER_S as the first Rule of the - period which is in local standard time. */ - if (zone_line->save_seconds) - save_seconds = zone_line->save_seconds; - is_daylight = save_seconds ? TRUE : FALSE; - vzictime_start = &g_array_index (changes, VzicTime, start_index); - walloff = vzictime_start->walloff = stdoff + save_seconds; - - /* TEST: See if the first Rule time is exactly the same as the change from - the Zone line. In which case we can remove the Zone line change. */ - if (changes->len > start_index + 1) { - int prev_stdoff, prev_walloff; - - if (start_index > 0) { - VzicTime *v = &g_array_index (changes, VzicTime, start_index - 1); - prev_stdoff = v->stdoff; - prev_walloff = v->walloff; - } else { - prev_stdoff = 0; - prev_walloff = 0; - } - vzictime_first_rule_change = &g_array_index (changes, VzicTime, - start_index + 1); - if (times_match (vzictime_start, prev_stdoff, prev_walloff, - vzictime_first_rule_change, stdoff, walloff)) { -#if 0 - printf ("Removing zone-line change (using new offsets)\n"); -#endif - g_array_remove_index (changes, start_index); - vzictime_start = NULL; - } else if (times_match (vzictime_start, prev_stdoff, prev_walloff, - vzictime_first_rule_change, prev_stdoff, prev_walloff)) { -#if 0 - printf ("Removing zone-line change (using previous offsets)\n"); -#endif - g_array_remove_index (changes, start_index); - vzictime_start = NULL; - } - } - - - if (vzictime_start) { - vzictime_start->tzname = expand_tzname (zone_name, zone_line->format, - found_letter_s, - start_letter_s, is_daylight); - } - - /* The start of the next Zone line is the end time of this one. */ - start = end; - } - - set_previous_offsets (changes); - - output_zone_components (fp, zone_name, changes); - - if (VzicDumpChanges) - dump_changes (changes_fp, zone_name, changes); - - /* Free all the TZNAME fields. */ - for (i = 0; i < changes->len; i++) { - vzictime = &g_array_index (changes, VzicTime, i); - g_free (vzictime->tzname); - } - - g_array_free (changes, TRUE); -} - - -/* This appends any timezone changes specified by the rules associated with - the timezone, that happen between the start and end times. - It returns the letter_s field of the first STANDARD rule found in the - search. We need this to fill in any %s in the FORMAT field of the first - component of the time period (the Zone line). */ -static gboolean -add_rule_changes (ZoneLineData *zone_line, - char *zone_name, - GArray *changes, - GHashTable *rule_data, - VzicTime *start, - VzicTime *end, - char **start_letter_s, - int *save_seconds) -{ - GArray *rule_array; - RuleData *rule, *prev_rule = NULL; - int stdoff, walloff, i, prev_stdoff, prev_walloff; - VzicTime vzictime; - gboolean is_daylight, found_start_letter_s = FALSE; - gboolean checked_for_previous = FALSE; - - *save_seconds = 0; - - rule_array = g_hash_table_lookup (rule_data, zone_line->rules); - if (!rule_array) { - fprintf (stderr, "Couldn't access rules: %s\n", zone_line->rules); - exit (1); - } - - /* The stdoff is the same for all the rules. */ - stdoff = start->stdoff; - - /* The walloff changes as we go through the rules. */ - walloff = start->walloff; - - /* Get the stdoff & walloff from the last change before this period. */ - if (changes->len >= 2) { - VzicTime *change = &g_array_index (changes, VzicTime, changes->len - 2); - prev_stdoff = change->stdoff; - prev_walloff = change->walloff; - } else { - prev_stdoff = prev_walloff = 0; - } - - - for (i = 0; i < rule_array->len; i++) { - rule = &g_array_index (rule_array, RuleData, i); - - is_daylight = rule->save_seconds != 0 ? TRUE : FALSE; - - vzictime_init (&vzictime); - vzictime.year = rule->from_year; - vzictime.month = rule->in_month; - vzictime.day_code = rule->on_day_code; - vzictime.day_number = rule->on_day_number; - vzictime.day_weekday = rule->on_day_weekday; - vzictime.time_seconds = rule->at_time_seconds; - vzictime.time_code = rule->at_time_code; - vzictime.stdoff = stdoff; - vzictime.walloff = stdoff + rule->save_seconds; - vzictime.is_infinite = (rule->to_year == YEAR_MAXIMUM) ? TRUE : FALSE; - - /* If the rule time is before the given start time, skip it. */ - if (compare_times (&vzictime, stdoff, walloff, - start, prev_stdoff, prev_walloff) < 0) - continue; - - /* If the previous Rule was a daylight Rule, then we may want to use the - walloff from that. */ - if (!checked_for_previous) { - checked_for_previous = TRUE; - if (i > 0) { - prev_rule = &g_array_index (rule_array, RuleData, i - 1); - if (prev_rule->save_seconds) { - walloff = start->walloff = stdoff + prev_rule->save_seconds; - *save_seconds = prev_rule->save_seconds; - found_start_letter_s = TRUE; - *start_letter_s = prev_rule->letter_s; -#if 0 - printf ("Could use save_seconds from previous Rule: %s\n", - zone_name); -#endif - } - } - } - - /* If an end time has been given, then if the rule time is on or after it - break out of the loop. */ - if (end->year != YEAR_MAXIMUM - && compare_times (&vzictime, stdoff, walloff, - end, stdoff, walloff) >= 0) - break; - - vzictime.tzname = expand_tzname (zone_name, zone_line->format, TRUE, - rule->letter_s, is_daylight); - - g_array_append_val (changes, vzictime); - - /* When we find the first STANDARD time we set letter_s. */ - if (!found_start_letter_s && !is_daylight) { - found_start_letter_s = TRUE; - *start_letter_s = rule->letter_s; - } - - /* Now that we have added the Rule, the new walloff comes into effect - for any following Rules. */ - walloff = vzictime.walloff; - } - - return found_start_letter_s; -} - - -/* This expands the Zone line FORMAT field, using the given LETTER_S from a - Rule line. There are 3 types of FORMAT field: - 1. a string with an %s in, e.g. "WE%sT". The %s is replaced with LETTER_S. - 2. a string with an '/' in, e.g. "CAT/CAWT". The first part is used for - standard time and the second part for when daylight-saving is in effect. - 3. a plain string, e.g. "LMT", which we leave as-is. - Note that (1) is the only type in which letter_s is required. -*/ -static char* -expand_tzname (char *zone_name, - char *format, - gboolean have_letter_s, - char *letter_s, - gboolean is_daylight) -{ - char *p, buffer256, *guess = NULL; - int len; - -#if 0 - printf ("Expanding %s with %s\n", format, letter_s); -#endif - - if (!format || !format0) { - fprintf (stderr, "Missing FORMAT\n"); - exit (1); - } - - /* 1. Look for a "%s". */ - p = strchr (format, '%'); - if (p && *(p + 1) == 's') { - if (!have_letter_s) { - - /* NOTE: These are a few hard-coded TZNAMEs that I've looked up myself. - These are needed in a few places where a Zone line comes into effect - but no Rule has been found, so we have no LETTER_S to use. - I've tried to use whatever is the normal LETTER_S in the Rules for - the particular zone, in local standard time. */ - if (!strcmp (zone_name, "Asia/Macao") - && !strcmp (format, "C%sT")) - guess = "CST"; - else if (!strcmp (zone_name, "Asia/Macau") - && !strcmp (format, "C%sT")) - guess = "CST"; - else if (!strcmp (zone_name, "Asia/Ashgabat") - && !strcmp (format, "ASH%sT")) - guess = "ASHT"; - else if (!strcmp (zone_name, "Asia/Ashgabat") - && !strcmp (format, "TM%sT")) - guess = "TMT"; - else if (!strcmp (zone_name, "Asia/Samarkand") - && !strcmp (format, "TAS%sT")) - guess = "TAST"; - else if (!strcmp (zone_name, "Atlantic/Azores") - && !strcmp (format, "WE%sT")) - guess = "WET"; - else if (!strcmp (zone_name, "Europe/Paris") - && !strcmp (format, "WE%sT")) - guess = "WET"; - else if (!strcmp (zone_name, "Europe/Warsaw") - && !strcmp (format, "CE%sT")) - guess = "CET"; - else if (!strcmp (zone_name, "America/Phoenix") - && !strcmp (format, "M%sT")) - guess = "MST"; - else if (!strcmp (zone_name, "America/Nome") - && !strcmp (format, "Y%sT")) - guess = "YST"; - - if (guess) { -#if 0 - fprintf (stderr, - "WARNING: Couldn't find a LETTER_S to use in FORMAT: %s in Zone: %s Guessing: %s\n", - format, zone_name, guess); -#endif - return g_strdup (guess); - } - -#if 1 - fprintf (stderr, - "WARNING: Couldn't find a LETTER_S to use in FORMAT: %s in Zone: %s Leaving TZNAME empty\n", - format, zone_name); -#endif - -#if 0 - /* This is useful to spot exactly which component had a problem. */ - sprintf (buffer, "FIXME: %s", format); - return g_strdup (buffer); -#else - /* We give up and don't output a TZNAME. */ - return NULL; -#endif - } - - sprintf (buffer, format, letter_s ? letter_s : ""); - return g_strdup (buffer); - } - - /* 2. Look for a "/". */ - p = strchr (format, '/'); - if (p) { - if (is_daylight) { - return g_strdup (p + 1); - } else { - len = p - format; - strncpy (buffer, format, len); - bufferlen = '\0'; - return g_strdup (buffer); - } - } - - /* 3. Just use format as it is. */ - return g_strdup (format); -} - - -/* Compares 2 VzicTimes, returning strcmp()-like values, i.e. 0 if equal, - 1 if the 1st is after the 2nd and -1 if the 1st is before the 2nd. */ -static int -compare_times (VzicTime *time1, - int stdoff1, - int walloff1, - VzicTime *time2, - int stdoff2, - int walloff2) -{ - VzicTime t1, t2; - int result; - - t1 = *time1; - t2 = *time2; - - calculate_actual_time (&t1, TIME_UNIVERSAL, stdoff1, walloff1); - calculate_actual_time (&t2, TIME_UNIVERSAL, stdoff2, walloff2); - - /* Now we can compare the entire time. */ - if (t1.year > t2.year) - result = 1; - else if (t1.year < t2.year) - result = -1; - - else if (t1.month > t2.month) - result = 1; - else if (t1.month < t2.month) - result = -1; - - else if (t1.day_number > t2.day_number) - result = 1; - else if (t1.day_number < t2.day_number) - result = -1; - - else if (t1.time_seconds > t2.time_seconds) - result = 1; - else if (t1.time_seconds < t2.time_seconds) - result = -1; - - else - result = 0; - -#if 0 - printf ("%i/%i/%i %i <=> %i/%i/%i %i -> %i\n", - t1.day_number, t1.month + 1, t1.year, t1.time_seconds, - t2.day_number, t2.month + 1, t2.year, t2.time_seconds, - result); -#endif - - return result; -} - - -/* Returns TRUE if the 2 times are exactly the same. It will calculate the - actual day, but doesn't convert times. */ -static gboolean -times_match (VzicTime *time1, - int stdoff1, - int walloff1, - VzicTime *time2, - int stdoff2, - int walloff2) -{ - VzicTime t1, t2; - - t1 = *time1; - t2 = *time2; - - calculate_actual_time (&t1, TIME_UNIVERSAL, stdoff1, walloff1); - calculate_actual_time (&t2, TIME_UNIVERSAL, stdoff2, walloff2); - - if (t1.year == t2.year - && t1.month == t2.month - && t1.day_number == t2.day_number - && t1.time_seconds == t2.time_seconds) - return TRUE; - - return FALSE; -} - - -static void -output_zone_components (FILE *fp, - char *name, - GArray *changes) -{ - VzicTime *vzictime; - int i, start_index = 0; - gboolean only_one_change = FALSE; - char start_buffer1024; - time_t now = time(0); - struct tm *tm = gmtime(&now); - - fprintf (fp, "BEGIN:VTIMEZONE\r\nTZID:%s%s\r\n", TZIDPrefixExpanded, name); - - /* Use current time as LAST-MODIFIED */ - fprintf (fp, "LAST-MODIFIED:%04i%02i%02iT%02i%02i%02iZ\r\n", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec); - - if (VzicUrlPrefix != NULL) - fprintf (fp, "TZURL:%s/%s\r\n", VzicUrlPrefix, name); - -#if 0 - /* We use an 'X-' property to place the city name in. */ - fprintf (fp, "X-LIC-LOCATION:%s\r\n", name); -#endif - - /* We try to find any recurring components first, or they may get output - as lots of RDATES instead. */ - if (!VzicNoRRules) { - int num_rrules_output = 0; - - for (i = 1; i < changes->len; i++) { - if (check_for_recurrence (fp, changes, i)) { - num_rrules_output++; - } - } - -#if 0 - printf ("Zone: %s had %i infinite RRULEs\n", CurrentZoneName, - num_rrules_output); -#endif - - if (!VzicPureOutput && num_rrules_output == 2) { -#if 0 - printf ("Zone: %s using 2 RRULEs\n", CurrentZoneName); -#endif - fprintf (fp, "END:VTIMEZONE\r\n"); - return; - } - } - - /* We skip the first change, which starts at -infinity, unless it is the only - change for the timezone. */ - if (changes->len > 1) - start_index = 1; - else - only_one_change = TRUE; - - /* For pure output, we start at the start of the array and step through it - outputting RDATEs. For Outlook-compatible output we start at the end - and step backwards to find the first STANDARD time to output. */ - if (VzicPureOutput) - i = start_index - 1; - else - i = changes->len; - - for (;;) { - if (VzicPureOutput) - i++; - else - i--; - - if (VzicPureOutput) { - if (i >= changes->len) - break; - } else { - if (i < start_index) - break; - } - - vzictime = &g_array_index (changes, VzicTime, i); - - /* If we have already output this component as part of an RRULE or RDATE, - then we skip it. */ - if (vzictime->output) - continue; - - /* For Outlook-compatible output we only want to output the last STANDARD - time as a DTSTART, so skip any DAYLIGHT changes. */ - if (!VzicPureOutput && vzictime->stdoff != vzictime->walloff) { - printf ("Skipping DAYLIGHT change\n"); - continue; - } - -#if 0 - printf ("Zone: %s using DTSTART Year: %i\n", CurrentZoneName, - vzictime->year); -#endif - - if (VzicPureOutput) { - output_component_start (start_buffer, vzictime, TRUE, only_one_change); - } else { - /* For Outlook compatability we don't output the RDATE and use the same - TZOFFSET for TZOFFSETFROM and TZOFFSETTO. */ - vzictime->year = RDATE_YEAR; - vzictime->month = 0; - vzictime->day_code = DAY_SIMPLE; - vzictime->day_number = 1; - vzictime->time_code = TIME_WALL; - vzictime->time_seconds = 0; - - output_component_start (start_buffer, vzictime, FALSE, TRUE); - } - - fprintf (fp, "%s", start_buffer); - - /* This will look for matching components and output them as RDATEs - instead of separate components. */ - if (VzicPureOutput && !VzicNoRDates) - check_for_rdates (fp, changes, i); - - output_component_end (fp, vzictime); - - vzictime->output = TRUE; - - if (!VzicPureOutput) - break; - } - - fprintf (fp, "END:VTIMEZONE\r\n"); -} - - -/* This sets the prev_stdoff and prev_walloff (i.e. the TZOFFSETFROM) of each - VzicTime, using the stdoff and walloff of the previous VzicTime. It makes - the rest of the code much simpler. */ -static void -set_previous_offsets (GArray *changes) -{ - VzicTime *vzictime, *prev_vzictime; - int i; - - prev_vzictime = &g_array_index (changes, VzicTime, 0); - prev_vzictime->prev_stdoff = 0; - prev_vzictime->prev_walloff = 0; - - for (i = 1; i < changes->len; i++) { - vzictime = &g_array_index (changes, VzicTime, i); - - vzictime->prev_stdoff = prev_vzictime->stdoff; - vzictime->prev_walloff = prev_vzictime->walloff; - - prev_vzictime = vzictime; - } -} - - -/* Returns TRUE if we output an infinite recurrence. */ -static gboolean -check_for_recurrence (FILE *fp, - GArray *changes, - int idx) -{ - VzicTime *vzictime_start, *vzictime, vzictime_start_copy; - gboolean is_daylight_start, is_daylight; - int last_match, i, next_year, day_offset; - char until256, rrule_buffer2048, start_buffer1024; - GList *matching_elements = NULL, *elem; - - vzictime_start = &g_array_index (changes, VzicTime, idx); - - /* If this change has already been output, skip it. */ - if (vzictime_start->output) - return FALSE; - - /* There can't possibly be an RRULE starting from YEAR_MINIMUM. */ - if (vzictime_start->year == YEAR_MINIMUM) - return FALSE; - - is_daylight_start = (vzictime_start->stdoff != vzictime_start->walloff) - ? TRUE : FALSE; - -#if 0 - printf ("\nChecking: %s OFFSETFROM: %i %s\n", - format_vzictime (vzictime_start), vzictime_start->prev_walloff, - is_daylight_start ? "DAYLIGHT" : ""); -#endif - - /* If this is an infinitely recurring change, output the RRULE and return. - There won't be any changes after it that we could merge. */ - if (vzictime_start->is_infinite) { - - /* Change the year to our minimum start year. */ - vzictime_start_copy = *vzictime_start; - if (!VzicPureOutput) - vzictime_start_copy.year = RRULE_START_YEAR; - - day_offset = output_component_start (start_buffer, &vzictime_start_copy, - FALSE, FALSE); - - if (!output_rrule (rrule_buffer, vzictime_start_copy.month, - vzictime_start_copy.day_code, - vzictime_start_copy.day_number, - vzictime_start_copy.day_weekday, day_offset, "")) { - if (vzictime_start->year != MAX_TIME_T_YEAR) { - fprintf (stderr, "WARNING: Failed to output infinite recurrence with start year: %i\n", vzictime_start->year); - } - return TRUE; - } - - fprintf (fp, "%s%s", start_buffer, rrule_buffer); - output_component_end (fp, vzictime_start); - vzictime_start->output = TRUE; - return TRUE; - } - - last_match = idx; - next_year = vzictime_start->year + 1; - for (i = idx + 1; i < changes->len; i++) { - vzictime = &g_array_index (changes, VzicTime, i); - - is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; - - if (vzictime->output) - continue; - -#if 0 - printf (" %s OFFSETFROM: %i %s\n", - format_vzictime (vzictime), vzictime->prev_walloff, - is_daylight ? "DAYLIGHT" : ""); -#endif - - /* If it is more than one year ahead, we are finished, since we want - consecutive years. */ - if (vzictime->year > next_year) { - break; - } - - /* It must be the same type of component - STANDARD or DAYLIGHT. */ - if (is_daylight != is_daylight_start) { - continue; - } - - /* It must be the following year, with the same month, day & time. - It is possible that the time has a different code but does in fact - match when normalized, but we don't care (for now at least). */ - if (vzictime->year != next_year - || vzictime->month != vzictime_start->month - || vzictime->day_code != vzictime_start->day_code - || vzictime->day_number != vzictime_start->day_number - || vzictime->day_weekday != vzictime_start->day_weekday - || vzictime->time_seconds != vzictime_start->time_seconds - || vzictime->time_code != vzictime_start->time_code) { - continue; - } - - /* The TZOFFSETFROM and TZOFFSETTO must match. */ - if (vzictime->prev_walloff != vzictime_start->prev_walloff) { - continue; - } - - if (vzictime->walloff != vzictime_start->walloff) { - continue; - } - - /* TZNAME must match. */ - if (!timezones_match (vzictime->tzname, vzictime_start->tzname)) { - continue; - } - - /* We have a match. */ - last_match = i; - next_year = vzictime->year + 1; - - matching_elements = g_list_prepend (matching_elements, vzictime); - } - - if (last_match == idx) - return FALSE; - -#if 0 - printf ("Found recurrence %i - %i!!!\n", vzictime_start->year, - next_year - 1); -#endif - - vzictime = &g_array_index (changes, VzicTime, last_match); - -/* We only use RRULEs if there are at least MIN_RRULE_OCCURRENCES occurrences, - since otherwise RDATEs are more efficient. */ - if (!vzictime->is_infinite) { - int years = vzictime->year - vzictime_start->year + 1; -#if 0 - printf ("RRULE Years: %i\n", years); -#endif - if (years < MIN_RRULE_OCCURRENCES) - return FALSE; - } - - if (vzictime->is_infinite) { - until0 = '\0'; - } else { - VzicTime t1 = *vzictime; - -#if 0 - printf ("RRULE with UNTIL - aborting\n"); - abort (); -#endif - - calculate_actual_time (&t1, TIME_UNIVERSAL, vzictime->prev_stdoff, - vzictime->prev_walloff); - - /* Output UNTIL, in UTC. */ - sprintf (until, ";UNTIL=%sZ", format_time (t1.year, t1.month, - t1.day_number, - t1.time_seconds)); - } - - /* Change the year to our minimum start year. */ - vzictime_start_copy = *vzictime_start; - if (!VzicPureOutput) - vzictime_start_copy.year = RRULE_START_YEAR; - - day_offset = output_component_start (start_buffer, &vzictime_start_copy, - FALSE, FALSE); - if (output_rrule (rrule_buffer, vzictime_start_copy.month, - vzictime_start_copy.day_code, - vzictime_start_copy.day_number, - vzictime_start_copy.day_weekday, day_offset, until)) { - fprintf (fp, "%s%s", start_buffer, rrule_buffer); - output_component_end (fp, vzictime_start); - - /* Mark all the changes as output. */ - vzictime_start->output = TRUE; - for (elem = matching_elements; elem; elem = elem->next) { - vzictime = elem->data; - vzictime->output = TRUE; - } - } - - g_list_free (matching_elements); - - return TRUE; -} - - -static void -check_for_rdates (FILE *fp, - GArray *changes, - int idx) -{ - VzicTime *vzictime_start, *vzictime, tmp_vzictime; - gboolean is_daylight_start, is_daylight; - int i, year, month, day, time; - - vzictime_start = &g_array_index (changes, VzicTime, idx); - - is_daylight_start = (vzictime_start->stdoff != vzictime_start->walloff) - ? TRUE : FALSE; - -#if 0 - printf ("\nChecking: %s OFFSETFROM: %i %s\n", - format_vzictime (vzictime_start), vzictime_start->prev_walloff, - is_daylight_start ? "DAYLIGHT" : ""); -#endif - - /* We want to go backwards through the array now, for Outlook compatability. - (It only looks at the first DTSTART/RDATE.) */ - for (i = idx + 1; i < changes->len; i++) { - vzictime = &g_array_index (changes, VzicTime, i); - - is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; - - if (vzictime->output) - continue; - -#if 0 - printf (" %s OFFSETFROM: %i %s\n", format_vzictime (vzictime), - vzictime->prev_walloff, is_daylight ? "DAYLIGHT" : ""); -#endif - - /* It must be the same type of component - STANDARD or DAYLIGHT. */ - if (is_daylight != is_daylight_start) { - continue; - } - - /* The TZOFFSETFROM and TZOFFSETTO must match. */ - if (vzictime->prev_walloff != vzictime_start->prev_walloff) { - continue; - } - - if (vzictime->walloff != vzictime_start->walloff) { - continue; - } - - /* TZNAME must match. */ - if (!timezones_match (vzictime->tzname, vzictime_start->tzname)) { - continue; - } - - /* We have a match. */ - - tmp_vzictime = *vzictime; - calculate_actual_time (&tmp_vzictime, TIME_WALL, vzictime->prev_stdoff, - vzictime->prev_walloff); - - fprintf (fp, "RDATE:%s\r\n", format_time (tmp_vzictime.year, - tmp_vzictime.month, - tmp_vzictime.day_number, - tmp_vzictime.time_seconds)); - - vzictime->output = TRUE; - } -} - - -static gboolean -timezones_match (char *tzname1, - char *tzname2) -{ - if (tzname1 && tzname2 && !strcmp (tzname1, tzname2)) - return TRUE; - - if (!tzname1 && !tzname2) - return TRUE; - - return FALSE; -} - - -/* Outputs the start of a VTIMEZONE component, with the BEGIN line, - the DTSTART, TZOFFSETFROM, TZOFFSETTO & TZNAME properties. */ -static int -output_component_start (char *buffer, - VzicTime *vzictime, - gboolean output_rdate, - gboolean use_same_tz_offset) -{ - gboolean is_daylight, skip_day_offset = FALSE; - gint year, month, day, time, day_offset = 0; - GDate old_date, new_date; - char *formatted_time; - char line11024, line21024, line31024; - char line41024, line51024, line61024; - VzicTime tmp_vzictime; - int prev_walloff; - - is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; - - tmp_vzictime = *vzictime; - day_offset = calculate_actual_time (&tmp_vzictime, TIME_WALL, - vzictime->prev_stdoff, - vzictime->prev_walloff); - - sprintf (line1, "BEGIN:%s\r\n", is_daylight ? "DAYLIGHT" : "STANDARD"); - - /* If the timezone only has one change, that means it uses the same offset - forever, so we use the same TZOFFSETFROM as the TZOFFSETTO. (If the zone - has more than one change, we don't output the first one.) */ - if (use_same_tz_offset) - prev_walloff = vzictime->walloff; - else - prev_walloff = vzictime->prev_walloff; - - sprintf (line2, "TZOFFSETFROM:%s\r\n", - format_tz_offset (prev_walloff, !VzicPureOutput)); - - sprintf (line3, "TZOFFSETTO:%s\r\n", - format_tz_offset (vzictime->walloff, !VzicPureOutput)); - - if (vzictime->tzname) - sprintf (line4, "TZNAME:%s\r\n", vzictime->tzname); - else - line40 = '\0'; - - formatted_time = format_time (tmp_vzictime.year, tmp_vzictime.month, - tmp_vzictime.day_number, - tmp_vzictime.time_seconds); - sprintf (line5, "DTSTART:%s\r\n", formatted_time); -#if 0 /* The RDATE matching DTSTART is unnecessary */ - if (output_rdate) - sprintf (line6, "RDATE:%s\r\n", formatted_time); - else -#endif - line60 = '\0'; - - sprintf (buffer, "%s%s%s%s%s%s", line1, line2, line3, line4, line5, line6); - - return day_offset; -} - - -/* Outputs the END line of the VTIMEZONE component. */ -static void -output_component_end (FILE *fp, - VzicTime *vzictime) -{ - gboolean is_daylight; - - is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; - - fprintf (fp, "END:%s\r\n", is_daylight ? "DAYLIGHT" : "STANDARD"); -} - - -/* Initializes a VzicTime to 1st Jan in YEAR_MINIMUM at midnight, with all - offsets set to 0. */ -static void -vzictime_init (VzicTime *vzictime) -{ - vzictime->year = YEAR_MINIMUM; - vzictime->month = 0; - vzictime->day_code = DAY_SIMPLE; - vzictime->day_number = 1; - vzictime->day_weekday = 0; - vzictime->time_seconds = 0; - vzictime->time_code = TIME_UNIVERSAL; - vzictime->stdoff = 0; - vzictime->walloff = 0; - vzictime->is_infinite = FALSE; - vzictime->output = FALSE; - vzictime->prev_stdoff = 0; - vzictime->prev_walloff = 0; - vzictime->tzname = NULL; -} - - -/* This calculates the actual local time that a change will occur, given - the offsets from standard and wall-clock time. It returns -1 or 1 if it - had to move backwards or forwards one day while converting to local time. - If it does this then we need to change the RRULEs we output. */ -static int -calculate_actual_time (VzicTime *vzictime, - TimeCode time_code, - int stdoff, - int walloff) -{ - GDate date; - gint day_offset, days_in_month, weekday, offset, result; - - vzictime->time_seconds = calculate_wall_time (vzictime->time_seconds, - vzictime->time_code, - stdoff, walloff, &day_offset); - - if (vzictime->day_code != DAY_SIMPLE) { - if (vzictime->year == YEAR_MINIMUM || vzictime->year == YEAR_MAXIMUM) { - fprintf (stderr, "In calculate_actual_time: invalid year\n"); - exit (0); - } - - g_date_clear (&date, 1); - days_in_month = g_date_days_in_month (vzictime->month + 1, vzictime->year); - - /* Note that the day_code refers to the date before we convert it to - a wall-clock date and time. So we find the day it was referring to, - then make any adjustments needed due to converting the time. */ - if (vzictime->day_code == DAY_LAST_WEEKDAY) { - /* Find out what day the last day of the month is. */ - g_date_set_dmy (&date, days_in_month, vzictime->month + 1, - vzictime->year); - weekday = g_date_weekday (&date) % 7; - - /* Calculate how many days we have to go back to get to day_weekday. */ - offset = (weekday + 7 - vzictime->day_weekday) % 7; - - vzictime->day_number = days_in_month - offset; - } else { - /* Find out what day day_number actually is. */ - g_date_set_dmy (&date, vzictime->day_number, vzictime->month + 1, - vzictime->year); - weekday = g_date_weekday (&date) % 7; - - if (vzictime->day_code == DAY_WEEKDAY_ON_OR_AFTER) - offset = (vzictime->day_weekday + 7 - weekday) % 7; - else - offset = - ((weekday + 7 - vzictime->day_weekday) % 7); - - vzictime->day_number = vzictime->day_number + offset; - } - - vzictime->day_code = DAY_SIMPLE; - - if (vzictime->day_number <= 0 || vzictime->day_number > days_in_month) { - fprintf (stderr, "Day overflow: %i\n", vzictime->day_number); - exit (1); - } - } - -#if 0 - fprintf (stderr, "%s -> %i/%i/%i\n", - dump_day_coded (vzictime->day_code, vzictime->day_number, - vzictime->day_weekday), - vzictime->day_number, vzictime->month + 1, vzictime->year); -#endif - - fix_time_overflow (&vzictime->year, &vzictime->month, - &vzictime->day_number, day_offset); - - /* If we want UTC time, we have to convert it now. */ - if (time_code == TIME_UNIVERSAL) { - vzictime->time_seconds = calculate_until_time (vzictime->time_seconds, - TIME_WALL, stdoff, walloff, - &vzictime->year, - &vzictime->month, - &vzictime->day_number); - } - - return day_offset; -} - - -/* This converts the given time into universal time (UTC), to be used in - the UNTIL property. */ -static int -calculate_until_time (int time, - TimeCode time_code, - int stdoff, - int walloff, - int *year, - int *month, - int *day) -{ - int result, day_offset; - - day_offset = 0; - - switch (time_code) { - case TIME_WALL: - result = time - walloff; - break; - case TIME_STANDARD: - result = time - stdoff; - break; - case TIME_UNIVERSAL: - return time; - default: - fprintf (stderr, "Invalid time code\n"); - exit (1); - } - - if (result < 0) { - result += 24 * 60 * 60; - day_offset = -1; - } else if (result >= 24 * 60 * 60) { - result -= 24 * 60 * 60; - day_offset = 1; - } - - /* Sanity check - we shouldn't have an overflow any more. */ - if (result < 0 || result >= 24 * 60 * 60) { - fprintf (stderr, "Time overflow: %i\n", result); - abort (); - } - - fix_time_overflow (year, month, day, day_offset); - - return result; -} - - -/* This converts the given time into wall clock time (the local standard time - with any adjustment for daylight-saving). */ -static int -calculate_wall_time (int time, - TimeCode time_code, - int stdoff, - int walloff, - int *day_offset) -{ - int result; - - *day_offset = 0; - - switch (time_code) { - case TIME_WALL: - return time; - case TIME_STANDARD: - /* We have a local standard time, so we have to subtract stdoff to get - back to UTC, then add walloff to get wall time. */ - result = time - stdoff + walloff; - break; - case TIME_UNIVERSAL: - result = time + walloff; - break; - default: - fprintf (stderr, "Invalid time code\n"); - exit (1); - } - - if (result < 0) { - result += 24 * 60 * 60; - *day_offset = -1; - } else if (result >= 24 * 60 * 60) { - result -= 24 * 60 * 60; - *day_offset = 1; - } - - /* Sanity check - we shouldn't have an overflow any more. */ - if (result < 0 || result >= 24 * 60 * 60) { - fprintf (stderr, "Time overflow: %i\n", result); - exit (1); - } - -#if 0 - printf ("%s -> ", dump_time (time, time_code, TRUE)); - printf ("%s (%i)\n", dump_time (result, TIME_WALL, TRUE), *day_offset); -#endif - - return result; -} - - -static void -fix_time_overflow (int *year, - int *month, - int *day, - int day_offset) -{ - if (day_offset == -1) { - *day = *day - 1; - - if (*day == 0) { - *month = *month - 1; - if (*month == -1) { - *month = 11; - *year = *year - 1; - } - *day = g_date_days_in_month (*month + 1, *year); - } - } else if (day_offset == 1) { - *day = *day + 1; - - if (*day > g_date_days_in_month (*month + 1, *year)) { - *month = *month + 1; - if (*month == 12) { - *month = 0; - *year = *year + 1; - } - *day = 1; - } - } -} - - -static char* -format_time (int year, - int month, - int day, - int time) -{ - static char buffer128; - int hour, minute, second; - - /* When we are outputting the first component year will be YEAR_MINIMUM. - We used to use 1 when outputting this, but Outlook doesn't like any years - less that 1600, so we use 1600 instead. We don't output the first change - for most zones now, so it doesn't matter too much. */ - if (year == YEAR_MINIMUM) - year = 1601; - - /* We just use 9999 here, so we keep to 4 characters. But this should only - be needed when debugging - it shouldn't be needed in the VTIMEZONEs. */ - if (year == YEAR_MAXIMUM) { - fprintf (stderr, "format_time: YEAR_MAXIMUM used\n"); - year = 9999; - } - - hour = time / 3600; - minute = (time % 3600) / 60; - second = time % 60; - - sprintf (buffer, "%04i%02i%02iT%02i%02i%02i", - year, month + 1, day, hour, minute, second); - - return buffer; -} - - -/* Outlook doesn't support 6-digit values, i.e. including the seconds, so - we round to the nearest minute. No current offsets use the seconds value, - so we aren't losing much. */ -static char* -format_tz_offset (int tz_offset, - gboolean round_seconds) -{ - static char buffer128; - char *sign = "+"; - int hours, minutes, seconds; - - if (tz_offset < 0) { - tz_offset = -tz_offset; - sign = "-"; - } - - if (round_seconds) - tz_offset += 30; - - hours = tz_offset / 3600; - minutes = (tz_offset % 3600) / 60; - seconds = tz_offset % 60; - - if (round_seconds) - seconds = 0; - - /* Sanity check. Standard timezone offsets shouldn't be much more than 12 - hours, and daylight saving shouldn't change it by more than a few hours. - (The maximum offset is 15 hours 56 minutes at present.) */ - if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60 - || seconds < 0 || seconds >= 60) { - fprintf (stderr, "WARNING: Strange timezone offset: H:%i M:%i S:%i\n", - hours, minutes, seconds); - } - - if (seconds == 0) - sprintf (buffer, "%s%02i%02i", sign, hours, minutes); - else - sprintf (buffer, "%s%02i%02i%02i", sign, hours, minutes, seconds); - - return buffer; -} - - -static gboolean -output_rrule (char *rrule_buffer, - int month, - DayCode day_code, - int day_number, - int day_weekday, - int day_offset, - char *until) -{ - char buffer1024, buffer21024; - - buffer0 = '\0'; - - if (day_offset > 1 || day_offset < -1) { - fprintf (stderr, "Invalid day_offset: %i\n", day_offset); - exit (0); - } - - /* If the DTSTART time was moved to another day when converting to local - time, we need to adjust the RRULE accordingly. e.g. If the original RRULE - was on the 19th of the month, but DTSTART was moved 1 day forward, then - we output the 20th of the month instead. */ - if (day_offset == 1) { - if (day_code != DAY_LAST_WEEKDAY) - day_number++; - day_weekday = (day_weekday + 1) % 7; - - /* Check we don't use February 29th. */ - if (month == 1 && day_number > 28) { - fprintf (stderr, "Can't format RRULE - out of bounds. Month: %i Day number: %i\n", month + 1, day_number); - exit (0); - } - - /* If we go past the end of the month, move to the next month. */ - if (day_code != DAY_LAST_WEEKDAY && day_number > DaysInMonthmonth) { - month++; - day_number = 1; - } - - } else if (day_offset == -1) { - if (day_code != DAY_LAST_WEEKDAY) - day_number--; - day_weekday = (day_weekday + 6) % 7; - - if (day_code != DAY_LAST_WEEKDAY && day_number < 1) - fprintf (stderr, "Month: %i Day number: %i\n", month + 1, day_number); - } - - switch (day_code) { - case DAY_SIMPLE: - /* Outlook (2000) will not parse the simple YEARLY RRULEs in VTIMEZONEs, - or BYMONTHDAY, or BYYEARDAY, which makes this option difficult! - Currently we use something like BYDAY=1SU, which will be incorrect - at times. This only affects Asia/Baghdad, Asia/Gaza, Asia/Jerusalem & - Asia/Damascus at present (and Jerusalem doesn't have specific rules - at the moment anyway, so that isn't a big loss). */ - if (!VzicPureOutput) { - if (day_number < 8) { - printf ("WARNING: %s: Outputting BYDAY=1SU instead of BYMONTHDAY=1-7 for Outlook compatability\n", CurrentZoneName); - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1SU", - month + 1); - } else if (day_number < 15) { - printf ("WARNING: %s: Outputting BYDAY=2SU instead of BYMONTHDAY=8-14 for Outlook compatability\n", CurrentZoneName); - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2SU", - month + 1); - } else if (day_number < 22) { - printf ("WARNING: %s: Outputting BYDAY=3SU instead of BYMONTHDAY=15-21 for Outlook compatability\n", CurrentZoneName); - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=3SU", - month + 1); - } else { - printf ("ERROR: %s: Couldn't output RRULE (day=%i) compatible with Outlook\n", CurrentZoneName, day_number); - exit (1); - } - } else { - sprintf (buffer, "RRULE:FREQ=YEARLY"); - } - break; - - case DAY_WEEKDAY_ON_OR_AFTER: - if (day_number > DaysInMonthmonth - 6) { - /* This isn't actually needed at present. */ -#if 0 - fprintf (stderr, "DAY_WEEKDAY_ON_OR_AFTER: %i %i\n", day_number, - month + 1); -#endif - - if (!VzicPureOutput) { - printf ("ERROR: %s: Couldn't output RRULE (day>=x) compatible with Outlook\n", CurrentZoneName); - exit (1); - } else { - /* We do 6 days at the end of this month, and 1 at the start of the - next. We can't do this if we want Outlook compatability, as it - needs BYMONTHDAY, which Outlook doesn't support. */ -/* - sprintf (buffer, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i;BYDAY=%s", - month + 1, - day_number, day_number + 1, day_number + 2, day_number + 3, - day_number + 4, day_number + 5, - WeekDaysday_weekday); - - sprintf (buffer2, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=1;BYDAY=%s", - (month + 1) % 12 + 1, - WeekDaysday_weekday); - - sprintf (rrule_buffer, "%s%s\n%s%s\r\n", - buffer, until, buffer2, until); -*/ - /* Multiple RRULEs within the component are illegal according to new iCal RFC 5545, - so combine the above RRULEs (commented) into a single RRULE using BYYEARDAY */ - day_number = 0; - int i; - for (i = month+1; i < 12; i++) { - day_number += DaysInMonthi; - } - sprintf (rrule_buffer, "RRULE:FREQ=YEARLY;BYYEARDAY=-%i,-%i,-%i,-%i,-%i,-%i,-%i;BYDAY=%s%s\r\n", - day_number, day_number+1, day_number+2, day_number+3, - day_number+4, day_number+5, day_number+6, WeekDaysday_weekday, until); - - return TRUE; - } - } - - if (!output_rrule_2 (buffer, month, day_number, day_weekday)) - return FALSE; - - break; - - case DAY_WEEKDAY_ON_OR_BEFORE: - if (day_number < 7) { - /* FIXME: This is unimplemented, but it isn't needed at present anway. */ - fprintf (stderr, "DAY_WEEKDAY_ON_OR_BEFORE: %i. Unimplemented. Exiting...\n", day_number); - exit (0); - } - - if (!output_rrule_2 (buffer, month, day_number - 6, day_weekday)) - return FALSE; - - break; - - case DAY_LAST_WEEKDAY: - if (day_offset == 1) { - if (month == 1) { - fprintf (stderr, "DAY_LAST_WEEKDAY - day moved, in February - can't fix\n"); - exit (0); - } - - /* This is only used once at present, for Africa/Cairo. */ -#if 0 - fprintf (stderr, "DAY_LAST_WEEKDAY - day moved\n"); -#endif - - if (!VzicPureOutput) { - printf ("WARNING: %s: Modifying RRULE (last weekday) for Outlook compatability\n", CurrentZoneName); - sprintf (buffer, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", - month + 1, WeekDaysday_weekday); - printf (" Outputting: %s\n", buffer); - } else { - /* We do 6 days at the end of this month, and 1 at the start of the - next. We can't do this if we want Outlook compatability, as it needs - BYMONTHDAY, which Outlook doesn't support. */ -/* - day_number = DaysInMonthmonth; - sprintf (buffer, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i;BYDAY=%s", - month + 1, - day_number - 5, day_number - 4, day_number - 3, - day_number - 2, day_number - 1, day_number, - WeekDaysday_weekday); - - sprintf (buffer2, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=1;BYDAY=%s", - (month + 1) % 12 + 1, - WeekDaysday_weekday); - - sprintf (rrule_buffer, "%s%s\r\n%s%s\r\n", - buffer, until, buffer2, until); -*/ - /* Multiple RRULEs within the component are illegal according to new iCal RFC 5545, - so combine the above RRULEs (commented) into a single RRULE using BYYEARDAY */ - day_number = 0; - int i; - for (i = month+1; i < 12; i++) { - day_number += DaysInMonthi; - } - sprintf (rrule_buffer, "RRULE:FREQ=YEARLY;BYYEARDAY=-%i,-%i,-%i,-%i,-%i,-%i,-%i;BYDAY=%s%s\r\n", - day_number, day_number+1, day_number+2, day_number+3, - day_number+4, day_number+5, day_number+6, WeekDaysday_weekday, until); - - return TRUE; - } - - } else if (day_offset == -1) { - /* We do 7 days 1 day before the end of this month. */ - day_number = DaysInMonthmonth; - - if (!output_rrule_2 (buffer, month, day_number - 7, day_weekday)) - return FALSE; - - sprintf (rrule_buffer, "%s%s\r\n", buffer, until); - return TRUE; - } - - sprintf (buffer, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", - month + 1, WeekDaysday_weekday); - break; - - default: - fprintf (stderr, "Invalid day code\n"); - exit (1); - } - - sprintf (rrule_buffer, "%s%s\r\n", buffer, until); - return TRUE; -} - - -/* This tries to convert a RRULE like 'BYMONTHDAY=8,9,10,11,12,13,14;BYDAY=FR' - into 'BYDAY=2FR'. We need this since Outlook doesn't accept BYMONTHDAY. - It returns FALSE if conversion is not possible. */ -static gboolean -output_rrule_2 (char *buffer, - int month, - int day_number, - int day_weekday) -{ - - if (day_number == 1) { - /* Convert it to a BYDAY=1SU type of RRULE. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1%s", - month + 1, WeekDaysday_weekday); - - } else if (day_number == 8) { - /* Convert it to a BYDAY=2SU type of RRULE. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2%s", - month + 1, WeekDaysday_weekday); - - } else if (day_number == 15) { - /* Convert it to a BYDAY=3SU type of RRULE. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=3%s", - month + 1, WeekDaysday_weekday); - - } else if (day_number == 22) { - /* Convert it to a BYDAY=4SU type of RRULE. (Currently not used.) */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=4%s", - month + 1, WeekDaysday_weekday); - - } else if (month != 1 && day_number == DaysInMonthmonth - 6) { - /* Convert it to a BYDAY=-1SU type of RRULE. (But never for February.) */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", - month + 1, WeekDaysday_weekday); - - } else { - /* Can't convert to a correct RRULE. If we want Outlook compatability we - have to use a slightly incorrect RRULE, so the time change will be 1 - week out every 7 or so years. Alternatively we could possibly move the - change by an hour or so so we would always be 1 or 2 hours out, but - never 1 week out. Yes, that sounds a better idea. */ - if (!VzicPureOutput) { - printf ("WARNING: %s: Modifying RRULE to be compatible with Outlook (day >= %i, month = %i)\n", CurrentZoneName, day_number, month + 1); - - if (day_number == 2) { - /* Convert it to a BYDAY=1SU type of RRULE. - This is needed for Asia/Karachi. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1%s", - month + 1, WeekDaysday_weekday); - } else if (day_number == 9) { - /* Convert it to a BYDAY=2SU type of RRULE. - This is needed for Antarctica/Palmer & America/Santiago. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2%s", - month + 1, WeekDaysday_weekday); - } else if (month != 1 && day_number <= DaysInMonthmonth - 7 && day_number > DaysInMonthmonth - 14) { - /* Convert it to a BYDAY=-1SU type of RRULE. (But never for February.) - This is needed for America/Godthab. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", - month + 1, WeekDaysday_weekday); - } else { - printf ("ERROR: %s: Couldn't modify RRULE to be compatible with Outlook (day >= %i, month = %i)\n", CurrentZoneName, day_number, month + 1); - exit (1); - } - - } else { - sprintf (buffer, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i,%i;BYDAY=%s", - month + 1, - day_number, day_number + 1, day_number + 2, day_number + 3, - day_number + 4, day_number + 5, day_number + 6, - WeekDaysday_weekday); - } - } - - return TRUE; -} - - -static char* -format_vzictime (VzicTime *vzictime) -{ - static char buffer1024; - - sprintf (buffer, "%s %2i %s %s %i %i %s", - dump_year (vzictime->year), vzictime->month + 1, - dump_day_coded (vzictime->day_code, vzictime->day_number, - vzictime->day_weekday), - dump_time (vzictime->time_seconds, vzictime->time_code, TRUE), - vzictime->stdoff, vzictime->walloff, - vzictime->is_infinite ? "INFINITE" : ""); - - return buffer; -} - - -static void -dump_changes (FILE *fp, - char *zone_name, - GArray *changes) -{ - VzicTime *vzictime, *vzictime2 = NULL; - int i, year_offset, year; - - for (i = 0; i < changes->len; i++) { - vzictime = &g_array_index (changes, VzicTime, i); - - if (vzictime->year > MAX_CHANGES_YEAR) - return; - - dump_change (fp, zone_name, vzictime, vzictime->year); - } - - if (changes->len < 2) - return; - - /* Now see if the changes array ends with a pair of recurring changes. */ - vzictime = &g_array_index (changes, VzicTime, changes->len - 2); - vzictime2 = &g_array_index (changes, VzicTime, changes->len - 1); - if (!vzictime->is_infinite || !vzictime2->is_infinite) - return; - - year_offset = 1; - for (;;) { - year = vzictime->year + year_offset; - if (year > MAX_CHANGES_YEAR) - break; - dump_change (fp, zone_name, vzictime, year); - - year = vzictime2->year + year_offset; - if (year > MAX_CHANGES_YEAR) - break; - dump_change (fp, zone_name, vzictime2, year); - - year_offset++; - } -} - - -static void -dump_change (FILE *fp, - char *zone_name, - VzicTime *vzictime, - int year) -{ - int hour, minute, second; - VzicTime tmp_vzictime; - static char *months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - - /* Output format is: - - Zone-Name tab Date tab Time tab UTC-Offset - - The Date and Time fields specify the time change in UTC. - - The UTC Offset is for local (wall-clock) time. It is the amount of time - to add to UTC to get local time. - */ - - fprintf (fp, "%s\t", zone_name); - - if (year == YEAR_MINIMUM) { - fprintf (fp, " 1 Jan 0001\t 0:00:00", zone_name); - } else if (year == YEAR_MAXIMUM) { - fprintf (stderr, "Maximum year found in change time\n"); - exit (1); - } else { - tmp_vzictime = *vzictime; - tmp_vzictime.year = year; - calculate_actual_time (&tmp_vzictime, TIME_UNIVERSAL, - vzictime->prev_stdoff, vzictime->prev_walloff); - - hour = tmp_vzictime.time_seconds / 3600; - minute = (tmp_vzictime.time_seconds % 3600) / 60; - second = tmp_vzictime.time_seconds % 60; - - fprintf (fp, "%2i %s %04i\t%2i:%02i:%02i", - tmp_vzictime.day_number, monthstmp_vzictime.month, - tmp_vzictime.year, hour, minute, second); - } - - fprintf (fp, "\t%s", format_tz_offset (vzictime->walloff, FALSE)); - - fprintf (fp, "\r\n"); -} - - -void -ensure_directory_exists (char *directory) -{ - struct stat filestat; - - if (stat (directory, &filestat) != 0) { - /* If the directory doesn't exist, try to create it. */ - if (errno == ENOENT) { - if (mkdir (directory, 0777) != 0) { - fprintf (stderr, "Can't create directory: %s\n", directory); - exit (1); - } - } else { - fprintf (stderr, "Error calling stat() on directory: %s\n", directory); - exit (1); - } - } else if (!S_ISDIR (filestat.st_mode)) { - fprintf (stderr, "Can't create directory, already exists: %s\n", - directory); - exit (1); - } -} - - -static void -expand_tzid_prefix (void) -{ - char *src, *dest; - char date_buf16; - char ch1, ch2; - time_t t; - struct tm *tm; - - /* Get today's date as a string in the format "YYYYMMDD". */ - t = time (NULL); - tm = localtime (&t); - sprintf (date_buf, "%4i%02i%02i", tm->tm_year + 1900, - tm->tm_mon + 1, tm->tm_mday); - - src = TZIDPrefix; - dest = TZIDPrefixExpanded; - - while (ch1 = *src++) { - - /* Look for a '%'. */ - if (ch1 == '%') { - ch2 = *src++; - - if (ch2 == 'D') { - /* '%D' gets expanded into the date string. */ - strcpy (dest, date_buf); - dest += strlen (dest); - } else if (ch2 == '%') { - /* '%%' gets converted into one '%'. */ - *dest++ = '%'; - } else { - /* Anything else is output as is. */ - *dest++ = '%'; - *dest++ = ch2; - } - } else { - *dest++ = ch1; - } - } - -#if 0 - printf ("TZID : %s\n", TZIDPrefix); - printf ("Expanded: %s\n", TZIDPrefixExpanded); -#endif -}
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-output.h
Deleted
@@ -1,38 +0,0 @@ -/* - * Vzic - a program to convert Olson timezone database files into VZTIMEZONE - * files compatible with the iCalendar specification (RFC2445). - * - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2003 Damon Chaplin. - * - * Author: Damon Chaplin <damon@gnome.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef _VZIC_OUTPUT_H_ -#define _VZIC_OUTPUT_H_ - -#include <glib.h> - -void output_vtimezone_files (char *directory, - GArray *zone_data, - GHashTable *rule_data, - GHashTable *link_data, - int max_until_year); - -void ensure_directory_exists (char *directory); - -#endif /* _VZIC_OUTPUT_H_ */
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-parse.c
Deleted
@@ -1,938 +0,0 @@ -/* - * Vzic - a program to convert Olson timezone database files into VZTIMEZONE - * files compatible with the iCalendar specification (RFC2445). - * - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2003 Damon Chaplin. - * - * Author: Damon Chaplin <damon@gnome.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - */ - -#include <ctype.h> -#include <limits.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <libgen.h> - -#include "vzic.h" -#include "vzic-parse.h" - -/* This is the maximum line length we allow. */ -#define MAX_LINE_LEN 1024 - -/* The maximum number of fields on a line. */ -#define MAX_FIELDS 12 - -#define CREATE_SYMLINK 1 - -typedef enum -{ - ZONE_ID = 0, /* The 'Zone' at the start of the line. */ - ZONE_NAME = 1, - ZONE_GMTOFF = 2, - ZONE_RULES_SAVE = 3, - ZONE_FORMAT = 4, - ZONE_UNTIL_YEAR = 5, - ZONE_UNTIL_MONTH = 6, - ZONE_UNTIL_DAY = 7, - ZONE_UNTIL_TIME = 8 -} ZoneFieldNumber; - - -typedef enum -{ - RULE_ID = 0, /* The 'Rule' at the start of the line. */ - RULE_NAME = 1, - RULE_FROM = 2, - RULE_TO = 3, - RULE_TYPE = 4, - RULE_IN = 5, - RULE_ON = 6, - RULE_AT = 7, - RULE_SAVE = 8, - RULE_LETTER_S = 9 -} RuleFieldNumber; - - -typedef enum -{ - LINK_ID = 0, /* The 'Link' at the start of the line. */ - LINK_FROM = 1, - LINK_TO = 2 -} LinkFieldNumber; - - -/* This struct contains information used while parsing the files, and is - passed to most parsing functions. */ -typedef struct _ParsingData ParsingData; -struct _ParsingData -{ - /* This is the line being parsed. buffer is a copy that we break into fields - and sub-fields as it is parsed. */ - char lineMAX_LINE_LEN; - char bufferMAX_LINE_LEN; - - /* These are pointers to the start of each field in buffer. */ - char *fieldsMAX_FIELDS; - int num_fields; - - /* These are just for producing error messages. */ - char *filename; - int line_number; - - - /* This is an array of ZoneData structs, 1 for each timezone read. */ - GArray *zone_data; - - /* This is a hash table of arrays of RuleData structs. As each Rule line is - read in, a new RuleData struct is filled in and appended to the - appropriate GArray in the hash table. */ - GHashTable *rule_data; - - /* A hash containing data on the Link lines. The keys are the timezones - where the link is from (i.e. the timezone we will be outputting anyway) - and the data is a GList of timezones to link to (where we will copy the - timezone data to). */ - GHashTable *link_data; - - int max_until_year; -}; - - -/* - * Parsing functions, used when reading the Olson timezone data file. - */ -static void parse_fields (ParsingData *data); -static gboolean parse_zone_line (ParsingData *data); -static gboolean parse_zone_continuation_line (ParsingData *data); -static gboolean parse_zone_common (ParsingData *data, - int offset); -static void parse_rule_line (ParsingData *data); -static void parse_link_line (ParsingData *data); - -static int parse_year (ParsingData *data, - char *field, - gboolean accept_only, - int only_value); -static int parse_month (ParsingData *data, - char *field); -static DayCode parse_day (ParsingData *data, - char *field, - int *day, - int *weekday); -static int parse_weekday (ParsingData *data, - char *field); -static int parse_time (ParsingData *data, - char *field, - TimeCode *time_code); -static int parse_number (ParsingData *data, - char **num); -static int parse_rules_save (ParsingData *data, - char *field, - char **rules); - -static void parse_coord (char *coord, - int len, - int *result); - -void -parse_olson_file (char *filename, - GArray **zone_data, - GHashTable **rule_data, - GHashTable **link_data, - int *max_until_year) -{ - ParsingData data; - FILE *fp; - int zone_continues = 0; - - *zone_data = g_array_new (FALSE, FALSE, sizeof (ZoneData)); - *rule_data = g_hash_table_new (g_str_hash, g_str_equal); - *link_data = g_hash_table_new (g_str_hash, g_str_equal); - - fp = fopen (filename, "r"); - if (!fp) { - fprintf (stderr, "Couldn't open file: %s\n", filename); - exit (1); - } - - data.filename = filename; - data.zone_data = *zone_data; - data.rule_data = *rule_data; - data.link_data = *link_data; - data.max_until_year = 0; - - for (data.line_number = 0; ; data.line_number++) { - if (fgets (data.line, sizeof (data.line), fp) != data.line) - break; - - strcpy (data.buffer, data.line); - - parse_fields (&data); - if (data.num_fields == 0) - continue; - - if (zone_continues) { - zone_continues = parse_zone_continuation_line (&data); - } else if (!strcmp (data.fields0, "Zone")) { - zone_continues = parse_zone_line (&data); - } else if (!strcmp (data.fields0, "Rule")) { - parse_rule_line (&data); - } else if (!strcmp (data.fields0, "Link")) { - parse_link_line (&data); - } else if (!strcmp (data.fields0, "Leap")) { - /* We don't care about Leap lines. */ - } else { - fprintf (stderr, "%s:%i: Invalid line.\n%s\n", filename, - data.line_number, data.line); - exit (1); - } - } - - if (ferror (fp)) { - fprintf (stderr, "Error reading file: %s\n", filename); - exit (1); - } - - if (zone_continues) { - fprintf (stderr, "%s:%i: Zone continuation line expected.\n%s\n", - filename, data.line_number, data.line); - exit (1); - } - - fclose (fp); - -#if 0 - printf ("Max UNTIL year: %i\n", data.max_until_year); -#endif - *max_until_year = data.max_until_year; -} - - -/* Converts the line into fields. */ -static void -parse_fields (ParsingData *data) -{ - int i; - char *p, *s, ch; - - /* Reset all fields to NULL. */ - for (i = 0; i < MAX_FIELDS; i++) - data->fieldsi = 0; - - data->num_fields = 0; - p = data->buffer; - - for (;;) { - /* Skip whitespace. */ - while (isspace (*p)) - p++; - - /* See if we have reached the end of the line or a comment. */ - if (*p == '\0' || *p == '#') - break; - - /* We must have another field, so save the start position. */ - data->fieldsdata->num_fields++ = p; - - /* Now find the end of the field. If the field contains '"' characters - they are removed and we have to move the rest of the chars back. */ - s = p; - for (;;) { - ch = *p; - if (ch == '\0' || ch == '#') { - /* Don't move p on since this is the end of the line. */ - *s = '\0'; - break; - } else if (isspace (ch)) { - *s = '\0'; - p++; - break; - } else if (ch == '"') { - p++; - for (;;) { - ch = *p; - if (ch == '\0') { - fprintf (stderr, - "%s:%i: Closing quote character ('\"') missing.\n%s\n", - data->filename, data->line_number, data->line); - exit (1); - } else if (ch == '"') { - p++; - break; - } else { - *s++ = ch; - } - p++; - } - } else { - *s++ = ch; - } - p++; - } - } - -#if 0 - printf ("%i fields: ", data->num_fields); - for (i = 0; i < data->num_fields; i++) - printf ("'%s' ", data->fieldsi); - printf ("\n"); -#endif -} - - -static gboolean -parse_zone_line (ParsingData *data) -{ - ZoneData zone; - - /* All 5 fields up to FORMAT must be present. */ - if (data->num_fields < 5 || data->num_fields > 9) { - fprintf (stderr, "%s:%i: Invalid Zone line - %i fields.\n%s\n", - data->filename, data->line_number, data->num_fields, - data->line); - exit (1); - } - - zone.zone_name = g_strdup (data->fieldsZONE_NAME); - zone.zone_line_data = g_array_new (FALSE, FALSE, sizeof (ZoneLineData)); - - g_array_append_val (data->zone_data, zone); - - return parse_zone_common (data, 0); -} - - -static gboolean -parse_zone_continuation_line (ParsingData *data) -{ - /* All 3 fields up to FORMAT must be present. */ - if (data->num_fields < 3 || data->num_fields > 7) { - fprintf (stderr, - "%s:%i: Invalid Zone continuation line - %i fields.\n%s\n", - data->filename, data->line_number, data->num_fields, - data->line); - exit (1); - } - - return parse_zone_common (data, -2); -} - - -static gboolean -parse_zone_common (ParsingData *data, - int offset) -{ - ZoneData *zone; - ZoneLineData zone_line; - TimeCode time_code; - - zone_line.stdoff_seconds = parse_time (data, - data->fieldsZONE_GMTOFF + offset, - &time_code); - zone_line.save_seconds = parse_rules_save (data, - data->fieldsZONE_RULES_SAVE + offset, - &zone_line.rules); - - if (!VzicPureOutput) { - /* We round the UTC offsets to the nearest minute, to be compatible with - Outlook. This also works with -ve numbers, I think. - -56 % 60 = -59. -61 % 60 = -1. */ - if (zone_line.stdoff_seconds >= 0) - zone_line.stdoff_seconds += 30; - else - zone_line.stdoff_seconds -= 29; - zone_line.stdoff_seconds -= zone_line.stdoff_seconds % 60; - - if (zone_line.save_seconds >= 0) - zone_line.save_seconds += 30; - else - zone_line.save_seconds -= 29; - zone_line.save_seconds -= zone_line.save_seconds % 60; - } - - zone_line.format = g_strdup (data->fieldsZONE_FORMAT + offset); - - if (data->num_fields - offset >= 6) { - zone_line.until_set = TRUE; - zone_line.until_year = parse_year (data, - data->fieldsZONE_UNTIL_YEAR + offset, - FALSE, 0); - zone_line.until_month = parse_month (data, - data->fieldsZONE_UNTIL_MONTH + offset); - zone_line.until_day_code = parse_day (data, - data->fieldsZONE_UNTIL_DAY + offset, - &zone_line.until_day_number, - &zone_line.until_day_weekday); - zone_line.until_time_seconds = parse_time (data, - data->fieldsZONE_UNTIL_TIME + offset, - &zone_line.until_time_code); - - /* We also want to know the maximum year used in any UNTIL value, so we - know where to expand all the infinite Rule data to. */ - if (zone_line.until_year != YEAR_MAXIMUM - && zone_line.until_year != YEAR_MINIMUM) - data->max_until_year = MAX (data->max_until_year, zone_line.until_year); - - } else { - zone_line.until_set = FALSE; - } - - /* Append it to the last Zone, since that is the one we are currently - reading. */ - zone = &g_array_index (data->zone_data, ZoneData, data->zone_data->len - 1); - g_array_append_val (zone->zone_line_data, zone_line); - - return zone_line.until_set; -} - - -static void -parse_rule_line (ParsingData *data) -{ - GArray *rule_array; - RuleData rule; - char *name; - TimeCode time_code; - - /* All 10 fields must be present. */ - if (data->num_fields != 10) { - fprintf (stderr, "%s:%i: Invalid Rule line - %i fields.\n%s\n", - data->filename, data->line_number, data->num_fields, - data->line); - exit (1); - } - - name = data->fieldsRULE_NAME; - - /* Create the GArray and add it to the hash table if it doesn't already - exist. */ - rule_array = g_hash_table_lookup (data->rule_data, name); - if (!rule_array) { - rule_array = g_array_new (FALSE, FALSE, sizeof (RuleData)); - g_hash_table_insert (data->rule_data, g_strdup (name), rule_array); - } - - rule.from_year = parse_year (data, data->fieldsRULE_FROM, FALSE, 0); - if (rule.from_year == YEAR_MAXIMUM) { - fprintf (stderr, "%s:%i: Invalid Rule FROM value: '%s'\n", - data->filename, data->line_number, data->fieldsRULE_FROM); - exit (1); - } - - rule.to_year = parse_year (data, data->fieldsRULE_TO, TRUE, - rule.from_year); - if (rule.to_year == YEAR_MINIMUM) { - fprintf (stderr, "%s:%i: Invalid Rule TO value: %s\n", - data->filename, data->line_number, data->fieldsRULE_TO); - exit (1); - } - - if (!strcmp (data->fieldsRULE_TYPE, "-")) - rule.type = NULL; - else { - printf ("Type: %s\n", data->fieldsRULE_TYPE); - rule.type = g_strdup (data->fieldsRULE_TYPE); - } - - rule.in_month = parse_month (data, data->fieldsRULE_IN); - rule.on_day_code = parse_day (data, data->fieldsRULE_ON, - &rule.on_day_number, &rule.on_day_weekday); - rule.at_time_seconds = parse_time (data, data->fieldsRULE_AT, - &rule.at_time_code); - rule.save_seconds = parse_time (data, data->fieldsRULE_SAVE, &time_code); - - if (!strcmp (data->fieldsRULE_LETTER_S, "-")) { - rule.letter_s = NULL; - } else { - rule.letter_s = g_strdup (data->fieldsRULE_LETTER_S); - } - - rule.is_shallow_copy = FALSE; - - g_array_append_val (rule_array, rule); -} - - -static void -parse_link_line (ParsingData *data) -{ - char *from, *to, *old_from; - GList *zone_list; - - /* We must have 3 fields for a Link. */ - if (data->num_fields != 3) { - fprintf (stderr, "%s:%i: Invalid Rule line - %i fields.\n%s\n", - data->filename, data->line_number, data->num_fields, - data->line); - exit (1); - } - - from = data->fieldsLINK_FROM; - to = data->fieldsLINK_TO; - -#if 0 - printf ("LINK FROM: %s\tTO: %s\n", from, to); -#endif - -#if CREATE_SYMLINK - { - int len = strnlen(to,254); - int dirs = 0; - int i; - for (i = 0; i < len; i++) { - dirs += toi == '/' ? 1 : 0; - } - if (dirs) { - char rel_from255; - char to_dir255; - char to_path255; - if (dirs == 1) { - sprintf(rel_from, "../%s.ics", from); - } else if (dirs == 2) { - sprintf(rel_from, "../../%s.ics", from); - } else { - return; - } - sprintf(to_path, "%s/%s.ics", VzicOutputDir, to); - strncpy(to_dir, to_path, 254); - ensure_directory_exists(dirname(to_dir)); - //printf("Creating symlink from %s to %s\n", rel_from, to_path); - symlink(rel_from, to_path); - } - } -#else - if (g_hash_table_lookup_extended (data->link_data, from, - (gpointer) &old_from, - (gpointer) &zone_list)) { - from = old_from; - } else { - from = g_strdup (from); - zone_list = NULL; - } - - zone_list = g_list_prepend (zone_list, g_strdup (to)); - - g_hash_table_insert (data->link_data, from, zone_list); -#endif -} - - -static int -parse_year (ParsingData *data, - char *field, - gboolean accept_only, - int only_value) -{ - int len, year = 0; - char *p; - - if (!field) { - fprintf (stderr, "%s:%i: Missing year.\n%s\n", data->filename, - data->line_number, data->line); - exit (1); - } - - len = strlen (field); - if (accept_only && !strncmp (field, "only", len)) - return only_value; - if (len >= 2) { - if (!strncmp (field, "maximum", len)) - return YEAR_MAXIMUM; - else if (!strncmp (field, "minimum", len)) - return YEAR_MINIMUM; - } - - for (p = field; *p; p++) { - if (*p < '0' || *p > '9') { - fprintf (stderr, "%s:%i: Invalid year: %s\n%s\n", data->filename, - data->line_number, field, data->line); - exit (1); - } - - year = year * 10 + *p - '0'; - } - - if (year < 1000 || year > 2038) { - fprintf (stderr, "%s:%i: Strange year: %s\n%s\n", data->filename, - data->line_number, field, data->line); - exit (1); - } - - return year; -} - - -/* Parses a month name, returning 0 (Jan) to 11 (Dec). */ -static int -parse_month (ParsingData *data, - char *field) -{ - static char* months = { "january", "february", "march", "april", "may", - "june", "july", "august", "september", "october", - "november", "december" }; - char *p; - int len, i; - - /* If the field is missing, it must be the optional UNTIL month, so we return - 0 for January. */ - if (!field) - return 0; - - for (p = field, len = 0; *p; p++, len++) { - *p = tolower (*p); - } - - for (i = 0; i < 12; i++) { - if (!strncmp (field, monthsi, len)) - return i; - } - - fprintf (stderr, "%s:%i: Invalid month: %s\n%s\n", data->filename, - data->line_number, field, data->line); - exit (1); -} - - -/* Parses a day specifier, returning a code representing the type of match - together with a day of the month and a weekday number (0=Sun). */ -static DayCode -parse_day (ParsingData *data, - char *field, - int *day, - int *weekday) -{ - char *day_part, *p; - DayCode day_code; - - if (!field) { - *day = 1; - return DAY_SIMPLE; - } - - *day = *weekday = 0; - - if (!strncmp (field, "last", 4)) { - *weekday = parse_weekday (data, field + 4); - /* We set the day to the end of the month to make sorting Rules easy. */ - *day = 31; - return DAY_LAST_WEEKDAY; - } - - day_part = field; - day_code = DAY_SIMPLE; - - for (p = field; *p; p++) { - if (*p == '<' || *p == '>') { - if (*(p + 1) == '=') { - day_code = (*p == '<') ? DAY_WEEKDAY_ON_OR_BEFORE - : DAY_WEEKDAY_ON_OR_AFTER; - *p = '\0'; - *weekday = parse_weekday (data, field); - day_part = p + 2; - break; - } - - fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename, - data->line_number, field, data->line); - exit (1); - } - } - - for (p = day_part; *p; p++) { - if (*p < '0' || *p > '9') { - fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename, - data->line_number, field, data->line); - exit (1); - } - - *day = *day * 10 + *p - '0'; - } - - if (*day < 1 || *day > 31) { - fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename, - data->line_number, field, data->line); - exit (1); - } - - return day_code; -} - - -/* Parses a weekday name, returning 0 (Sun) to 6 (Sat). */ -static int -parse_weekday (ParsingData *data, - char *field) -{ - static char* weekdays = { "sunday", "monday", "tuesday", "wednesday", - "thursday", "friday", "saturday" }; - char *p; - int len, i; - - for (p = field, len = 0; *p; p++, len++) { - *p = tolower (*p); - } - - for (i = 0; i < 7; i++) { - if (!strncmp (field, weekdaysi, len)) - return i; - } - - fprintf (stderr, "%s:%i: Invalid weekday: %s\n%s\n", data->filename, - data->line_number, field, data->line); - exit (1); -} - - -/* Parses a time (hour + minute + second) and returns the result in seconds, - together with a time code specifying whether it is Wall clock time, - local standard time, or universal time. - The time can start with a '-' in which case it will be negative. */ -static int -parse_time (ParsingData *data, - char *field, - TimeCode *time_code) -{ - char *p; - int hours = 0, minutes = 0, seconds = 0, result, negative = 0; - - if (!field) { - *time_code = TIME_WALL; - return 0; - } - - p = field; - if (*p == '-') { - p++; - negative = 1; - } - - hours = parse_number (data, &p); - - if (*p == ':') { - p++; - minutes = parse_number (data, &p); - - if (*p == ':') { - p++; - seconds = parse_number (data, &p); - } - } - - if (hours < 0 || hours > 24 - || minutes < 0 || minutes > 59 - || seconds < 0 || seconds > 59 - || (hours == 24 && (minutes != 0 || seconds != 0))) { - fprintf (stderr, "%s:%i: Invalid time: %s\n%s\n", data->filename, - data->line_number, field, data->line); - exit (1); - } - - if (hours == 24) { - hours = 23; - minutes = 59; - seconds = 59; - } - -#if 0 - printf ("Time: %s -> %i:%02i:%02i\n", field, hours, minutes, seconds); -#endif - - result = hours * 3600 + minutes * 60 + seconds; - if (negative) - result = -result; - - if (*p == '\0') { - *time_code = TIME_WALL; - return result; - } - - if (*(p + 1) == '\0') { - if (*p == 'w') { - *time_code = TIME_WALL; - return result; - } else if (*p == 's') { - *time_code = TIME_STANDARD; - return result; - } else if (*p == 'u' || *p == 'g' || *p == 'z') { - *time_code = TIME_UNIVERSAL; - return result; - } - } - - fprintf (stderr, "%s:%i: Invalid time: %s\n%s\n", data->filename, - data->line_number, field, data->line); - exit (1); -} - - -/* Parses a simple number and returns the result. The pointer argument - is moved to the first character after the number. */ -static int -parse_number (ParsingData *data, - char **num) -{ - char *p; - int result; - - p = *num; - -#if 0 - printf ("In parse_number p:%s\n", p); -#endif - - // potential null value where '-' is specified. assume zero. - if (*p == NULL) { - return 0; - } - - if (*p < '0' || *p > '9') { - fprintf (stderr, "%s:%i: Invalid number: %s\n%s\n", data->filename, - data->line_number, *num, data->line); - exit (1); - } - - result = *p++ - '0'; - - while (*p >= '0' && *p <= '9') - result = result * 10 + *p++ - '0'; - - *num = p; - return result; -} - - -static int -parse_rules_save (ParsingData *data, - char *field, - char **rules) -{ - TimeCode time_code; - - *rules = NULL; - - /* Check for just "-". */ - if (field0 == '-' && field1 == '\0') - return 0; - - /* Check for a time to add to local standard time. We don't care about a - time code here, since it is just an offset. */ - if (*field == '-' || (*field >= '0' && *field <= '9')) - return parse_time (data, field, &time_code); - - /* It must be a rules name. */ - *rules = g_strdup (field); - return 0; -} - - - - - -GHashTable* -parse_zone_tab (char *filename) -{ - GHashTable *zones_hash; - ZoneDescription *zone_desc; - FILE *fp; - char buf4096; - gchar **fields, *zone_name, *latitude, *longitude, *p; - - - fp = fopen (filename, "r"); - if (!fp) { - fprintf (stderr, "Couldn't open file: %s\n", filename); - exit (1); - } - - zones_hash = g_hash_table_new (g_str_hash, g_str_equal); - - while (fgets (buf, sizeof(buf), fp)) { - if (*buf == '#') continue; - - g_strchomp (buf); - fields = g_strsplit (buf,"\t", 4); - - if (strlen (fields0) != 2) { - fprintf (stderr, "Invalid zone description line: %s\n", buf); - exit (1); - } - - zone_name = g_strdup (fields2); - - zone_desc = g_new (ZoneDescription, 1); - zone_desc->country_code0 = fields00; - zone_desc->country_code1 = fields01; - zone_desc->comment = (fields3 && fields30) ? g_strdup (fields3) - : NULL; - - /* Now parse the latitude and longitude. */ - latitude = fields1; - longitude = latitude + 1; - while (*longitude != '+' && *longitude != '-') - longitude++; - - parse_coord (latitude, longitude - latitude, zone_desc->latitude); - parse_coord (longitude, strlen (longitude), zone_desc->longitude); - - g_hash_table_insert (zones_hash, zone_name, zone_desc); - -#if 0 - g_print ("Found zone: %s %i %02i %02i,%i %02i %02i\n", zone_name, - zone_desc->latitude0, zone_desc->latitude1, - zone_desc->latitude2, - zone_desc->longitude0, zone_desc->longitude1, - zone_desc->longitude2); -#endif - } - - fclose (fp); - - return zones_hash; -} - - -static void -parse_coord (char *coord, - int len, - int *result) -{ - int degrees = 0, minutes = 0, seconds = 0; - - if (len == 5) - sscanf (coord + 1, "%2d%2d", °rees, &minutes); - else if (len == 6) - sscanf (coord + 1, "%3d%2d", °rees, &minutes); - else if (len == 7) - sscanf (coord + 1, "%2d%2d%2d", °rees, &minutes, &seconds); - else if (len == 8) - sscanf (coord + 1, "%3d%2d%2d", °rees, &minutes, &seconds); - else { - fprintf (stderr, "Invalid coordinate: %s\n", coord); - exit (1); - } - - if (coord0 == '-') - degrees = -degrees; - - result0 = degrees; - result1 = minutes; - result2 = seconds; -} -
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-parse.h
Deleted
@@ -1,38 +0,0 @@ -/* - * Vzic - a program to convert Olson timezone database files into VZTIMEZONE - * files compatible with the iCalendar specification (RFC2445). - * - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2003 Damon Chaplin. - * - * Author: Damon Chaplin <damon@gnome.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef _VZIC_PARSE_H_ -#define _VZIC_PARSE_H_ - -#include <glib.h> - -void parse_olson_file (char *filename, - GArray **zone_data, - GHashTable **rule_data, - GHashTable **link_data, - int *max_until_year); - -GHashTable* parse_zone_tab (char *filename); - -#endif /* _VZIC_PARSE_H_ */
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic-test.pl
Deleted
@@ -1,164 +0,0 @@ -#!/usr/bin/perl -w - -# -# Vzic - a program to convert Olson timezone database files into VZTIMEZONE -# files compatible with the iCalendar specification (RFC2445). -# -# Copyright (C) 2001 Ximian, Inc. -# Copyright (C) 2003 Damon Chaplin. -# -# Author: Damon Chaplin <damon@gnome.org> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. -# - -# -# This outputs an iCalendar file containing one event in each timezone, -# as well as all the VTIMEZONEs. We use it for testing compatability with -# other iCalendar apps like Outlook, by trying to import it there. -# -# Currently we have 377 timezones (with tzdata2001d). -# - -# Set this to the toplevel directory of the VTIMEZONE files. -$ZONEINFO_DIR = "/home/damon/src/zoneinfo"; - -$output_file = "calendar.ics"; - - -# Save this so we can restore it later. -$input_record_separator = $/; - -chdir $ZONEINFO_DIR - || die "Can't cd to $ZONEINFO_DIR"; - -# Create the output file, to contain all the VEVENTs & VTIMEZONEs. -open (OUTPUTFILE, ">$output_file") - || die "Can't create file: $output_file"; - -# Output the standard header. - print OUTPUTFILE <<EOF; -BEGIN:VCALENDAR -PRODID:-//Ximian//NONSGML Vzic Test//EN -VERSION:2.0 -METHOD:PUBLISH -EOF - -$zone_num = 0; - -# 365 days in a non-leap year. -@days_in_month = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ); - -foreach $file (`find -name "*.ics"`) { - # Get rid of './' at start and whitespace at end. - $file =~ s/^\.\///; - $file =~ s/\s+$//; - - if ($file eq $output_file) { - next; - } - -# print "File: $file\n"; - - # Get the VTIMEZONE data. - open (ZONEFILE, "$file") - || die "Can't open file: $ZONEINFO_DIR/$file"; - undef $/; - $vtimezone = <ZONEFILE>; - $/ = $input_record_separator; - close (ZONEFILE); - - # Strip the stuff before and after the VTIMEZONE component - $vtimezone =~ s/^.*BEGIN:VTIMEZONE/BEGIN:VTIMEZONE/s; - $vtimezone =~ s/END:VTIMEZONE.*$/END:VTIMEZONE\n/s; - - print OUTPUTFILE $vtimezone; - - # Find the TZID. - $vtimezone =~ m/TZID:(.*)/; - $tzid = $1; -# print "TZID: $tzid\n"; - - # Find the location. - $file =~ m/(.*)\.ics/; - $location = $1; -# print "LOCATION: $location\n"; - - # Try to find the current UTC offset that Outlook will use. - # If there is an RRULE, we look for the first 2 TZOFFSETTO properties, - # else we just get the first one. - if ($vtimezone =~ m/RRULE/) { - $vtimezone =~ m/TZOFFSETTO:(+-?\d+)/; - $tzoffsetto = $1; - $vtimezone =~ m/TZOFFSETFROM:(+-?\d+)/; - $tzoffsetfrom = $1; - $tzoffset = "$tzoffsetfrom/$tzoffsetto"; - } else { - $vtimezone =~ m/TZOFFSETTO:(+-?\d+)/s; - $tzoffset = $1; - } -# print "TZOFFSET: $tzoffset\n"; - - # We put each event on a separate day in 2001 and Jan 2002. - $day_num = $zone_num; - if ($day_num >= 365) { - $year = 2002; - $day_num -= 365; - } else { - $year = 2001; - } - $month = -1; - for ($i = 0; $i < 12; $i++) { - if ($day_num < $days_in_month$i) { - $month = $i; - last; - } - $day_num -= $days_in_month$i - } - if ($month == -1) { - die "month = -1"; - } - - $month++; - $day_num++; - $date = sprintf ("%i%02i%02i", $year, $month, $day_num); -# print "Date: $date\n"; - - # Output a VEVENT using the timezone. - print OUTPUTFILE <<EOF; -BEGIN:VEVENT -UID:vzic-test-${zone_num} -DTSTAMP:20010101T000000Z -DTSTART;TZID=${tzid}:${date}T120000 -DTEND;TZID=${tzid}:${date}T130000 -RRULE:FREQ=MONTHLY;BYMONTHDAY=${day_num} -SUMMARY:($tzoffset) ${location} 12:00-13:00 UTC -SEQUENCE:1 -END:VEVENT -EOF - - $zone_num++; - - # Use this to stop after a certain number. -# last if ($zone_num == 100); -} - -# Output the standard footer. - print OUTPUTFILE <<EOF; -END:VCALENDAR -EOF - -close (OUTPUTFILE); -
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic.c
Deleted
@@ -1,321 +0,0 @@ -/* - * Vzic - a program to convert Olson timezone database files into VZTIMEZONE - * files compatible with the iCalendar specification (RFC2445). - * - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2003 Damon Chaplin. - * - * Author: Damon Chaplin <damon@gnome.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "vzic.h" -#include "vzic-parse.h" -#include "vzic-dump.h" -#include "vzic-output.h" - - -/* - * Global command-line options. - */ - -/* By default we output Outlook-compatible output. If --pure is used we - output pure output, with no changes to be compatible with Outlook. */ -gboolean VzicPureOutput = FALSE; - -gboolean VzicDumpOutput = FALSE; -gboolean VzicDumpChanges = FALSE; -gboolean VzicDumpZoneNamesAndCoords = FALSE; -gboolean VzicDumpZoneTranslatableStrings= TRUE; -gboolean VzicNoRRules = FALSE; -gboolean VzicNoRDates = FALSE; -char* VzicOutputDir = "zoneinfo"; -char* VzicUrlPrefix = NULL; -char* VzicOlsonDir = OLSON_DIR; - -GList* VzicTimeZoneNames = NULL; - -static void convert_olson_file (char *olson_file); - -static void usage (void); - -static void free_zone_data (GArray *zone_data); -static void free_rule_array (gpointer key, - gpointer value, - gpointer data); -static void free_link_data (gpointer key, - gpointer value, - gpointer data); - - -int -main (int argc, - char *argv) -{ - int i; - char directoryPATHNAME_BUFFER_SIZE; - char filenamePATHNAME_BUFFER_SIZE; - GHashTable *zones_hash; - - /* - * Command-Line Option Parsing. - */ - for (i = 1; i < argc; i++) { - /* - * User Options. - */ - - /* --pure: Output the perfect VCALENDAR data, which Outlook won't parse - as it has problems with certain iCalendar constructs. */ - if (!strcmp (argvi, "--pure")) - VzicPureOutput = TRUE; - - /* --output-dir: specify where to output all the files beneath. The - default is the current directory. */ - else if (argc > i + 1 && !strcmp (argvi, "--output-dir")) - VzicOutputDir = argv++i; - - /* --url-prefix: Used as the base for the TZURL property in each - VTIMEZONE. The default is to not output TZURL properties. */ - else if (argc > i + 1 && !strcmp (argvi, "--url-prefix")) { - int length; - VzicUrlPrefix = argv++i; - /* remove the trailing '/' if there is one */ - length = strlen (VzicUrlPrefix); - if (VzicUrlPrefixlength - 1 == '/') - VzicUrlPrefixlength - 1 = '\0'; - } - - else if (argc > i + 1 && !strcmp (argvi, "--olson-dir")) { - VzicOlsonDir = argv++i; - } - - /* - * Debugging Options. - */ - - /* --dump: Dump the Rule and Zone data that we parsed from the Olson - timezone files. This is used to test the parsing code. */ - else if (!strcmp (argvi, "--dump")) - VzicDumpOutput = TRUE; - - /* --dump-changes: Dumps a list of times when each timezone changed, - and the new local time offset from UTC. */ - else if (!strcmp (argvi, "--dump-changes")) - VzicDumpChanges = TRUE; - - /* --no-rrules: Don't output RRULE properties in the VTIMEZONEs. Instead - it will just output RDATEs for each year up to a certain year. */ - else if (!strcmp (argvi, "--no-rrules")) - VzicNoRRules = TRUE; - - /* --no-rdates: Don't output multiple RDATEs in a single VTIMEZONE - component. Instead they will be output separately. */ - else if (!strcmp (argvi, "--no-rdates")) - VzicNoRDates = TRUE; - - else - usage (); - } - - /* - * Create any necessary directories. - */ - ensure_directory_exists (VzicOutputDir); - - if (VzicDumpOutput) { - /* Create the directories for the dump output, if they don't exist. */ - sprintf (directory, "%s/ZonesVzic", VzicOutputDir); - ensure_directory_exists (directory); - sprintf (directory, "%s/RulesVzic", VzicOutputDir); - ensure_directory_exists (directory); - } - - if (VzicDumpChanges) { - /* Create the directory for the changes output, if it doesn't exist. */ - sprintf (directory, "%s/ChangesVzic", VzicOutputDir); - ensure_directory_exists (directory); - } - - /* - * Convert the Olson timezone files. - */ - convert_olson_file ("africa"); - convert_olson_file ("antarctica"); - convert_olson_file ("asia"); - convert_olson_file ("australasia"); - convert_olson_file ("europe"); - convert_olson_file ("northamerica"); - convert_olson_file ("southamerica"); - - /* These are backwards-compatability and weird stuff. */ - convert_olson_file ("backward"); -#if 0 - convert_olson_file ("etcetera"); - convert_olson_file ("leapseconds"); - convert_olson_file ("pacificnew"); - convert_olson_file ("solar87"); - convert_olson_file ("solar88"); - convert_olson_file ("solar89"); -#endif - - /* This doesn't really do anything and it messes up vzic-dump.pl so we - don't bother. */ -#if 0 - convert_olson_file ("factory"); -#endif - - /* This is old System V stuff, which we don't currently support since it - uses 'min' as a Rule FROM value which messes up our algorithm, making - it too slow and use too much memory. */ -#if 0 - convert_olson_file ("systemv"); -#endif - - /* Output the timezone names and coordinates in a zone.tab file, and - the translatable strings to feed to gettext. */ - if (VzicDumpZoneNamesAndCoords) { - sprintf (filename, "%s/zone.tab", VzicOlsonDir); - zones_hash = parse_zone_tab (filename); - - dump_time_zone_names (VzicTimeZoneNames, VzicOutputDir, zones_hash); - } - - return 0; -} - - -static void -convert_olson_file (char *olson_file) -{ - char input_filenamePATHNAME_BUFFER_SIZE; - GArray *zone_data; - GHashTable *rule_data, *link_data; - char dump_filenamePATHNAME_BUFFER_SIZE; - ZoneData *zone; - int i, max_until_year; - - sprintf (input_filename, "%s/%s", VzicOlsonDir, olson_file); - - parse_olson_file (input_filename, &zone_data, &rule_data, &link_data, - &max_until_year); - - if (VzicDumpOutput) { - sprintf (dump_filename, "%s/ZonesVzic/%s", VzicOutputDir, olson_file); - dump_zone_data (zone_data, dump_filename); - - sprintf (dump_filename, "%s/RulesVzic/%s", VzicOutputDir, olson_file); - dump_rule_data (rule_data, dump_filename); - } - - output_vtimezone_files (VzicOutputDir, zone_data, rule_data, link_data, - max_until_year); - - free_zone_data (zone_data); - g_hash_table_foreach (rule_data, free_rule_array, NULL); - g_hash_table_destroy (rule_data); - g_hash_table_foreach (link_data, free_link_data, NULL); - g_hash_table_destroy (link_data); -} - - -static void -usage (void) -{ - fprintf (stderr, "Usage: vzic --dump --dump-changes --no-rrules --no-rdates --pure --output-dir <directory> --url-prefix <url> --olson-dir <directory>\n"); - - exit (1); -} - - - - -/* - * Functions to free the data structures. - */ - -static void -free_zone_data (GArray *zone_data) -{ - ZoneData *zone; - ZoneLineData *zone_line; - int i, j; - - for (i = 0; i < zone_data->len; i++) { - zone = &g_array_index (zone_data, ZoneData, i); - - g_free (zone->zone_name); - - for (j = 0; j < zone->zone_line_data->len; j++) { - zone_line = &g_array_index (zone->zone_line_data, ZoneLineData, j); - - g_free (zone_line->rules); - g_free (zone_line->format); - } - - g_array_free (zone->zone_line_data, TRUE); - } - - g_array_free (zone_data, TRUE); -} - - -static void -free_rule_array (gpointer key, - gpointer value, - gpointer data) -{ - char *name = key; - GArray *rule_array = value; - RuleData *rule; - int i; - - for (i = 0; i < rule_array->len; i++) { - rule = &g_array_index (rule_array, RuleData, i); - - if (!rule->is_shallow_copy) { - g_free (rule->type); - g_free (rule->letter_s); - } - } - - g_array_free (rule_array, TRUE); - - g_free (name); -} - - -static void -free_link_data (gpointer key, - gpointer value, - gpointer data) -{ - GList *link = data; - - g_free (key); - - while (link) { - g_free (link->data); - link = link->next; - } - - g_list_free (data); -} -
View file
cyrus-imapd-2.5.tar.gz/tools/vzic/vzic.h
Deleted
@@ -1,197 +0,0 @@ -/* - * Vzic - a program to convert Olson timezone database files into VZTIMEZONE - * files compatible with the iCalendar specification (RFC2445). - * - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2003 Damon Chaplin. - * - * Author: Damon Chaplin <damon@gnome.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef _VZIC_H_ -#define _VZIC_H_ - -#include <glib.h> - - -/* - * Global command-line options. - */ - -/* By default we output Outlook-compatible output. If --pure is used we output - pure output, with no changes to be compatible with Outlook. */ -extern gboolean VzicPureOutput; - -extern gboolean VzicDumpOutput; -extern gboolean VzicDumpChanges; -extern gboolean VzicDumpZoneNamesAndCoords; -extern gboolean VzicDumpZoneTranslatableStrings; -extern gboolean VzicNoRRules; -extern gboolean VzicNoRDates; -extern char* VzicUrlPrefix; -extern char* VzicOutputDir; - -extern GList* VzicTimeZoneNames; - -/* The minimum & maximum years we can use. */ -#define YEAR_MINIMUM G_MININT -#define YEAR_MAXIMUM G_MAXINT - -/* The maximum size of any complete pathname. */ -#define PATHNAME_BUFFER_SIZE 1024 - -/* Days can be expressed either as a simple month day number, 1-31, or a rule - such as the last Sunday, or the first Monday on or after the 8th. */ -typedef enum -{ - DAY_SIMPLE, - DAY_WEEKDAY_ON_OR_AFTER, - DAY_WEEKDAY_ON_OR_BEFORE, - DAY_LAST_WEEKDAY -} DayCode; - - -/* Times can be given either as universal time (UTC), local standard time - (without daylight-saving adjustments) or wall clock time (local standard - time plus daylight-saving adjustments, i.e. what you would see on a clock - on the wall!). */ -typedef enum -{ - TIME_WALL, - TIME_STANDARD, - TIME_UNIVERSAL -} TimeCode; - - -/* This represents one timezone, e.g. "Africa/Algiers". - It contains the timezone name, and an array of ZoneLineData structs which - hold data from each Zone line, including the continuation lines. */ -typedef struct _ZoneData ZoneData; -struct _ZoneData -{ - char *zone_name; - - /* An array of ZoneLineData, one for each Zone & Zone continuation line - read in. */ - GArray *zone_line_data; -}; - - -typedef struct _ZoneLineData ZoneLineData; -struct _ZoneLineData -{ - /* The amount of time to add to UTC to get local standard time for the - current time range, in seconds. */ - int stdoff_seconds; - - /* Either rules is set to the name of a set of rules, or rules is NULL and - save is set to the time to add to local standard time to get wall time, in - seconds. If save is 0 as well, then standard time always applies. */ - char *rules; - int save_seconds; - - /* The format to use for the abbreviated timezone name, e.g. WE%sT. - The %s is replaced by variable part of the name. (See the letter_s field - in the RuleData struct below). */ - char *format; - - /* TRUE if an UNTIL time is given. */ - gboolean until_set; - - /* The UNTIL year, e.g. 2000. */ - int until_year; - - /* The UNTIL month 0 (Jan) to 11 (Dec). */ - int until_month; - - /* The UNTIL day, either a simple month day number, 1-31, or a rule such as - the last Sunday, or the first Monday on or after the 8th. */ - DayCode until_day_code; - int until_day_number; /* 1 to 31. */ - int until_day_weekday; /* 0 (Sun) to 6 (Sat). */ - - /* The UNTIL time, in seconds from midnight. The code specifies whether the - time is a wall clock time, local standard time, or universal time. */ - int until_time_seconds; - TimeCode until_time_code; -}; - - -typedef struct _RuleData RuleData; -struct _RuleData -{ - /* The first year that the rule applies to, e.g. 1996. - Can also be YEAR_MINIMUM. */ - int from_year; - - /* The last year that the rule applies to, e.g. 1996. - Can also be YEAR_MAXIMUM. */ - int to_year; - - /* A string used to only match certain years between from and to. - The rule only applies to the years which match. If type is NULL the rule - applies to all years betweeen from and to. - zic uses an external program called yearistype to check the string. - Currently it is not used in the Olson database. */ - char *type; - - /* The month of the rule 0 (Jan) to 11 (Dec). */ - int in_month; - - /* The day, either a simple month day number, 1-31, or a rule such as - the last Sunday, or the first Monday on or after the 8th. */ - DayCode on_day_code; - int on_day_number; - int on_day_weekday; /* 0 (Sun) to 6 (Sat). */ - - /* The time, in seconds from midnight. The code specifies whether the - time is a wall clock time, local standard time, or universal time. */ - int at_time_seconds; - TimeCode at_time_code; - - /* The amount of time to add to local standard time when the rule is in - effect, in seconds. If this is not 0 then it must be a daylight-saving - time. */ - int save_seconds; - - /* The letter(s) to use as the variable part in the abbreviated timezone - name. If this is NULL then no variable part is used. (See the format field - in the ZoneLineData struct above.) */ - char *letter_s; - - - /* This is set to TRUE if this element is a shallow copy of another one, - in which case we don't free any of the fields. */ - gboolean is_shallow_copy; -}; - - -typedef struct _ZoneDescription ZoneDescription; -struct _ZoneDescription -{ - /* 2-letter ISO 3166 country code. */ - char country_code2; - - /* latitude and longitude in degrees, minutes & seconds. The degrees value - holds the sign of the entire latitude/longitude. */ - int latitude3; - int longitude3; - - char *comment; -}; - -#endif /* _VZIC_H_ */
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.