diff --git a/src/lib-sieve/sieve-code.c b/src/lib-sieve/sieve-code.c index b4b74d9640d5df39690a550cab2cd80204a34f34..3d0f6d4ab1aba98bf02815da456c38602c8b3343 100644 --- a/src/lib-sieve/sieve-code.c +++ b/src/lib-sieve/sieve-code.c @@ -492,13 +492,17 @@ bool sieve_opr_string_dump_data { const struct sieve_opr_string_interface *intf; - if ( !sieve_operand_is_string(operand) ) + if ( !sieve_operand_is_string(operand) ) { + sieve_code_dumpf(denv, "ERROR: INVALID STRING OPERAND %s", operand->name); return FALSE; + } intf = (const struct sieve_opr_string_interface *) operand->interface; - if ( intf->dump == NULL ) + if ( intf->dump == NULL ) { + sieve_code_dumpf(denv, "ERROR: DUMP STRING OPERAND"); return FALSE; + } return intf->dump(denv, address, field_name); } @@ -511,6 +515,11 @@ bool sieve_opr_string_dump sieve_code_mark(denv); operand = sieve_operand_read(denv->sbin, address); + + if ( operand == NULL ) { + sieve_code_dumpf(denv, "ERROR: INVALID OPERAND"); + return FALSE; + } return sieve_opr_string_dump_data(denv, operand, address, field_name); } @@ -790,7 +799,8 @@ static bool opr_catenated_string_dump sieve_code_descend(denv); for ( i = 0; i < (unsigned int) elements; i++ ) { - sieve_opr_string_dump(denv, address, NULL); + if ( !sieve_opr_string_dump(denv, address, NULL) ) + return FALSE; } sieve_code_ascend(denv); diff --git a/src/testsuite/Makefile.am b/src/testsuite/Makefile.am index 61ebb99c33ef188146abdd4a99a075b1421dd42c..976cb5676565d7b9cdb41f8ad522acdbfac26d5a 100644 --- a/src/testsuite/Makefile.am +++ b/src/testsuite/Makefile.am @@ -47,6 +47,8 @@ tests = \ testsuite_SOURCES = \ testsuite-common.c \ testsuite-objects.c \ + testsuite-substitutions.c \ + testsuite-arguments.c \ testsuite-result.c \ $(commands) \ $(tests) \ @@ -56,5 +58,7 @@ testsuite_SOURCES = \ noinst_HEADERS = \ testsuite-common.h \ testsuite-objects.h \ + testsuite-substitutions.h \ + testsuite-arguments.h \ testsuite-result.h diff --git a/src/testsuite/ext-testsuite.c b/src/testsuite/ext-testsuite.c index 47c20f037d5c599efe271e9d5d7f8b4a98dfc693..f62cb19478bf3dba21f6a8287ab57f4aa0117cd7 100644 --- a/src/testsuite/ext-testsuite.c +++ b/src/testsuite/ext-testsuite.c @@ -45,6 +45,7 @@ #include "sieve-result.h" #include "testsuite-common.h" +#include "testsuite-arguments.h" /* * Operations @@ -65,8 +66,10 @@ const struct sieve_operation *testsuite_operations[] = { * Operands */ -const struct sieve_operand *testsuite_operands[] = - { &testsuite_object_operand }; +const struct sieve_operand *testsuite_operands[] = { + &testsuite_object_operand, + &testsuite_substitution_operand +}; /* * Extension @@ -92,7 +95,7 @@ const struct sieve_extension testsuite_extension = { ext_testsuite_binary_load, NULL, NULL, SIEVE_EXT_DEFINE_OPERATIONS(testsuite_operations), - SIEVE_EXT_DEFINE_OPERAND(testsuite_object_operand) + SIEVE_EXT_DEFINE_OPERANDS(testsuite_operands) }; /* Extension implementation */ @@ -108,6 +111,9 @@ static bool ext_testsuite_validator_load(struct sieve_validator *valdtr) sieve_validator_register_command(valdtr, &tst_test_error); sieve_validator_register_command(valdtr, &tst_test_result); + sieve_validator_argument_override(valdtr, SAT_VAR_STRING, + &testsuite_string_argument); + return testsuite_validator_context_initialize(valdtr); } diff --git a/src/testsuite/testsuite-arguments.c b/src/testsuite/testsuite-arguments.c new file mode 100644 index 0000000000000000000000000000000000000000..c86424babcf15d29232ddb23cd5667a8556ce874 --- /dev/null +++ b/src/testsuite/testsuite-arguments.c @@ -0,0 +1,192 @@ +/* Copyright (c) 2002-2008 Dovecot Sieve authors, see the included COPYING file + */ + +#include "lib.h" +#include "str.h" +#include "str-sanitize.h" +#include "array.h" + +#include "sieve-common.h" +#include "sieve-ast.h" +#include "sieve-commands.h" +#include "sieve-code.h" +#include "sieve-validator.h" +#include "sieve-generator.h" +#include "sieve-dump.h" + +#include "testsuite-common.h" +#include "testsuite-substitutions.h" +#include "testsuite-arguments.h" + +#include <ctype.h> + +/* + * Testsuite string argument + */ + +static bool arg_testsuite_string_validate + (struct sieve_validator *validator, struct sieve_ast_argument **arg, + struct sieve_command_context *context); + +const struct sieve_argument testsuite_string_argument = { + "@testsuite-string", + NULL, NULL, + arg_testsuite_string_validate, + NULL, + sieve_arg_catenated_string_generate, +}; + +static bool arg_testsuite_string_validate +(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, + struct sieve_command_context *cmd) +{ + enum { ST_NONE, ST_OPEN, ST_SUBSTITUTION, ST_PARAM, ST_CLOSE } state = + ST_NONE; + pool_t pool = sieve_ast_pool((*arg)->ast); + struct sieve_arg_catenated_string *catstr = NULL; + string_t *str = sieve_ast_argument_str(*arg); + const char *p, *strstart, *substart = NULL; + const char *strval = (const char *) str_data(str); + const char *strend = strval + str_len(str); + bool result = TRUE; + string_t *subs_name = t_str_new(256); + string_t *subs_param = t_str_new(256); + + T_BEGIN { + /* Initialize substitution structure */ + + p = strval; + strstart = p; + while ( result && p < strend ) { + switch ( state ) { + + /* Nothing found yet */ + case ST_NONE: + if ( *p == '%' ) { + substart = p; + state = ST_OPEN; + str_truncate(subs_name, 0); + str_truncate(subs_param, 0); + } + p++; + break; + + /* Got '%' */ + case ST_OPEN: + if ( *p == '{' ) { + state = ST_SUBSTITUTION; + p++; + } else + state = ST_NONE; + break; + + /* Got '%{' */ + case ST_SUBSTITUTION: + state = ST_PARAM; + + while ( *p != '}' && *p != ':' ) { + if ( !i_isalnum(*p) ) { + state = ST_NONE; + break; + } + str_append_c(subs_name, *p); + p++; + } + break; + + /* Got '%{name' */ + case ST_PARAM: + if ( *p == ':' ) { + p++; + while ( *p != '}' ) { + str_append_c(subs_param, *p); + p++; + } + } + state = ST_CLOSE; + break; + + /* Finished parsing param, expecting '}' */ + case ST_CLOSE: + if ( *p == '}' ) { + struct sieve_ast_argument *strarg; + + /* We now know that the substitution is valid */ + + if ( catstr == NULL ) { + catstr = sieve_arg_catenated_string_create(*arg); + } + + /* Add the substring that is before the substitution to the + * variable-string AST. + */ + if ( substart > strstart ) { + string_t *newstr = str_new(pool, substart - strstart); + str_append_n(newstr, strstart, substart - strstart); + + strarg = sieve_ast_argument_string_create_raw + ((*arg)->ast, newstr, (*arg)->source_line); + sieve_arg_catenated_string_add_element(catstr, strarg); + + /* Give other substitution extensions a chance to do their work */ + if ( !sieve_validator_argument_activate_super + (valdtr, cmd, strarg, FALSE) ) { + result = FALSE; + break; + } + } + + strarg = testsuite_substitution_argument_create + (valdtr, (*arg)->ast, (*arg)->source_line, str_c(subs_name), + str_c(subs_param)); + + if ( strarg != NULL ) + sieve_arg_catenated_string_add_element(catstr, strarg); + else { + sieve_argument_validate_error(valdtr, *arg, + "unknown testsuite substitution type '%s'", str_c(subs_name)); + } + + strstart = p + 1; + substart = strstart; + + p++; + } + + /* Finished, reset for the next substitution */ + state = ST_NONE; + } + } + } T_END; + + /* Bail out early if substitution is invalid */ + if ( !result ) return FALSE; + + /* Check whether any substitutions were found */ + if ( catstr == NULL ) { + /* No substitutions in this string, pass it on to any other substution + * extension. + */ + return sieve_validator_argument_activate_super(valdtr, cmd, *arg, TRUE); + } + + /* Add the final substring that comes after the last substitution to the + * variable-string AST. + */ + if ( strend > strstart ) { + struct sieve_ast_argument *strarg; + string_t *newstr = str_new(pool, strend - strstart); + str_append_n(newstr, strstart, strend - strstart); + + strarg = sieve_ast_argument_string_create_raw + ((*arg)->ast, newstr, (*arg)->source_line); + sieve_arg_catenated_string_add_element(catstr, strarg); + + /* Give other substitution extensions a chance to do their work */ + if ( !sieve_validator_argument_activate_super + (valdtr, cmd, strarg, FALSE) ) + return FALSE; + } + + return TRUE; +} diff --git a/src/testsuite/testsuite-arguments.h b/src/testsuite/testsuite-arguments.h new file mode 100644 index 0000000000000000000000000000000000000000..5c268f1dd598b542a27bcf73b688b2e5b9f7fcb6 --- /dev/null +++ b/src/testsuite/testsuite-arguments.h @@ -0,0 +1,6 @@ +#ifndef __TESTSUITE_ARGUMENTS_H +#define __TESTSUITE_ARGUMENTS_H + +extern const struct sieve_argument testsuite_string_argument; + +#endif diff --git a/src/testsuite/testsuite-common.h b/src/testsuite/testsuite-common.h index 7e6300d75ed0979b93e5ff5def1109eb419225c6..a0c09e0e850df548ccbbe5f9a740b4d858d7ee34 100644 --- a/src/testsuite/testsuite-common.h +++ b/src/testsuite/testsuite-common.h @@ -94,9 +94,11 @@ extern const struct sieve_operation test_result_operation; */ extern const struct sieve_operand testsuite_object_operand; +extern const struct sieve_operand testsuite_substitution_operand; enum testsuite_operand_code { - TESTSUITE_OPERAND_OBJECT + TESTSUITE_OPERAND_OBJECT, + TESTSUITE_OPERAND_SUBSTITUTION }; /* diff --git a/src/testsuite/testsuite-substitutions.c b/src/testsuite/testsuite-substitutions.c new file mode 100644 index 0000000000000000000000000000000000000000..44a6ffd1c98e68119556439de201a5e1ee32303c --- /dev/null +++ b/src/testsuite/testsuite-substitutions.c @@ -0,0 +1,274 @@ +#include "lib.h" + +#include "sieve.h" +#include "sieve-code.h" +#include "sieve-commands.h" +#include "sieve-binary.h" +#include "sieve-generator.h" +#include "sieve-interpreter.h" +#include "sieve-dump.h" + +#include "testsuite-common.h" +#include "testsuite-substitutions.h" + +/* + * Forward declarations + */ + +void testsuite_opr_substitution_emit + (struct sieve_binary *sbin, const struct testsuite_substitution *tsub, + const char *param); + +/* + * Testsuite substitutions + */ + +/* FIXME: make this extendible */ + +enum { + TESTSUITE_SUBSTITUTION_FILE, + TESTSUITE_SUBSTITUTION_MAILBOX, + TESTSUITE_SUBSTITUTION_SMTPOUT +}; + +static const struct testsuite_substitution testsuite_file_substitution; +static const struct testsuite_substitution testsuite_mailbox_substitution; +static const struct testsuite_substitution testsuite_smtpout_substitution; + +static const struct testsuite_substitution *substitutions[] = { + &testsuite_file_substitution, + &testsuite_mailbox_substitution, + &testsuite_smtpout_substitution +}; + +static const unsigned int substitutions_count = N_ELEMENTS(substitutions); + +static inline const struct testsuite_substitution *testsuite_substitution_get +(unsigned int code) +{ + if ( code > substitutions_count ) + return NULL; + + return substitutions[code]; +} + +const struct testsuite_substitution *testsuite_substitution_find +(const char *identifier) +{ + unsigned int i; + + for ( i = 0; i < substitutions_count; i++ ) { + if ( strcasecmp(substitutions[i]->object.identifier, identifier) == 0 ) + return substitutions[i]; + } + + return NULL; +} + +/* + * Substitution argument + */ + +static bool arg_testsuite_substitution_generate + (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg, + struct sieve_command_context *context); + +struct _testsuite_substitution_context { + const struct testsuite_substitution *tsub; + const char *param; +}; + +const struct sieve_argument testsuite_substitution_argument = { + "@testsuite-substitution", + NULL, NULL, NULL, NULL, + arg_testsuite_substitution_generate +}; + +struct sieve_ast_argument *testsuite_substitution_argument_create +(struct sieve_validator *validator ATTR_UNUSED, struct sieve_ast *ast, + unsigned int source_line, const char *substitution, const char *param) +{ + const struct testsuite_substitution *tsub; + struct _testsuite_substitution_context *tsctx; + struct sieve_ast_argument *arg; + pool_t pool; + + tsub = testsuite_substitution_find(substitution); + if ( tsub == NULL ) + return NULL; + + arg = sieve_ast_argument_create(ast, source_line); + arg->type = SAAT_STRING; + arg->argument = &testsuite_substitution_argument; + + pool = sieve_ast_pool(ast); + tsctx = p_new(pool, struct _testsuite_substitution_context, 1); + tsctx->tsub = tsub; + tsctx->param = p_strdup(pool, param); + arg->context = (void *) tsctx; + + return arg; +} + +static bool arg_testsuite_substitution_generate +(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg, + struct sieve_command_context *context ATTR_UNUSED) +{ + struct _testsuite_substitution_context *tsctx = + (struct _testsuite_substitution_context *) arg->context; + + testsuite_opr_substitution_emit(cgenv->sbin, tsctx->tsub, tsctx->param); + + return TRUE; +} + +/* + * Substitution operand + */ + +static bool opr_substitution_dump + (const struct sieve_dumptime_env *denv, sieve_size_t *address, + const char *field_name); +static bool opr_substitution_read_value + (const struct sieve_runtime_env *renv, sieve_size_t *address, string_t **str); + +const struct sieve_opr_string_interface testsuite_substitution_interface = { + opr_substitution_dump, + opr_substitution_read_value +}; + +const struct sieve_operand testsuite_substitution_operand = { + "test-substitution", + &testsuite_extension, + TESTSUITE_OPERAND_SUBSTITUTION, + &string_class, + &testsuite_substitution_interface +}; + +void testsuite_opr_substitution_emit +(struct sieve_binary *sbin, const struct testsuite_substitution *tsub, + const char *param) +{ + /* Default variable storage */ + (void) sieve_operand_emit_code(sbin, &testsuite_substitution_operand); + (void) sieve_binary_emit_unsigned(sbin, tsub->object.code); + (void) sieve_binary_emit_cstring(sbin, param); +} + +static bool opr_substitution_dump +(const struct sieve_dumptime_env *denv, sieve_size_t *address, + const char *field_name) +{ + unsigned int code = 0; + const struct testsuite_substitution *tsub; + string_t *param; + + if ( !sieve_binary_read_unsigned(denv->sbin, address, &code) ) + return FALSE; + + tsub = testsuite_substitution_get(code); + if ( tsub == NULL ) + return FALSE; + + if ( !sieve_binary_read_string(denv->sbin, address, ¶m) ) + return FALSE; + + if ( field_name != NULL ) + sieve_code_dumpf(denv, "%s: TEST_SUBS %%{%s:%s}", + field_name, tsub->object.identifier, str_c(param)); + else + sieve_code_dumpf(denv, "TEST_SUBS %%{%s:%s}", + tsub->object.identifier, str_c(param)); + return TRUE; +} + +static bool opr_substitution_read_value +(const struct sieve_runtime_env *renv, sieve_size_t *address, string_t **str) +{ + const struct testsuite_substitution *tsub; + unsigned int code = 0; + string_t *param; + + if ( !sieve_binary_read_unsigned(renv->sbin, address, &code) ) + return FALSE; + + tsub = testsuite_substitution_get(code); + if ( tsub == NULL ) + return FALSE; + + /* Parameter str can be NULL if we are requested to only skip and not + * actually read the argument. + */ + if ( str == NULL ) + return sieve_binary_read_string(renv->sbin, address, NULL); + + if ( !sieve_binary_read_string(renv->sbin, address, ¶m) ) + return FALSE; + + return tsub->get_value(str_c(param), str); +} + +/* + * Testsuite substitution definitions + */ + +static bool testsuite_file_substitution_get_value + (const char *param, string_t **result); +static bool testsuite_mailbox_substitution_get_value + (const char *param, string_t **result); +static bool testsuite_smtpout_substitution_get_value + (const char *param, string_t **result); + +static const struct testsuite_substitution testsuite_file_substitution = { + SIEVE_OBJECT( + "file", + &testsuite_substitution_operand, + TESTSUITE_SUBSTITUTION_FILE + ), + testsuite_file_substitution_get_value +}; + +static const struct testsuite_substitution testsuite_mailbox_substitution = { + SIEVE_OBJECT( + "mailbox", + &testsuite_substitution_operand, + TESTSUITE_SUBSTITUTION_MAILBOX + ), + testsuite_mailbox_substitution_get_value +}; + +static const struct testsuite_substitution testsuite_smtpout_substitution = { + SIEVE_OBJECT( + "smtpout", + &testsuite_substitution_operand, + TESTSUITE_SUBSTITUTION_SMTPOUT + ), + testsuite_smtpout_substitution_get_value +}; + +static bool testsuite_file_substitution_get_value + (const char *param, string_t **result) +{ + *result = t_str_new(256); + + str_printfa(*result, "[FILE: %s]", param); + return TRUE; +} + +static bool testsuite_mailbox_substitution_get_value + (const char *param, string_t **result) +{ + *result = t_str_new(256); + + str_printfa(*result, "[MAILBOX: %s]", param); + return TRUE; +} + +static bool testsuite_smtpout_substitution_get_value + (const char *param, string_t **result) +{ + *result = t_str_new(256); + + str_printfa(*result, "[SMTPOUT: %s]", param); + return TRUE; +} diff --git a/src/testsuite/testsuite-substitutions.h b/src/testsuite/testsuite-substitutions.h new file mode 100644 index 0000000000000000000000000000000000000000..9fd0718271ee821a8b9bcccdbb2705243869c9d8 --- /dev/null +++ b/src/testsuite/testsuite-substitutions.h @@ -0,0 +1,20 @@ +#ifndef __TESTSUITE_SUBSTITUTIONS_H +#define __TESTSUITE_SUBSTITUTIONS_H + +#include "sieve-common.h" +#include "sieve-objects.h" + +struct testsuite_substitution { + struct sieve_object object; + + bool (*get_value)(const char *param, string_t **result); +}; + +const struct testsuite_substitution *testsuite_substitution_find + (const char *identifier); + +struct sieve_ast_argument *testsuite_substitution_argument_create + (struct sieve_validator *validator, struct sieve_ast *ast, + unsigned int source_line, const char *substitution, const char *param); + +#endif /* __TESTSUITE_SUBSTITUTIONS_H */ diff --git a/tests/testsuite.svtest b/tests/testsuite.svtest index 5cf884ed1f6ee128db0891afd62a6f99d901120c..9e4823ccb9914b1f18984ff66a9f88cdf3995fcc 100644 --- a/tests/testsuite.svtest +++ b/tests/testsuite.svtest @@ -1,6 +1,8 @@ require "vnd.dovecot.testsuite"; require "envelope"; +/* Test message environment */ + test "Message Environment" { test_set "message" text: From: sirius@rename-it.nl @@ -31,6 +33,8 @@ Friep! keep; } +/* Test envelope environment */ + test "Envelope Environment" { test_set "envelope.from" "stephan@hutsefluts.nl"; @@ -68,3 +72,9 @@ test "Envelope Environment" { test_fail "envelope.auth data not reset properly (2)."; } } + +/* Test substitutions */ + +test "Substitutions" { + test_fail "file substitution failed: %{file:frop}."; +}