Projects
Kolab:3.4
cyrus-imapd
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 75
View file
cyrus-imapd.spec
Changed
@@ -68,6 +68,7 @@ Patch0001: cyrus-imapd-2.5-default-twoskip.patch Patch0002: cyrus-imapd-2.5-ctl_mboxlist-mbtype.patch +Patch0003: cyrus-imapd-2.5-sieve-date-extension.patch BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) @@ -253,6 +254,7 @@ #%patch0001 -p1 %patch0002 -p1 +%patch0003 -p1 # only to update config.* files aclocal -I cmulocal
View file
cyrus-imapd-2.5-sieve-date-extension.patch
Added
@@ -0,0 +1,1323 @@ +From d54c1fb7cd90610d60a5263b6372b8d0cb1bbdb8 Mon Sep 17 00:00:00 2001 +From: "Guillermo A. Amaral" <gamaral@kdab.com> +Date: Thu, 31 Jul 2014 18:17:34 -0700 +Subject: PATCH Sieve Date Extension + +--- + cunit/sieve.testc | 317 ++++++++++++++++++++- + lib/imapoptions | 2 +- + sieve/bc_emit.c | 70 +++++ + sieve/bc_eval.c | 189 ++++++++++++ + sieve/bc_generate.c | 111 ++++++++ + sieve/bytecode.h | 34 ++- + sieve/script.c | 4 + + sieve/script.h | 1 + + sieve/sieve-lex.l | 4 + + sieve/sieve.y | 212 ++++++++++++++ + sieve/sieved.c | 59 ++++ + .../tests/testExtension/uberExtensionTestScript.s | 26 +- + sieve/tree.c | 7 + + sieve/tree.h | 10 + + 14 files changed, 1040 insertions(+), 6 deletions(-) + +diff --git a/cunit/sieve.testc b/cunit/sieve.testc +index 47b6070..e5390b1 100644 +--- a/cunit/sieve.testc ++++ b/cunit/sieve.testc +@@ -502,7 +502,7 @@ static int set_up(void) + "partition-"PARTITION": "DBDIR"/data\n" + "sievenotifier: mailto\n" + "sieve_extensions: fileinto reject vacation imapflags notify" \ +- " envelope body relational regex subaddress copy\n" ++ " envelope body relational regex subaddress copy date\n" + ); + libcyrus_init(); + return 0; +@@ -946,6 +946,321 @@ static void test_address_localpart(void) + context_cleanup(&ctx); + } + ++static void test_date_year(void) ++{ ++ static const char SCRIPT = ++ "if date :is \"received\" \"year\" \"1983\", \"1993\", \"2003\", \"2013\" \n" ++ "{redirect \"me@blah.com\";}\n" ++ ; ++ static const char MSG_TRUE = ++ "Date: Sat, 16 Nov 2013 12:46:49 +1100\r\n" ++ "Received: from localhost (localhost 127.0.0.1)\r\n" ++ " by mail.com (Cyrus v2.3.16) with LMTPA;\r\n" ++ " Tue, 16 Nov 2013 12:50:12 +1100\r\n" ++ "From: zme@true.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ static const char MSG_FALSE = ++ "Date: Tue, 16 Nov 2010 12:46:49 +1100\r\n" ++ "Received: from localhost (localhost 127.0.0.1)\r\n" ++ " by mail.com (Cyrus v2.3.16) with LMTPA;\r\n" ++ " Tue, 16 Nov 2010 12:50:12 +1100\r\n" ++ "From: yme@false.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ sieve_test_context_t ctx; ++ ++ context_setup(&ctx, SCRIPT); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ ++ run_message(&ctx, MSG_TRUE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 1); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 0); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ run_message(&ctx, MSG_FALSE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 2); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 1); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ context_cleanup(&ctx); ++} ++ ++static void test_date_zone_month(void) ++{ ++ static const char SCRIPT = ++ "if date :is :zone \"-0800\" \"date\" \"month\" \"11\"\n" ++ "{redirect \"me@blah.com\";}\n" ++ ; ++ static const char MSG_TRUE = ++ "Date: Fri, 1 Nov 2013 19:46:49 +1100\r\n" ++ "From: zme@true.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ static const char MSG_FALSE = ++ "Date: Fri, 1 Nov 2013 11:46:49 +1100\r\n" ++ "From: yme@false.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ sieve_test_context_t ctx; ++ ++ context_setup(&ctx, SCRIPT); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ ++ run_message(&ctx, MSG_TRUE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 1); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 0); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ run_message(&ctx, MSG_FALSE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 2); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 1); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ context_cleanup(&ctx); ++} ++ ++static void test_date_date(void) ++{ ++ static const char SCRIPT = ++ "if date :is :originalzone \"date\" \"date\" \"2013-11-02\"\n" ++ "{redirect \"me@blah.com\";}\n" ++ ; ++ static const char MSG_TRUE = ++ "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n" ++ "From: zme@true.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ static const char MSG_FALSE = ++ "Date: Fri, 1 Nov 2013 19:45:49 +1100\r\n" ++ "From: yme@false.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ sieve_test_context_t ctx; ++ ++ context_setup(&ctx, SCRIPT); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ ++ run_message(&ctx, MSG_TRUE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 1); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 0); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ run_message(&ctx, MSG_FALSE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 2); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 1); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ context_cleanup(&ctx); ++} ++ ++static void test_date_time(void) ++{ ++ static const char SCRIPT = ++ "if date :is :originalzone \"date\" \"time\" \"19:46:49\"\n" ++ "{redirect \"me@blah.com\";}\n" ++ ; ++ static const char MSG_TRUE = ++ "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n" ++ "From: zme@true.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ static const char MSG_FALSE = ++ "Date: Sat, 2 Nov 2013 19:45:49 +1100\r\n" ++ "From: yme@false.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ sieve_test_context_t ctx; ++ ++ context_setup(&ctx, SCRIPT); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ ++ run_message(&ctx, MSG_TRUE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 1); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 0); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ run_message(&ctx, MSG_FALSE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 2); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 1); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ context_cleanup(&ctx); ++} ++ ++static void test_date_originalzone_day(void) ++{ ++ static const char SCRIPT = ++ "if date :is :originalzone \"date\" \"day\" \"16\"\n" ++ "{redirect \"me@blah.com\";}\n" ++ ; ++ static const char MSG_TRUE = ++ "Date: Sat, 16 Nov 2013 12:46:49 +1100\r\n" ++ "From: zme@true.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ static const char MSG_FALSE = ++ "Date: Fri, 15 Nov 2013 12:46:49 +1100\r\n" ++ "From: yme@false.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ sieve_test_context_t ctx; ++ ++ context_setup(&ctx, SCRIPT); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ ++ run_message(&ctx, MSG_TRUE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 1); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 0); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ run_message(&ctx, MSG_FALSE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 2); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 1); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ context_cleanup(&ctx); ++} ++ ++static void test_date_weekend_weekday(void) ++{ ++ static const char SCRIPT = ++ "if anyof(date :is :zone \"-0800\" \"date\" \"weekday\" \"0\",\n" ++ " date :is :zone \"-0800\" \"date\" \"weekday\" \"6\")\n" ++ "{redirect \"me@blah.com\";}\n" ++ ; ++ static const char MSG_TRUE = ++ "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n" ++ "From: zme@true.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ static const char MSG_FALSE = ++ "Date: Fri, 1 Nov 2013 11:46:49 +1100\r\n" ++ "From: yme@false.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ sieve_test_context_t ctx; ++ ++ context_setup(&ctx, SCRIPT); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ ++ run_message(&ctx, MSG_TRUE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 1); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 0); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ run_message(&ctx, MSG_FALSE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 2); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 1); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ context_cleanup(&ctx); ++} ++ ++static void test_date_zone(void) ++{ ++ static const char SCRIPT = ++ "if date :is :originalzone \"date\" \"zone\" \"+1100\"\n" ++ "{redirect \"me@blah.com\";}\n" ++ ; ++ static const char MSG_TRUE = ++ "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n" ++ "From: zme@true.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ static const char MSG_FALSE = ++ "Date: Fri, 1 Nov 2013 11:46:49 -0700\r\n" ++ "From: yme@false.com\r\n" ++ "To: you\r\n" ++ "Subject: simple address test\r\n" ++ "\r\n" ++ "blah\n" ++ ; ++ sieve_test_context_t ctx; ++ ++ context_setup(&ctx, SCRIPT); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ ++ run_message(&ctx, MSG_TRUE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 1); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 0); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ run_message(&ctx, MSG_FALSE); ++ CU_ASSERT_EQUAL(ctx.stats.errors, 0); ++ CU_ASSERT_EQUAL(ctx.stats.actions, 2); ++ CU_ASSERT_EQUAL(ctx.stats.redirects, 1); ++ CU_ASSERT_EQUAL(ctx.stats.keeps, 1); ++ CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com"); ++ ++ context_cleanup(&ctx); ++} ++ + // TODO: test + // if size :over 10K { redirect "me@blah.com"; } + // TODO: test +diff --git a/lib/imapoptions b/lib/imapoptions +index 2c477a6..ee5db48 100644 +--- a/lib/imapoptions ++++ b/lib/imapoptions +@@ -1311,7 +1311,7 @@ product version in the capabilities */ + user's scripts reside on a remote server (in a Murder). + Otherwise, timsieved will proxy traffic to the remote server. */ + +-{ "sieve_extensions", "fileinto reject vacation vacation-seconds imapflags notify envelope relational regex subaddress copy", BITFIELD("fileinto", "reject", "vacation", "vacation-seconds", "imapflags", "notify", "include", "envelope", "body", "relational", "regex", "subaddress", "copy") } ++{ "sieve_extensions", "fileinto reject vacation vacation-seconds imapflags notify envelope relational regex subaddress copy date", BITFIELD("fileinto", "reject", "vacation", "vacation-seconds", "imapflags", "notify", "include", "envelope", "body", "relational", "regex", "subaddress", "copy", "date") } + /* Space-separated list of Sieve extensions allowed to be used in + sieve scripts, enforced at submission by timsieved(8). Any + previously installed script will be unaffected by this option and +diff --git a/sieve/bc_emit.c b/sieve/bc_emit.c +index f010d61..f4d48c9 100644 +--- a/sieve/bc_emit.c ++++ b/sieve/bc_emit.c +@@ -347,6 +347,76 @@ static int bc_test_emit(int fd, int *codep, bytecode_info_t *bc) + break; + } + ++ case BC_DATE: ++ case BC_CURRENTDATE: ++ { ++ int ret; ++ int datalen; ++ int tmp; ++ ++ /* drop zone tag */ ++ tmp = bc->data(*codep).value; ++ if(write_int(fd, bc->data(*codep).value) == -1) ++ return -1; ++ wrote += sizeof(int); ++ (*codep)++; ++ ++ /* drop timezone offset */ ++ if (tmp == B_TIMEZONE) { ++ if(write_int(fd, bc->data(*codep).value) == -1) ++ return -1; ++ wrote += sizeof(int); ++ (*codep)++; ++ } ++ ++ /* drop match type */ ++ if(write_int(fd, bc->data(*codep).value) == -1) ++ return -1; ++ wrote += sizeof(int); ++ (*codep)++; ++ ++ /* drop relation */ ++ if(write_int(fd, bc->data(*codep).value) == -1) ++ return -1; ++ wrote += sizeof(int); ++ (*codep)++; ++ ++ /* drop comparator */ ++ if(write_int(fd, bc->data(*codep).value) == -1) ++ return -1; ++ wrote += sizeof(int); ++ (*codep)++; ++ ++ /* drop date-part */ ++ if(write_int(fd, bc->data(*codep).value) == -1) ++ return -1; ++ wrote += sizeof(int); ++ (*codep)++; ++ ++ /* drop header-name */ ++ { ++ datalen = bc->data(*codep)++.len; ++ ++ if(write_int(fd, datalen) == -1) return -1; ++ wrote += sizeof(int); ++ ++ if(write(fd, bc->data(*codep)++.str, datalen) == -1) return -1; ++ wrote += datalen; ++ ++ ret = align_string(fd,datalen); ++ if(ret == -1) return -1; ++ ++ wrote+=ret; ++ } ++ ++ /* drop keywords */ ++ ret = bc_stringlist_emit(fd, codep, bc); ++ if(ret < 0) return -1; ++ wrote+=ret; ++ ++ break; ++ } ++ + default: + /* Unknown testcode? */ + return -1; +diff --git a/sieve/bc_eval.c b/sieve/bc_eval.c +index 94c30ff..104748f 100644 +--- a/sieve/bc_eval.c ++++ b/sieve/bc_eval.c +@@ -883,6 +883,195 @@ envelope_err: + + break; + } ++ case BC_DATE:/*11*/ ++ case BC_CURRENTDATE:/*12*/ ++ { ++ char buffer64; ++ const char **headers = NULL; ++ const char **key; ++ const char **keylist = NULL; ++ const char *header = NULL; ++ const char *header_data; ++ const char *header_name = NULL; ++ int comparator; ++ int date_part; ++ int index; ++ int match; ++ int relation; ++ int timezone_offset = 0; ++ int zone; ++ struct tm *tm; ++ time_t t; ++ ++ ++i; /* BC_DATE */ ++ ++ /* zone tag */ ++ zone = ntohl(bci++.value); ++ ++ /* timezone offset */ ++ if (zone == B_TIMEZONE) { ++ timezone_offset = ntohl(bci++.value); ++ } ++ ++ /* comparator */ ++ match = ntohl(bci++.value); ++ relation = ntohl(bci++.value); ++ comparator = ntohl(bci++.value); ++ ++ /* find comparator function */ ++ comp = lookup_comp(comparator, match, relation, &comprock); ++ if(!comp) { ++ res = SIEVE_RUN_ERROR; ++ break; ++ } ++ ++ /* date-part */ ++ date_part = ntohl(bci++.value); ++ ++ /* header name */ ++ i = unwrap_string(bc, i, &header_name, NULL); ++ ++ ++ /* ++ * Process header ++ */ ++ ++ /* TODO: implement index extension */ ++ index = 0; ++ ++ if (interp->getheader(m, header_name, &headers) != SIEVE_OK ++ || headersindex == NULL) { ++ res = SIEVE_FAIL; ++ goto alldone; ++ } ++ header = headersindex; ++ ++ if (BC_CURRENTDATE == op) { ++ t = time(NULL); ++ } ++ else { ++ /* look for separator */ ++ header_data = strrchr(header, ';'); ++ if (header_data) { ++ /* separator found, skip character and continue */ ++ ++header_data; ++ } ++ else { ++ /* separator not found, use full header */ ++ header_data = header; ++ } ++ ++ if (-1 == time_from_rfc822(header_data, &t)) { ++ res = SIEVE_FAIL; ++ goto alldone; ++ } ++ } ++ ++ /* timezone offset */ ++ if (zone == B_ORIGINALZONE) { ++ char *zone; ++ char sign; ++ int hours; ++ int minutes; ++ ++ zone = strrchr(header, ' '); ++ if (!zone || ++ 3 != sscanf(zone + 1, "%c%02d%02d", &sign, &hours, &minutes)) { ++ res = SIEVE_FAIL; ++ goto alldone; ++ } ++ ++ timezone_offset = (sign == '-' ? -1 : 1) * ((hours * 60) + (minutes)); ++ } ++ ++ /* apply timezone_offset (if any) */ ++ t += timezone_offset * 60; ++ ++ /* get tm struct */ ++ tm = gmtime(&t); ++ ++ ++ /* ++ * Tests ++ */ ++ ++ if (match == B_COUNT) { ++ res = SIEVE_OK; ++ goto alldone; ++ } ++ ++ keylist = bc_makeArray(bc, &i); ++ for (key = keylist; *key; ++key) { ++ switch (date_part) { ++ case B_YEAR: ++ snprintf(buffer, sizeof(buffer), "%04d", 1900 + tm->tm_year); ++ break; ++ case B_MONTH: ++ snprintf(buffer, sizeof(buffer), "%02d", 1 + tm->tm_mon); ++ break; ++ case B_DAY: ++ snprintf(buffer, sizeof(buffer), "%02d", tm->tm_mday); ++ break; ++ case B_DATE: ++ snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d", ++ 1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday); ++ break; ++ case B_JULIAN: { ++ int month, year; ++ int c, ya; ++ ++ month = 1 + tm->tm_mon; ++ year = 1900 + tm->tm_year; ++ ++ if (month > 2) { ++ month -= 3; ++ } ++ else { ++ month += 9; ++ --year; ++ } ++ c = year / 100; ++ ya = year - c * 100; ++ ++ snprintf(buffer, sizeof(buffer), "%d", ++ (c * 146097 / 4 + ya * 1461 / 4 + ++ (month * 153 + 2) / 5 + tm->tm_mday + 1721119)); ++ } break; ++ case B_HOUR: ++ snprintf(buffer, sizeof(buffer), "%02d", tm->tm_hour); ++ break; ++ case B_MINUTE: ++ snprintf(buffer, sizeof(buffer), "%02d", tm->tm_min); ++ break; ++ case B_SECOND: ++ snprintf(buffer, sizeof(buffer), "%02d", tm->tm_sec); ++ break; ++ case B_TIME: ++ snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d", ++ tm->tm_hour, tm->tm_min, tm->tm_sec); ++ break; ++ case B_ISO8601: ++ time_to_iso8601(t, buffer, sizeof(buffer)); ++ break; ++ case B_STD11: ++ time_to_rfc822(t, buffer, sizeof(buffer)); ++ break; ++ case B_ZONE: ++ snprintf(buffer, sizeof(buffer), "%c%02d%02d", ++ timezone_offset >= 0 ? '+' : '-', ++ abs(timezone_offset) / 60, ++ abs(timezone_offset) % 60); ++ break; ++ case B_WEEKDAY: ++ snprintf(buffer, sizeof(buffer), "%1d", tm->tm_wday); ++ break; ++ } ++ ++ res |= comp(buffer, strlen(buffer), *key, comprock); ++ } ++ free(keylist); ++ break; ++ } + default: + #if VERBOSE + printf("WERT, can't evaluate if statement. %d is not a valid command", +diff --git a/sieve/bc_generate.c b/sieve/bc_generate.c +index bb80a2b..8185fed 100644 +--- a/sieve/bc_generate.c ++++ b/sieve/bc_generate.c +@@ -249,6 +249,40 @@ static int bc_comparator_generate(int codep, bytecode_info_t *retval, + return codep; + } + ++static int bc_zone_generate(int codep, bytecode_info_t *retval, ++ int zonetag, const char *zone) ++{ ++ unsigned hours; ++ unsigned minutes; ++ char sign; ++ ++ assert(retval != NULL); ++ ++ /* zonetag */ ++ if (!atleast(retval, codep + 1)) return -1; ++ ++ switch (zonetag) { ++ case ZONE: ++ retval->datacodep++.value = B_TIMEZONE; ++ ++ /* time-zone offset in minutes */ ++ if (!atleast(retval, codep + 1) || ++ sscanf(zone, "%c%02u%02u", &sign, &hours, &minutes) != 3) ++ return -1; ++ ++ retval->datacodep++.value = (sign == '-' ? -1 : 1) * (hours * 60) + minutes; ++ break; ++ case ORIGINALZONE: ++ retval->datacodep++.value = B_ORIGINALZONE; ++ break; ++ default: ++ return -1; ++ } ++ ++ return codep; ++} ++ ++ + + + /* writes a single test into almost-flat form starting at codep. +@@ -410,6 +444,83 @@ static int bc_test_generate(int codep, bytecode_info_t *retval, test_t *t) + if (codep == -1) return -1; + + break; ++ case DATE: ++ case CURRENTDATE: ++ /* BC_DATE { time-zone: string} { c: comparator } ++ * { header-name : string } { date-part: string } ++ * { key-list : string list } ++ */ ++ ++ if(!atleast(retval,codep + 1)) return -1; ++ retval->datacodep++.op = (DATE == t->type) ? BC_DATE : BC_CURRENTDATE; ++ ++ /* zone */ ++ codep = bc_zone_generate(codep, retval, ++ t->u.dt.zonetag, ++ t->u.dt.zone); ++ if (codep == -1) return -1; ++ ++ /* comparator */ ++ codep = bc_comparator_generate(codep, retval, ++ t->u.dt.comptag, ++ t->u.dt.relation, ++ t->u.dt.comparator); ++ if (codep == -1) return -1; ++ ++ /* date-part */ ++ if(!atleast(retval,codep + 1)) return -1; ++ switch (t->u.dt.date_part) { ++ case YEAR: ++ retval->datacodep++.value = B_YEAR; ++ break; ++ case MONTH: ++ retval->datacodep++.value = B_MONTH; ++ break; ++ case DAY: ++ retval->datacodep++.value = B_DAY; ++ break; ++ case DATE: ++ retval->datacodep++.value = B_DATE; ++ break; ++ case JULIAN: ++ retval->datacodep++.value = B_JULIAN; ++ break; ++ case HOUR: ++ retval->datacodep++.value = B_HOUR; ++ break; ++ case MINUTE: ++ retval->datacodep++.value = B_MINUTE; ++ break; ++ case SECOND: ++ retval->datacodep++.value = B_SECOND; ++ break; ++ case TIME: ++ retval->datacodep++.value = B_TIME; ++ break; ++ case ISO8601: ++ retval->datacodep++.value = B_ISO8601; ++ break; ++ case STD11: ++ retval->datacodep++.value = B_STD11; ++ break; ++ case ZONE: ++ retval->datacodep++.value = B_ZONE; ++ break; ++ case WEEKDAY: ++ retval->datacodep++.value = B_WEEKDAY; ++ break; ++ } ++ ++ /* header-name */ ++ if(!atleast(retval,codep + 2)) return -1; ++ retval->datacodep++.len = strlen(t->u.dt.header_name); ++ retval->datacodep++.str = t->u.dt.header_name; ++ ++ /* keywords */ ++ codep = bc_stringlist_generate(codep, retval, t->u.dt.kl); ++ if (codep == -1) return -1; ++ ++ break; + default: + return -1; + +diff --git a/sieve/bytecode.h b/sieve/bytecode.h +index acb3755..e8998b4 100644 +--- a/sieve/bytecode.h ++++ b/sieve/bytecode.h +@@ -158,7 +158,9 @@ enum bytecode_comps { + BC_ADDRESS, + BC_ENVELOPE, /* require envelope */ + BC_HEADER, +- BC_BODY /* require body */ ++ BC_BODY, /* require body */ ++ BC_DATE, /* require date */ ++ BC_CURRENTDATE /* require date */ + }; + + /* currently one enum so as to help determine where values are being misused. +@@ -246,8 +248,34 @@ enum bytecode_tags { + B_LOCATION_PLACEHOLDER_1, + B_LOCATION_PLACEHOLDER_2, + B_LOCATION_PLACEHOLDER_3, +- B_LOCATION_PLACEHOLDER_4 +- ++ B_LOCATION_PLACEHOLDER_4, ++ ++ /* Zones */ ++ B_TIMEZONE, ++ B_ORIGINALZONE, ++ ++ B_ZONE_PLACEHOLDER_1, ++ B_ZONE_PLACEHOLDER_2, ++ ++ /* Date Parts */ ++ B_YEAR, ++ B_MONTH, ++ B_DAY, ++ B_DATE, ++ B_JULIAN, ++ B_HOUR, ++ B_MINUTE, ++ B_SECOND, ++ B_TIME, ++ B_ISO8601, ++ B_STD11, ++ B_ZONE, ++ B_WEEKDAY, ++ ++ B_DATEPART_PLACEHOLDER_1, ++ B_DATEPART_PLACEHOLDER_2, ++ B_DATEPART_PLACEHOLDER_3, ++ B_DATEPART_PLACEHOLDER_4 + }; + + #endif +diff --git a/sieve/script.c b/sieve/script.c +index e47917c..043dba0 100644 +--- a/sieve/script.c ++++ b/sieve/script.c +@@ -180,6 +180,10 @@ int script_require(sieve_script_t *s, char *req) + (config_sieve_extensions & IMAP_ENUM_SIEVE_EXTENSIONS_COPY)) { + s->support.copy = 1; + return 1; ++ } else if (!strcmp("date", req) && ++ (config_sieve_extensions & IMAP_ENUM_SIEVE_EXTENSIONS_DATE)) { ++ s->support.date = 1; ++ return 1; + } + return 0; + } +diff --git a/sieve/script.h b/sieve/script.h +index fefa4af..490cf45 100644 +--- a/sieve/script.h ++++ b/sieve/script.h +@@ -68,6 +68,7 @@ struct sieve_script { + int i_ascii_numeric: 1; + int include : 1; + int copy : 1; ++ int date : 1; + int vacation_seconds: 1; + } support; + +diff --git a/sieve/sieve-lex.l b/sieve/sieve-lex.l +index 00c82fe..876f4c9 100644 +--- a/sieve/sieve-lex.l ++++ b/sieve/sieve-lex.l +@@ -171,6 +171,10 @@ CRLF (\r\n|\r|\n) + <INITIAL>:once return ONCE; + <INITIAL>return return RETURN; + <INITIAL>:copy return COPY; ++<INITIAL>date return DATE; ++<INITIAL>currentdate return CURRENTDATE; ++<INITIAL>:zone return ZONE; ++<INITIAL>:originalzone return ORIGINALZONE; + <INITIAL> \t\n\r ; /* ignore whitespace */ + <INITIAL>#.* ; /* ignore hash comments */ + <INITIAL>"/*"(^\*|\*^\/)*\*?"*/" ; /* ignore bracket comments */ +diff --git a/sieve/sieve.y b/sieve/sieve.y +index f69ddc4..310aa3c 100644 +--- a/sieve/sieve.y ++++ b/sieve/sieve.y +@@ -123,12 +123,22 @@ struct itags { + int optional; + }; + ++struct dttags { ++ int zonetag; ++ char *zone; ++ int comptag; ++ int relation; ++ char *comparator; ++ int date_part; ++}; ++ + static char *check_reqs(sieve_script_t *script, strarray_t *sl); + static test_t *build_address(int t, struct aetags *ae, + strarray_t *sl, strarray_t *pl); + static test_t *build_header(int t, struct htags *h, + strarray_t *sl, strarray_t *pl); + static test_t *build_body(int t, struct btags *b, strarray_t *pl); ++static test_t *build_date(int t, struct dttags *dt, char *hn, strarray_t *kl); + static commandlist_t *build_vacation(int t, struct vtags *h, char *s); + static commandlist_t *build_notify(int t, struct ntags *n); + static commandlist_t *build_denotify(int t, struct dtags *n); +@@ -154,6 +164,9 @@ static struct dtags *new_dtags(void); + static struct dtags *canon_dtags(struct dtags *d); + static void free_dtags(struct dtags *d); + static struct itags *new_itags(void); ++static struct dttags *new_dttags(void); ++static struct dttags *canon_dttags(struct dttags *dt); ++static void free_dttags(struct dttags *b); + + static int verify_stringlist(sieve_script_t*, strarray_t *sl, int (*verify)(sieve_script_t*, char *)); + static int verify_mailbox(sieve_script_t*, char *s); +@@ -163,6 +176,8 @@ static int verify_addrheader(sieve_script_t*, char *s); + static int verify_envelope(sieve_script_t*, char *s); + static int verify_flag(sieve_script_t*, char *s); + static int verify_relat(sieve_script_t*, char *s); ++static int verify_zone(sieve_script_t*, char *s); ++static int verify_date_part(sieve_script_t *parse_script, char *dp); + #ifdef ENABLE_REGEX + static int verify_regex(sieve_script_t*, char *s, int cflags); + static int verify_regexs(sieve_script_t*,const strarray_t *sl, char *comp); +@@ -194,6 +209,7 @@ extern void sieverestart(FILE *f); + struct ntags *ntag; + struct dtags *dtag; + struct itags *itag; ++ struct dttags *dttag; + } + + %token <nval> NUMBER +@@ -211,6 +227,8 @@ extern void sieverestart(FILE *f); + %token METHOD ID OPTIONS LOW NORMAL HIGH ANY MESSAGE + %token INCLUDE PERSONAL GLOBAL RETURN OPTIONAL ONCE + %token COPY ++%token DATE CURRENTDATE ZONE ORIGINALZONE ++%token YEAR MONTH DAY JULIAN HOUR MINUTE SECOND TIME ISO8601 STD11 ZONE WEEKDAY + + %type <cl> commands command action elsif block + %type <sl> stringlist strings +@@ -224,6 +242,7 @@ extern void sieverestart(FILE *f); + %type <ntag> ntags + %type <dtag> dtags + %type <itag> itags ++%type <dttag> dttags + %type <nval> priority + + %name-prefix="sieve" +@@ -585,6 +604,32 @@ test: ANYOF testlist { $$ = new_test(ANYOF); $$->u.tl = $2; } + | NOT test { $$ = new_test(NOT); $$->u.t = $2; } + | SIZE sizetag NUMBER { $$ = new_test(SIZE); $$->u.sz.t = $2; + $$->u.sz.n = $3; } ++ | DATE dttags STRING STRING stringlist ++ { ++ $2->date_part = verify_date_part(parse_script, $4); ++ if ($2->date_part == -1) ++ { YYERROR; /*vr called yyerror()*/ } ++ ++ $2 = canon_dttags($2); ++ ++ $$ = build_date(DATE, $2, $3, $5); ++ if ($$ == NULL) { ++ yyerror(parse_script, "unable to find a compatible comparator"); ++ YYERROR; } ++ } ++ | CURRENTDATE dttags STRING STRING stringlist ++ { ++ $2->date_part = verify_date_part(parse_script, $4); ++ if ($2->date_part == -1) ++ { YYERROR; /*vr called yyerror()*/ } ++ ++ $2 = canon_dttags($2); ++ ++ $$ = build_date(CURRENTDATE, $2, $3, $5); ++ if ($$ == NULL) { ++ yyerror(parse_script, "unable to find a compatible comparator"); ++ YYERROR; } ++ } + | error { $$ = NULL; } + ; + +@@ -688,6 +733,43 @@ btags: /* empty */ { $$ = new_btags(); } + $$->comparator = $3; } } + ; + ++dttags: /* empty */ { $$ = new_dttags(); } ++ | dttags comptag { $$ = $1; ++ if ($$->comptag != -1) { ++ yyerror(parse_script, "duplicate comparator type tag"); YYERROR; } ++ else { $$->comptag = $2; } } ++ ++ | dttags relcomp STRING { $$ = $1; ++ if ($$->comptag != -1) { ++ yyerror(parse_script, "duplicate comparator type tag"); YYERROR; } ++ else { ++ $$->comptag = $2; ++ $$->relation = verify_relat(parse_script, $3); ++ if ($$->relation == -1) { ++ YYERROR; /*vr called yyerror()*/ } } } ++ ++ | dttags COMPARATOR STRING { $$ = $1; ++ if ($$->comparator != NULL) { ++ yyerror(parse_script, "duplicate comparator tag"); YYERROR; } ++ else if (!strcmp($3, "i;ascii-numeric") && ++ !parse_script->support.i_ascii_numeric) { ++ yyerror(parse_script, "comparator-i;ascii-numeric MUST be enabled with \"require\""); YYERROR; } ++ else { $$->comparator = $3; } } ++ ++ | dttags ZONE STRING { $$ = $1; ++ if ($$->zonetag != -1) { ++ yyerror(parse_script, "duplicate zone tag"); YYERROR; } ++ else { ++ if (verify_zone(parse_script, $3) == -1) { ++ YYERROR; /*vr called yyerror()*/ } ++ else { $$->zone = $3; ++ $$->zonetag = ZONE; } } } ++ ++ | dttags ORIGINALZONE { $$ = $1; ++ if ($$->zonetag != -1) { ++ yyerror(parse_script, "duplicate zone tag"); YYERROR; } ++ else { $$->zonetag = ORIGINALZONE; } } ++ ; + + addrparttag: ALL { $$ = ALL; } + | LOCALPART { $$ = LOCALPART; } +@@ -943,6 +1025,27 @@ static commandlist_t *build_include(int t, struct itags *i, char* script) + return ret; + } + ++static test_t *build_date(int t, struct dttags *dt, ++ char *hn, strarray_t *kl) ++{ ++ test_t *ret = new_test(t); ++ assert(t == DATE || t == CURRENTDATE); ++ ++ if (ret) { ++ ret->u.dt.zone = (dt->zone ? xstrdup(dt->zone) : NULL); ++ ret->u.dt.comparator = xstrdup(dt->comparator); ++ ret->u.dt.zonetag = dt->zonetag; ++ ret->u.dt.comptag = dt->comptag; ++ ret->u.dt.relation = dt->relation; ++ ret->u.dt.date_part = dt->date_part; ++ ret->u.dt.header_name = hn; ++ ret->u.dt.kl = kl; ++ free_dttags(dt); ++ } ++ return ret; ++} ++ ++ + static struct aetags *new_aetags(void) + { + struct aetags *r = (struct aetags *) xmalloc(sizeof(struct aetags)); +@@ -1077,6 +1180,54 @@ static struct itags *new_itags() { + return r; + } + ++static struct dttags *new_dttags(void) ++{ ++ struct dttags *dt = (struct dttags *) xmalloc(sizeof(struct dttags)); ++ dt->comptag = -1; ++ dt->zonetag = -1; ++ dt->relation = -1; ++ dt->comparator = NULL; ++ dt->zone = NULL; ++ dt->date_part = -1; ++ return dt; ++} ++ ++static struct dttags *canon_dttags(struct dttags *dt) ++{ ++ char zone6; ++ int gmoffset; ++ int hours; ++ int minutes; ++ struct tm *tm; ++ time_t t; ++ ++ if (dt->comparator == NULL) { ++ dt->comparator = xstrdup("i;ascii-casemap"); ++ } ++ if (dt->zonetag == -1) { ++ t = time(NULL); ++ tm = localtime(&t); ++ gmoffset = gmtoff_of(tm, &t) / 60; ++ hours = abs(gmoffset) / 60; ++ minutes = abs(gmoffset) % 60; ++ snprintf(zone, 6, "%c%02d%02d", (gmoffset >= 0 ? '+' : '-'), hours, minutes); ++ dt->zone = xstrdup(zone); ++ dt->zonetag = ZONE; ++ } ++ if (dt->comptag == -1) { ++ dt->comptag = IS; ++ } ++ return dt; ++} ++ ++static void free_dttags(struct dttags *dt) ++{ ++ free(dt->comparator); ++ free(dt->zone); ++ free(dt); ++} ++ ++ + static struct ntags *new_ntags(void) + { + struct ntags *r = (struct ntags *) xmalloc(sizeof(struct ntags)); +@@ -1242,6 +1393,67 @@ static int verify_relat(sieve_script_t *parse_script, char *r) + + } + ++static int verify_zone(sieve_script_t *parse_script, char *tz) ++{ ++ int valid = 0; ++ unsigned hours; ++ unsigned minutes; ++ char sign; ++ ++ if (sscanf(tz, "%c%02u%02u", &sign, &hours, &minutes) != 3) { ++ valid |= -1; ++ } ++ ++ // test sign ++ switch (sign) { ++ case '+': ++ case '-': ++ break; ++ ++ default: ++ valid |= -1; ++ break; ++ } ++ ++ // test minutes ++ if (minutes > 59) { ++ valid |= -1; ++ } ++ ++ if (valid != 0) { ++ snprintf(parse_script->sieveerr, ERR_BUF_SIZE, ++ "flag '%s': not a valid timezone offset", tz); ++ yyerror(parse_script, parse_script->sieveerr); ++ } ++ ++ return valid; ++} ++ ++static int verify_date_part(sieve_script_t *parse_script, char *dp) ++{ ++ lcase(dp); ++ if (!strcmp(dp, "year")) { return YEAR; } ++ else if (!strcmp(dp, "month")) { return MONTH; } ++ else if (!strcmp(dp, "day")) { return DAY; } ++ else if (!strcmp(dp, "date")) { return DATE; } ++ else if (!strcmp(dp, "julian")) { return JULIAN; } ++ else if (!strcmp(dp, "hour")) { return HOUR; } ++ else if (!strcmp(dp, "minute")) { return MINUTE; } ++ else if (!strcmp(dp, "second")) { return SECOND; } ++ else if (!strcmp(dp, "time")) { return TIME; } ++ else if (!strcmp(dp, "iso8601")) { return ISO8601; } ++ else if (!strcmp(dp, "std11")) { return STD11; } ++ else if (!strcmp(dp, "zone")) { return ZONE; } ++ else if (!strcmp(dp, "weekday")) { return WEEKDAY; } ++ else { ++ snprintf(parse_script->sieveerr, ERR_BUF_SIZE, ++ "flag '%s': not a valid relational operation", dp); ++ yyerror(parse_script, parse_script->sieveerr); ++ } ++ ++ return -1; ++} ++ + + + +diff --git a/sieve/sieved.c b/sieve/sieved.c +index df09c39..4700c54 100644 +--- a/sieve/sieved.c ++++ b/sieve/sieved.c +@@ -324,6 +324,65 @@ static int dump2_test(bytecode_input_t * d, int i) + i=write_list(ntohl(di.len), i+1, d); + printf(" \n"); + break; ++ case BC_DATE:/*11*/ ++ case BC_CURRENTDATE:/*12*/ ++ /* current date */ ++ if (BC_DATE == ntohl(di++.value)) { ++ printf("date "); ++ } ++ else { ++ printf("currentdate "); ++ } ++ ++ /* zone tag */ ++ { ++ int zone; ++ int timezone_offset; ++ ++ printf("Zone-Tag: "); ++ zone = ntohl(di++.value); ++ switch (zone) { ++ case B_TIMEZONE: ++ timezone_offset = ntohl(di++.value); ++ printf("Specific timezone: offset by %d minutes.\n", timezone_offset); ++ break; ++ case B_ORIGINALZONE: ++ printf("Original zone.\n"); ++ break; ++ } ++ } ++ ++ i=printComparison(d, i); ++ ++ printf(" Date-Type: "); ++ switch(ntohl(di++.value)) ++ { ++ case B_YEAR: printf("year\n"); break; ++ case B_MONTH: printf("month\n"); break; ++ case B_DAY: printf("day\n"); break; ++ case B_JULIAN: printf("julian\n"); break; ++ case B_HOUR: printf("hour\n"); break; ++ case B_MINUTE: printf("minute\n"); break; ++ case B_SECOND: printf("second\n"); break; ++ case B_TIME: printf("time\n"); break; ++ case B_ISO8601: printf("iso8601\n"); break; ++ case B_STD11: printf("std11\n"); break; ++ case B_ZONE: printf("zone\n"); break; ++ case B_WEEKDAY: printf("weekday\n"); break; ++ } ++ ++ /* header name */ ++ { ++ const char *data; ++ int len; ++ i = unwrap_string(d, i, &data, &len); ++ printf(" Header Name: {%d}%s\n", len, data); ++ } ++ ++ printf(" Key List: "); ++ i=write_list(ntohl(di.len), i+1, d); ++ printf(" \n"); ++ break; + default: + printf("WERT %d ", ntohl(di.value)); + } +diff --git a/sieve/tests/testExtension/uberExtensionTestScript.s b/sieve/tests/testExtension/uberExtensionTestScript.s +index d5d75a1..0ce0379 100644 +--- a/sieve/tests/testExtension/uberExtensionTestScript.s ++++ b/sieve/tests/testExtension/uberExtensionTestScript.s +@@ -1,4 +1,4 @@ +-require "regex", "relational", "comparator-i;ascii-numeric", "subaddress", "envelope"; ++require "regex", "relational", "comparator-i;ascii-numeric", "subaddress", "envelope", "date"; + + #this is for the extra thigns we have added to sieve + #test extensions +@@ -145,3 +145,27 @@ if envelope :detail :contains "from" "k" + + if envelope :detail :matches "from" "e*k" + {redirect "me+goodedetailmatches@blah.com";} ++ ++###################################################################### ++#DATE ++###################################################################### ++ ++if allof(header :is "from" "boss@example.com", ++ date :value "ge" :originalzone "date" "hour" "09", ++ date :value "lt" :originalzone "date" "hour" "17") ++{redirect "me+urgent@blah.com";} ++ ++if anyof(date :is "received" "weekday" "0", ++ date :is "received" "weekday" "6") ++{redirect "me+weekend@blah.com";} ++ ++if anyof(date :is :zone "-0800" "received" "weekday" "0", ++ date :is :zone "-0800" "received" "weekday" "6") ++{redirect "me+weekend(pst)@blah.com";} ++ ++if date :is "received" "year" "1983", "1993", "2003", "2013" ++{redirect "me+yearsofthree@blah.com";} ++ ++if currentdate :value "ge" :originalzone "received" "year" "2013" ++{redirect "me+yearsofthree@blah.com";} ++ +diff --git a/sieve/tree.c b/sieve/tree.c +index fa4b629..fff83fd 100644 +--- a/sieve/tree.c ++++ b/sieve/tree.c +@@ -159,6 +159,13 @@ void free_test(test_t *t) + case NOT: + free_test(t->u.t); + break; ++ ++ case DATE: ++ case CURRENTDATE: ++ free(t->u.dt.comparator); ++ free(t->u.dt.zone); ++ strarray_free(t->u.dt.kl); ++ break; + } + + free(t); +diff --git a/sieve/tree.h b/sieve/tree.h +index 3884e54..ecb42d6 100644 +--- a/sieve/tree.h ++++ b/sieve/tree.h +@@ -101,6 +101,16 @@ struct Test { + int t; /* tag */ + int n; /* param */ + } sz; ++ struct { /* it's a date test */ ++ int zonetag; ++ char *zone; ++ int comptag; ++ int relation; ++ char *comparator; ++ int date_part; ++ char *header_name; ++ strarray_t *kl; ++ } dt; + } u; + }; + +-- +1.9.3 +
View file
debian.series
Changed
@@ -1,1 +1,2 @@ cyrus-imapd-2.5-ctl_mboxlist-mbtype.patch -p1 +cyrus-imapd-2.5-sieve-date-extension.patch -p1
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
.