diff --git a/src/lib-sieve/plugins/date/Makefile.am b/src/lib-sieve/plugins/date/Makefile.am index 8d1f731225a554fffcd18709b9eaf8fcfa778c55..3d761c3f4ba2da2a455d04bf52cb3b7872b9890a 100644 --- a/src/lib-sieve/plugins/date/Makefile.am +++ b/src/lib-sieve/plugins/date/Makefile.am @@ -12,6 +12,7 @@ tests = \ libsieve_ext_date_la_SOURCES = \ $(tests) \ + ext-date-common.c \ ext-date.c noinst_HEADERS = \ diff --git a/src/lib-sieve/plugins/date/ext-date-common.c b/src/lib-sieve/plugins/date/ext-date-common.c new file mode 100644 index 0000000000000000000000000000000000000000..efea31680dfa3aca495532523885e981d3509b7c --- /dev/null +++ b/src/lib-sieve/plugins/date/ext-date-common.c @@ -0,0 +1,355 @@ +/* Copyright (c) 2002-2009 Dovecot Sieve authors, see the included COPYING file + */ + +#include "lib.h" + +#include "sieve-common.h" +#include "sieve-interpreter.h" +#include "sieve-message.h" + +#include "ext-date-common.h" + +#include <time.h> +#include <ctype.h> + +struct ext_date_context { + time_t current_date; +}; + +/* + * Runtime initialization + */ + +static void ext_date_runtime_init +(const struct sieve_runtime_env *renv, void *context ATTR_UNUSED) +{ + struct ext_date_context *dctx; + pool_t pool; + time_t current_date; + + /* Get current time at instance main script is started */ + time(¤t_date); + + /* Create context */ + pool = sieve_message_context_pool(renv->msgctx); + dctx = p_new(pool, struct ext_date_context, 1); + dctx->current_date = current_date; + + sieve_message_context_extension_set + (renv->msgctx, &date_extension, (void *) dctx); +} + +static struct sieve_interpreter_extension date_interpreter_extension = { + &date_extension, + ext_date_runtime_init, + NULL, +}; + +bool ext_date_interpreter_load +(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED) +{ + if ( renv->msgctx == NULL || + sieve_message_context_extension_get(renv->msgctx, &date_extension) + == NULL ) { + sieve_interpreter_extension_register + (renv->interp, &date_interpreter_extension, NULL); + } + + return TRUE; +} + +/* + * Zone string + */ + +bool ext_date_parse_timezone +(const char *zone, int *zone_offset_r) +{ + const unsigned char *str = (const unsigned char *) zone; + size_t len = strlen(zone); + + if (len == 5 && (*str == '+' || *str == '-')) { + int offset; + + if (!i_isdigit(str[1]) || !i_isdigit(str[2]) || + !i_isdigit(str[3]) || !i_isdigit(str[4])) + return FALSE; + + offset = ((str[1]-'0') * 10 + (str[2]-'0')) * 60 + + (str[3]-'0') * 10 + (str[4]-'0'); + + if ( zone_offset_r != NULL ) + *zone_offset_r = *str == '+' ? offset : -offset; + + return TRUE; + } + + return FALSE; +} + +/* + * Current date + */ + +time_t ext_date_get_current_date(const struct sieve_runtime_env *renv) +{ + struct ext_date_context *dctx = (struct ext_date_context *) + sieve_message_context_extension_get(renv->msgctx, &date_extension); + + i_assert( dctx != NULL ); + + return dctx->current_date; +} + +/* + * Date parts + */ + +/* "year" => the year, "0000" .. "9999". + */ + +static const char *ext_date_year_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part year_date_part = { + "year", + ext_date_year_part_get +}; + +/* "month" => the month, "01" .. "12". + */ + +static const char *ext_date_month_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part month_date_part = { + "month", + ext_date_month_part_get +}; + +/* "day" => the day, "01" .. "31". + */ + +static const char *ext_date_day_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part day_date_part = { + "day", + ext_date_day_part_get +}; + +/* "date" => the date in "yyyy-mm-dd" format. + */ + +static const char *ext_date_date_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part date_date_part = { + "date", + ext_date_date_part_get +}; + +/* "julian" => the Modified Julian Day, that is, the date + * expressed as an integer number of days since + * 00:00 UTC on November 17, 1858 (using the Gregorian + * calendar). This corresponds to the regular + * Julian Day minus 2400000.5. Sample routines to + * convert to and from modified Julian dates are + * given in Appendix A. + */ + +static const char *ext_date_julian_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part julian_date_part = { + "julian", + ext_date_julian_part_get +}; + +/* "hour" => the hour, "00" .. "23". + */ +static const char *ext_date_hour_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part hour_date_part = { + "hour", + ext_date_hour_part_get +}; + +/* "minute" => the minute, "00" .. "59". + */ +static const char *ext_date_minute_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part minute_date_part = { + "minute", + ext_date_minute_part_get +}; + +/* "second" => the second, "00" .. "60". + */ +static const char *ext_date_second_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part second_date_part = { + "second", + ext_date_second_part_get +}; + +/* "time" => the time in "hh:mm:ss" format. + */ +static const char *ext_date_time_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part time_date_part = { + "time", + ext_date_time_part_get +}; + +/* "iso8601" => the date and time in restricted ISO 8601 format. + */ +static const char *ext_date_iso8601_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part iso8601_date_part = { + "iso8601", + ext_date_iso8601_part_get +}; + +/* "std11" => the date and time in a format appropriate + * for use in a Date: header field [RFC2822]. + */ +static const char *ext_date_std11_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part std11_date_part = { + "std11", + ext_date_std11_part_get +}; + +/* "zone" => the time zone in use. If the user specified a + * time zone with ":zone", "zone" will + * contain that value. If :originalzone is specified + * this value will be the original zone specified + * in the date-time value. If neither argument is + * specified the value will be the server's default + * time zone in offset format "+hhmm" or "-hhmm". An + * offset of 0 (Zulu) always has a positive sign. + */ +static const char *ext_date_zone_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part zone_date_part = { + "zone", + ext_date_zone_part_get +}; + +/* "weekday" => the day of the week expressed as an integer between + * "0" and "6". "0" is Sunday, "1" is Monday, etc. + */ +static const char *ext_date_weekday_part_get(struct tm *tm, int zone_offset); + +static const struct ext_date_part weekday_date_part = { + "weekday", + ext_date_weekday_part_get +}; + +/* + * Date part extraction + */ + +static const struct ext_date_part *date_parts[] = { + &year_date_part, &month_date_part, &day_date_part, &date_date_part, + &julian_date_part, &hour_date_part, &minute_date_part, &second_date_part, + &iso8601_date_part, &std11_date_part, &zone_date_part, &weekday_date_part +}; + +unsigned int date_parts_count = N_ELEMENTS(date_parts); + +const char *ext_date_part_extract +(const char *part, struct tm *tm, int zone_offset) +{ + unsigned int i; + + for ( i = 0; i < date_parts_count; i++ ) { + if ( strcasecmp(date_parts[i]->identifier, part) == 0 ) { + if ( date_parts[i]->get_string != NULL ) + return date_parts[i]->get_string(tm, zone_offset); + + return NULL; + } + } + + return NULL; +} + +/* + * Date part implementations + */ + +static const char *ext_date_year_part_get +(struct tm *tm, int zone_offset ATTR_UNUSED) +{ + return t_strdup_printf("%04d", tm->tm_year + 1900); +} + +static const char *ext_date_month_part_get +(struct tm *tm, int zone_offset ATTR_UNUSED) +{ + return t_strdup_printf("%02d", tm->tm_mon); +} + +static const char *ext_date_day_part_get +(struct tm *tm, int zone_offset ATTR_UNUSED) +{ + return t_strdup_printf("%02d", tm->tm_mday); +} + +static const char *ext_date_date_part_get +(struct tm *tm, int zone_offset ATTR_UNUSED) +{ + return t_strdup_printf("%04d-%02d-%02d", + tm->tm_year + 1900, tm->tm_mon, tm->tm_mday); +} + +static const char *ext_date_julian_part_get +(struct tm *tm, int zone_offset ATTR_UNUSED) +{ + return ""; +} + +static const char *ext_date_hour_part_get +(struct tm *tm, int zone_offset ATTR_UNUSED) +{ + return t_strdup_printf("%02d", tm->tm_hour); +} + +static const char *ext_date_minute_part_get +(struct tm *tm, int zone_offset ATTR_UNUSED) +{ + return t_strdup_printf("%02d", tm->tm_min); +} + +static const char *ext_date_second_part_get +(struct tm *tm, int zone_offset ATTR_UNUSED) +{ + return t_strdup_printf("%02d", tm->tm_sec); +} + +static const char *ext_date_time_part_get +(struct tm *tm, int zone_offset ATTR_UNUSED) +{ + return t_strdup_printf("%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); +} + +static const char *ext_date_iso8601_part_get +(struct tm *tm, int zone_offset) +{ + return ""; +} + +static const char *ext_date_std11_part_get +(struct tm *tm, int zone_offset) +{ + return ""; +} + +static const char *ext_date_zone_part_get +(struct tm *tm ATTR_UNUSED, int zone_offset) +{ + return ""; +} + +static const char *ext_date_weekday_part_get(struct tm *tm, int zone_offset) +{ + return ""; +} + diff --git a/src/lib-sieve/plugins/date/ext-date-common.h b/src/lib-sieve/plugins/date/ext-date-common.h index 81001c44ac8a698ba6d575f28360e9c90a8ca73a..9d8d54e94f5d554bbe96f4477b31009e7956d67b 100644 --- a/src/lib-sieve/plugins/date/ext-date-common.h +++ b/src/lib-sieve/plugins/date/ext-date-common.h @@ -4,12 +4,19 @@ #ifndef __EXT_DATE_COMMON_H #define __EXT_DATE_COMMON_H +#include "sieve-common.h" + +#include <time.h> + /* * Extension */ extern const struct sieve_extension date_extension; +bool ext_date_interpreter_load + (const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED); + /* * Tests */ @@ -29,4 +36,29 @@ enum ext_date_opcode { extern const struct sieve_operation date_operation; extern const struct sieve_operation currentdate_operation; +/* + * Zone string + */ + +bool ext_date_parse_timezone(const char *zone, int *zone_offset_r); + +/* + * Current date + */ + +time_t ext_date_get_current_date(const struct sieve_runtime_env *renv); + +/* + * Date part + */ + +struct ext_date_part { + const char *identifier; + + const char *(*get_string)(struct tm *tm, int zone_offset); +}; + +const char *ext_date_part_extract + (const char *part, struct tm *tm, int zone_offset); + #endif /* __EXT_DATE_COMMON_H */ diff --git a/src/lib-sieve/plugins/date/ext-date.c b/src/lib-sieve/plugins/date/ext-date.c index a899fb0cd199b979c4aa4c3da2aa32e410f16af4..5d809688f4f1ddf0439ba821d506af210b0b7096 100644 --- a/src/lib-sieve/plugins/date/ext-date.c +++ b/src/lib-sieve/plugins/date/ext-date.c @@ -48,7 +48,9 @@ const struct sieve_extension date_extension = { &ext_date_my_id, NULL, NULL, ext_date_validator_load, - NULL, NULL, NULL, NULL, NULL, + NULL, + ext_date_interpreter_load, + NULL, NULL, NULL, SIEVE_EXT_DEFINE_OPERATIONS(ext_date_operations), SIEVE_EXT_DEFINE_NO_OPERANDS }; diff --git a/src/lib-sieve/plugins/date/tst-date.c b/src/lib-sieve/plugins/date/tst-date.c index ecc78d18d603fdebc3f0f01fb13c25d7de628393..7f7b5ab09fd2f525f8c27d4ed1c02a3eb54fae02 100644 --- a/src/lib-sieve/plugins/date/tst-date.c +++ b/src/lib-sieve/plugins/date/tst-date.c @@ -3,6 +3,7 @@ #include "lib.h" #include "str-sanitize.h" +#include "message-date.h" #include "sieve-common.h" #include "sieve-commands.h" @@ -18,6 +19,8 @@ #include "ext-date-common.h" +#include <time.h> + /* * Tests */ @@ -146,25 +149,26 @@ static bool tag_zone_validate (struct sieve_validator *validator, struct sieve_ast_argument **arg, struct sieve_command_context *cmd) { - struct sieve_ast_argument *tag = *arg; + struct sieve_ast_argument *tag = *arg; - if ( (bool) cmd->data ) { + if ( (bool) cmd->data ) { if ( cmd->command == &date_test ) { sieve_argument_validate_error(validator, *arg, "multiple :zone or :originalzone arguments specified for " "the currentdate test"); } else { - sieve_argument_validate_error(validator, *arg, - "multiple :zone arguments specified for the currentdate test"); + sieve_argument_validate_error(validator, *arg, + "multiple :zone arguments specified for the currentdate test"); } return FALSE; - } + } /* Skip tag */ *arg = sieve_ast_argument_next(*arg); /* :content tag has a string-list argument */ if ( tag->argument == &date_zone_tag ) { + /* Check syntax: * :zone <time-zone: string> */ @@ -173,7 +177,18 @@ static bool tag_zone_validate return FALSE; } - /* Assign tag parameters */ + /* Check it */ + if ( sieve_argument_is_string_literal(*arg) ) { + const char *zone = sieve_ast_argument_strc(*arg); + + if ( !ext_date_parse_timezone(zone, NULL) ) { + sieve_argument_validate_warning(validator, *arg, + "specified :zone argument '%s' is not a valid timezone", + str_sanitize(zone, 40)); + } + } + + /* Assign tag parameters */ tag->parameters = *arg; *arg = sieve_ast_arguments_detach(*arg,1); } @@ -319,15 +334,14 @@ static bool tst_date_operation_dump sieve_code_descend(denv); /* Handle any optional arguments */ - do { - - if ( !sieve_match_dump_optional_operands(denv, address, &opt_code) ) - return FALSE; + do { + if ( !sieve_match_dump_optional_operands(denv, address, &opt_code) ) + return FALSE; - switch ( opt_code ) { - case SIEVE_MATCH_OPT_END: - break; - case OPT_DATE_ZONE: + switch ( opt_code ) { + case SIEVE_MATCH_OPT_END: + break; + case OPT_DATE_ZONE: operand = sieve_operand_read(denv->sbin, address); if ( operand == NULL ) { sieve_code_dumpf(denv, "ERROR: INVALID OPERAND"); @@ -337,16 +351,15 @@ static bool tst_date_operation_dump if ( sieve_operand_is_omitted(operand) ) { sieve_code_dumpf(denv, "zone: ORIGINAL"); } else { - if ( !sieve_opr_string_dump_data + if ( !sieve_opr_string_dump_data (denv, operand, address, "zone") ) return FALSE; } break; - - default: - return FALSE; - } - } while ( opt_code != SIEVE_MATCH_OPT_END ); + default: + return FALSE; + } + } while ( opt_code != SIEVE_MATCH_OPT_END ); if ( op == &date_operation && !sieve_opr_string_dump(denv, address, "header name") ) @@ -365,15 +378,19 @@ static int tst_date_operation_execute (const struct sieve_operation *op, const struct sieve_runtime_env *renv, sieve_size_t *address) { - bool result = TRUE; + bool result = TRUE, matched = FALSE; int opt_code = 0; + const struct sieve_message_data *msgdata = renv->msgdata; const struct sieve_comparator *cmp = &i_ascii_casemap_comparator; const struct sieve_match_type *mtch = &is_match_type; const struct sieve_operand *operand; struct sieve_match_context *mctx; string_t *header_name = NULL, *date_part = NULL, *zone = NULL; struct sieve_coded_stringlist *key_list; - bool matched = FALSE; + time_t date_value; + struct tm *date_tm; + const char *part_value; + int zone_offset = 0; int ret; /* Read optional operands */ @@ -398,7 +415,7 @@ static int tst_date_operation_execute sieve_runtime_trace_error(renv, "invalid zone operand"); return SIEVE_EXEC_BIN_CORRUPT; } - } + } break; default: sieve_runtime_trace_error(renv, "unknown optional operand"); @@ -431,8 +448,80 @@ static int tst_date_operation_execute sieve_runtime_trace(renv, "%s test", op->mnemonic); - /* FIXME: implement */ + /* Get the date value */ + + if ( op == &date_operation ) { + const char *header_value; + const char *date_string; + + /* Get date from the message */ - sieve_interpreter_set_test_result(renv->interp, matched); - return SIEVE_EXEC_OK; + /* Read first header + * NOTE: need something for index extension to hook into some time. + */ + if ( (ret = mail_get_first_header + (msgdata->mail, str_c(header_name), &header_value)) < 0 ) { + /* No such header, test failed */ + sieve_interpreter_set_test_result(renv->interp, FALSE); + return SIEVE_EXEC_OK; + } + + /* Extract the date string value */ + date_string = strrchr(header_value, ';'); + if ( date_string == NULL ) + /* Direct header value */ + date_string = header_value; + else { + /* Delimited by ';', e.g. a Received: header */ + date_string++; + } + + /* Parse the date value */ + if ( !message_date_parse((const unsigned char *) date_string, + strlen(date_string), &date_value, &zone_offset) ) { + /* Uparseable addres, test failed */ + sieve_interpreter_set_test_result(renv->interp, FALSE); + return SIEVE_EXEC_OK; + } + + } else if ( op == ¤tdate_operation ) { + /* Use time stamp recorded at the time the script first started */ + + date_value = ext_date_get_current_date(renv); + + } else { + i_unreached(); + } + + if ( (date_tm=gmtime(&date_value)) == NULL ) { + sieve_interpreter_set_test_result(renv->interp, FALSE); + return SIEVE_EXEC_OK; + } + + /* Extract the date part */ + part_value = ext_date_part_extract(str_c(date_part), date_tm, zone_offset); + + /* Initialize match */ + mctx = sieve_match_begin(renv->interp, mtch, cmp, NULL, key_list); + + /* Match value */ + if ( (ret=sieve_match_value(mctx, part_value, strlen(part_value))) < 0 ) + result = FALSE; + else + matched = ret > 0; + + /* Finish match */ + if ( (ret=sieve_match_end(&mctx)) < 0 ) + result = FALSE; + else + matched = ( ret > 0 || matched ); + + /* Set test result for subsequent conditional jump */ + if ( result ) { + sieve_interpreter_set_test_result(renv->interp, matched); + return SIEVE_EXEC_OK; + } + + sieve_runtime_trace_error(renv, "invalid string-list item"); + return SIEVE_EXEC_BIN_CORRUPT; }