diff --git a/TODO b/TODO index 65114c92e45c120607a753ac255c69eb54347568..238f1d43c96c8bb069b0d8fdb7d800a8c6b92e16 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,6 @@ Current: * Implement enotify extension: - - Implement parsing :options argument (not used for now however) - Check whether handling of error conditions matches the standard - Limit the number of notifications generated (on a per-method basis) * Implement mailto method for the enotify extension: diff --git a/src/lib-sieve/plugins/enotify/cmd-notify.c b/src/lib-sieve/plugins/enotify/cmd-notify.c index 3dc4f075c48f42076de3d18343519e54b1cd387c..b6e973efa7845f18d2a9ed02c8ddc78817f99593 100644 --- a/src/lib-sieve/plugins/enotify/cmd-notify.c +++ b/src/lib-sieve/plugins/enotify/cmd-notify.c @@ -169,6 +169,7 @@ const struct sieve_action act_notify = { struct cmd_notify_context_data { struct sieve_ast_argument *from; struct sieve_ast_argument *message; + struct sieve_ast_argument *options; }; /* @@ -214,6 +215,8 @@ static bool cmd_notify_validate_stringlist_tag struct sieve_command_context *cmd) { struct sieve_ast_argument *tag = *arg; + struct cmd_notify_context_data *ctx_data = + (struct cmd_notify_context_data *) cmd->data; /* Detach the tag itself */ *arg = sieve_ast_arguments_detach(*arg,1); @@ -223,6 +226,9 @@ static bool cmd_notify_validate_stringlist_tag */ if ( !sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, SAAT_STRING_LIST) ) return FALSE; + + /* Assign context */ + ctx_data->options = *arg; /* Skip parameter */ *arg = sieve_ast_argument_next(*arg); @@ -325,7 +331,7 @@ static bool cmd_notify_validate return FALSE; return ext_enotify_compile_check_arguments - (valdtr, arg, ctx_data->message, ctx_data->from); + (valdtr, arg, ctx_data->message, ctx_data->from, ctx_data->options); } /* diff --git a/src/lib-sieve/plugins/enotify/ext-enotify-common.c b/src/lib-sieve/plugins/enotify/ext-enotify-common.c index c26a801a526b4630775b4f81bdf1ee7b1cc7c4e8..e0bc2a28a527095c52ffe969d8b22c3d8282b612 100644 --- a/src/lib-sieve/plugins/enotify/ext-enotify-common.c +++ b/src/lib-sieve/plugins/enotify/ext-enotify-common.c @@ -133,9 +133,107 @@ static const char *ext_enotify_uri_scheme_parse(const char **uri_p) return str_c(scheme); } +static bool ext_enotify_option_parse +(struct sieve_enotify_log *nlog, const char *option, bool name_only, + const char **opt_name, const char **opt_value) +{ + const char *p = option; + + /* + * Parse option name + */ + + if ( *p == '\0' ) { + sieve_enotify_error(nlog, "empty option specified"); + return FALSE; + } + + if ( i_isalnum(*p) ) { + p++; + + while ( i_isalnum(*p) || *p == '.' || *p == '-' || *p == '_' ) + p++; + } + + if ( *p != '=' || p == option ) { + sieve_enotify_error(nlog, "invalid option name specified in option '%s'", + str_sanitize(option, 80)); + return FALSE; + } + + if ( opt_name != NULL ) + *opt_name = t_strdup_until(option, p); + p++; + + if ( name_only ) + return TRUE; + + /* + * Parse option value + */ + + while ( *p != '\0' && *p != 0x0A && *p != 0x0D ) + p++; + + if ( *p != '\0' ) { + sieve_enotify_error(nlog, + "notify command: invalid option value specified in option '%s'", + str_sanitize(option, 80)); + return FALSE; + } + + if ( opt_value != NULL ) + *opt_value = p; + + return TRUE; +} + +struct _ext_enotify_option_check_context { + struct sieve_validator *valdtr; + const struct sieve_enotify_method *method; +}; + +static int _ext_enotify_option_check +(void *context, struct sieve_ast_argument *arg) +{ + struct _ext_enotify_option_check_context *optn_context = + (struct _ext_enotify_option_check_context *) context; + struct sieve_validator *valdtr = optn_context->valdtr; + const struct sieve_enotify_method *method = optn_context->method; + struct sieve_enotify_log nlog; + const char *option = sieve_ast_argument_strc(arg); + const char *opt_name = NULL, *opt_value = NULL; + bool literal = sieve_argument_is_string_literal(arg); + + memset(&nlog, 0, sizeof(nlog)); + nlog.ehandler = sieve_validator_error_handler(valdtr); + nlog.prefix = "notify command"; + nlog.location = sieve_error_script_location + (sieve_validator_script(valdtr), arg->source_line); + + /* Parse option */ + + if ( !literal ) { + if ( !ext_enotify_option_parse(NULL, option, TRUE, &opt_name, &opt_value) ) + return TRUE; + } else { + if ( !ext_enotify_option_parse + (&nlog, option, FALSE, &opt_name, &opt_value) ) + return FALSE; + } + + /* Check option */ + + if ( method->compile_check_option != NULL ) + return method->compile_check_option(&nlog, opt_name, opt_value); + + return TRUE; +} + bool ext_enotify_compile_check_arguments (struct sieve_validator *valdtr, struct sieve_ast_argument *uri_arg, - struct sieve_ast_argument *msg_arg, struct sieve_ast_argument *from_arg) + struct sieve_ast_argument *msg_arg, struct sieve_ast_argument *from_arg, + struct sieve_ast_argument *options_arg) { const char *uri = sieve_ast_argument_strc(uri_arg); const char *scheme; @@ -190,6 +288,22 @@ bool ext_enotify_compile_check_arguments return FALSE; } + if ( options_arg != NULL ) { + struct sieve_ast_argument *option = options_arg; + struct _ext_enotify_option_check_context optn_context = { valdtr, method }; + + if ( sieve_ast_stringlist_map + (&option, (void *) &optn_context, _ext_enotify_option_check) <= 0 ) + return FALSE; + + /* Discard argument if options are not accepted by method */ + if ( method->compile_check_option == NULL ) { + sieve_argument_validate_warning(valdtr, options_arg, + "notify command: method '%s' accepts no options", scheme); + (void)sieve_ast_arguments_detach(options_arg,1); + } + } + return TRUE; } diff --git a/src/lib-sieve/plugins/enotify/ext-enotify-common.h b/src/lib-sieve/plugins/enotify/ext-enotify-common.h index 99012e210995f47f3cf099c7b46f899c62be9651..d3c172079081e7f5a89cf80709cf6bcec4273ade 100644 --- a/src/lib-sieve/plugins/enotify/ext-enotify-common.h +++ b/src/lib-sieve/plugins/enotify/ext-enotify-common.h @@ -72,7 +72,8 @@ const struct sieve_enotify_method *ext_enotify_method_find bool ext_enotify_compile_check_arguments (struct sieve_validator *valdtr, struct sieve_ast_argument *uri_arg, - struct sieve_ast_argument *msg_arg, struct sieve_ast_argument *from_arg); + struct sieve_ast_argument *msg_arg, struct sieve_ast_argument *from_arg, + struct sieve_ast_argument *options_arg); /* * Runtime diff --git a/src/lib-sieve/plugins/enotify/ntfy-mailto.c b/src/lib-sieve/plugins/enotify/ntfy-mailto.c index e2871b7a88d7ae498783083c22ae02bd0b2a714f..918f6fc2b57638721cd81efd41aa4e1f45f88616 100644 --- a/src/lib-sieve/plugins/enotify/ntfy-mailto.c +++ b/src/lib-sieve/plugins/enotify/ntfy-mailto.c @@ -80,6 +80,7 @@ const struct sieve_enotify_method mailto_notify = { ntfy_mailto_compile_check_uri, NULL, ntfy_mailto_compile_check_from, + NULL, ntfy_mailto_runtime_get_notify_capability, ntfy_mailto_runtime_check_operands, ntfy_mailto_action_print, @@ -239,6 +240,9 @@ static bool _uri_parse_recipients { string_t *to = t_str_new(128); const char *p = *uri_p; + + if ( *p == '\0' || *p == '?' ) + return TRUE; while ( *p != '\0' && *p != '?' ) { if ( *p == '%' ) { @@ -455,7 +459,7 @@ static bool _uri_parse_headers static bool ntfy_mailto_parse_uri (const struct sieve_enotify_log *nlog, const char *uri_body, - ARRAY_TYPE(recipients) *recipients_r, ARRAY_TYPE(headers) *headers_r, + ARRAY_TYPE(recipients) *recipients_r, ARRAY_TYPE(headers) *headers_r, const char **body, const char **subject) { const char *p = uri_body; @@ -501,7 +505,17 @@ static bool ntfy_mailto_compile_check_uri (const struct sieve_enotify_log *nlog, const char *uri ATTR_UNUSED, const char *uri_body) { - return ntfy_mailto_parse_uri(nlog, uri_body, NULL, NULL, NULL, NULL); + ARRAY_TYPE(recipients) recipients; + + t_array_init(&recipients, 4); + + if ( !ntfy_mailto_parse_uri(nlog, uri_body, &recipients, NULL, NULL, NULL) ) + return FALSE; + + if ( array_count(&recipients) == 0 ) + sieve_enotify_warning(nlog, "notification URI specifies no recipients"); + + return TRUE; } static bool ntfy_mailto_compile_check_from @@ -611,19 +625,26 @@ static void ntfy_mailto_action_print sieve_result_printf(rpenv, " => from : %s\n", act->from); sieve_result_printf(rpenv, " => recipients :\n" ); + recipients = array_get(&mtctx->recipients, &count); - for ( i = 0; i < count; i++ ) { - if ( recipients[i].carbon_copy ) - sieve_result_printf(rpenv, " + Cc: %s\n", recipients[i].full); - else - sieve_result_printf(rpenv, " + To: %s\n", recipients[i].full); + if ( count == 0 ) { + sieve_result_printf(rpenv, " NONE, action has no effect\n"); + } else { + for ( i = 0; i < count; i++ ) { + if ( recipients[i].carbon_copy ) + sieve_result_printf(rpenv, " + Cc: %s\n", recipients[i].full); + else + sieve_result_printf(rpenv, " + To: %s\n", recipients[i].full); + } } - sieve_result_printf(rpenv, " => headers :\n" ); headers = array_get(&mtctx->headers, &count); - for ( i = 0; i < count; i++ ) { - sieve_result_printf(rpenv, " + %s: %s\n", - headers[i].name, headers[i].body); + if ( count > 0 ) { + sieve_result_printf(rpenv, " => headers :\n" ); + for ( i = 0; i < count; i++ ) { + sieve_result_printf(rpenv, " + %s: %s\n", + headers[i].name, headers[i].body); + } } if ( mtctx->body != NULL ) @@ -666,6 +687,14 @@ static bool ntfy_mailto_send FILE *f; const char *outmsgid; + /* Get recipients */ + recipients = array_get(&mtctx->recipients, &count); + if ( count == 0 ) { + sieve_enotify_warning(nlog, + "notify mailto uri specifies no recipients; action has no effect."); + return TRUE; + } + /* Just to be sure */ if ( senv->smtp_open == NULL || senv->smtp_close == NULL ) { sieve_enotify_warning(nlog, @@ -697,8 +726,6 @@ static bool ntfy_mailto_send subject = "Notification: (no subject)"; } - recipients = array_get(&mtctx->recipients, &count); - /* Compose To and Cc headers */ to = NULL; cc = NULL; diff --git a/src/lib-sieve/plugins/enotify/sieve-ext-enotify.h b/src/lib-sieve/plugins/enotify/sieve-ext-enotify.h index 768907bd29c6ae0c296b150724ca269db6d502c6..44f794af3d18468177893798d1eaca0720fd9496 100644 --- a/src/lib-sieve/plugins/enotify/sieve-ext-enotify.h +++ b/src/lib-sieve/plugins/enotify/sieve-ext-enotify.h @@ -50,6 +50,9 @@ struct sieve_enotify_method { (const struct sieve_enotify_log *nlog, string_t *message); bool (*compile_check_from) (const struct sieve_enotify_log *nlog, string_t *from); + bool (*compile_check_option) + (const struct sieve_enotify_log *nlog, const char *option, + const char *value); /* Runtime */ const char *(*runtime_get_method_capability) @@ -74,7 +77,18 @@ struct sieve_enotify_method { void sieve_enotify_method_register(const struct sieve_enotify_method *method); /* - * Action context + * Notify execution environment + */ + +struct sieve_enotify_exec_env { + const struct sieve_enotify_log *notify_log; + + const struct sieve_script_env *scriptenv; + const struct sieve_message_data *msgdata; +}; + +/* + * Notify action */ struct sieve_enotify_action { @@ -86,13 +100,6 @@ struct sieve_enotify_action { const char *from; }; -struct sieve_enotify_exec_env { - const struct sieve_enotify_log *notify_log; - - const struct sieve_script_env *scriptenv; - const struct sieve_message_data *msgdata; -}; - /* * Logging */ diff --git a/tests/extensions/enotify/errors.svtest b/tests/extensions/enotify/errors.svtest index da11ff662c2a97021790ac7aae9fe51758c1227f..995e9c8f72d66f1478a901a76c163b3edc277934 100644 --- a/tests/extensions/enotify/errors.svtest +++ b/tests/extensions/enotify/errors.svtest @@ -34,3 +34,13 @@ test "Invalid mailto :from address (FIXME: count only)" { test_fail "wrong number of errors reported"; } } + +test "Invalid :options argument (FIXME: count only)" { + if test_compile "errors/options.sieve" { + test_fail "compile should have failed"; + } + + if not test_error :count "eq" :comparator "i;ascii-numeric" "6" { + test_fail "wrong number of errors reported"; + } +} diff --git a/tests/extensions/enotify/errors/options.sieve b/tests/extensions/enotify/errors/options.sieve new file mode 100644 index 0000000000000000000000000000000000000000..8c84632f8e458c013de61cadec6c850dedc397c8 --- /dev/null +++ b/tests/extensions/enotify/errors/options.sieve @@ -0,0 +1,18 @@ +require "enotify"; + +# 1: empty option +notify :options "" "mailto:stephan@rename-it.nl"; + +# 2: invalid option name syntax +notify :options "frop" "mailto:stephan@rename-it.nl"; + +# 3: invalid option name syntax +notify :options "_frop=" "mailto:stephan@rename-it.nl"; + +# 4: invalid option name syntax +notify :options "=frop" "mailto:stephan@rename-it.nl"; + +# 5: invalid value +notify :options "frop=frml +frop" "mailto:stephan@rename-it.nl"; +