From 9a193393de7512ee252c8cb820a9e2be9971b748 Mon Sep 17 00:00:00 2001 From: Stephan Bosch <stephan@rename-it.nl> Date: Wed, 12 Nov 2014 22:10:25 +0100 Subject: [PATCH] lib-sieve: Implemented the Sieve index extension (RFC 5260). --- Makefile.am | 2 + configure.ac | 1 + src/lib-sieve/Makefile.am | 1 + src/lib-sieve/plugins/Makefile.am | 1 + src/lib-sieve/plugins/index/Makefile.am | 13 + .../plugins/index/ext-index-common.c | 15 + .../plugins/index/ext-index-common.h | 32 +++ src/lib-sieve/plugins/index/ext-index.c | 69 +++++ src/lib-sieve/plugins/index/tag-index.c | 272 ++++++++++++++++++ src/lib-sieve/sieve-extensions.c | 4 +- tests/extensions/index/basic.svtest | 93 ++++++ tests/extensions/index/errors.svtest | 20 ++ tests/extensions/index/errors/syntax.sieve | 20 ++ 13 files changed, 542 insertions(+), 1 deletion(-) create mode 100644 src/lib-sieve/plugins/index/Makefile.am create mode 100644 src/lib-sieve/plugins/index/ext-index-common.c create mode 100644 src/lib-sieve/plugins/index/ext-index-common.h create mode 100644 src/lib-sieve/plugins/index/ext-index.c create mode 100644 src/lib-sieve/plugins/index/tag-index.c create mode 100644 tests/extensions/index/basic.svtest create mode 100644 tests/extensions/index/errors.svtest create mode 100644 tests/extensions/index/errors/syntax.sieve diff --git a/Makefile.am b/Makefile.am index 87b6fded9..07f4f2d70 100644 --- a/Makefile.am +++ b/Makefile.am @@ -140,6 +140,8 @@ test_cases = \ tests/extensions/date/basic.svtest \ tests/extensions/date/date-parts.svtest \ tests/extensions/date/zones.svtest \ + tests/extensions/index/basic.svtest \ + tests/extensions/index/errors.svtest \ tests/extensions/spamvirustest/spamtest.svtest \ tests/extensions/spamvirustest/virustest.svtest \ tests/extensions/spamvirustest/spamtestplus.svtest \ diff --git a/configure.ac b/configure.ac index a1e5cbe5c..0ccaa9403 100644 --- a/configure.ac +++ b/configure.ac @@ -203,6 +203,7 @@ src/lib-sieve/plugins/ihave/Makefile 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/vnd.dovecot/Makefile src/lib-sieve/plugins/vnd.dovecot/debug/Makefile src/lib-sieve-tool/Makefile diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am index aeaaa2b47..143287bbc 100644 --- a/src/lib-sieve/Makefile.am +++ b/src/lib-sieve/Makefile.am @@ -76,6 +76,7 @@ plugins = \ $(extdir)/ihave/libsieve_ext_ihave.la \ $(extdir)/editheader/libsieve_ext_editheader.la \ $(extdir)/duplicate/libsieve_ext_duplicate.la \ + $(extdir)/index/libsieve_ext_index.la \ $(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \ $(unfinished_plugins) diff --git a/src/lib-sieve/plugins/Makefile.am b/src/lib-sieve/plugins/Makefile.am index 915ada271..ee69303f4 100644 --- a/src/lib-sieve/plugins/Makefile.am +++ b/src/lib-sieve/plugins/Makefile.am @@ -22,6 +22,7 @@ SUBDIRS = \ ihave \ editheader \ duplicate \ + index \ vnd.dovecot \ $(UNFINISHED) diff --git a/src/lib-sieve/plugins/index/Makefile.am b/src/lib-sieve/plugins/index/Makefile.am new file mode 100644 index 000000000..434ca1c06 --- /dev/null +++ b/src/lib-sieve/plugins/index/Makefile.am @@ -0,0 +1,13 @@ +noinst_LTLIBRARIES = libsieve_ext_index.la + +AM_CPPFLAGS = \ + -I$(srcdir)/../.. \ + $(LIBDOVECOT_INCLUDE) + +libsieve_ext_index_la_SOURCES = \ + ext-index-common.c \ + ext-index.c \ + tag-index.c + +noinst_HEADERS = \ + ext-index-common.h diff --git a/src/lib-sieve/plugins/index/ext-index-common.c b/src/lib-sieve/plugins/index/ext-index-common.c new file mode 100644 index 000000000..53eb2554a --- /dev/null +++ b/src/lib-sieve/plugins/index/ext-index-common.c @@ -0,0 +1,15 @@ +/* Copyright (c) 2002-2014 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "utc-offset.h" +#include "str.h" + +#include "sieve-common.h" +#include "sieve-stringlist.h" +#include "sieve-code.h" +#include "sieve-interpreter.h" +#include "sieve-message.h" + +#include "ext-index-common.h" + diff --git a/src/lib-sieve/plugins/index/ext-index-common.h b/src/lib-sieve/plugins/index/ext-index-common.h new file mode 100644 index 000000000..ca4f484e5 --- /dev/null +++ b/src/lib-sieve/plugins/index/ext-index-common.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2002-2014 Pigeonhole authors, see the included COPYING file + */ + +#ifndef __EXT_INDEX_COMMON_H +#define __EXT_INDEX_COMMON_H + +#include "sieve-common.h" + +#include <time.h> + +#define SIEVE_EXT_INDEX_HDR_OVERRIDE_SEQUENCE 100 + +/* + * Tagged arguments + */ + +extern const struct sieve_argument_def index_tag; +extern const struct sieve_argument_def last_tag; + +/* + * Operands + */ + +extern const struct sieve_operand_def index_operand; + +/* + * Extension + */ + +extern const struct sieve_extension_def index_extension; + +#endif /* __EXT_INDEX_COMMON_H */ diff --git a/src/lib-sieve/plugins/index/ext-index.c b/src/lib-sieve/plugins/index/ext-index.c new file mode 100644 index 000000000..c22253564 --- /dev/null +++ b/src/lib-sieve/plugins/index/ext-index.c @@ -0,0 +1,69 @@ +/* Copyright (c) 2002-2014 Pigeonhole authors, see the included COPYING file + */ + +/* Extension index + * ------------------ + * + * Authors: Stephan Bosch + * Specification: RFC 5260 + * Implementation: full + * Status: testing + * + */ + +#include "lib.h" +#include "array.h" + +#include "sieve-common.h" +#include "sieve-message.h" +#include "sieve-extensions.h" +#include "sieve-commands.h" + +#include "sieve-validator.h" +#include "sieve-generator.h" +#include "sieve-binary.h" +#include "sieve-interpreter.h" +#include "sieve-dump.h" + +#include "ext-index-common.h" + +/* + * Extension + */ + +static bool ext_index_validator_load +(const struct sieve_extension *ext, struct sieve_validator *validator); + +const struct sieve_extension_def index_extension = { + .name = "index", + .validator_load = ext_index_validator_load, + SIEVE_EXT_DEFINE_OPERAND(index_operand) +}; + +static bool ext_index_validator_load +(const struct sieve_extension *ext, struct sieve_validator *valdtr) +{ + /* Register :index and :last tags with header, address and date 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, &index_tag, SIEVE_OPT_MESSAGE_OVERRIDE); + sieve_validator_register_external_tag + (valdtr, "header", ext, &last_tag, 0); + + sieve_validator_register_external_tag + (valdtr, "address", ext, &index_tag, SIEVE_OPT_MESSAGE_OVERRIDE); + sieve_validator_register_external_tag + (valdtr, "address", ext, &last_tag, 0); + + sieve_validator_register_external_tag + (valdtr, "date", ext, &index_tag, SIEVE_OPT_MESSAGE_OVERRIDE); + sieve_validator_register_external_tag + (valdtr, "date", ext, &last_tag, 0); + + return TRUE; +} + + diff --git a/src/lib-sieve/plugins/index/tag-index.c b/src/lib-sieve/plugins/index/tag-index.c new file mode 100644 index 000000000..56657a403 --- /dev/null +++ b/src/lib-sieve/plugins/index/tag-index.c @@ -0,0 +1,272 @@ +/* Copyright (c) 2002-2014 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "mail-storage.h" +#include "mail-namespace.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-index-common.h" + +/* + * Tagged argument + */ + +static bool tag_index_validate + (struct sieve_validator *valdtr, struct sieve_ast_argument **arg, + struct sieve_command *cmd); +static bool tag_index_generate + (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg, + struct sieve_command *context); + +const struct sieve_argument_def index_tag = { + "index", + NULL, + tag_index_validate, + NULL, NULL, + tag_index_generate +}; + +static bool tag_last_validate + (struct sieve_validator *valdtr, struct sieve_ast_argument **arg, + struct sieve_command *cmd); + +const struct sieve_argument_def last_tag = { + "last", + NULL, + tag_last_validate, + NULL, NULL, NULL +}; + + +/* + * Header override + */ + +static bool svmo_index_dump_context + (const struct sieve_message_override *svmo, + const struct sieve_dumptime_env *denv, sieve_size_t *address); +static int svmo_index_read_context + (const struct sieve_message_override *svmo, + const struct sieve_runtime_env *renv, sieve_size_t *address, + void **ho_context); +static int svmo_index_header_override + (const struct sieve_message_override *svmo, + const struct sieve_runtime_env *renv, + struct sieve_stringlist **headers); + +const struct sieve_message_override_def index_header_override = { + SIEVE_OBJECT("index", &index_operand, 0), + .sequence = SIEVE_EXT_INDEX_HDR_OVERRIDE_SEQUENCE, + .dump_context = svmo_index_dump_context, + .read_context = svmo_index_read_context, + .header_override = svmo_index_header_override +}; + +/* + * Operand + */ + +static const struct sieve_extension_objects ext_header_overrides = + SIEVE_EXT_DEFINE_MESSAGE_OVERRIDE(index_header_override); + +const struct sieve_operand_def index_operand = { + "index operand", + &index_extension, + 0, + &sieve_message_override_operand_class, + &ext_header_overrides +}; + +/* + * Tag data + */ + +struct tag_index_data { + sieve_number_t fieldno; + unsigned int last:1; +}; + +/* + * Tag validation + */ + +static bool tag_index_validate +(struct sieve_validator *valdtr ATTR_UNUSED, + struct sieve_ast_argument **arg, struct sieve_command *cmd) +{ + struct sieve_ast_argument *tag = *arg; + struct tag_index_data *data; + + /* Skip the tag itself */ + *arg = sieve_ast_argument_next(*arg); + + /* Check syntax: + * ":index" <fieldno: number> + */ + if ( !sieve_validate_tag_parameter + (valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) ) { + return FALSE; + } + + if (tag->argument->data == NULL) { + data = p_new(sieve_command_pool(cmd), struct tag_index_data, 1); + tag->argument->data = (void *)data; + } else { + data = (struct tag_index_data *)tag->argument->data; + } + + data->fieldno = sieve_ast_argument_number(*arg); + + /* Detach parameter */ + *arg = sieve_ast_arguments_detach(*arg,1); + return TRUE; +} + +static bool tag_last_validate +(struct sieve_validator *valdtr ATTR_UNUSED, + struct sieve_ast_argument **arg, struct sieve_command *cmd) +{ + struct sieve_ast_argument *index_arg; + struct tag_index_data *data; + + index_arg = sieve_command_find_argument(cmd, &index_tag); + if (index_arg == NULL) { + sieve_argument_validate_error(valdtr, *arg, + "the :last tag for the %s %s cannot be specified " + "without the :index tag", + sieve_command_identifier(cmd), sieve_command_type_name(cmd)); + return FALSE; + } + + /* Set :last flag */ + if (index_arg->argument->data == NULL) { + data = p_new(sieve_command_pool(cmd), struct tag_index_data, 1); + index_arg->argument->data = (void*)data; + } else { + data = (struct tag_index_data *)index_arg->argument->data; + } + data->last = TRUE; + + /* Detach */ + *arg = sieve_ast_arguments_detach(*arg,1); + return TRUE; +} + +/* + * Code generation + */ + +static bool tag_index_generate +(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg, + struct sieve_command *cmd ATTR_UNUSED) +{ + struct tag_index_data *data = + (struct tag_index_data *)arg->argument->data; + + if ( sieve_ast_argument_type(arg) != SAAT_TAG ) + return FALSE; + + sieve_opr_message_override_emit + (cgenv->sblock, arg->argument->ext, &index_header_override); + + (void)sieve_binary_emit_integer + (cgenv->sblock, data->fieldno); + (void)sieve_binary_emit_byte + (cgenv->sblock, ( data->last ? 1 : 0 )); + + return TRUE; +} + +/* + * Header override implementation + */ + +/* Context data */ + +struct svmo_index_context { + unsigned int fieldno; + unsigned int last:1; +}; + +/* Context coding */ + +static bool svmo_index_dump_context +(const struct sieve_message_override *svmo ATTR_UNUSED, + const struct sieve_dumptime_env *denv, sieve_size_t *address) +{ + sieve_number_t fieldno = 0; + unsigned int last; + + if ( !sieve_binary_read_integer(denv->sblock, address, &fieldno) ) + return FALSE; + + sieve_code_dumpf(denv, "fieldno: %llu", + (unsigned long long) fieldno); + + if ( !sieve_binary_read_byte(denv->sblock, address, &last) ) + return FALSE; + + if (last) + sieve_code_dumpf(denv, "last"); + return TRUE; +} + +static int svmo_index_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); + struct svmo_index_context *ctx; + sieve_number_t fieldno; + unsigned int last = 0; + + if ( !sieve_binary_read_integer(renv->sblock, address, &fieldno) ) { + sieve_runtime_trace_error(renv, "fieldno: invalid number"); + return SIEVE_EXEC_BIN_CORRUPT; + } + + if ( !sieve_binary_read_byte(renv->sblock, address, &last) ) { + sieve_runtime_trace_error(renv, "last: invalid byte"); + return SIEVE_EXEC_BIN_CORRUPT; + } + + ctx = p_new(pool, struct svmo_index_context, 1); + ctx->fieldno = fieldno; + ctx->last = (last == 0 ? FALSE : TRUE); + + *ho_context = (void *) ctx; + + return SIEVE_EXEC_OK; +} + +/* Override */ + +static int svmo_index_header_override +(const struct sieve_message_override *svmo, + const struct sieve_runtime_env *renv, + struct sieve_stringlist **headers) +{ + struct svmo_index_context *ctx = + (struct svmo_index_context *)svmo->context; + + sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, + "header index override: only returning index %d%s", + ctx->fieldno, ( ctx->last ? " (from last)" : "" )); + + *headers = sieve_index_stringlist_create(renv, *headers, + (int)ctx->fieldno * ( ctx->last ? -1 : 1 )); + return SIEVE_EXEC_OK; +} + diff --git a/src/lib-sieve/sieve-extensions.c b/src/lib-sieve/sieve-extensions.c index 03183c3fb..ff887afa4 100644 --- a/src/lib-sieve/sieve-extensions.c +++ b/src/lib-sieve/sieve-extensions.c @@ -103,6 +103,7 @@ extern const struct sieve_extension_def enotify_extension; extern const struct sieve_extension_def environment_extension; extern const struct sieve_extension_def mailbox_extension; 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; @@ -117,7 +118,8 @@ const struct sieve_extension_def *sieve_core_extensions[] = { &relational_extension, ®ex_extension, &imap4flags_extension, ©_extension, &include_extension, &body_extension, &variables_extension, &enotify_extension, &environment_extension, - &mailbox_extension, &date_extension, &ihave_extension, &duplicate_extension + &mailbox_extension, &date_extension, &index_extension, &ihave_extension, + &duplicate_extension }; const unsigned int sieve_core_extensions_count = diff --git a/tests/extensions/index/basic.svtest b/tests/extensions/index/basic.svtest new file mode 100644 index 000000000..070602251 --- /dev/null +++ b/tests/extensions/index/basic.svtest @@ -0,0 +1,93 @@ +require "vnd.dovecot.testsuite"; +require "index"; +require "date"; +require "variables"; +require "subaddress"; + +test_set "message" text: +To: first@friep.example.com +X-A: First +Received: from mx.example.com (127.0.0.13) by mx.example.org + (127.0.0.12) with Macrosoft SMTP Server (TLS) id 1.2.3.4; + Wed, 12 Nov 2014 18:18:31 +0100 +To: second@friep.example.com +From: stephan@example.org +Received: from mx.example.com (127.0.0.13) by mx.example.org + (127.0.0.12) with Macrosoft SMTP Server (TLS) id 1.2.3.4; + Wed, 12 Nov 2014 18:18:30 +0100 +X-A: Second +To: third@friep.example.com +X-A: Third +Received: from mx.example.com (127.0.0.13) by mx.example.org + (127.0.0.12) with Macrosoft SMTP Server (TLS) id 1.2.3.4; + Wed, 12 Nov 2014 18:18:29 +0100 +Subject: Frop! +X-A: Fourth +To: fourth@friep.example.com +Received: from mx.example.com (127.0.0.13) by mx.example.org + (127.0.0.12) with Macrosoft SMTP Server (TLS) id 1.2.3.4; + Wed, 12 Nov 2014 18:18:28 +0100 + +Frop +. +; + +test "Header :index" { + if not header :index 3 "x-a" "Third" { + test_fail "wrong header retrieved"; + } + + if header :index 3 "x-a" ["First", "Second", "Fourth"] { + test_fail "other header retrieved"; + } +} + +test "Header :index :last" { + if not header :index 3 :last "x-a" "Second" { + test_fail "wrong header retrieved"; + } + + if header :index 3 :last "x-a" ["First", "Third", "Fourth"] { + test_fail "other header retrieved"; + } +} + +test "Address :index" { + if not address :localpart :index 2 "to" "second" { + test_fail "wrong header retrieved"; + } + + if address :localpart :index 2 "to" ["first", "third", "fourth"] { + test_fail "other header retrieved"; + } +} + +test "Address :index :last" { + if not address :localpart :index 2 :last "to" "third" { + test_fail "wrong header retrieved"; + } + + if address :localpart :index 2 :last "to" ["first", "second", "fourth"] { + test_fail "other header retrieved"; + } +} + +test "Date :index" { + if not date :index 1 "received" "second" "31" { + test_fail "wrong header retrieved"; + } + + if date :index 1 "received" "second" ["30", "29", "28"] { + test_fail "other header retrieved"; + } +} + +test "Date :index :last" { + if not date :index 1 :last "received" "second" "28"{ + test_fail "wrong header retrieved"; + } + + if date :index 1 :last "received" "second" ["31", "30", "29"] { + test_fail "other header retrieved"; + } +} diff --git a/tests/extensions/index/errors.svtest b/tests/extensions/index/errors.svtest new file mode 100644 index 000000000..b975fd4f6 --- /dev/null +++ b/tests/extensions/index/errors.svtest @@ -0,0 +1,20 @@ +require "vnd.dovecot.testsuite"; + +require "relational"; +require "comparator-i;ascii-numeric"; + +/* + * Invalid syntax + */ + +test "Invalid Syntax" { + if test_script_compile "errors/syntax.sieve" { + test_fail "compile should have failed"; + } + + if not test_error :count "eq" :comparator "i;ascii-numeric" "5" { + test_fail "wrong number of errors reported"; + } +} + + diff --git a/tests/extensions/index/errors/syntax.sieve b/tests/extensions/index/errors/syntax.sieve new file mode 100644 index 000000000..afa31e3cb --- /dev/null +++ b/tests/extensions/index/errors/syntax.sieve @@ -0,0 +1,20 @@ +require "date"; +require "index"; + +# Not an error +if header :last :index 2 "to" "ok" { } + +# Not an error +if header :index 444 :last "to" "ok" { } + +# 1: missing argument +if header :index "to" "ok" {} + +# 2: missing argument +if header :index :last "to" "ok" {} + +# 3: erroneous string argument +if header :index "frop" "to" "ok" {} + +# 4: last without index +if header :last "to" "ok" {} -- GitLab