diff --git a/Makefile.am b/Makefile.am index 330c5046ad12bec7b3bbb9a6a07c801c403fbb7a..064df045809da3a50c5915660ed673a0b378d22f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,6 +55,7 @@ test_cases = \ tests/extensions/variables/regex.svtest \ tests/extensions/include/errors.svtest \ tests/extensions/include/variables.svtest \ + tests/extensions/include/once.svtest \ tests/extensions/imap4flags/basic.svtest \ tests/extensions/imap4flags/hasflag.svtest \ tests/extensions/imap4flags/errors.svtest \ diff --git a/README b/README index 4585a30d0065145b468c507b043cae820494fa1f..05ef0d1c8aa2e0f2d7cee328b9b0b07630ff4a2e 100644 --- a/README +++ b/README @@ -96,7 +96,7 @@ following list outlines the implementation status of each supported extension: regex: mostly full; but suboptimal and no UTF-8 body: mostly full, but text body-transform implementation is simple and some issues make it still not completely RFC incompliant. - include: almost full; new specification requires changes. + include: almost full; global namespace missing vacation: mostly full; handling of utf-8 in headers is non-existant imap4flags: full (old imapflags supported for backwards compatibility) variables: mostly full; currently no support for future namespaces diff --git a/TODO b/TODO index 3abfdf05438e8504c3044d56825f3bfe9afd396d..02389df35baeb6a8d60db052e3af415cc7247840 100644 --- a/TODO +++ b/TODO @@ -6,9 +6,6 @@ Current: Next (in order of descending priority/precedence): -* Upgrade include extension to latest draft specification. -* Implement namespace support for variables extension (to complete include - extension) * Improve error handling. Now it is not very consistent, especially for the Sieve command line tools. * Improve debugging support in the sieve-test tool: @@ -39,6 +36,8 @@ Next (in order of descending priority/precedence): compliance. Body test support currently matches but barely exceeds the original CMU Sieve implentation in terms of standards compliance. - Improve handling of invalid addresses in headers (requires Dovecot changes) +* Implement namespace support for variables extension (to complete include + extension) * Implement environment extension * Add normalize() method to comparators to normalize the string before matching (for efficiency). diff --git a/src/lib-sieve/plugins/include/cmd-include.c b/src/lib-sieve/plugins/include/cmd-include.c index 5dbb27ceb29b03883eb66739eb7dcc1b3f4ee09e..8ee6a33099f59240a5c83218013b5214b0ad4c29 100644 --- a/src/lib-sieve/plugins/include/cmd-include.c +++ b/src/lib-sieve/plugins/include/cmd-include.c @@ -277,7 +277,8 @@ static bool cmd_include_generate * This yields the id of the binary block containing the compiled byte code. */ if ( !ext_include_generate_include - (cgenv, cmd, ctx_data->location, ctx_data->script, &included) ) + (cgenv, cmd, ctx_data->location, ctx_data->script, &included, + ctx_data->include_once) ) return FALSE; (void)sieve_operation_emit_code(cgenv->sbin, &include_operation); diff --git a/src/lib-sieve/plugins/include/ext-include-common.c b/src/lib-sieve/plugins/include/ext-include-common.c index d7559e8449f36581edeee625b73472d98925a4f3..fd9cc2f23834884d70a3baba91c977f69968a3df 100644 --- a/src/lib-sieve/plugins/include/ext-include-common.c +++ b/src/lib-sieve/plugins/include/ext-include-common.c @@ -2,6 +2,7 @@ */ #include "lib.h" +#include "array.h" #include "str-sanitize.h" #include "sieve-common.h" @@ -38,14 +39,19 @@ static inline struct ext_include_generator_context * /* Interpreter context */ +struct ext_include_interpreter_global { + ARRAY_DEFINE(included_scripts, struct sieve_script *); + + struct sieve_variable_storage *variables; +}; + struct ext_include_interpreter_context { struct ext_include_interpreter_context *parent; + struct ext_include_interpreter_global *global; struct sieve_interpreter *interp; pool_t pool; - struct sieve_variable_storage *global_variables; - unsigned int nesting_level; struct sieve_script *script; @@ -231,14 +237,16 @@ static void ext_include_runtime_init (struct ext_include_interpreter_context *) context; if ( ctx->parent == NULL ) { - ctx->global_variables = sieve_variable_storage_create + ctx->global = p_new(ctx->pool, struct ext_include_interpreter_global, 1); + ctx->global->variables = sieve_variable_storage_create (ctx->pool, ext_include_binary_get_global_scope(renv->sbin), 0); + p_array_init(&ctx->global->included_scripts, ctx->pool, 10); } else { - ctx->global_variables = ctx->parent->global_variables; + ctx->global = ctx->parent->global; } sieve_ext_variables_set_storage - (renv->interp, ctx->global_variables, &include_extension); + (renv->interp, ctx->global->variables, &include_extension); } static struct sieve_interpreter_extension include_interpreter_extension = { @@ -325,7 +333,7 @@ struct sieve_variable_storage *ext_include_interpreter_get_global_variables struct ext_include_interpreter_context *ctx = ext_include_get_interpreter_context(interp); - return ctx->global_variables; + return ctx->global->variables; } /* @@ -335,7 +343,7 @@ struct sieve_variable_storage *ext_include_interpreter_get_global_variables bool ext_include_generate_include (const struct sieve_codegen_env *cgenv, struct sieve_command_context *cmd, enum ext_include_script_location location, struct sieve_script *script, - const struct ext_include_script_info **included_r) + const struct ext_include_script_info **included_r, bool once) { bool result = TRUE; struct sieve_ast *ast; @@ -366,16 +374,18 @@ bool ext_include_generate_include } /* Check for circular include */ - pctx = ctx; - while ( pctx != NULL ) { - if ( sieve_script_equals(pctx->script, script) ) { - sieve_command_generate_error(gentr, cmd, "circular include"); + if ( !once ) { + pctx = ctx; + while ( pctx != NULL ) { + if ( sieve_script_equals(pctx->script, script) ) { + sieve_command_generate_error(gentr, cmd, "circular include"); - return FALSE; - } + return FALSE; + } - pctx = pctx->parent; - } + pctx = pctx->parent; + } + } /* Get binary context */ binctx = ext_include_binary_init(sbin, cgenv->ast); @@ -438,7 +448,11 @@ bool ext_include_generate_include if ( sbin != NULL ) (void) sieve_binary_block_set_active(sbin, this_block_id, NULL); sieve_generator_free(&subgentr); - } else result = FALSE; + } else { + sieve_sys_error("include: failed to activate binary block %d for " + "generating code for the included script", inc_block_id); + result = FALSE; + } /* Cleanup */ sieve_ast_unref(&ast); @@ -454,27 +468,39 @@ bool ext_include_generate_include */ static int ext_include_runtime_check_circular -(const struct sieve_runtime_env *renv, - struct ext_include_interpreter_context *ctx, +(struct ext_include_interpreter_context *ctx, const struct ext_include_script_info *include) { struct ext_include_interpreter_context *pctx; - int result = SIEVE_EXEC_OK; pctx = ctx; - while ( result > 0 && pctx != NULL ) { + while ( pctx != NULL ) { - if ( sieve_script_equals(include->script, pctx->script) ) { - sieve_runtime_trace_error(renv, - "circular include for script: %s [%d]", - sieve_script_name(include->script), include->block_id); - result = SIEVE_EXEC_BIN_CORRUPT; - } + if ( sieve_script_equals(include->script, pctx->script) ) + return TRUE; pctx = pctx->parent; } - return result; + return FALSE; +} + +static bool ext_include_runtime_include_mark +(struct ext_include_interpreter_context *ctx, + const struct ext_include_script_info *include, bool once) +{ + struct sieve_script *const *includes; + unsigned int count, i; + + includes = array_get(&ctx->global->included_scripts, &count); + for ( i = 0; i < count; i++ ) { + if ( sieve_script_equals(include->script, includes[i]) ) + return ( !once ); + } + + array_append(&ctx->global->included_scripts, &include->script, 1); + + return TRUE; } bool ext_include_execute_include @@ -490,7 +516,7 @@ bool ext_include_execute_include included = ext_include_binary_script_get_included(binctx, include_id); if ( included == NULL ) { sieve_runtime_trace_error(renv, "invalid include id: %d", include_id); - return SIEVE_EXEC_BIN_CORRUPT; + return SIEVE_EXEC_BIN_CORRUPT; } ctx = ext_include_get_interpreter_context(renv->interp); @@ -499,6 +525,27 @@ bool ext_include_execute_include "INCLUDE command (script: %s, id: %d block: %d) START::", sieve_script_name(included->script), include_id, included->block_id); + /* If :once modifier is specified, check for duplicate include */ + if ( !ext_include_runtime_include_mark(ctx, included, once) ) { + /* skip */ + + sieve_runtime_trace(renv, + "INCLUDE command (block: %d) SKIPPED ::", included->block_id); + return result; + } + + /* Check circular include during interpretation as well. + * Let's not trust binaries. + */ + if ( ext_include_runtime_check_circular(ctx, included) ) { + sieve_runtime_trace_error(renv, + "circular include for script: %s [%d]", + sieve_script_name(included->script), included->block_id); + + /* Situation has no valid way to emerge at runtime */ + return SIEVE_EXEC_BIN_CORRUPT; + } + if ( ctx->parent == NULL ) { struct ext_include_interpreter_context *curctx = NULL; struct sieve_error_handler *ehandler = @@ -508,7 +555,7 @@ bool ext_include_execute_include bool interrupted = FALSE; /* We are the top-level interpreter instance */ - + /* Activate block for included script */ if ( !sieve_binary_block_set_active (renv->sbin, included->block_id, &this_block_id) ) { @@ -571,28 +618,23 @@ bool ext_include_execute_include if ( curctx->include != NULL ) { /* Sub-include requested */ - - /* Check circular include during interpretation as well. - * Let's not trust binaries. - */ - result = ext_include_runtime_check_circular - (renv, curctx, curctx->include); - + /* Activate the sub-include's block */ - if ( result > 0 && !sieve_binary_block_set_active + if ( !sieve_binary_block_set_active (renv->sbin, curctx->include->block_id, NULL) ) { sieve_runtime_trace_error(renv, "invalid block id: %d", curctx->include->block_id); result = SIEVE_EXEC_BIN_CORRUPT; } - + if ( result > 0 ) { /* Create sub-interpreter */ subinterp = sieve_interpreter_create(renv->sbin, ehandler); if ( subinterp != NULL ) { curctx = ext_include_interpreter_context_init_child - (subinterp, curctx, curctx->include->script, curctx->include); + (subinterp, curctx, curctx->include->script, + curctx->include); /* Start the sub-include's interpreter */ curctx->include = NULL; @@ -633,8 +675,9 @@ bool ext_include_execute_include (void) sieve_binary_block_set_active(renv->sbin, this_block_id, NULL); } else { /* We are an included script already, defer inclusion to main interpreter */ + ctx->include = included; - sieve_interpreter_interrupt(renv->interp); + sieve_interpreter_interrupt(renv->interp); } return result; diff --git a/src/lib-sieve/plugins/include/ext-include-common.h b/src/lib-sieve/plugins/include/ext-include-common.h index 5d2e8d84f333f7da83234e7782d1d7695d39922d..06e191a2a94ad72cebafc06de13638374b6a0686 100644 --- a/src/lib-sieve/plugins/include/ext-include-common.h +++ b/src/lib-sieve/plugins/include/ext-include-common.h @@ -94,7 +94,7 @@ void ext_include_register_generator_context bool ext_include_generate_include (const struct sieve_codegen_env *cgenv, struct sieve_command_context *cmd, enum ext_include_script_location location, struct sieve_script *script, - const struct ext_include_script_info **included_r); + const struct ext_include_script_info **included_r, bool once); /* Interpreter context */ diff --git a/src/lib-sieve/plugins/include/ext-include.c b/src/lib-sieve/plugins/include/ext-include.c index a1430d9ad56e794a2d46069f3e0500eae07c5d32..14c7eac45bec282c3d1b726bcedf515caaaf0e87 100644 --- a/src/lib-sieve/plugins/include/ext-include.c +++ b/src/lib-sieve/plugins/include/ext-include.c @@ -6,7 +6,7 @@ * * Authors: Stephan Bosch * Specification: draft-ietf-sieve-include-01 - * Implementation: almost full; :once modifier and global namespace are missing. + * Implementation: almost full; global namespace is missing. * Status: experimental * */ diff --git a/tests/extensions/include/included/once-2.sieve b/tests/extensions/include/included/once-2.sieve index 226df6de39d1ac05cb357db22cabd76a4f936e58..53dc287630b31a72c6da555bfd87c7979b63d08c 100644 --- a/tests/extensions/include/included/once-2.sieve +++ b/tests/extensions/include/included/once-2.sieve @@ -7,6 +7,6 @@ set "result" "${result} TWO"; keep; -include "once-1.sieve"; +include :once "once-1.sieve"; return; diff --git a/tests/extensions/include/included/once-3.sieve b/tests/extensions/include/included/once-3.sieve new file mode 100644 index 0000000000000000000000000000000000000000..739651ed063c40c443218b54a8f9ae2e44305090 --- /dev/null +++ b/tests/extensions/include/included/once-3.sieve @@ -0,0 +1,3 @@ +require "include"; + +include "once-4"; diff --git a/tests/extensions/include/included/once-4.sieve b/tests/extensions/include/included/once-4.sieve new file mode 100644 index 0000000000000000000000000000000000000000..9cc1a47a3601c85718ccb6989021db04d10f09d4 --- /dev/null +++ b/tests/extensions/include/included/once-4.sieve @@ -0,0 +1,3 @@ +require "include"; + +include :once "once-3"; diff --git a/tests/extensions/include/once.svtest b/tests/extensions/include/once.svtest index 1a08c8da4d1a05684fee7288ecb6a76a5160c4c9..3395c6b6b8f2b59968709b7510aa438b168900b4 100644 --- a/tests/extensions/include/once.svtest +++ b/tests/extensions/include/once.svtest @@ -18,3 +18,7 @@ test "Included Once" { test_fail "unexpected result value: ${result}"; } } + +test "Included Once recursive" { + include "once-3"; +}