From 725a6c66a2ff03a5fe55f2a16f8f9ab1eec68abd Mon Sep 17 00:00:00 2001 From: Stephan Bosch <stephan@rename-it.nl> Date: Sun, 29 Nov 2015 11:50:59 +0100 Subject: [PATCH] lib-sieve: Implemented the foreverypart and mime extensions (RFC 5703). --- Makefile.am | 2 + README | 4 +- TODO | 2 +- configure.ac | 1 + src/lib-sieve/Makefile.am | 1 + src/lib-sieve/plugins/Makefile.am | 1 + src/lib-sieve/plugins/mime/Makefile.am | 24 + src/lib-sieve/plugins/mime/cmd-break.c | 273 +++++++ src/lib-sieve/plugins/mime/cmd-foreverypart.c | 339 +++++++++ src/lib-sieve/plugins/mime/ext-foreverypart.c | 62 ++ src/lib-sieve/plugins/mime/ext-mime-common.c | 27 + src/lib-sieve/plugins/mime/ext-mime-common.h | 80 ++ src/lib-sieve/plugins/mime/ext-mime.c | 77 ++ src/lib-sieve/plugins/mime/tag-mime.c | 704 ++++++++++++++++++ src/lib-sieve/sieve-extensions.c | 4 +- tests/extensions/mime/content-header.svtest | 161 ++++ tests/extensions/mime/errors.svtest | 54 ++ .../mime/errors/address-mime-tag.sieve | 38 + tests/extensions/mime/errors/break.sieve | 157 ++++ .../mime/errors/exists-mime-tag.sieve | 43 ++ .../extensions/mime/errors/foreverypart.sieve | 45 ++ .../mime/errors/header-mime-tag.sieve | 100 +++ tests/extensions/mime/execute.svtest | 23 + .../mime/execute/foreverypart.sieve | 7 + 24 files changed, 2226 insertions(+), 3 deletions(-) create mode 100644 src/lib-sieve/plugins/mime/Makefile.am create mode 100644 src/lib-sieve/plugins/mime/cmd-break.c create mode 100644 src/lib-sieve/plugins/mime/cmd-foreverypart.c create mode 100644 src/lib-sieve/plugins/mime/ext-foreverypart.c create mode 100644 src/lib-sieve/plugins/mime/ext-mime-common.c create mode 100644 src/lib-sieve/plugins/mime/ext-mime-common.h create mode 100644 src/lib-sieve/plugins/mime/ext-mime.c create mode 100644 src/lib-sieve/plugins/mime/tag-mime.c create mode 100644 tests/extensions/mime/content-header.svtest create mode 100644 tests/extensions/mime/errors.svtest create mode 100644 tests/extensions/mime/errors/address-mime-tag.sieve create mode 100644 tests/extensions/mime/errors/break.sieve create mode 100644 tests/extensions/mime/errors/exists-mime-tag.sieve create mode 100644 tests/extensions/mime/errors/foreverypart.sieve create mode 100644 tests/extensions/mime/errors/header-mime-tag.sieve create mode 100644 tests/extensions/mime/execute.svtest create mode 100644 tests/extensions/mime/execute/foreverypart.sieve diff --git a/Makefile.am b/Makefile.am index efca13914..6fe58623a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -166,6 +166,8 @@ test_cases = \ tests/extensions/duplicate/execute-vnd.svtest \ tests/extensions/metadata/execute.svtest \ tests/extensions/metadata/errors.svtest \ + tests/extensions/mime/errors.svtest \ + tests/extensions/mime/content-header.svtest \ tests/extensions/vnd.dovecot/debug/execute.svtest \ tests/extensions/vnd.dovecot/environment/basic.svtest \ tests/extensions/vnd.dovecot/environment/variables.svtest \ diff --git a/README b/README index 4ea77be22..ca04fae8b 100644 --- a/README +++ b/README @@ -120,6 +120,8 @@ following list outlines the implementation status of each supported extension: mailbox (RFC 5490; Section 3): fully supported (v0.1.10+), but ACL permissions are not verified for mailboxexists. mboxmetadata and servermetadata (RFC 5490): fully supported (v0.4.7+) + foreverypart (RFC 5703; Section 3): fully supported (v0.4.10+). + mime (RFC 5703; Section 4): fully supported (v0.4.10+). include (RFC 6609): fully supported (v0.4.0+) duplicate (RFC 7352): fully supported (v0.4.3+). regex (draft v08; not latest version): almost fully supported, but @@ -154,7 +156,7 @@ following list outlines the implementation status of each supported extension: useful for Dovecot in particular, but many of them are. Currently, the author has taken notice of the following extensions: - foreverypart, mime, replace, enclose, and extracttext (RFC 5703): planned. + replace, enclose, and extracttext (RFC 5703): planned. imapsieve (RFC 6785): planned. envelope-dsn, envelope-deliverby, redirect-dsn and redirect-deliverby (RFC 6009): planned; depends on lib-smtp changes in diff --git a/TODO b/TODO index 82675b7f5..285914545 100644 --- a/TODO +++ b/TODO @@ -33,7 +33,7 @@ Next (mostly in order of descending priority/precedence): - Adjust Sieve script API to support asynchronous script retrieval to retrieve scripts in parallel when possible. * Implement message modification and extraction API in order to: - - Implement replace, enclose, foreverypart, mime and extracttext extensions + - Implement replace, enclose, and extracttext extensions * Improve error handling. - Implement dropping errors in the user's mailbox as a mail message. * Finish body extension: diff --git a/configure.ac b/configure.ac index c64f3dabe..1fd885d8b 100644 --- a/configure.ac +++ b/configure.ac @@ -209,6 +209,7 @@ src/lib-sieve/plugins/editheader/Makefile src/lib-sieve/plugins/metadata/Makefile src/lib-sieve/plugins/duplicate/Makefile src/lib-sieve/plugins/index/Makefile +src/lib-sieve/plugins/mime/Makefile src/lib-sieve/plugins/vnd.dovecot/Makefile src/lib-sieve/plugins/vnd.dovecot/debug/Makefile src/lib-sieve/plugins/vnd.dovecot/environment/Makefile diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am index 98ae11f17..99ca22bb2 100644 --- a/src/lib-sieve/Makefile.am +++ b/src/lib-sieve/Makefile.am @@ -77,6 +77,7 @@ plugins = \ $(extdir)/duplicate/libsieve_ext_duplicate.la \ $(extdir)/index/libsieve_ext_index.la \ $(extdir)/metadata/libsieve_ext_metadata.la \ + $(extdir)/mime/libsieve_ext_mime.la \ $(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \ $(extdir)/vnd.dovecot/environment/libsieve_ext_vnd_environment.la \ $(unfinished_plugins) diff --git a/src/lib-sieve/plugins/Makefile.am b/src/lib-sieve/plugins/Makefile.am index 4e626582d..887235f1c 100644 --- a/src/lib-sieve/plugins/Makefile.am +++ b/src/lib-sieve/plugins/Makefile.am @@ -24,6 +24,7 @@ SUBDIRS = \ duplicate \ index \ metadata \ + mime \ vnd.dovecot \ $(UNFINISHED) diff --git a/src/lib-sieve/plugins/mime/Makefile.am b/src/lib-sieve/plugins/mime/Makefile.am new file mode 100644 index 000000000..07901912b --- /dev/null +++ b/src/lib-sieve/plugins/mime/Makefile.am @@ -0,0 +1,24 @@ +noinst_LTLIBRARIES = libsieve_ext_mime.la + +AM_CPPFLAGS = \ + -I$(srcdir)/../.. \ + -I$(srcdir)/../../util \ + $(LIBDOVECOT_INCLUDE) + +commands = \ + cmd-foreverypart.c \ + cmd-break.c + +tags = \ + tag-mime.c + +libsieve_ext_mime_la_SOURCES = \ + ext-mime.c \ + ext-foreverypart.c \ + ext-mime-common.c \ + $(commands) \ + $(tags) + +noinst_HEADERS = \ + ext-mime-common.h + diff --git a/src/lib-sieve/plugins/mime/cmd-break.c b/src/lib-sieve/plugins/mime/cmd-break.c new file mode 100644 index 000000000..a5204f668 --- /dev/null +++ b/src/lib-sieve/plugins/mime/cmd-break.c @@ -0,0 +1,273 @@ +/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file + */ + +#include "sieve-common.h" +#include "sieve-code.h" +#include "sieve-extensions.h" +#include "sieve-commands.h" +#include "sieve-validator.h" +#include "sieve-generator.h" +#include "sieve-interpreter.h" +#include "sieve-binary.h" +#include "sieve-dump.h" + +#include "ext-mime-common.h" + +#include <ctype.h> + +/* break + * + * Syntax: + * break [":name" <name: string>] + * + */ + +static bool cmd_break_registered + (struct sieve_validator *valdtr, const struct sieve_extension *ext, + struct sieve_command_registration *cmd_reg); +static bool cmd_break_pre_validate + (struct sieve_validator *valdtr, struct sieve_command *cmd); +static bool cmd_break_validate + (struct sieve_validator *valdtr, struct sieve_command *cmd); +static bool cmd_break_generate + (const struct sieve_codegen_env *cgenv, + struct sieve_command *ctx); + +const struct sieve_command_def cmd_break = { + "break", + SCT_COMMAND, + 0, 0, FALSE, FALSE, + cmd_break_registered, + cmd_break_pre_validate, + cmd_break_validate, + NULL, + cmd_break_generate, + NULL, +}; + +/* + * Tagged arguments + */ + +/* Forward declarations */ + +static bool cmd_break_validate_name_tag + (struct sieve_validator *valdtr, struct sieve_ast_argument **arg, + struct sieve_command *cmd); + +/* Argument objects */ + +static const struct sieve_argument_def break_name_tag = { + "name", + NULL, + cmd_break_validate_name_tag, + NULL, NULL, NULL +}; + +/* + * Break operation + */ + +static bool cmd_break_operation_dump + (const struct sieve_dumptime_env *denv, sieve_size_t *address); +static int cmd_break_operation_execute + (const struct sieve_runtime_env *renv, sieve_size_t *address); + +const struct sieve_operation_def break_operation = { + "break", + &foreverypart_extension, + EXT_FOREVERYPART_OPERATION_BREAK, + cmd_break_operation_dump, + cmd_break_operation_execute +}; + +/* + * Validation data + */ + +struct cmd_break_data { + struct sieve_ast_argument *name; + struct sieve_command *loop_cmd; +}; + +/* + * Tag validation + */ + +static bool cmd_break_validate_name_tag +(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, + struct sieve_command *cmd) +{ + struct cmd_break_data *data = + (struct cmd_break_data *)cmd->data; + struct sieve_ast_argument *tag = *arg; + + /* Detach the tag itself */ + *arg = sieve_ast_arguments_detach(*arg, 1); + + /* Check syntax: + * :name <string> + */ + if ( !sieve_validate_tag_parameter + (valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, TRUE) ) + return FALSE; + data->name = *arg; + + /* Skip parameter */ + *arg = sieve_ast_argument_next(*arg); + return TRUE; +} + +/* + * Command registration + */ + +static bool cmd_break_registered +(struct sieve_validator *valdtr, const struct sieve_extension *ext, + struct sieve_command_registration *cmd_reg) +{ + sieve_validator_register_tag + (valdtr, cmd_reg, ext, &break_name_tag, 0); + + return TRUE; +} + +/* + * Command validation + */ + +static bool cmd_break_pre_validate +(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd) +{ + struct cmd_break_data *data; + pool_t pool = sieve_command_pool(cmd); + + data = p_new(pool, struct cmd_break_data, 1); + cmd->data = data; + return TRUE; +} + +static bool cmd_break_validate +(struct sieve_validator *valdtr, struct sieve_command *cmd) +{ + struct cmd_break_data *data = + (struct cmd_break_data *)cmd->data; + struct sieve_ast_node *node = cmd->ast_node; + const char *name = ( data->name == NULL ? + NULL : sieve_ast_argument_strc(data->name) ); + + i_assert(node != NULL); + while ( node != NULL && node->command != NULL ) { + if ( sieve_command_is(node->command, cmd_foreverypart) ) { + struct ext_foreverypart_loop *loop = + (struct ext_foreverypart_loop *)node->command->data; + if ( name == NULL || + (name != NULL && loop->name != NULL && + strcmp(name, loop->name) == 0) ) { + data->loop_cmd = node->command; + break; + } + } + node = sieve_ast_node_parent(node); + } + + if ( data->loop_cmd == NULL ) { + if ( name == NULL ) { + sieve_command_validate_error(valdtr, cmd, + "the break command is not placed inside " + "a foreverypart loop"); + } else { + sieve_command_validate_error(valdtr, cmd, + "the break command is not placed inside " + "a foreverypart loop named `%s'", + name); + } + return FALSE; + } + + return TRUE; +} + +/* + * Code generation + */ + +static bool cmd_break_generate +(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd) +{ + struct cmd_break_data *data = + (struct cmd_break_data *)cmd->data; + struct ext_foreverypart_loop *loop; + + i_assert( data->loop_cmd != NULL ); + loop = (struct ext_foreverypart_loop *)data->loop_cmd->data; + + sieve_operation_emit(cgenv->sblock, cmd->ext, &break_operation); + sieve_jumplist_add(loop->exit_jumps, + sieve_binary_emit_offset(cgenv->sblock, 0)); + return TRUE; +} + +/* + * Code dump + */ + +static bool cmd_break_operation_dump +(const struct sieve_dumptime_env *denv, sieve_size_t *address) +{ + unsigned int pc = *address; + sieve_offset_t offset; + + sieve_code_dumpf(denv, "BREAK"); + sieve_code_descend(denv); + + if ( !sieve_binary_read_offset(denv->sblock, address, &offset) ) + return FALSE; + + sieve_code_dumpf(denv, "END: %d [%08x]", offset, pc + offset); + return TRUE; +} + +/* + * Code execution + */ + +static int cmd_break_operation_execute +(const struct sieve_runtime_env *renv, sieve_size_t *address) +{ + struct sieve_interpreter_loop *loop; + unsigned int pc = *address; + sieve_offset_t offset; + sieve_size_t loop_end; + + /* + * Read operands + */ + + if ( !sieve_binary_read_offset(renv->sblock, address, &offset) ) + { + sieve_runtime_trace_error(renv, "invalid loop end offset"); + return SIEVE_EXEC_BIN_CORRUPT; + } + + loop_end = pc + offset; + + /* + * Perform operation + */ + + sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "break command"); + sieve_runtime_trace_descend(renv); + + loop = sieve_interpreter_loop_get + (renv->interp, loop_end, &foreverypart_extension); + if ( loop == NULL ) { + sieve_runtime_trace_error(renv, "no matching loop found"); + return SIEVE_EXEC_BIN_CORRUPT; + } + + sieve_interpreter_loop_break(renv->interp, loop); + return SIEVE_EXEC_OK; +} + + diff --git a/src/lib-sieve/plugins/mime/cmd-foreverypart.c b/src/lib-sieve/plugins/mime/cmd-foreverypart.c new file mode 100644 index 000000000..c5d2ba617 --- /dev/null +++ b/src/lib-sieve/plugins/mime/cmd-foreverypart.c @@ -0,0 +1,339 @@ +/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file + */ + +#include "sieve-common.h" +#include "sieve-code.h" +#include "sieve-extensions.h" +#include "sieve-commands.h" +#include "sieve-validator.h" +#include "sieve-generator.h" +#include "sieve-interpreter.h" +#include "sieve-binary.h" +#include "sieve-dump.h" +#include "sieve-message.h" + +#include "ext-mime-common.h" + +#include <ctype.h> + +/* Foreverypart + * + * Syntax: + * foreverypart [":name" <name: string>] <block> + * + */ + +static bool cmd_foreverypart_registered + (struct sieve_validator *valdtr, const struct sieve_extension *ext, + struct sieve_command_registration *cmd_reg); +static bool cmd_foreverypart_pre_validate + (struct sieve_validator *valdtr, struct sieve_command *cmd); +static bool cmd_foreverypart_generate + (const struct sieve_codegen_env *cgenv, + struct sieve_command *ctx); + +const struct sieve_command_def cmd_foreverypart = { + "foreverypart", + SCT_COMMAND, + 0, 0, TRUE, TRUE, + cmd_foreverypart_registered, + cmd_foreverypart_pre_validate, + NULL, NULL, + cmd_foreverypart_generate, + NULL, +}; + +/* + * Tagged arguments + */ + +/* Forward declarations */ + +static bool cmd_foreverypart_validate_name_tag + (struct sieve_validator *valdtr, struct sieve_ast_argument **arg, + struct sieve_command *cmd); + +/* Argument objects */ + +static const struct sieve_argument_def foreverypart_name_tag = { + "name", + NULL, + cmd_foreverypart_validate_name_tag, + NULL, NULL, NULL +}; + +/* + * foreverypart operation + */ + +static bool cmd_foreverypart_begin_operation_dump + (const struct sieve_dumptime_env *denv, sieve_size_t *address); +static int cmd_foreverypart_begin_operation_execute + (const struct sieve_runtime_env *renv, sieve_size_t *address); + +const struct sieve_operation_def foreverypart_begin_operation = { + "FOREVERYPART_BEGIN", + &foreverypart_extension, + EXT_FOREVERYPART_OPERATION_FOREVERYPART_BEGIN, + cmd_foreverypart_begin_operation_dump, + cmd_foreverypart_begin_operation_execute +}; + +static bool cmd_foreverypart_end_operation_dump + (const struct sieve_dumptime_env *denv, sieve_size_t *address); +static int cmd_foreverypart_end_operation_execute + (const struct sieve_runtime_env *renv, sieve_size_t *address); + +const struct sieve_operation_def foreverypart_end_operation = { + "FOREVERYPART_END", + &foreverypart_extension, + EXT_FOREVERYPART_OPERATION_FOREVERYPART_END, + cmd_foreverypart_end_operation_dump, + cmd_foreverypart_end_operation_execute +}; + +/* + * Tag validation + */ + +static bool cmd_foreverypart_validate_name_tag +(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, + struct sieve_command *cmd) +{ + struct ext_foreverypart_loop *loop = + (struct ext_foreverypart_loop *)cmd->data; + struct sieve_ast_argument *tag = *arg; + + /* Detach the tag itself */ + *arg = sieve_ast_arguments_detach(*arg, 1); + + /* Check syntax: + * :name <string> + */ + if ( !sieve_validate_tag_parameter + (valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, TRUE) ) + return FALSE; + loop->name = sieve_ast_argument_strc(*arg); + + /* Detach parameter */ + *arg = sieve_ast_arguments_detach(*arg, 1); + return TRUE; +} + +/* + * Command registration + */ + +static bool cmd_foreverypart_registered +(struct sieve_validator *valdtr, const struct sieve_extension *ext, + struct sieve_command_registration *cmd_reg) +{ + sieve_validator_register_tag + (valdtr, cmd_reg, ext, &foreverypart_name_tag, 0); + return TRUE; +} + +/* + * Command validation + */ + +static bool cmd_foreverypart_pre_validate +(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd) +{ + struct ext_foreverypart_loop *loop; + pool_t pool = sieve_command_pool(cmd); + + loop = p_new(pool, struct ext_foreverypart_loop, 1); + cmd->data = loop; + + return TRUE; +} + +/* + * Code generation + */ + +static bool cmd_foreverypart_generate +(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd) +{ + struct ext_foreverypart_loop *loop = + (struct ext_foreverypart_loop *)cmd->data; + sieve_size_t block_begin, loop_jump; + + /* Emit FOREVERYPART_BEGIN operation */ + sieve_operation_emit(cgenv->sblock, + cmd->ext, &foreverypart_begin_operation); + + /* Emit exit address */ + loop->exit_jumps = sieve_jumplist_create + (sieve_command_pool(cmd), cgenv->sblock); + sieve_jumplist_add(loop->exit_jumps, + sieve_binary_emit_offset(cgenv->sblock, 0)); + block_begin = sieve_binary_block_get_size(cgenv->sblock); + + /* Generate loop block */ + if ( !sieve_generate_block(cgenv, cmd->ast_node) ) + return FALSE; + + /* Emit FOREVERYPART_END operation */ + sieve_operation_emit(cgenv->sblock, + cmd->ext, &foreverypart_end_operation); + loop_jump = sieve_binary_block_get_size(cgenv->sblock); + i_assert(loop_jump > block_begin); + (void)sieve_binary_emit_offset + (cgenv->sblock, (loop_jump - block_begin)); + + /* Resolve exit address */ + sieve_jumplist_resolve(loop->exit_jumps); + + return TRUE; +} + +/* + * Code dump + */ + +static bool cmd_foreverypart_begin_operation_dump +(const struct sieve_dumptime_env *denv, sieve_size_t *address) +{ + unsigned int pc = *address; + sieve_offset_t offset; + + sieve_code_dumpf(denv, "FOREVERYPART_BEGIN"); + sieve_code_descend(denv); + + if ( !sieve_binary_read_offset(denv->sblock, address, &offset) ) + return FALSE; + + sieve_code_dumpf(denv, "END: %d [%08x]", offset, pc + offset); + return TRUE; +} + +static bool cmd_foreverypart_end_operation_dump +(const struct sieve_dumptime_env *denv, + sieve_size_t *address ATTR_UNUSED) +{ + unsigned int pc = *address; + sieve_offset_t offset; + + sieve_code_dumpf(denv, "FOREVERYPART_END"); + sieve_code_descend(denv); + + if ( !sieve_binary_read_offset(denv->sblock, address, &offset) ) + return FALSE; + + sieve_code_dumpf(denv, "BEGIN: -%d [%08x]", offset, pc - offset); + return TRUE; +} + +/* + * Code execution + */ + +static int cmd_foreverypart_begin_operation_execute +(const struct sieve_runtime_env *renv, sieve_size_t *address) +{ + struct sieve_interpreter_loop *loop; + struct ext_foreverypart_runtime_loop *fploop, *sfploop; + unsigned int pc = *address; + sieve_offset_t offset; + sieve_size_t loop_end; + pool_t pool; + int ret; + + /* + * Read operands + */ + + if ( !sieve_binary_read_offset(renv->sblock, address, &offset) ) + { + sieve_runtime_trace_error(renv, "invalid loop end offset"); + return SIEVE_EXEC_BIN_CORRUPT; + } + + loop_end = pc + offset; + + /* + * Perform operation + */ + + sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "foreverypart loop begin"); + sieve_runtime_trace_descend(renv); + + sfploop = ext_foreverypart_runtime_loop_get_current(renv); + + loop = sieve_interpreter_loop_start + (renv->interp, loop_end, &foreverypart_extension); + if ( loop == NULL ) + return SIEVE_EXEC_BIN_CORRUPT; + + pool = sieve_interpreter_loop_get_pool(loop); + fploop = p_new(pool, struct ext_foreverypart_runtime_loop, 1); + + if ( sfploop == NULL ) { + if ( (ret=sieve_message_part_iter_init + (&fploop->part_iter, renv)) <= 0 ) + return ret; + } else { + sieve_message_part_iter_children(&sfploop->part_iter, + &fploop->part_iter); + } + fploop->part = sieve_message_part_iter_current(&fploop->part_iter); + if (fploop->part != NULL) { + sieve_interpreter_loop_set_context(loop, (void*)fploop); + } else { + /* No children parts to iterate */ + sieve_interpreter_loop_break(renv->interp, loop); + } + return SIEVE_EXEC_OK; +} + +static int cmd_foreverypart_end_operation_execute +(const struct sieve_runtime_env *renv, sieve_size_t *address) +{ + struct sieve_interpreter_loop *loop; + struct ext_foreverypart_runtime_loop *fploop; + unsigned int pc = *address; + sieve_offset_t offset; + sieve_size_t loop_begin; + + /* + * Read operands + */ + + if ( !sieve_binary_read_offset(renv->sblock, address, &offset) ) + { + sieve_runtime_trace_error(renv, "invalid loop begin offset"); + return SIEVE_EXEC_BIN_CORRUPT; + } + + loop_begin = pc - offset; + + /* + * Perform operation + */ + + sieve_runtime_trace(renv, + SIEVE_TRLVL_ACTIONS, "foreverypart loop end"); + sieve_runtime_trace_descend(renv); + + loop = sieve_interpreter_loop_get + (renv->interp, *address, &foreverypart_extension); + if ( loop == NULL ) { + sieve_runtime_trace_error(renv, "no matching loop found"); + return SIEVE_EXEC_BIN_CORRUPT; + } + + fploop = (struct ext_foreverypart_runtime_loop *) + sieve_interpreter_loop_get_context(loop); + i_assert(fploop->part != NULL); + fploop->part = sieve_message_part_iter_next(&fploop->part_iter); + if ( fploop->part == NULL ) + sieve_interpreter_loop_break(renv->interp, loop); + else + sieve_interpreter_loop_next(renv->interp, loop, loop_begin); + + return SIEVE_EXEC_OK; +} + + diff --git a/src/lib-sieve/plugins/mime/ext-foreverypart.c b/src/lib-sieve/plugins/mime/ext-foreverypart.c new file mode 100644 index 000000000..c0e223880 --- /dev/null +++ b/src/lib-sieve/plugins/mime/ext-foreverypart.c @@ -0,0 +1,62 @@ +/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file + */ + +/* Extension foreverypart + * ---------------------- + * + * Authors: Stephan Bosch + * Specification: RFC 5703,Section 3 + * Implementation: skeleton + * Status: development + * + */ + +#include "sieve-common.h" + +#include "sieve-code.h" +#include "sieve-extensions.h" +#include "sieve-actions.h" +#include "sieve-commands.h" +#include "sieve-validator.h" +#include "sieve-generator.h" +#include "sieve-interpreter.h" +#include "sieve-result.h" + +#include "ext-mime-common.h" + +/* + * Operations + */ + +const struct sieve_operation_def *ext_foreverypart_operations[] = { + &foreverypart_begin_operation, + &foreverypart_end_operation, + &break_operation +}; + +/* + * Extension + */ + +static bool ext_foreverypart_validator_load + (const struct sieve_extension *ext, struct sieve_validator *valdtr); + +const struct sieve_extension_def foreverypart_extension = { + .name = "foreverypart", + .validator_load = ext_foreverypart_validator_load, + SIEVE_EXT_DEFINE_OPERATIONS(ext_foreverypart_operations) +}; + +/* + * Extension validation + */ + +static bool ext_foreverypart_validator_load +(const struct sieve_extension *ext, struct sieve_validator *valdtr) +{ + /* Register new commands */ + sieve_validator_register_command(valdtr, ext, &cmd_foreverypart); + sieve_validator_register_command(valdtr, ext, &cmd_break); + + return TRUE; +} diff --git a/src/lib-sieve/plugins/mime/ext-mime-common.c b/src/lib-sieve/plugins/mime/ext-mime-common.c new file mode 100644 index 000000000..21d49048a --- /dev/null +++ b/src/lib-sieve/plugins/mime/ext-mime-common.c @@ -0,0 +1,27 @@ +/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file + */ + +#include "sieve-common.h" +#include "sieve-interpreter.h" + +#include "ext-mime-common.h" + +struct ext_foreverypart_runtime_loop * +ext_foreverypart_runtime_loop_get_current +(const struct sieve_runtime_env *renv) +{ + struct sieve_interpreter_loop *loop; + struct ext_foreverypart_runtime_loop *fploop; + + loop = sieve_interpreter_loop_get_surrounding + (renv->interp, NULL, &foreverypart_extension); + if ( loop == NULL ) { + fploop = NULL; + } else { + fploop = (struct ext_foreverypart_runtime_loop *) + sieve_interpreter_loop_get_context(loop); + i_assert(fploop->part != NULL); + } + + return fploop; +} diff --git a/src/lib-sieve/plugins/mime/ext-mime-common.h b/src/lib-sieve/plugins/mime/ext-mime-common.h new file mode 100644 index 000000000..9607bfa93 --- /dev/null +++ b/src/lib-sieve/plugins/mime/ext-mime-common.h @@ -0,0 +1,80 @@ +/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file + */ + +#ifndef __EXT_FOREVERYPART_COMMON_H +#define __EXT_FOREVERYPART_COMMON_H + +#include "sieve-message.h" + +/* + * Extension + */ + +extern const struct sieve_extension_def foreverypart_extension; +extern const struct sieve_extension_def mime_extension; + +/* + * Tagged arguments + */ + +extern const struct sieve_argument_def mime_tag; +extern const struct sieve_argument_def mime_anychild_tag; +extern const struct sieve_argument_def mime_type_tag; +extern const struct sieve_argument_def mime_subtype_tag; +extern const struct sieve_argument_def mime_contenttype_tag; +extern const struct sieve_argument_def mime_param_tag; + +/* + * Commands + */ + +struct ext_foreverypart_loop { + const char *name; + struct sieve_jumplist *exit_jumps; +}; + +extern const struct sieve_command_def cmd_foreverypart; +extern const struct sieve_command_def cmd_break; + +/* + * Operations + */ + +extern const struct sieve_operation_def foreverypart_begin_operation; +extern const struct sieve_operation_def foreverypart_end_operation; +extern const struct sieve_operation_def break_operation; + +enum ext_foreverypart_opcode { + EXT_FOREVERYPART_OPERATION_FOREVERYPART_BEGIN, + EXT_FOREVERYPART_OPERATION_FOREVERYPART_END, + EXT_FOREVERYPART_OPERATION_BREAK, +}; + +/* + * Operands + */ + +enum ext_mime_option { + EXT_MIME_OPTION_NONE = 0, + EXT_MIME_OPTION_TYPE, + EXT_MIME_OPTION_SUBTYPE, + EXT_MIME_OPTION_CONTENTTYPE, + EXT_MIME_OPTION_PARAM +}; + +extern const struct sieve_operand_def mime_operand; + +/* + * Foreverypart loop + */ + +struct ext_foreverypart_runtime_loop { + struct sieve_message_part_iter part_iter; + struct sieve_message_part *part; +}; + +struct ext_foreverypart_runtime_loop * +ext_foreverypart_runtime_loop_get_current +(const struct sieve_runtime_env *renv); + +#endif /* __EXT_FOREVERYPART_COMMON_H */ diff --git a/src/lib-sieve/plugins/mime/ext-mime.c b/src/lib-sieve/plugins/mime/ext-mime.c new file mode 100644 index 000000000..5f8e786ac --- /dev/null +++ b/src/lib-sieve/plugins/mime/ext-mime.c @@ -0,0 +1,77 @@ +/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file + */ + +/* Extension mime + * -------------- + * + * Authors: Stephan Bosch + * Specification: RFC 5703,Section 4 + * Implementation: skeleton + * Status: development + * + */ + +#include "sieve-common.h" + +#include "sieve-code.h" +#include "sieve-extensions.h" +#include "sieve-actions.h" +#include "sieve-commands.h" +#include "sieve-validator.h" +#include "sieve-generator.h" +#include "sieve-interpreter.h" +#include "sieve-message.h" +#include "sieve-result.h" + +#include "ext-mime-common.h" + +/* + * Extension + */ + +static bool ext_mime_validator_load + (const struct sieve_extension *ext, struct sieve_validator *valdtr); + +const struct sieve_extension_def mime_extension = { + .name = "mime", + .validator_load = ext_mime_validator_load, + SIEVE_EXT_DEFINE_OPERAND(mime_operand) +}; + +/* + * Extension validation + */ + +static bool ext_mime_validator_load +(const struct sieve_extension *ext, struct sieve_validator *valdtr) +{ + /* Register :mime tag and friends with header, address and exists test + * commands and we don't care whether these command are registered or + * even whether these will be registered at all. The validator handles + * either situation gracefully. + */ + sieve_validator_register_external_tag + (valdtr, "header", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE); + sieve_validator_register_external_tag + (valdtr, "header", ext, &mime_anychild_tag, 0); + sieve_validator_register_external_tag + (valdtr, "header", ext, &mime_type_tag, 0); + sieve_validator_register_external_tag + (valdtr, "header", ext, &mime_subtype_tag, 0); + sieve_validator_register_external_tag + (valdtr, "header", ext, &mime_contenttype_tag, 0); + sieve_validator_register_external_tag + (valdtr, "header", ext, &mime_param_tag, 0); + + sieve_validator_register_external_tag + (valdtr, "address", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE); + sieve_validator_register_external_tag + (valdtr, "address", ext, &mime_anychild_tag, 0); + + sieve_validator_register_external_tag + (valdtr, "exists", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE); + sieve_validator_register_external_tag + (valdtr, "exists", ext, &mime_anychild_tag, 0); + + return TRUE; +} diff --git a/src/lib-sieve/plugins/mime/tag-mime.c b/src/lib-sieve/plugins/mime/tag-mime.c new file mode 100644 index 000000000..28ee54572 --- /dev/null +++ b/src/lib-sieve/plugins/mime/tag-mime.c @@ -0,0 +1,704 @@ +/* Copyright (c) 2002-2015 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "rfc822-parser.h" +#include "rfc2231-parser.h" +#include "mail-storage.h" + +#include "sieve-common.h" +#include "sieve-stringlist.h" +#include "sieve-extensions.h" +#include "sieve-commands.h" +#include "sieve-binary.h" +#include "sieve-code.h" +#include "sieve-message.h" +#include "sieve-result.h" +#include "sieve-validator.h" +#include "sieve-generator.h" + +#include "ext-mime-common.h" + +/* + * Tagged argument + */ + +static bool tag_mime_validate + (struct sieve_validator *valdtr, struct sieve_ast_argument **arg, + struct sieve_command *cmd); +static bool tag_mime_generate + (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg, + struct sieve_command *context); + +const struct sieve_argument_def mime_tag = { + "mime", + NULL, + tag_mime_validate, + NULL, NULL, + tag_mime_generate +}; + +static bool tag_mime_option_validate + (struct sieve_validator *valdtr, struct sieve_ast_argument **arg, + struct sieve_command *cmd); + +const struct sieve_argument_def mime_anychild_tag = { + "anychild", + NULL, + tag_mime_option_validate, + NULL, NULL, NULL +}; + +const struct sieve_argument_def mime_type_tag = { + "type", + NULL, + tag_mime_option_validate, + NULL, NULL, NULL +}; + +const struct sieve_argument_def mime_subtype_tag = { + "subtype", + NULL, + tag_mime_option_validate, + NULL, NULL, NULL +}; + +const struct sieve_argument_def mime_contenttype_tag = { + "contenttype", + NULL, + tag_mime_option_validate, + NULL, NULL, NULL +}; + +const struct sieve_argument_def mime_param_tag = { + "param", + NULL, + tag_mime_option_validate, + NULL, NULL, NULL +}; + +/* + * Header override + */ + +static bool svmo_mime_dump_context + (const struct sieve_message_override *svmo, + const struct sieve_dumptime_env *denv, sieve_size_t *address); +static int svmo_mime_read_context + (const struct sieve_message_override *svmo, + const struct sieve_runtime_env *renv, sieve_size_t *address, + void **ho_context); +static int svmo_mime_header_override + (const struct sieve_message_override *svmo, + const struct sieve_runtime_env *renv, + bool mime_decode, struct sieve_stringlist **headers); + +const struct sieve_message_override_def mime_header_override = { + SIEVE_OBJECT("mime", &mime_operand, 0), + .sequence = 0, /* Completely replace header source */ + .dump_context = svmo_mime_dump_context, + .read_context = svmo_mime_read_context, + .header_override = svmo_mime_header_override +}; + +/* + * Operand + */ + +static const struct sieve_extension_objects ext_header_overrides = + SIEVE_EXT_DEFINE_MESSAGE_OVERRIDE(mime_header_override); + +const struct sieve_operand_def mime_operand = { + "mime operand", + &mime_extension, + 0, + &sieve_message_override_operand_class, + &ext_header_overrides +}; + +/* + * Tag data + */ + +struct tag_mime_data { + enum ext_mime_option mimeopt; + struct sieve_ast_argument *param_arg; + unsigned int anychild:1; +}; + +/* + * Tag validation + */ + +static struct tag_mime_data * +tag_mime_get_data(struct sieve_command *cmd, + struct sieve_ast_argument *tag) +{ + struct tag_mime_data *data; + + if (tag->argument->data == NULL) { + data = p_new(sieve_command_pool(cmd), struct tag_mime_data, 1); + tag->argument->data = (void *)data; + } else { + data = (struct tag_mime_data *)tag->argument->data; + } + + return data; +} + +static bool tag_mime_validate +(struct sieve_validator *valdtr ATTR_UNUSED, + struct sieve_ast_argument **arg, struct sieve_command *cmd) +{ + struct sieve_ast_argument *tag = *arg; + + /* Skip the tag itself */ + *arg = sieve_ast_argument_next(*arg); + + (void)tag_mime_get_data(cmd, tag); + return TRUE; +} + +static bool tag_mime_option_validate +(struct sieve_validator *valdtr ATTR_UNUSED, + struct sieve_ast_argument **arg, struct sieve_command *cmd) +{ + struct sieve_ast_argument *tag = *arg; + struct sieve_ast_argument *mime_arg; + struct tag_mime_data *data; + + /* Detach tag itself */ + *arg = sieve_ast_arguments_detach(*arg,1); + + /* Find required ":mime" tag */ + mime_arg = sieve_command_find_argument(cmd, &mime_tag); + if ( mime_arg == NULL ) { + sieve_argument_validate_error(valdtr, tag, + "the :%s tag for the %s %s cannot be specified " + "without the :mime tag", sieve_ast_argument_tag(tag), + sieve_command_identifier(cmd), sieve_command_type_name(cmd)); + return FALSE; + } + + /* Annotate ":mime" tag with the data provided by this option tag */ + data = tag_mime_get_data(cmd, mime_arg); + if ( sieve_argument_is(tag, mime_anychild_tag) ) + data->anychild = TRUE; + else { + if ( data->mimeopt != EXT_MIME_OPTION_NONE ) { + sieve_argument_validate_error(valdtr, *arg, + "the :type, :subtype, :contenttype, and :param " + "arguments for the %s test are mutually exclusive, " + "but more than one was specified", + sieve_command_identifier(cmd)); + return FALSE; + } + if ( sieve_argument_is(tag, mime_type_tag) ) + data->mimeopt = EXT_MIME_OPTION_TYPE; + else if ( sieve_argument_is(tag, mime_subtype_tag) ) + data->mimeopt = EXT_MIME_OPTION_SUBTYPE; + else if ( sieve_argument_is(tag, mime_contenttype_tag) ) + data->mimeopt = EXT_MIME_OPTION_CONTENTTYPE; + else if ( sieve_argument_is(tag, mime_param_tag) ) { + /* Check syntax: + * ":param" <param-list: string-list> + */ + if ( !sieve_validate_tag_parameter + (valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE) ) { + return FALSE; + } + + data->mimeopt = EXT_MIME_OPTION_PARAM; + data->param_arg = *arg; + + /* Detach parameter */ + *arg = sieve_ast_arguments_detach(*arg,1); + } else + i_unreached(); + } + return TRUE; +} + +/* + * Code generation + */ + +static bool tag_mime_generate +(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg, + struct sieve_command *cmd) +{ + struct tag_mime_data *data = + (struct tag_mime_data *)arg->argument->data; + + if ( sieve_ast_argument_type(arg) != SAAT_TAG ) + return FALSE; + + sieve_opr_message_override_emit + (cgenv->sblock, arg->argument->ext, &mime_header_override); + + (void)sieve_binary_emit_byte + (cgenv->sblock, ( data->anychild ? 1 : 0 )); + (void)sieve_binary_emit_byte + (cgenv->sblock, data->mimeopt); + if ( data->mimeopt == EXT_MIME_OPTION_PARAM && + !sieve_generate_argument(cgenv, data->param_arg, cmd) ) + return FALSE; + return TRUE; +} + +/* + * Content-type stringlist + */ + +enum content_type_part { + CONTENT_TYPE_PART_NONE = 0, + CONTENT_TYPE_PART_TYPE, + CONTENT_TYPE_PART_SUBTYPE, + CONTENT_TYPE_PART_CONTENTTYPE, +}; + +/* Object */ + +static int content_header_stringlist_next_item + (struct sieve_stringlist *_strlist, string_t **str_r); +static void content_header_stringlist_reset + (struct sieve_stringlist *_strlist); +static int content_header_stringlist_get_length + (struct sieve_stringlist *_strlist); +static void content_header_stringlist_set_trace + (struct sieve_stringlist *strlist, bool trace); + +struct content_header_stringlist { + struct sieve_stringlist strlist; + + struct sieve_header_list *source; + + enum ext_mime_option option; + const char *const *params; + + const char *const *param_values; +}; + +static struct sieve_stringlist *content_header_stringlist_create +(const struct sieve_runtime_env *renv, + struct sieve_header_list *source, + enum ext_mime_option option, const char *const *params) +{ + struct content_header_stringlist *strlist; + + strlist = t_new(struct content_header_stringlist, 1); + strlist->strlist.runenv = renv; + strlist->strlist.exec_status = SIEVE_EXEC_OK; + strlist->strlist.next_item = content_header_stringlist_next_item; + strlist->strlist.reset = content_header_stringlist_reset; + strlist->strlist.set_trace = content_header_stringlist_set_trace; + strlist->source = source; + strlist->option = option; + strlist->params = params; + + if ( option != EXT_MIME_OPTION_PARAM ) { + /* One header can have multiple parameters, so we cannot rely + on the source length for the :param option. */ + strlist->strlist.get_length = content_header_stringlist_get_length; + } + + return &strlist->strlist; +} + +/* Implementation */ + +static inline int _decode_hex_digit(const unsigned char digit) +{ + switch ( digit ) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return digit - '0'; + + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + return digit - 'a' + 0x0a; + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + return digit - 'A' + 0x0A; + } + return -1; +} + +static string_t * +content_type_param_decode(const char *value) +{ + const unsigned char *p, *plast; + + string_t *str = t_str_new(64); + plast = p = (const unsigned char *)value; + while ( *p != '\0' ) { + unsigned char ch; + int digit; + + if ( *p == '%' ) { + if ( p - plast > 0 ) + str_append_data(str, plast, (p - plast)); + p++; + if ( *p == '\0' || (digit=_decode_hex_digit(*p)) < 0 ) + return NULL; + ch = (unsigned char)digit; + p++; + if ( *p == '\0' || (digit=_decode_hex_digit(*p)) < 0 ) + return NULL; + ch = (ch << 4) + (unsigned char)digit; + str_append_data(str, &ch, 1); + plast = p + 1; + } + p++; + } + if ( p - plast > 0 ) + str_append_data(str, plast, (p - plast)); + return str; +} + +static string_t * +content_type_param_next(struct content_header_stringlist *strlist) +{ + const char *const *values = strlist->param_values; + + i_assert( strlist->params != NULL ); + + for ( ; *values != NULL; values += 2 ) { + const char *const *params = strlist->params; + const char *name = values[0], *value = values[1]; + size_t nlen = strlen(name); + + for ( ; *params != NULL; params++ ) { + size_t plen = strlen(*params); + + if ( plen != nlen && + (nlen != plen + 1 || name[nlen-1] != '*') ) + continue; + + if ( plen == nlen ) { + if ( strcasecmp(name, *params) == 0 ) { + strlist->param_values = values + 2; + return t_str_new_const(value, strlen(value)); + } + } else { + if ( strncasecmp(name, *params, plen) == 0 ) { + string_t *result = NULL; + + strlist->param_values = values + 2; + + // FIXME: transcode charset + value = strchr(value, '\''); + if (value != NULL) + value = strchr(value+1, '\''); + if (value != NULL) + result = content_type_param_decode(value + 1); + if (result == NULL) + strlist->param_values = NULL; + return result; + } + } + } + } + + strlist->param_values = NULL; + return NULL; +} + +// FIXME: not too happy with the use of string_t like this. +// Sieve should have a special runtime string type (TODO) +static string_t * +content_header_parse(struct content_header_stringlist *strlist, + const char *hdr_name, string_t *str) +{ + struct rfc822_parser_context parser; + const char *type, *p; + bool is_ctype = FALSE; + string_t *content; + + if ( strlist->option == EXT_MIME_OPTION_NONE ) + return str; + + if ( strcasecmp(hdr_name, "content-type") == 0 ) + is_ctype = TRUE; + else if ( strcasecmp(hdr_name, "content-disposition") != 0 ) + return t_str_new(0); + + /* Initialize parsing */ + rfc822_parser_init(&parser, str_data(str), str_len(str), NULL); + (void)rfc822_skip_lwsp(&parser); + + /* Parse content type/disposition */ + content = t_str_new(64); + if ( is_ctype ){ + if (rfc822_parse_content_type(&parser, content) < 0) { + str_truncate(content, 0); + return content; + } + } else { + if (rfc822_parse_mime_token(&parser, content) < 0) { + str_truncate(content, 0); + return content; + } + } + + /* Content-type value must end here, otherwise it is invalid after all */ + (void)rfc822_skip_lwsp(&parser); + if ( parser.data != parser.end && *parser.data != ';' ) { + str_truncate(content, 0); + return content; + } + + if ( strlist->option == EXT_MIME_OPTION_PARAM ) { + string_t *param_val; + + i_assert( strlist->params != NULL ); + + // FIXME: not very nice when multiple parameters in the same header + // are queried in successive tests. + str_truncate(content, 0); + rfc2231_parse(&parser, &strlist->param_values); + + param_val = content_type_param_next(strlist); + if ( param_val != NULL ) + content = param_val; + } else { + type = str_c(content); + if ( (p=strchr(type, '/')) == NULL ) { + i_assert( !is_ctype ); + if ( strlist->option == EXT_MIME_OPTION_SUBTYPE ) + str_truncate(content, 0); + } else { + i_assert( is_ctype ); + if ( strlist->option == EXT_MIME_OPTION_TYPE ) + str_truncate(content, (p - type)); + else if ( strlist->option == EXT_MIME_OPTION_SUBTYPE ) + str_delete(content, 0, (p - type) + 1); + } + } + + /* Success */ + return content; +} + +static int content_header_stringlist_next_item +(struct sieve_stringlist *_strlist, string_t **str_r) +{ + struct content_header_stringlist *strlist = + (struct content_header_stringlist *)_strlist; + const char *hdr_name; + int ret; + + if ( strlist->param_values != NULL ) { + string_t *param_val; + + i_assert( strlist->option == EXT_MIME_OPTION_PARAM ); + param_val = content_type_param_next(strlist); + if ( param_val != NULL ) { + *str_r = param_val; + return 1; + } + } + + if ( (ret=sieve_header_list_next_item + (strlist->source, &hdr_name, str_r)) <= 0 ) { + if (ret < 0) { + _strlist->exec_status = + strlist->source->strlist.exec_status; + } + return ret; + } + + *str_r = content_header_parse(strlist, hdr_name, *str_r); + return 1; +} + +static void content_header_stringlist_reset +(struct sieve_stringlist *_strlist) +{ + struct content_header_stringlist *strlist = + (struct content_header_stringlist *)_strlist; + sieve_header_list_reset(strlist->source); +} + +static int content_header_stringlist_get_length +(struct sieve_stringlist *_strlist) +{ + struct content_header_stringlist *strlist = + (struct content_header_stringlist *)_strlist; + return sieve_header_list_get_length(strlist->source); +} + +static void content_header_stringlist_set_trace +(struct sieve_stringlist *_strlist, bool trace) +{ + struct content_header_stringlist *strlist = + (struct content_header_stringlist *)_strlist; + sieve_header_list_set_trace(strlist->source, trace); +} + +/* + * Header override implementation + */ + +/* Context data */ + +struct svmo_mime_context { + enum ext_mime_option mimeopt; + const char *const *params; + unsigned int anychild:1; +}; + +/* Context coding */ + +static bool svmo_mime_dump_context +(const struct sieve_message_override *svmo ATTR_UNUSED, + const struct sieve_dumptime_env *denv, sieve_size_t *address) +{ + unsigned int anychild, mimeopt; + + if ( !sieve_binary_read_byte(denv->sblock, address, &anychild) ) + return FALSE; + if ( anychild > 0 ) + sieve_code_dumpf(denv, "anychild"); + + if ( !sieve_binary_read_byte(denv->sblock, address, &mimeopt) ) + return FALSE; + + switch ( mimeopt ) { + case EXT_MIME_OPTION_NONE: + break; + case EXT_MIME_OPTION_TYPE: + sieve_code_dumpf(denv, "option: type"); + break; + case EXT_MIME_OPTION_SUBTYPE: + sieve_code_dumpf(denv, "option: subtype"); + break; + case EXT_MIME_OPTION_CONTENTTYPE: + sieve_code_dumpf(denv, "option: contenttype"); + break; + case EXT_MIME_OPTION_PARAM: + sieve_code_dumpf(denv, "option: param"); + sieve_code_descend(denv); + if ( !sieve_opr_stringlist_dump(denv, address, "param-list") ) + return FALSE; + sieve_code_ascend(denv); + default: + return FALSE; + } + return TRUE; +} + +static int svmo_mime_read_context +(const struct sieve_message_override *svmo ATTR_UNUSED, + const struct sieve_runtime_env *renv, sieve_size_t *address, + void **ho_context) +{ + pool_t pool = sieve_result_pool(renv->result); // FIXME: investigate + struct svmo_mime_context *ctx; + unsigned int anychild = 0, mimeopt = EXT_MIME_OPTION_NONE; + struct sieve_stringlist *param_list = NULL; + int ret; + + if ( !sieve_binary_read_byte + (renv->sblock, address, &anychild) ) { + sieve_runtime_trace_error(renv, + "anychild: invalid byte"); + return SIEVE_EXEC_BIN_CORRUPT; + } + + if ( !sieve_binary_read_byte + (renv->sblock, address, &mimeopt) || + mimeopt > EXT_MIME_OPTION_PARAM ) { + sieve_runtime_trace_error(renv, + "option: invalid mime option code"); + return SIEVE_EXEC_BIN_CORRUPT; + } + + if ( mimeopt == EXT_MIME_OPTION_PARAM && + (ret=sieve_opr_stringlist_read + (renv, address, "param-list", ¶m_list)) <= 0 ) + return ret; + + ctx = p_new(pool, struct svmo_mime_context, 1); + ctx->anychild = (anychild == 0 ? FALSE : TRUE); + ctx->mimeopt = (enum ext_mime_option)mimeopt; + + if ( param_list != NULL && sieve_stringlist_read_all + (param_list, pool, &ctx->params) < 0 ) { + sieve_runtime_trace_error(renv, + "failed to read param-list operand"); + return param_list->exec_status; + } + + *ho_context = (void *) ctx; + return SIEVE_EXEC_OK; +} + +/* Override */ + +static int svmo_mime_header_override +(const struct sieve_message_override *svmo, + const struct sieve_runtime_env *renv, bool mime_decode, + struct sieve_stringlist **headers_r) +{ + struct svmo_mime_context *ctx = + (struct svmo_mime_context *)svmo->context; + struct ext_foreverypart_runtime_loop *sfploop; + struct sieve_header_list *headers; + struct sieve_stringlist *values; + + sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, + "header mime override:"); + sieve_runtime_trace_descend(renv); + + if ( ctx->anychild ) { + sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, + "headers from current mime part and children"); + } else { + sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, + "headers from current mime part"); + } + + sfploop = ext_foreverypart_runtime_loop_get_current(renv); + if ( sfploop == NULL ) { + headers = sieve_message_header_list_create + (renv, *headers_r, mime_decode); + } else { + headers = sieve_mime_header_list_create + (renv, *headers_r, &sfploop->part_iter, + mime_decode, ctx->anychild); + } + values = &headers->strlist; + + switch ( ctx->mimeopt ) { + case EXT_MIME_OPTION_NONE: + break; + case EXT_MIME_OPTION_TYPE: + sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, + "extract mime type from header value"); + break; + case EXT_MIME_OPTION_SUBTYPE: + sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, + "extract mime subtype from header value"); + break; + case EXT_MIME_OPTION_CONTENTTYPE: + sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, + "extract mime contenttype from header value"); + break; + case EXT_MIME_OPTION_PARAM: + sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, + "extract mime parameters from header value"); + break; + default: + i_unreached(); + } + + if ( ctx->mimeopt != EXT_MIME_OPTION_NONE ) { + values = content_header_stringlist_create + (renv, headers, ctx->mimeopt, ctx->params); + } + *headers_r = values; + + sieve_runtime_trace_ascend(renv); + return SIEVE_EXEC_OK; +} + diff --git a/src/lib-sieve/sieve-extensions.c b/src/lib-sieve/sieve-extensions.c index d3d022f85..4f75420b0 100644 --- a/src/lib-sieve/sieve-extensions.c +++ b/src/lib-sieve/sieve-extensions.c @@ -106,6 +106,8 @@ extern const struct sieve_extension_def date_extension; extern const struct sieve_extension_def index_extension; extern const struct sieve_extension_def ihave_extension; extern const struct sieve_extension_def duplicate_extension; +extern const struct sieve_extension_def mime_extension; +extern const struct sieve_extension_def foreverypart_extension; extern const struct sieve_extension_def mboxmetadata_extension; extern const struct sieve_extension_def servermetadata_extension; @@ -121,7 +123,7 @@ const struct sieve_extension_def *sieve_core_extensions[] = { ©_extension, &include_extension, &body_extension, &variables_extension, &enotify_extension, &environment_extension, &mailbox_extension, &date_extension, &index_extension, &ihave_extension, - &duplicate_extension + &duplicate_extension, &mime_extension, &foreverypart_extension }; const unsigned int sieve_core_extensions_count = diff --git a/tests/extensions/mime/content-header.svtest b/tests/extensions/mime/content-header.svtest new file mode 100644 index 000000000..9686e3544 --- /dev/null +++ b/tests/extensions/mime/content-header.svtest @@ -0,0 +1,161 @@ +require "vnd.dovecot.testsuite"; +require "relational"; +require "mime"; + +test_set "message" text: +From: stephan@example.com +To: timo@example.com +Subject: Frop +Content-Type: text/plain + +Frop +. +; + +test "Simple Content-Type :type" { + if not header :mime :type "content-type" "text" { + test_fail "wrong type extracted"; + } +} + +test "Simple Content-Type :subype" { + if not header :mime :subtype "content-type" "plain" { + test_fail "wrong subtype extracted"; + } +} + +test "Simple Content-Type :contenttype" { + if not header :mime :contenttype "content-type" "text/plain" { + test_fail "wrong contenttype extracted"; + } +} + +test_set "message" text: +From: stephan@example.com +To: timo@example.com +Subject: Frop +Content-Type: text/calendar; method=request; charset=UTF-8; + +Frop +. +; + +test "Advanced Content-Type :type" { + if not header :mime :type "content-type" "text" { + test_fail "wrong type extracted"; + } +} + +test "Advanced Content-Type :subype" { + if not header :mime :subtype "content-type" "calendar" { + test_fail "wrong subtype extracted"; + } +} + +test "Advanced Content-Type :contenttype" { + if not header :mime :contenttype "content-type" "text/calendar" { + test_fail "wrong contenttype extracted"; + } +} + +test "Advanced Content-Type :param" { + if not header :mime :param "method" "content-type" "request" { + test_fail "wrong method param extracted"; + } + + if not header :mime :param "charset" "content-type" "UTF-8" { + test_fail "wrong charset param extracted"; + } + + if not header :mime :param ["method", "charset"] + "content-type" "request" { + test_fail "wrong method param extracted"; + } + + if not header :mime :param ["method", "charset"] + "content-type" "UTF-8" { + test_fail "wrong charset param extracted"; + } + + if not header :count "eq" :mime :param ["method", "charset"] + "content-type" "2" { + test_fail "wrong number of parameters"; + } +} + +test_set "message" text: +From: stephan@example.com +To: timo@example.com +Subject: Frop +Content-Type: application/x-stuff; + title*0*=us-ascii'en'This%20is%20even%20more%20; + title*1*=%2A%2A%2Afun%2A%2A%2A%20; + title*2="isn't it!" + +Frop +. +; + +test "Encoded Content-Type :param" { + if not header :mime :param "title" "content-type" + "This is even more ***fun*** isn't it!" { + test_fail "wrong method param extracted"; + } +} + +test_set "message" text: +From: stephan@example.com +To: timo@example.com +Subject: Frop +Content-Type: image/png +Content-Disposition: inline; filename="frop.exe"; title="Frop!" + +Frop +. +; + +test "Content-Disposition :type" { + if not header :mime :type "content-disposition" "inline" { + test_fail "wrong type extracted"; + } +} + +test "Content-Disposition :subype" { + if not header :mime :subtype "content-disposition" "" { + test_fail "wrong subtype extracted"; + } +} + +test "Content-Disposition :contenttype" { + if not header :mime :contenttype "content-disposition" "inline" { + test_fail "wrong contenttype extracted"; + } +} + +test "Content-Disposition :param" { + if not header :mime :param "filename" "content-disposition" "frop.exe" { + test_fail "wrong filename param extracted"; + } + + if not header :mime :param "title" "content-disposition" "Frop!" { + test_fail "wrong title param extracted"; + } + + if not header :mime :param ["filename", "title"] + "content-disposition" "frop.exe" { + test_fail "wrong filename param extracted"; + } + + if not header :mime :param ["filename", "title"] + "content-disposition" "Frop!" { + test_fail "wrong title param extracted"; + } + + if not header :count "eq" :mime :param ["filename", "title"] + "content-disposition" "2" { + test_fail "wrong number of parameters"; + } + +} + + diff --git a/tests/extensions/mime/errors.svtest b/tests/extensions/mime/errors.svtest new file mode 100644 index 000000000..979da0953 --- /dev/null +++ b/tests/extensions/mime/errors.svtest @@ -0,0 +1,54 @@ +require "vnd.dovecot.testsuite"; + +require "relational"; +require "comparator-i;ascii-numeric"; + +test "Foreverypart command" { + if test_script_compile "errors/foreverypart.sieve" { + test_fail "compile should have failed"; + } + + if test_error :count "ne" :comparator "i;ascii-numeric" "12" { + test_fail "incorrect number of compile errors reported"; + } +} + +test "Break command" { + if test_script_compile "errors/break.sieve" { + test_fail "compile should have failed"; + } + + if test_error :count "ne" :comparator "i;ascii-numeric" "21" { + test_fail "incorrect number of compile errors reported"; + } +} + +test "Header test with :mime tag" { + if test_script_compile "errors/header-mime-tag.sieve" { + test_fail "compile should have failed"; + } + + if test_error :count "ne" :comparator "i;ascii-numeric" "10" { + test_fail "incorrect number of compile errors reported"; + } +} + +test "Address test with :mime tag" { + if test_script_compile "errors/address-mime-tag.sieve" { + test_fail "compile should have failed"; + } + + if test_error :count "ne" :comparator "i;ascii-numeric" "6" { + test_fail "incorrect number of compile errors reported"; + } +} + +test "Exists test with :mime tag" { + if test_script_compile "errors/exists-mime-tag.sieve" { + test_fail "compile should have failed"; + } + + if test_error :count "ne" :comparator "i;ascii-numeric" "6" { + test_fail "incorrect number of compile errors reported"; + } +} diff --git a/tests/extensions/mime/errors/address-mime-tag.sieve b/tests/extensions/mime/errors/address-mime-tag.sieve new file mode 100644 index 000000000..7adb7bcc5 --- /dev/null +++ b/tests/extensions/mime/errors/address-mime-tag.sieve @@ -0,0 +1,38 @@ +require "mime"; + +## Address + +# No error +if address :contains :mime "To" "frop@example.com" { + discard; +} + +# No error +if address :anychild :contains :mime "To" "frop@example.com" { + discard; +} + +# 1: Bare anychild option +if address :anychild "To" "frop@example.com" { + discard; +} + +# 2: Inappropriate option +if address :mime :anychild :type "To" "frop@example.com" { + discard; +} + +# 3: Inappropriate option +if address :mime :anychild :subtype "To" "frop@example.com" { + discard; +} + +# 4: Inappropriate option +if address :mime :anychild :contenttype "To" "frop@example.com" { + discard; +} + +# 5: Inappropriate option +if address :mime :anychild :param "frop" "To" "frop@example.com" { + discard; +} diff --git a/tests/extensions/mime/errors/break.sieve b/tests/extensions/mime/errors/break.sieve new file mode 100644 index 000000000..185867351 --- /dev/null +++ b/tests/extensions/mime/errors/break.sieve @@ -0,0 +1,157 @@ +require "foreverypart"; + +foreverypart :name "frop" { + # 1: Spurious tag + break :tag; + + # 2: Spurious tests + break true; + + # 3: Spurious tests + break anyof(true, false); + + # 4: Bare string + break "frop"; + + # 5: Bare string-list + break ["frop", "friep"]; + + # 6: Several bad arguments + break 13 ["frop", "friep"]; + + # 7: Spurious additional tag + break :name "frop" :friep; + + # 8: Spurious additional string + break :name "frop" "friep"; + + # 9: Bad name + break :name 13; + + # 10: Bad name + break :name ["frop", "friep"]; + + # No error + break; + + # No error + break :name "frop"; + + # No error + if exists "frop" { + break; + } + + # No error + if exists "frop" { + break :name "frop"; + } + + # No error + foreverypart { + break :name "frop"; + } + + # No error + foreverypart :name "friep" { + break :name "frop"; + } + + # No error + foreverypart :name "friep" { + break :name "friep"; + } + + # No error + foreverypart :name "friep" { + break; + } + + # No error + foreverypart { + if exists "frop" { + break :name "frop"; + } + } + + # No error + foreverypart :name "friep" { + if exists "frop" { + break :name "frop"; + } + } + + # No error + foreverypart :name "friep" { + if exists "frop" { + break :name "friep"; + } + } + + # No error + foreverypart :name "friep" { + if exists "frop" { + break; + } + } +} + +# 11: Outside loop +break; + +# 12: Outside loop +if exists "frop" { + break; +} + +# 13: Outside loop +break :name "frop"; + +# 14: Outside loop +if exists "frop" { + break :name "frop"; +} + +# 15: Bad name +foreverypart { + break :name "frop"; +} + +# 16: Bad name +foreverypart { + if exists "frop" { + break :name "frop"; + } +} + +# 17: Bad name +foreverypart :name "friep" { + break :name "frop"; +} + +# 18: Bad name +foreverypart :name "friep" { + if exists "frop" { + break :name "frop"; + } +} + +# 19: Bad name +foreverypart :name "friep" { + foreverypart :name "frop" { + break :name "frml"; + } +} + +# 20: Bad name +foreverypart :name "friep" { + foreverypart :name "frop" { + if exists "frop" { + break :name "frml"; + } + } +} + + + + diff --git a/tests/extensions/mime/errors/exists-mime-tag.sieve b/tests/extensions/mime/errors/exists-mime-tag.sieve new file mode 100644 index 000000000..84c86a777 --- /dev/null +++ b/tests/extensions/mime/errors/exists-mime-tag.sieve @@ -0,0 +1,43 @@ +require "mime"; + +## Exists + +# No error +if exists :mime "To" { + discard; +} + +# No error +if exists :anychild :mime "To" { + discard; +} + +# 1: Inappropriate option +if exists :anychild "To" { + discard; +} + +# 2: Inappropriate option +if exists :mime :type "To" { + discard; +} + +# 3: Inappropriate option +if exists :mime :subtype "To" { + discard; +} + +# 4: Inappropriate option +if exists :mime :contenttype "To" { + discard; +} + +# 5: Inappropriate option +if exists :mime :param ["frop", "friep"] "To" { + discard; +} + + + + + diff --git a/tests/extensions/mime/errors/foreverypart.sieve b/tests/extensions/mime/errors/foreverypart.sieve new file mode 100644 index 000000000..38a28d496 --- /dev/null +++ b/tests/extensions/mime/errors/foreverypart.sieve @@ -0,0 +1,45 @@ +require "foreverypart"; + +# 1: No block +foreverypart; + +# 2: Spurious tag +foreverypart :tag { } + +# 3: Spurious tests +foreverypart true { } + +# 4: Spurious tests +foreverypart anyof(true, false) { } + +# 5: Bare string +foreverypart "frop" { } + +# 6: Bare string-list +foreverypart ["frop", "friep"] { } + +# 7: Several bad arguments +foreverypart 13 ["frop", "friep"] { } + +# 8: Spurious additional tag +foreverypart :name "frop" :friep { } + +# 9: Spurious additional string +foreverypart :name "frop" "friep" { } + +# 10: Bad name +foreverypart :name 13 { } + +# 11: Bad name +foreverypart :name ["frop", "friep"] { } + +# No error +foreverypart { keep; } + +# No error +foreverypart :name "frop" { keep; } + +# No error +foreverypart :name "frop" { foreverypart { keep; } } + + diff --git a/tests/extensions/mime/errors/header-mime-tag.sieve b/tests/extensions/mime/errors/header-mime-tag.sieve new file mode 100644 index 000000000..85782af40 --- /dev/null +++ b/tests/extensions/mime/errors/header-mime-tag.sieve @@ -0,0 +1,100 @@ +require "mime"; + +## Header + +# No error +if header :contains :mime "Content-Type" "text/plain" { + discard; +} + +# No error +if header :mime :type "Content-Type" "text" { + discard; +} + +# No error +if header :mime :subtype "Content-Type" "plain" { + discard; +} + +# No error +if header :mime :contenttype "Content-Type" "text/plain" { + discard; +} + +# No error +if header :mime :param ["frop", "friep"] "Content-Type" "frml" { + discard; +} + +# No error +if header :anychild :contains :mime "Content-Type" "text/plain" { + discard; +} + +# No error +if header :mime :anychild :type "Content-Type" "text" { + discard; +} + +# No error +if header :mime :subtype :anychild "Content-Type" "plain" { + discard; +} + +# No error +if header :anychild :mime :contenttype "Content-Type" "text/plain" { + discard; +} + +# No error +if header :mime :param ["frop", "friep"] :anychild "Content-Type" "frml" { + discard; +} + +# 1: Bare anychild option +if header :anychild "Content-Type" "frml" { + discard; +} + +# 2: Bare mime option +if header :type "Content-Type" "frml" { + discard; +} + +# 3: Bare mime option +if header :subtype "Content-Type" "frml" { + discard; +} + +# 4: Bare mime option +if header :contenttype "Content-Type" "frml" { + discard; +} + +# 5: Bare mime option +if header :param "frop" "Content-Type" "frml" { + discard; +} + +# 6: Multiple option tags +if header :mime :type :subtype "Content-Type" "frml" { + discard; +} + +# 7: Bad param argument +if header :mime :param 13 "Content-Type" "frml" { + discard; +} + +# 8: Missing param argument +if header :mime :param :anychild "Content-Type" "frml" { + discard; +} + +# 9: Missing param argument +if header :mime :param :frop "Content-Type" "frml" { + discard; +} + + diff --git a/tests/extensions/mime/execute.svtest b/tests/extensions/mime/execute.svtest new file mode 100644 index 000000000..701d817a4 --- /dev/null +++ b/tests/extensions/mime/execute.svtest @@ -0,0 +1,23 @@ +require "vnd.dovecot.testsuite"; + +/* + * Execution testing (currently just meant to trigger any segfaults) + */ + +test "Basic" { + if not test_script_compile "execute/ihave.sieve" { + test_fail "script compile failed"; + } + + if not test_script_run { + test_fail "script run failed"; + } + + if not test_result_execute { + test_fail "result execute failed"; + } + + test_binary_save "ihave-basic"; + test_binary_load "ihave-basic"; +} + diff --git a/tests/extensions/mime/execute/foreverypart.sieve b/tests/extensions/mime/execute/foreverypart.sieve new file mode 100644 index 000000000..0fe84c827 --- /dev/null +++ b/tests/extensions/mime/execute/foreverypart.sieve @@ -0,0 +1,7 @@ +require "ihave"; + +if ihave "nonsense-extension" { + nonsense_command "Frop!"; +} + +redirect "frop@example.com"; -- GitLab