From 169dd8d4da784c500194d5f8796e73fbb4dbbe7e Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan@rename-it.nl>
Date: Fri, 30 Jul 2010 14:53:14 +0200
Subject: [PATCH] Implemented generic string list interface and simplified
 matching API.

---
 TODO                                          |  17 +-
 src/lib-sieve/Makefile.am                     |   2 +
 src/lib-sieve/ext-envelope.c                  | 432 +++++++++++-------
 src/lib-sieve/mcht-contains.c                 |  15 +-
 src/lib-sieve/mcht-is.c                       |  15 +-
 src/lib-sieve/mcht-matches.c                  |  19 +-
 src/lib-sieve/plugins/body/ext-body-common.c  |  96 +++-
 src/lib-sieve/plugins/body/ext-body-common.h  |  24 +-
 src/lib-sieve/plugins/body/tst-body.c         |  90 +---
 src/lib-sieve/plugins/date/ext-date-common.c  | 147 ++++++
 src/lib-sieve/plugins/date/ext-date-common.h  |  15 +
 src/lib-sieve/plugins/date/tst-date.c         | 123 ++---
 src/lib-sieve/plugins/enotify/cmd-notify.c    |   2 +-
 .../plugins/enotify/ext-enotify-common.c      |  19 +-
 .../plugins/enotify/ext-enotify-common.h      |   2 +-
 .../enotify/tst-notify-method-capability.c    |  25 +-
 .../plugins/enotify/tst-valid-notify-method.c |  12 +-
 .../plugins/environment/tst-environment.c     |  32 +-
 src/lib-sieve/plugins/imap4flags/cmd-flag.c   |  10 +-
 .../imap4flags/ext-imap4flags-common.c        | 176 +++++--
 .../imap4flags/ext-imap4flags-common.h        |  14 +-
 src/lib-sieve/plugins/imap4flags/tag-flags.c  |  10 +-
 .../plugins/imap4flags/tst-hasflag.c          |  95 +---
 .../plugins/mailbox/tst-mailboxexists.c       |  20 +-
 src/lib-sieve/plugins/notify/cmd-denotify.c   |  18 +-
 src/lib-sieve/plugins/notify/cmd-notify.c     |  12 +-
 src/lib-sieve/plugins/regex/mcht-regex.c      | 213 +++++----
 .../relational/ext-relational-common.h        |   6 +-
 src/lib-sieve/plugins/relational/mcht-count.c | 118 ++---
 src/lib-sieve/plugins/relational/mcht-value.c |  27 +-
 .../plugins/spamvirustest/tst-spamvirustest.c |  37 +-
 src/lib-sieve/plugins/vacation/cmd-vacation.c |  14 +-
 src/lib-sieve/plugins/variables/tst-string.c  | 110 +++--
 src/lib-sieve/sieve-address-parts.c           | 138 +++---
 src/lib-sieve/sieve-address-parts.h           |  12 +-
 src/lib-sieve/sieve-address.c                 | 138 ++++++
 src/lib-sieve/sieve-address.h                 |  51 ++-
 src/lib-sieve/sieve-code.c                    | 142 +++---
 src/lib-sieve/sieve-code.h                    |  27 +-
 src/lib-sieve/sieve-common.h                  |   4 +
 src/lib-sieve/sieve-match-types.h             |  36 +-
 src/lib-sieve/sieve-match.c                   | 179 ++++----
 src/lib-sieve/sieve-match.h                   |  29 +-
 src/lib-sieve/sieve-message.c                 | 106 +++++
 src/lib-sieve/sieve-message.h                 |   7 +
 src/lib-sieve/sieve-stringlist.c              | 139 ++++++
 src/lib-sieve/sieve-stringlist.h              |  51 +++
 src/lib-sieve/tst-address.c                   |  59 +--
 src/lib-sieve/tst-exists.c                    |  10 +-
 src/lib-sieve/tst-header.c                    |  67 +--
 src/testsuite/testsuite-log.c                 |  77 ++++
 src/testsuite/testsuite-log.h                 |   3 +
 src/testsuite/testsuite-result.c              |  80 ++++
 src/testsuite/testsuite-result.h              |   3 +
 src/testsuite/tst-test-error.c                |  48 +-
 src/testsuite/tst-test-multiscript.c          |  19 +-
 src/testsuite/tst-test-result.c               |  61 +--
 tests/extensions/envelope.svtest              |   4 +
 58 files changed, 2136 insertions(+), 1321 deletions(-)
 create mode 100644 src/lib-sieve/sieve-stringlist.c
 create mode 100644 src/lib-sieve/sieve-stringlist.h

diff --git a/TODO b/TODO
index 5b936e961..199e8e777 100644
--- a/TODO
+++ b/TODO
@@ -35,14 +35,15 @@ Next (in order of descending priority/precedence):
 	- Implement proper :content "multipart" behavior
 	- Implement proper :content "message/rfc822" behavior
 	- Build test cases for decoding MIME encodings to UTF-8
+* Implement index extension
 * Build a sieve tool to filter an entire existing mailbox through a Sieve 
   script:
 	- Add commandline options to fully customize execution
 	- Write manual page
 * Vacation extension improvements:
-    - Implement configurable sender exclusion list.
-    - Implement mechanism for implicitly including an account's aliases in the
-      vacation command's :addresses list.
+	- Implement configurable sender exclusion list.
+	- Implement mechanism for implicitly including an account's aliases in the
+	  vacation command's :addresses list.
 * Improve error handling. 
 	- Implement dropping errors in the user's mailbox as a mail message.
 * Fix remaining RFC deviations:
@@ -69,8 +70,8 @@ Next (in order of descending priority/precedence):
   them to the user if appropriate/safe.
 * Implement proper support for ManageSieve SASL ANONYMOUS login.
 * Test ManageSieve behavior thoroughly:
-  - Test pipelined behavior
-  - Test proxy authentication
+	- Test pipelined behavior
+	- Test proxy authentication
 * Code cleanup:
 	- Make address handling more uniform. 
 	- Review all FIXMEs 
@@ -80,11 +81,7 @@ Next (in order of descending priority/precedence):
 
 * ## MAKE A THIRD RELEASE (0.3.x) ##
 
-* Implement abstract string list and make matching interface more generic. Use
-  this to:
-	- Simplify matching API
-	- Implement index extension
-	- Implement extlists extension
+* Implement extlists extension as a plugin
 * Enotify extension: detect use of variable values extracted from the message 
   that are used in the method argument. RFC reports this as a security issue.
 * Make the sieve storage a base class with (possibly) various implementations, 
diff --git a/src/lib-sieve/Makefile.am b/src/lib-sieve/Makefile.am
index 7e0493f24..509efffda 100644
--- a/src/lib-sieve/Makefile.am
+++ b/src/lib-sieve/Makefile.am
@@ -91,6 +91,7 @@ libdovecot_sieve_la_SOURCES = \
 	sieve-result.c \
 	sieve-error.c \
 	sieve-objects.c \
+	sieve-stringlist.c \
 	sieve-comparators.c \
 	sieve-match-types.c \
 	sieve-address-parts.c \
@@ -136,6 +137,7 @@ headers = \
 	sieve-error.h \
 	sieve-error-private.h \
 	sieve-objects.h \
+	sieve-stringlist.h \
 	sieve-match.h \
 	sieve-comparators.h \
 	sieve-match-types.h \
diff --git a/src/lib-sieve/ext-envelope.c b/src/lib-sieve/ext-envelope.c
index 1b095776e..7b7465abd 100644
--- a/src/lib-sieve/ext-envelope.c
+++ b/src/lib-sieve/ext-envelope.c
@@ -18,6 +18,7 @@
 #include "sieve-common.h"
 #include "sieve-extensions.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-address.h"
 #include "sieve-comparators.h"
@@ -176,6 +177,250 @@ static const struct sieve_envelope_part *_envelope_part_find
 	return NULL;
 }
 
+/* Envelope parts implementation */
+
+static const struct sieve_address *const *_from_part_get_addresses
+(const struct sieve_runtime_env *renv)
+{
+	ARRAY_DEFINE(envelope_values, const struct sieve_address *);
+	const struct sieve_address *address =
+		sieve_message_get_sender_address(renv->msgctx);
+	
+	if ( address != NULL ) {
+		t_array_init(&envelope_values, 2);
+
+        array_append(&envelope_values, &address, 1);
+
+	    (void)array_append_space(&envelope_values);
+    	return array_idx(&envelope_values, 0);
+	} 
+
+	return NULL;
+}
+
+static const char *const *_from_part_get_values
+(const struct sieve_runtime_env *renv)
+{
+	ARRAY_DEFINE(envelope_values, const char *);
+
+	t_array_init(&envelope_values, 2);
+
+	if ( renv->msgdata->return_path != NULL ) {
+        array_append(&envelope_values, &renv->msgdata->return_path, 1);
+	}
+
+	(void)array_append_space(&envelope_values);
+
+	return array_idx(&envelope_values, 0);
+}
+
+static const struct sieve_address *const *_to_part_get_addresses
+(const struct sieve_runtime_env *renv)
+{
+	ARRAY_DEFINE(envelope_values, const struct sieve_address *);
+	const struct sieve_address *address = 
+		sieve_message_get_recipient_address(renv->msgctx);	
+
+	if ( address != NULL && address->local_part != NULL ) {
+		t_array_init(&envelope_values, 2);
+
+		array_append(&envelope_values, &address, 1);
+
+		(void)array_append_space(&envelope_values);
+		return array_idx(&envelope_values, 0);
+	}
+
+	return NULL;
+}
+
+static const char *const *_to_part_get_values
+(const struct sieve_runtime_env *renv)
+{
+	ARRAY_DEFINE(envelope_values, const char *);
+
+	t_array_init(&envelope_values, 2);
+
+	if ( renv->msgdata->to_address != NULL ) {
+        array_append(&envelope_values, &renv->msgdata->to_address, 1);
+	}
+
+	(void)array_append_space(&envelope_values);
+
+	return array_idx(&envelope_values, 0);
+}
+
+
+static const char *const *_auth_part_get_values
+(const struct sieve_runtime_env *renv)
+{
+	ARRAY_DEFINE(envelope_values, const char *);
+
+	t_array_init(&envelope_values, 2);
+
+	if ( renv->msgdata->auth_user != NULL )
+        array_append(&envelope_values, &renv->msgdata->auth_user, 1);
+
+	(void)array_append_space(&envelope_values);
+
+	return array_idx(&envelope_values, 0);
+}
+
+/*
+ * Envelope address list
+ */
+
+/* Forward declarations */
+
+static int sieve_envelope_address_list_next_string_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static int sieve_envelope_address_list_next_item
+	(struct sieve_address_list *_addrlist, struct sieve_address *addr_r, 
+		string_t **unparsed_r);
+static void sieve_envelope_address_list_reset
+	(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct sieve_envelope_address_list {
+	struct sieve_address_list addrlist;
+
+	struct sieve_stringlist *env_parts;
+
+	const struct sieve_address *const *cur_addresses;
+	const char * const *cur_values;
+
+	int value_index; 
+};
+
+static struct sieve_address_list *sieve_envelope_address_list_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *env_parts)
+{
+	struct sieve_envelope_address_list *addrlist;
+	    
+	addrlist = t_new(struct sieve_envelope_address_list, 1);
+	addrlist->addrlist.strlist.runenv = renv;
+	addrlist->addrlist.strlist.next_item = 
+		sieve_envelope_address_list_next_string_item;
+	addrlist->addrlist.strlist.reset = sieve_envelope_address_list_reset;
+	addrlist->addrlist.next_item = sieve_envelope_address_list_next_item;
+	addrlist->env_parts = env_parts;
+  
+	return &addrlist->addrlist;
+}
+
+static int sieve_envelope_address_list_next_item
+(struct sieve_address_list *_addrlist, struct sieve_address *addr_r, 
+	string_t **unparsed_r)
+{
+	struct sieve_envelope_address_list *addrlist = 
+		(struct sieve_envelope_address_list *) _addrlist;	
+	const struct sieve_runtime_env *renv = _addrlist->strlist.runenv;
+
+	if ( addr_r != NULL ) addr_r->local_part = NULL;
+	if ( unparsed_r != NULL ) *unparsed_r = NULL;
+
+	while ( addrlist->cur_addresses == NULL && addrlist->cur_values == NULL ) {
+		const struct sieve_envelope_part *epart;
+		string_t *envp_item = NULL;
+		int ret;
+
+		/* Read next header value from source list */
+		if ( (ret=sieve_stringlist_next_item(addrlist->env_parts, &envp_item)) 
+			<= 0 )
+			return ret;
+			
+		if ( (epart=_envelope_part_find(str_c(envp_item))) != NULL ) {
+			addrlist->value_index = 0;
+
+			if ( epart->get_addresses != NULL ) {
+				/* Field contains addresses */
+				addrlist->cur_addresses = epart->get_addresses(renv);
+
+				/* Drop empty list */
+				if ( addrlist->cur_addresses != NULL &&
+					addrlist->cur_addresses[0] == NULL )
+					addrlist->cur_addresses = NULL;
+			}  
+
+			if ( addrlist->cur_addresses == NULL && epart->get_values != NULL ) {
+				/* Field contains something else */
+				addrlist->cur_values = epart->get_values(renv);
+
+				/* Drop empty list */
+				if ( addrlist->cur_values != NULL && addrlist->cur_values[0] == NULL )
+					addrlist->cur_values = NULL;
+			}
+		}
+	}
+	
+	/* Return next item */
+	if ( addrlist->cur_addresses != NULL ) {
+		const struct sieve_address *addr = 
+			addrlist->cur_addresses[addrlist->value_index];
+
+		if ( addr->local_part == NULL ) {
+			/* Null path <> */
+			if ( unparsed_r != NULL ) 
+				*unparsed_r = t_str_new_const("", 0);
+		} else {
+			if ( addr_r != NULL )
+				*addr_r = *addr;
+		}
+
+		/* Advance to next value */
+		addrlist->value_index++;
+		if ( addrlist->cur_addresses[addrlist->value_index] == NULL ) {
+			addrlist->cur_addresses = NULL;
+			addrlist->value_index = 0;
+		}
+	} else {
+		if ( unparsed_r != NULL ) {
+			const char *value = addrlist->cur_values[addrlist->value_index];
+
+			*unparsed_r = t_str_new_const(value, strlen(value));
+		}
+
+		/* Advance to next value */
+		addrlist->value_index++;
+		if ( addrlist->cur_values[addrlist->value_index] == NULL ) {
+			addrlist->cur_values = NULL;
+			addrlist->value_index = 0;
+		}
+	}
+
+	return 1;
+}
+
+static int sieve_envelope_address_list_next_string_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_address_list *addrlist = (struct sieve_address_list *)_strlist;
+	struct sieve_address addr;
+	int ret;
+
+	if ( (ret=sieve_envelope_address_list_next_item
+		(addrlist, &addr, str_r)) <= 0 )
+		return ret;
+
+	if ( addr.local_part != NULL ) {
+		const char *addr_str = sieve_address_to_string(&addr);
+		*str_r = t_str_new_const(addr_str, strlen(addr_str));
+	}
+
+	return 1;
+}
+
+static void sieve_envelope_address_list_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_envelope_address_list *addrlist = 
+		(struct sieve_envelope_address_list *)_strlist;
+
+	sieve_stringlist_reset(addrlist->env_parts);
+	addrlist->cur_addresses = NULL;
+	addrlist->cur_values = NULL;
+	addrlist->value_index = 0;
+}
 
 /* 
  * Command Registration 
@@ -320,107 +565,17 @@ static bool ext_envelope_operation_dump
  * Interpretation
  */
 
-static const struct sieve_address *const *_from_part_get_addresses
-(const struct sieve_runtime_env *renv)
-{
-	ARRAY_DEFINE(envelope_values, const struct sieve_address *);
-	const struct sieve_address *address =
-		sieve_message_get_sender_address(renv->msgctx);
-	
-	if ( address != NULL ) {
-		t_array_init(&envelope_values, 2);
-
-        array_append(&envelope_values, &address, 1);
-
-	    (void)array_append_space(&envelope_values);
-    	return array_idx(&envelope_values, 0);
-	} 
-
-	return NULL;
-}
-
-static const char *const *_from_part_get_values
-(const struct sieve_runtime_env *renv)
-{
-	ARRAY_DEFINE(envelope_values, const char *);
-
-	t_array_init(&envelope_values, 2);
-
-	if ( renv->msgdata->return_path != NULL ) {
-        array_append(&envelope_values, &renv->msgdata->return_path, 1);
-	}
-
-	(void)array_append_space(&envelope_values);
-
-	return array_idx(&envelope_values, 0);
-}
-
-static const struct sieve_address *const *_to_part_get_addresses
-(const struct sieve_runtime_env *renv)
-{
-	ARRAY_DEFINE(envelope_values, const struct sieve_address *);
-	const struct sieve_address *address = 
-		sieve_message_get_recipient_address(renv->msgctx);	
-
-	if ( address != NULL && address->local_part != NULL ) {
-		t_array_init(&envelope_values, 2);
-
-        array_append(&envelope_values, &address, 1);
-
-	    (void)array_append_space(&envelope_values);
-    	return array_idx(&envelope_values, 0);
-	}
-
-	return NULL;
-}
-
-static const char *const *_to_part_get_values
-(const struct sieve_runtime_env *renv)
-{
-	ARRAY_DEFINE(envelope_values, const char *);
-
-	t_array_init(&envelope_values, 2);
-
-	if ( renv->msgdata->to_address != NULL ) {
-        array_append(&envelope_values, &renv->msgdata->to_address, 1);
-	}
-
-	(void)array_append_space(&envelope_values);
-
-	return array_idx(&envelope_values, 0);
-}
-
-
-static const char *const *_auth_part_get_values
-(const struct sieve_runtime_env *renv)
-{
-	ARRAY_DEFINE(envelope_values, const char *);
-
-	t_array_init(&envelope_values, 2);
-
-	if ( renv->msgdata->auth_user != NULL )
-        array_append(&envelope_values, &renv->msgdata->auth_user, 1);
-
-	(void)array_append_space(&envelope_values);
-
-	return array_idx(&envelope_values, 0);
-}
-
 static int ext_envelope_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
-	bool result = TRUE;
 	struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
 	struct sieve_match_type mcht = 
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
 	struct sieve_address_part addrp = 
 		SIEVE_ADDRESS_PART_DEFAULT(all_address_part);
-	struct sieve_match_context *mctx;
-	struct sieve_coded_stringlist *envp_list;
-	struct sieve_coded_stringlist *key_list;
-	string_t *envp_item;
-	bool matched;
+	struct sieve_stringlist *env_part_list, *value_list, *key_list;
+	struct sieve_address_list *addr_list;
 	int ret;
 
 	/*
@@ -433,7 +588,7 @@ static int ext_envelope_operation_execute
 		return SIEVE_EXEC_BIN_CORRUPT;
 
 	/* Read envelope-part */
-	if ( (envp_list=sieve_opr_stringlist_read(renv, address, "envelope-part"))
+	if ( (env_part_list=sieve_opr_stringlist_read(renv, address, "envelope-part"))
 		== NULL )
 		return SIEVE_EXEC_BIN_CORRUPT;
 
@@ -447,90 +602,21 @@ static int ext_envelope_operation_execute
 	 */
 
 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "envelope test");
-	
-	/* Initialize match */
-	mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, key_list);
-	
-	/* Iterate through all requested headers to match */
-	envp_item = NULL;
-	matched = FALSE;
-	while ( result && !matched && 
-		(result=sieve_coded_stringlist_next_item(envp_list, &envp_item)) 
-		&& envp_item != NULL ) {
-		const struct sieve_envelope_part *epart;
 
-		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
-            "  matching envelope part `%s'", str_sanitize(str_c(envp_item), 80));
-			
-		if ( (epart=_envelope_part_find(str_c(envp_item))) != NULL ) {
-			const struct sieve_address * const *addresses = NULL;
-			int i;
-
-			if ( epart->get_addresses != NULL ) {
-				/* Field contains addresses */
-				addresses = epart->get_addresses(renv);
-
-				if ( addresses != NULL ) {
-					for ( i = 0; !matched && addresses[i] != NULL; i++ ) {
-						if ( addresses[i]->local_part == NULL ) {
-							/* Null path <> */
-							ret = sieve_match_value(mctx, "", 0);
-						} else {
-							const char *part = NULL;
-
-							if ( addrp.def != NULL && addrp.def->extract_from	!= NULL )
-								part = addrp.def->extract_from(&addrp, addresses[i]);
-
-							if ( part != NULL ) 
-								ret = sieve_match_value(mctx, part, strlen(part));
-							else
-								ret = 0;
-						}
-
-						if ( ret < 0 ) {
-							result = FALSE;
-							break;
-						}
-
-						matched = ret > 0;
-					}
-				}
-			} 
-
-			if ( epart->get_values != NULL && addresses == NULL && 
-				sieve_address_part_is(&addrp, all_address_part) ) {
-				/* Field contains something else */
-				const char *const *values = epart->get_values(renv);
+	/* Create value stringlist */
+	addr_list = sieve_envelope_address_list_create(renv, env_part_list);
+	value_list = sieve_address_part_stringlist_create(renv, &addrp, addr_list);
 
-				if ( values == NULL ) continue;
+	/* Perform match */
+	ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
 	
-				for ( i = 0; !matched && values[i] != NULL; i++ ) {				
-
-					if ( (ret=sieve_match_value
-						(mctx, values[i], strlen(values[i]))) < 0 ) {
-						result = FALSE;
-						break;
-					}
-			
-					matched = ret > 0;				
-				}
-			}
-		}
-	}
-	
-	/* Finish match */
-	if ( (ret=sieve_match_end(&mctx)) < 0 ) 
-		result = FALSE;
-	else
-		matched = ( ret > 0 || matched );
-
-	if ( result ) {
-		/* Set test result for subsequent conditional jump */
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	/* Set test result for subsequent conditional jump */
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
-	}
-	
-	sieve_runtime_trace_error(renv, "invalid string-list item");	
+	}	
+
+	sieve_runtime_trace_error(renv, "invalid string-list item");
 	return SIEVE_EXEC_BIN_CORRUPT;
 }
 
diff --git a/src/lib-sieve/mcht-contains.c b/src/lib-sieve/mcht-contains.c
index e5cc61956..23b2afa43 100644
--- a/src/lib-sieve/mcht-contains.c
+++ b/src/lib-sieve/mcht-contains.c
@@ -17,9 +17,9 @@
  * Forward declarations
  */ 
 
-static int mcht_contains_match
+static int mcht_contains_match_key
 	(struct sieve_match_context *mctx, const char *val, size_t val_size, 
-		const char *key, size_t key_size, int key_index);
+		const char *key, size_t key_size);
 
 /*
  * Match-type object
@@ -27,11 +27,10 @@ static int mcht_contains_match
 
 const struct sieve_match_type_def contains_match_type = {
 	SIEVE_OBJECT("contains", &match_type_operand,	SIEVE_MATCH_TYPE_CONTAINS),
-	TRUE, TRUE,
 	NULL,
 	sieve_match_substring_validate_context,
-	NULL,
-	mcht_contains_match,
+	NULL, NULL, NULL,
+	mcht_contains_match_key,
 	NULL
 };
 
@@ -42,9 +41,9 @@ const struct sieve_match_type_def contains_match_type = {
 /* FIXME: Naive substring match implementation. Should switch to more 
  * efficient algorithm if large values need to be searched (e.g. message body).
  */
-static int mcht_contains_match
+static int mcht_contains_match_key
 (struct sieve_match_context *mctx, const char *val, size_t val_size, 
-	const char *key, size_t key_size, int key_index ATTR_UNUSED)
+	const char *key, size_t key_size)
 {
 	const struct sieve_comparator *cmp = mctx->comparator;
 	const char *vend = (const char *) val + val_size;
@@ -52,7 +51,7 @@ static int mcht_contains_match
 	const char *vp = val;
 	const char *kp = key;
 
-	if ( val == NULL || val_size == 0 ) 
+	if ( val_size == 0 ) 
 		return ( key_size == 0 );
 
 	if ( cmp->def == NULL || cmp->def->char_match == NULL ) 
diff --git a/src/lib-sieve/mcht-is.c b/src/lib-sieve/mcht-is.c
index 154dff792..c3ac2fab3 100644
--- a/src/lib-sieve/mcht-is.c
+++ b/src/lib-sieve/mcht-is.c
@@ -17,9 +17,9 @@
  * Forward declarations 
  */
 
-static int mcht_is_match
+static int mcht_is_match_key
 	(struct sieve_match_context *mctx, const char *val, size_t val_size, 
-		const char *key, size_t key_size, int key_index);
+		const char *key, size_t key_size);
 
 /* 
  * Match-type object 
@@ -27,9 +27,8 @@ static int mcht_is_match
 
 const struct sieve_match_type_def is_match_type = {
 	SIEVE_OBJECT("is", &match_type_operand, SIEVE_MATCH_TYPE_IS),
-	TRUE, TRUE,
-	NULL, NULL, NULL,
-	mcht_is_match,
+	NULL, NULL, NULL, NULL, NULL,
+	mcht_is_match_key,
 	NULL
 };
 
@@ -37,12 +36,12 @@ const struct sieve_match_type_def is_match_type = {
  * Match-type implementation
  */
 
-static int mcht_is_match
+static int mcht_is_match_key
 (struct sieve_match_context *mctx ATTR_UNUSED, 
 	const char *val, size_t val_size, 
-	const char *key, size_t key_size, int key_index ATTR_UNUSED)
+	const char *key, size_t key_size)
 {
-	if ( (val == NULL || val_size == 0) ) 
+	if ( val_size == 0 ) 
 		return ( key_size == 0 );
 
 	if ( mctx->comparator->def != NULL && mctx->comparator->def->compare != NULL )
diff --git a/src/lib-sieve/mcht-matches.c b/src/lib-sieve/mcht-matches.c
index 3a1f1652e..e14f1dd6a 100644
--- a/src/lib-sieve/mcht-matches.c
+++ b/src/lib-sieve/mcht-matches.c
@@ -18,9 +18,9 @@
  * Forward declarations
  */
 
-static int mcht_matches_match
+static int mcht_matches_match_key
 	(struct sieve_match_context *mctx, const char *val, size_t val_size, 
-		const char *key, size_t key_size, int key_index);
+		const char *key, size_t key_size);
 
 /*
  * Match-type object
@@ -28,11 +28,10 @@ static int mcht_matches_match
 
 const struct sieve_match_type_def matches_match_type = {
 	SIEVE_OBJECT("matches", &match_type_operand, SIEVE_MATCH_TYPE_MATCHES),
-	TRUE, FALSE,
 	NULL,
 	sieve_match_substring_validate_context, 
-	NULL,
-	mcht_matches_match,
+	NULL, NULL, NULL,
+	mcht_matches_match_key,
 	NULL
 };
 
@@ -83,9 +82,9 @@ static char _scan_key_section
 	return '\0';
 }
 
-static int mcht_matches_match
+static int mcht_matches_match_key
 (struct sieve_match_context *mctx, const char *val, size_t val_size, 
-	const char *key, size_t key_size, int key_index ATTR_UNUSED)
+	const char *key, size_t key_size)
 {
 	const struct sieve_comparator *cmp = mctx->comparator;
 	struct sieve_match_values *mvalues;
@@ -100,12 +99,6 @@ static int mcht_matches_match
 	if ( cmp->def == NULL || cmp->def->char_match == NULL )
 		return FALSE;
 
-	/* Value may be NULL, parse empty string in stead */
-	if ( val == NULL ) {
-		val = "";
-		val_size = 0;
-	}
-	
 	/* Key sections */
 	section = t_str_new(32);    /* Section (after beginning or *) */
 	subsection = t_str_new(32); /* Sub-section (after ?) */
diff --git a/src/lib-sieve/plugins/body/ext-body-common.c b/src/lib-sieve/plugins/body/ext-body-common.c
index 559588688..2d80d8ae4 100644
--- a/src/lib-sieve/plugins/body/ext-body-common.c
+++ b/src/lib-sieve/plugins/body/ext-body-common.c
@@ -13,15 +13,22 @@
 #include "message-decoder.h"
 
 #include "sieve-common.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-message.h"
 #include "sieve-interpreter.h"
 
 #include "ext-body-common.h"
 
-/* This implementation is largely borrowed from the original sieve-cmu.c of the 
- * cmusieve plugin.
+/* FIXME: This implementation is largely borrowed from the original sieve-cmu.c
+ * of the old cmusieve plugin. This nees work to match current specification of
+ * the body extension.
  */
+
+struct ext_body_part {
+	const char *content;
+	unsigned long size;
+};
  
 struct ext_body_part_cached {
 	const char *content_type;
@@ -147,6 +154,7 @@ static void ext_body_part_save
 	if ( !decoded ) {
 		body_part->raw_body = part_data;
 		body_part->raw_body_size = part_size;
+		printf("%ld <=> %ld\n", (long) buf->used - 1, (long) part->body_size.physical_size);
 		i_assert(buf->used - 1 == part->body_size.physical_size);
 	} else {
 		body_part->decoded_body = part_data;
@@ -325,7 +333,7 @@ static struct ext_body_message_context *ext_body_get_context
 	return ctx;
 }
 
-bool ext_body_get_content
+static bool ext_body_get_content
 (const struct sieve_runtime_env *renv, const char * const *content_types,
 	int decode_to_plain, struct ext_body_part **parts_r)
 {
@@ -351,7 +359,7 @@ bool ext_body_get_content
 	return result;
 }
 
-bool ext_body_get_raw
+static bool ext_body_get_raw
 (const struct sieve_runtime_env *renv, struct ext_body_part **parts_r)
 {
 	const struct sieve_extension *this_ext = renv->oprtn->ext;
@@ -406,3 +414,83 @@ bool ext_body_get_raw
 
 	return TRUE;
 }
+
+/*
+ * Body part stringlist
+ */
+
+static int ext_body_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void ext_body_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+struct ext_body_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct ext_body_part *body_parts;
+	struct ext_body_part *body_parts_iter;
+};
+
+struct sieve_stringlist *ext_body_get_part_list
+(const struct sieve_runtime_env *renv, enum tst_body_transform transform,
+	const char * const *content_types)
+{
+	static const char * const _no_content_types[] = { "", NULL };
+	struct ext_body_stringlist *strlist;
+	struct ext_body_part *body_parts;
+
+	if ( content_types == NULL ) content_types = _no_content_types;
+
+	switch ( transform ) {
+	case TST_BODY_TRANSFORM_RAW:
+		if ( !ext_body_get_raw(renv, &body_parts) )
+			return NULL;
+		break;
+	case TST_BODY_TRANSFORM_CONTENT:
+		/* FIXME: check these parameters */
+		if ( !ext_body_get_content(renv, content_types, TRUE, &body_parts) )
+			return NULL;
+		break;
+	case TST_BODY_TRANSFORM_TEXT:
+		/* FIXME: check these parameters */
+		if ( !ext_body_get_content(renv, content_types, TRUE, &body_parts) )
+			return NULL;
+		break;
+	default:
+		i_unreached();
+	}
+
+	strlist = t_new(struct ext_body_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = ext_body_stringlist_next_item;
+	strlist->strlist.reset = ext_body_stringlist_reset;
+	strlist->body_parts = body_parts;
+	strlist->body_parts_iter = body_parts;
+
+	return &strlist->strlist;
+}
+
+static int ext_body_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct ext_body_stringlist *strlist = 
+		(struct ext_body_stringlist *)_strlist;
+
+	*str_r = NULL;
+
+	if ( strlist->body_parts_iter->content == NULL ) return 0;
+
+	*str_r = t_str_new_const
+		(strlist->body_parts_iter->content, strlist->body_parts_iter->size);
+	strlist->body_parts_iter++;		
+	return 1;
+}
+
+static void ext_body_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct ext_body_stringlist *strlist = 
+		(struct ext_body_stringlist *)_strlist;
+
+	strlist->body_parts_iter = strlist->body_parts;
+}
diff --git a/src/lib-sieve/plugins/body/ext-body-common.h b/src/lib-sieve/plugins/body/ext-body-common.h
index ba61bbc33..d2c1fa58b 100644
--- a/src/lib-sieve/plugins/body/ext-body-common.h
+++ b/src/lib-sieve/plugins/body/ext-body-common.h
@@ -4,6 +4,16 @@
 #ifndef __EXT_BODY_COMMON_H
 #define __EXT_BODY_COMMON_H
 
+/*
+ * Types
+ */
+
+enum tst_body_transform {
+	TST_BODY_TRANSFORM_RAW,
+	TST_BODY_TRANSFORM_CONTENT,
+	TST_BODY_TRANSFORM_TEXT
+};
+
 /*
  * Extension
  */
@@ -26,16 +36,8 @@ extern const struct sieve_operation_def body_operation;
  * Message body part extraction
  */
 
-struct ext_body_part {
-	const char *content;
-	unsigned long size;
-};
-
-bool ext_body_get_content
-	(const struct sieve_runtime_env *renv, const char * const *content_types,
-		int decode_to_plain, struct ext_body_part **parts_r);
-
-bool ext_body_get_raw
-	(const struct sieve_runtime_env *renv, struct ext_body_part **parts_r);
+struct sieve_stringlist *ext_body_get_part_list
+	(const struct sieve_runtime_env *renv, enum tst_body_transform transform,
+		const char * const *content_types);
 
 #endif /* __EXT_BODY_COMMON_H */
diff --git a/src/lib-sieve/plugins/body/tst-body.c b/src/lib-sieve/plugins/body/tst-body.c
index f416f586c..f3033bc87 100644
--- a/src/lib-sieve/plugins/body/tst-body.c
+++ b/src/lib-sieve/plugins/body/tst-body.c
@@ -3,6 +3,7 @@
  
 #include "sieve-extensions.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-comparators.h"
 #include "sieve-match-types.h"
@@ -17,16 +18,6 @@
 
 #include "ext-body-common.h"
 
-/*
- * Types
- */
-
-enum tst_body_transform {
-	TST_BODY_TRANSFORM_RAW,
-	TST_BODY_TRANSFORM_CONTENT,
-	TST_BODY_TRANSFORM_TEXT
-};
-
 /* 
  * Body test 
  *
@@ -311,21 +302,16 @@ static bool ext_body_operation_dump
 static int ext_body_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
-	static const char * const _no_content_types[] = { "", NULL };
-	int ret = SIEVE_EXEC_OK;
+	int ret;
 	int opt_code = 0;
-	int mret;
 	struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
-	struct sieve_match_type mtch = 
+	struct sieve_match_type mcht = 
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
-	enum tst_body_transform transform;
-	struct sieve_coded_stringlist *key_list, *ctype_list = NULL;
-	struct sieve_match_context *mctx;
-	const char * const *content_types = _no_content_types;
-	struct ext_body_part *body_parts;
+	enum tst_body_transform transform = TST_BODY_TRANSFORM_TEXT;
+	struct sieve_stringlist *ctype_list, *value_list, *key_list;
 	bool mvalues_active;
-	bool matched;
+	const char * const *content_types = NULL;
 
 	/*
 	 * Read operands
@@ -333,12 +319,12 @@ static int ext_body_operation_execute
 	
 	/* Optional operands */
 
+	ctype_list = NULL;
 	for (;;) {
 		bool opok = TRUE;
-		int ret;
 
 		if ( (ret=sieve_match_opr_optional_read
-			(renv, address, &opt_code, &cmp, &mtch)) < 0 )
+			(renv, address, &opt_code, &cmp, &mcht)) < 0 )
 			return SIEVE_EXEC_BIN_CORRUPT;
 
 		if ( ret == 0 ) break;
@@ -371,9 +357,9 @@ static int ext_body_operation_execute
 	if ( (key_list=sieve_opr_stringlist_read(renv, address, "key-list")) == NULL ) 
 		return SIEVE_EXEC_BIN_CORRUPT;
 	
-	if ( ctype_list != NULL && !sieve_coded_stringlist_read_all
+	if ( ctype_list != NULL && !sieve_stringlist_read_all
 		(ctype_list, pool_datastack_create(), &content_types) ) {
-		sieve_runtime_trace_error(renv, "invalid content-type-list operand");
+		sieve_runtime_trace_error(renv, "failed to read content-type-list operand");
 		return SIEVE_EXEC_BIN_CORRUPT;
 	}
 	
@@ -384,53 +370,25 @@ static int ext_body_operation_execute
 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "body test");
 	
 	/* Extract requested parts */
-	
-	if ( transform == TST_BODY_TRANSFORM_RAW ) {
-		if ( !ext_body_get_raw(renv, &body_parts) ) {
-			return SIEVE_EXEC_FAILURE;
-		}
-	} else {
-		if ( !ext_body_get_content
-			(renv, content_types, TRUE, &body_parts) ) {
-			return SIEVE_EXEC_FAILURE;
-		}
-	}
+	value_list = ext_body_get_part_list(renv, transform, content_types);
+	if ( value_list == FALSE )
+		return SIEVE_EXEC_FAILURE;
 
 	/* Disable match values processing as required by RFC */
-		
 	mvalues_active = sieve_match_values_set_enabled(renv, FALSE);
 
-	/* Iterate through all requested body parts to match */
-
-	matched = FALSE;	
-	mctx = sieve_match_begin(renv, &mtch, &cmp, NULL, key_list); 	
-	while ( !matched && body_parts->content != NULL ) {
-		if ( (mret=sieve_match_value(mctx, body_parts->content, body_parts->size)) 	
-			< 0) 
-		{
-			sieve_runtime_trace_error(renv, "invalid string list item");
-			ret = SIEVE_EXEC_BIN_CORRUPT;
-			break;
-		}
-		
-		matched = ( mret > 0 );			
-		body_parts++;	
-	}
+	/* Perform match */
+	ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
 
-	if ( (mret=sieve_match_end(&mctx)) < 0 ) {
-		sieve_runtime_trace_error(renv, "invalid string list item");
-		ret = SIEVE_EXEC_BIN_CORRUPT;
-	} else	
-		matched = ( mret > 0 || matched ); 	
-	
-	/* Restore match values processing */ 
-	
+	/* Restore match values processing */ 	
 	(void)sieve_match_values_set_enabled(renv, mvalues_active);
 	
-	/* Set test result */	
-	
-	if ( ret == SIEVE_EXEC_OK )
-		sieve_interpreter_set_test_result(renv->interp, matched);
-
-	return ret;
+	/* Set test result for subsequent conditional jump */
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
+		return SIEVE_EXEC_OK;
+	}	
+
+	sieve_runtime_trace_error(renv, "invalid string-list item");
+	return SIEVE_EXEC_BIN_CORRUPT;
 }
diff --git a/src/lib-sieve/plugins/date/ext-date-common.c b/src/lib-sieve/plugins/date/ext-date-common.c
index 28ce60634..7891b17e5 100644
--- a/src/lib-sieve/plugins/date/ext-date-common.c
+++ b/src/lib-sieve/plugins/date/ext-date-common.c
@@ -3,8 +3,11 @@
 
 #include "lib.h"
 #include "utc-offset.h"
+#include "str.h"
+#include "message-date.h"
 
 #include "sieve-common.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-interpreter.h"
 #include "sieve-message.h"
@@ -465,3 +468,147 @@ static const char *ext_date_weekday_part_get
 	return t_strdup_printf("%d", tm->tm_wday);
 }
 
+/*
+ * Date stringlist
+ */
+
+/* Forward declarations */
+
+static int ext_date_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void ext_date_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct ext_date_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_stringlist *field_values;	
+	int time_zone;
+	const char *date_part;
+
+	time_t local_time;
+	int local_zone;
+
+	unsigned int read:1;
+};
+
+struct sieve_stringlist *ext_date_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values,
+	int time_zone, const char *date_part)
+{
+	struct ext_date_stringlist *strlist;
+	    
+	strlist = t_new(struct ext_date_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = ext_date_stringlist_next_item;
+	strlist->strlist.reset = ext_date_stringlist_reset;
+	strlist->field_values = field_values;
+	strlist->time_zone = time_zone;
+	strlist->date_part = date_part;
+  
+	strlist->local_time = ext_date_get_current_date(renv, &strlist->local_zone);
+
+	return &strlist->strlist;
+}
+
+/* Stringlist implementation */
+
+static int ext_date_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct ext_date_stringlist *strlist = 
+		(struct ext_date_stringlist *) _strlist;
+	bool got_date = FALSE;
+	time_t date_value;
+	const char *part_value = NULL;
+	int original_zone;
+
+	/* Check whether the item was already read */
+	if ( strlist->read ) return 0;
+
+	if ( strlist->field_values != NULL ) {
+		string_t *hdr_item;
+		const char *header_value, *date_string;
+		int ret;
+
+		/* Use header field value */
+
+		/* Read first */
+		if ( (ret=sieve_stringlist_next_item(strlist->field_values, &hdr_item))
+			<= 0 )
+			return ret;
+
+		/* Extract the date string value */
+
+		header_value = str_c(hdr_item);
+		date_string = strrchr(header_value, ';');
+
+		if ( date_string == NULL ) {
+			/* Direct header value */
+			date_string = header_value;
+		} else {
+			/* Delimited by ';', e.g. a Received: header */
+			date_string++; 
+		}
+
+		/* Parse the date value */
+		if ( message_date_parse((const unsigned char *) date_string,
+			strlen(date_string), &date_value, &original_zone) ) {
+			got_date = TRUE;
+		}
+	} else {
+		/* Use time stamp recorded at the time the script first started */
+		date_value = strlist->local_time;
+		original_zone = strlist->local_zone;
+		got_date = TRUE;
+	}
+
+	if ( got_date ) {
+		int wanted_zone;
+		struct tm *date_tm;
+
+		/* Apply wanted timezone */
+
+		switch ( strlist->time_zone ) {
+		case EXT_DATE_TIMEZONE_LOCAL:
+			wanted_zone = strlist->local_zone;
+			break;
+		case EXT_DATE_TIMEZONE_ORIGINAL:
+			wanted_zone = original_zone;
+			break;
+		default:
+			wanted_zone = strlist->time_zone;
+		}
+
+		date_value += wanted_zone * 60;
+
+		/* Convert timestamp to struct tm */
+
+		if ( (date_tm=gmtime(&date_value)) != NULL ) {
+			/* Extract the date part */
+			part_value = ext_date_part_extract
+				(strlist->date_part, date_tm, wanted_zone);
+		}
+	}
+
+	strlist->read = TRUE;
+
+	if ( part_value == NULL ) 
+		return 0;
+
+	*str_r = t_str_new_const(part_value, strlen(part_value));
+	return 1;
+}
+
+static void ext_date_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct ext_date_stringlist *strlist = 
+		(struct ext_date_stringlist *) _strlist;
+
+	if ( strlist->field_values != NULL )
+		sieve_stringlist_reset(strlist->field_values);
+	strlist->read = FALSE;
+}
diff --git a/src/lib-sieve/plugins/date/ext-date-common.h b/src/lib-sieve/plugins/date/ext-date-common.h
index a6f9cee51..07e578e70 100644
--- a/src/lib-sieve/plugins/date/ext-date-common.h
+++ b/src/lib-sieve/plugins/date/ext-date-common.h
@@ -63,4 +63,19 @@ struct ext_date_part {
 const char *ext_date_part_extract
 	(const char *part, struct tm *tm, int zone_offset);
 
+/*
+ * Date stringlist
+ */
+
+enum ext_date_timezone_special {
+	EXT_DATE_TIMEZONE_LOCAL    = 100,
+	EXT_DATE_TIMEZONE_ORIGINAL = 101
+};
+
+struct sieve_stringlist *ext_date_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values,
+	int time_zone, const char *date_part);
+
+
+
 #endif /* __EXT_DATE_COMMON_H */
diff --git a/src/lib-sieve/plugins/date/tst-date.c b/src/lib-sieve/plugins/date/tst-date.c
index 541cbabaa..90b62c3b5 100644
--- a/src/lib-sieve/plugins/date/tst-date.c
+++ b/src/lib-sieve/plugins/date/tst-date.c
@@ -3,7 +3,6 @@
 
 #include "lib.h"
 #include "str-sanitize.h"
-#include "message-date.h"
 
 #include "sieve-common.h"
 #include "sieve-commands.h"
@@ -11,6 +10,7 @@
 #include "sieve-comparators.h"
 #include "sieve-match-types.h"
 #include "sieve-address-parts.h"
+#include "sieve-message.h"
 #include "sieve-validator.h"
 #include "sieve-generator.h"
 #include "sieve-interpreter.h"
@@ -372,6 +372,7 @@ static bool tst_date_operation_dump
 		sieve_opr_stringlist_dump(denv, address, "key list");
 }
 
+
 /* 
  * Code execution 
  */
@@ -380,21 +381,17 @@ static int tst_date_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {	
 	const struct sieve_operation *op = renv->oprtn;
-	bool result = TRUE, zone_specified = FALSE, got_date = FALSE, matched = FALSE;
 	int opt_code = 0;
-	const struct sieve_message_data *msgdata = renv->msgdata;
 	struct sieve_match_type mcht = 
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
 	struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
 	struct sieve_operand operand;
-	struct sieve_match_context *mctx;
-	string_t *header_name = NULL, *date_part = NULL, *zone = NULL;
-	struct sieve_coded_stringlist *key_list;
-	time_t date_value, local_time;
-	struct tm *date_tm;
-	const char *part_value;
-	int local_zone = 0, original_zone = 0, wanted_zone = 0;
+	string_t *date_part = NULL, *zone = NULL;
+	struct sieve_stringlist *hdr_list = NULL, *hdr_value_list;
+	struct sieve_stringlist *value_list, *key_list;
+	bool zone_specified = FALSE;
+	int time_zone;
 	int ret;
 	
 	/* Read optional operands */
@@ -427,8 +424,9 @@ static int tst_date_operation_execute
 	} 
 
 	if ( sieve_operation_is(op, date_operation) ) {
-		/* Read header name */
-		if ( !sieve_opr_string_read(renv, address, "header-name", &header_name) )
+		/* Read header name as stringlist */
+		if ( (hdr_list=sieve_opr_stringlist_read(renv, address, "header-name"))
+			== NULL )
 			return SIEVE_EXEC_BIN_CORRUPT;
 	}
 
@@ -441,102 +439,47 @@ static int tst_date_operation_execute
 		== NULL )
 		return SIEVE_EXEC_BIN_CORRUPT;
 	
+	/* Determine what time zone to use in the result */
+	if ( !zone_specified ) {
+		time_zone = EXT_DATE_TIMEZONE_LOCAL;
+	} else if ( zone == NULL ) {
+		time_zone = EXT_DATE_TIMEZONE_ORIGINAL;
+	} else if ( !ext_date_parse_timezone(str_c(zone), &time_zone) ) {
+		/* FIXME: warn about parse failures */
+		time_zone = EXT_DATE_TIMEZONE_LOCAL;
+	}
+
 	/* 
 	 * Perform test 
 	 */
-
-	/* Get the date value */
-
-	local_time = ext_date_get_current_date(renv, &local_zone);
-
+	
 	if ( sieve_operation_is(op, date_operation) ) {
-		const char *header_value;
-		const char *date_string;
-
+	
 		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "date test");
 
-		/* Get date from the message */
-
-		/* Read first header
-		 *   NOTE: need something for index extension to hook into some time. 
-		 */
-		if ( (ret=mail_get_first_header
-			(msgdata->mail, str_c(header_name), &header_value)) > 0 ) {
-
-			/* Extract the date string value */
-			date_string = strrchr(header_value, ';');
-			if ( date_string == NULL )
-				/* Direct header value */
-				date_string = header_value;
-			else {
-				/* Delimited by ';', e.g. a Received: header */
-				date_string++; 
-			}
+		/* Create value stringlist */
+		hdr_value_list = sieve_message_header_stringlist_create(renv, hdr_list);
+		value_list = ext_date_stringlist_create
+			(renv, hdr_value_list, time_zone, str_c(date_part));
 
-			/* Parse the date value */
-			if ( message_date_parse((const unsigned char *) date_string,
-				strlen(date_string), &date_value, &original_zone) ) {
-				got_date = TRUE;
-			}
-		}
 	} else if ( sieve_operation_is(op, currentdate_operation) ) {
 		/* Use time stamp recorded at the time the script first started */
 
 		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "currentdatedate test");
 
-		date_value = local_time;
-		original_zone = local_zone;
-		got_date = TRUE;
-
+		/* Create value stringlist */
+		value_list = ext_date_stringlist_create
+			(renv, NULL, time_zone, str_c(date_part));
 	} else {
 		i_unreached();
 	}
 
-	if ( got_date ) {
-		/* Apply wanted timezone */
-
-		if ( !zone_specified )
-			wanted_zone = local_zone;
-		else if ( zone == NULL 
-			|| !ext_date_parse_timezone(str_c(zone), &wanted_zone) ) {
-
-			/* FIXME: warn about parse failures */
-			wanted_zone = original_zone;
-		}
-
-		date_value += wanted_zone * 60;
-
-		/* Convert timestamp to struct tm */
-
-		if ( (date_tm=gmtime(&date_value)) == NULL ) {
-			got_date = FALSE;
-		} else {
-			/* Extract the date part */
-			part_value = ext_date_part_extract
-				(str_c(date_part), date_tm, wanted_zone);
-		}
-	}
-
-	/* Initialize match */
-	mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, key_list); 	
-	
-	if ( got_date && part_value != NULL ) {		
-		/* Match value */
-		if ( (ret=sieve_match_value(mctx, part_value, strlen(part_value))) < 0 )
-			result = FALSE;
-		else
-			matched = ret > 0;
-	}
-
-	/* Finish match */
-	if ( (ret=sieve_match_end(&mctx)) < 0 ) 
-		result = FALSE;
-	else
-		matched = ( ret > 0 || matched );
+	/* Perform match */
+	ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
 	
 	/* Set test result for subsequent conditional jump */
-	if ( result ) {
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
 	}	
 
diff --git a/src/lib-sieve/plugins/enotify/cmd-notify.c b/src/lib-sieve/plugins/enotify/cmd-notify.c
index 2db21f783..6be5703ca 100644
--- a/src/lib-sieve/plugins/enotify/cmd-notify.c
+++ b/src/lib-sieve/plugins/enotify/cmd-notify.c
@@ -401,7 +401,7 @@ static int cmd_notify_operation_execute
 	pool_t pool;
 	int opt_code = 0, result = SIEVE_EXEC_OK;
 	sieve_number_t importance = 2;
-	struct sieve_coded_stringlist *options = NULL;
+	struct sieve_stringlist *options = NULL;
 	const struct sieve_enotify_method *method;
 	string_t *method_uri, *message = NULL, *from = NULL; 
 	unsigned int source_line;
diff --git a/src/lib-sieve/plugins/enotify/ext-enotify-common.c b/src/lib-sieve/plugins/enotify/ext-enotify-common.c
index ac77d628a..d4df47f0f 100644
--- a/src/lib-sieve/plugins/enotify/ext-enotify-common.c
+++ b/src/lib-sieve/plugins/enotify/ext-enotify-common.c
@@ -8,6 +8,7 @@
 
 #include "sieve-common.h"
 #include "sieve-ast.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-commands.h"
 #include "sieve-validator.h"
@@ -579,7 +580,7 @@ const char *ext_enotify_runtime_get_method_capability
 int ext_enotify_runtime_check_operands
 (const struct sieve_runtime_env *renv, unsigned int source_line,
 	string_t *method_uri, string_t *message, string_t *from, 
-	struct sieve_coded_stringlist *options, 
+	struct sieve_stringlist *options, 
 	const struct sieve_enotify_method **method_r, void **method_context)
 {
 	const struct sieve_enotify_method *method;
@@ -592,7 +593,7 @@ int ext_enotify_runtime_check_operands
 	/* Check provided operands */
 	if ( method->def != NULL && method->def->runtime_check_operands != NULL ) {
 		struct sieve_enotify_env nenv; 
-		int ret = SIEVE_EXEC_OK;
+		int result = SIEVE_EXEC_OK;
 
 		memset(&nenv, 0, sizeof(nenv));
 		nenv.method = method;
@@ -608,13 +609,11 @@ int ext_enotify_runtime_check_operands
 			
 			/* Check any provided options */
 			if ( options != NULL ) {			
-				int result = TRUE;
 				string_t *option = NULL;
+				int ret;
 			
 				/* Iterate through all provided options */
-				while ( result && 
-					(result=sieve_coded_stringlist_next_item(options, &option)) && 
-					option != NULL ) {
+				while ( (ret=sieve_stringlist_next_item(options, &option)) > 0 ) {
 					const char *opt_name = NULL, *opt_value = NULL;
 				
 					/* Parse option into <optionname> and <value> */
@@ -631,13 +630,13 @@ int ext_enotify_runtime_check_operands
 			
 				/* Check for binary corruptions encountered during string list iteration
 				 */
-				if ( result ) {
+				if ( ret >= 0 ) {
 					*method_r = method;
 				} else {
 					/* Binary corrupt */
 					sieve_runtime_trace_error
 						(renv, "invalid item in options string list");
-					ret = SIEVE_EXEC_BIN_CORRUPT;
+					result = SIEVE_EXEC_BIN_CORRUPT;
 				}
 
 			} else {
@@ -647,11 +646,11 @@ int ext_enotify_runtime_check_operands
 
 		} else { 	
 			/* Operand check failed */
-			ret = SIEVE_EXEC_FAILURE;
+			result = SIEVE_EXEC_FAILURE;
 		}
 
 		sieve_error_handler_unref(&nenv.ehandler);
-		return ret;
+		return result;
 	}
 
 	/* No check function defined: a most unlikely situation */
diff --git a/src/lib-sieve/plugins/enotify/ext-enotify-common.h b/src/lib-sieve/plugins/enotify/ext-enotify-common.h
index 97ab61746..65c2274af 100644
--- a/src/lib-sieve/plugins/enotify/ext-enotify-common.h
+++ b/src/lib-sieve/plugins/enotify/ext-enotify-common.h
@@ -112,7 +112,7 @@ const char *ext_enotify_runtime_get_method_capability
 int ext_enotify_runtime_check_operands
 	(const struct sieve_runtime_env *renv, unsigned int source_line,
 		string_t *method_uri, string_t *message, string_t *from, 
-		struct sieve_coded_stringlist *options, 
+		struct sieve_stringlist *options, 
 		const struct sieve_enotify_method **method_r, void **method_context);
 		
 /*
diff --git a/src/lib-sieve/plugins/enotify/tst-notify-method-capability.c b/src/lib-sieve/plugins/enotify/tst-notify-method-capability.c
index e30217896..8ee7fb53f 100644
--- a/src/lib-sieve/plugins/enotify/tst-notify-method-capability.c
+++ b/src/lib-sieve/plugins/enotify/tst-notify-method-capability.c
@@ -5,6 +5,7 @@
 
 #include "sieve-common.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-comparators.h"
 #include "sieve-match-types.h"
@@ -176,18 +177,15 @@ static bool tst_notifymc_operation_dump
 static int tst_notifymc_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
-	int ret, mret;
-	bool result = TRUE;
+	int ret;
 	int opt_code = 0;
 	struct sieve_match_type mcht = 
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
 	struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
-	struct sieve_match_context *mctx;
 	string_t *notify_uri, *notify_capability;
-	struct sieve_coded_stringlist *key_list;
+	struct sieve_stringlist *value_list, *key_list;
 	const char *cap_value;
-	bool matched;
 
 	/*
 	 * Read operands 
@@ -228,21 +226,16 @@ static int tst_notifymc_operation_execute
 		(renv, 0 /* FIXME */, notify_uri, str_c(notify_capability));
 
 	if ( cap_value != NULL ) {
-		mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, key_list); 	
+		value_list = sieve_single_stringlist_create_cstr(renv, cap_value, TRUE);
 
-		if ( (mret=sieve_match_value(mctx, cap_value, strlen(cap_value))) < 0 )
-			result = FALSE;
-		matched = ( mret > 0 );		
-
-		if ( (mret=sieve_match_end(&mctx)) < 0 ) 
-			result = FALSE;
-		matched = ( mret > 0 ) || matched;		
+		/* Perform match */
+		ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
 	} else {
-		matched = FALSE;
+		ret = 0;
 	}
 	
-	if ( result ) {
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
 	}
 	
diff --git a/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c b/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c
index 20fc00da6..493bc3dc8 100644
--- a/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c
+++ b/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c
@@ -3,6 +3,7 @@
 
 #include "sieve-common.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-comparators.h"
 #include "sieve-match-types.h"
@@ -104,9 +105,10 @@ static bool tst_vnotifym_operation_dump
 static int tst_vnotifym_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
-	struct sieve_coded_stringlist *notify_uris;
+	struct sieve_stringlist *notify_uris;
 	string_t *uri_item;
-	bool result = TRUE, all_valid = TRUE;
+	bool all_valid = TRUE;
+	int ret;
 
 	/*
 	 * Read operands 
@@ -124,16 +126,14 @@ static int tst_vnotifym_operation_execute
 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "valid_notify_method test");
 
 	uri_item = NULL;
-	while ( (result=sieve_coded_stringlist_next_item(notify_uris, &uri_item)) 
-		&& uri_item != NULL ) {
-		
+	while ( (ret=sieve_stringlist_next_item(notify_uris, &uri_item)) > 0 ) {		
 		if ( !ext_enotify_runtime_method_validate(renv, 0 /* FIXME */, uri_item) ) {
 			all_valid = FALSE;
 			break;
 		}
 	}
 	
-	if ( !result ) {
+	if ( ret < 0 ) {
 		sieve_runtime_trace_error(renv, "invalid method uri item");
 		return SIEVE_EXEC_BIN_CORRUPT;
 	}
diff --git a/src/lib-sieve/plugins/environment/tst-environment.c b/src/lib-sieve/plugins/environment/tst-environment.c
index 54e1fb49b..5f9496d0f 100644
--- a/src/lib-sieve/plugins/environment/tst-environment.c
+++ b/src/lib-sieve/plugins/environment/tst-environment.c
@@ -3,6 +3,7 @@
 
 #include "sieve-common.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-comparators.h"
 #include "sieve-match-types.h"
@@ -154,18 +155,15 @@ static int tst_environment_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
 	const struct sieve_extension *this_ext = renv->oprtn->ext;
-	int ret, mret;
-	bool result = TRUE;
+	int ret;
 	int opt_code = 0;
 	struct sieve_match_type mcht = 
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
 	struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
-	struct sieve_match_context *mctx;
 	string_t *name;
-	struct sieve_coded_stringlist *key_list;
+	struct sieve_stringlist *value_list, *key_list;
 	const char *env_item;
-	bool matched = FALSE;
 
 	/*
 	 * Read operands 
@@ -201,23 +199,17 @@ static int tst_environment_operation_execute
 		(this_ext, str_c(name), renv->scriptenv);
 
 	if ( env_item != NULL ) {
-		mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, key_list); 	
-
-		if ( (mret=sieve_match_value(mctx, strlen(env_item) == 0 ? NULL : env_item, 
-			strlen(env_item))) < 0 ) {
-			result = FALSE;
-		} else {
-			matched = ( mret > 0 );				
-		}
-
-		if ( (mret=sieve_match_end(&mctx)) < 0 )
-			result = FALSE;
-		else
-			matched = ( mret > 0 || matched );
+		/* Construct value list */
+		value_list = sieve_single_stringlist_create_cstr(renv, env_item, FALSE);
+
+		/* Perform match */
+		ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
+	} else {
+		ret = 0;
 	}
 	
-	if ( result ) {
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
 	}
 	
diff --git a/src/lib-sieve/plugins/imap4flags/cmd-flag.c b/src/lib-sieve/plugins/imap4flags/cmd-flag.c
index 805e79acb..13e43100e 100644
--- a/src/lib-sieve/plugins/imap4flags/cmd-flag.c
+++ b/src/lib-sieve/plugins/imap4flags/cmd-flag.c
@@ -4,6 +4,7 @@
 #include "lib.h"
 
 #include "sieve-code.h"
+#include "sieve-stringlist.h"
 #include "sieve-commands.h"
 #include "sieve-validator.h" 
 #include "sieve-generator.h"
@@ -176,9 +177,8 @@ static int cmd_flag_operation_execute
 {
 	const struct sieve_operation *op = renv->oprtn;
 	struct sieve_operand operand;
-	bool result = TRUE;
 	string_t *flag_item;
-	struct sieve_coded_stringlist *flag_list;
+	struct sieve_stringlist *flag_list;
 	struct sieve_variable_storage *storage;
 	unsigned int var_index;
 	ext_imapflag_flag_operation_t flag_op;
@@ -243,14 +243,12 @@ static int cmd_flag_operation_execute
 
 	/* Iterate through all flags and perform requested operation */
 	
-	while ( (result=sieve_coded_stringlist_next_item(flag_list, &flag_item)) && 
-		flag_item != NULL ) {
-
+	while ( (ret=sieve_stringlist_next_item(flag_list, &flag_item)) > 0 ) {
 		if ( (ret=flag_op(renv, storage, var_index, flag_item)) <= 0)
 			return ret;
 	}
 
-	if ( !result ) {	
+	if ( ret < 0 ) {	
 		sieve_runtime_trace_error(renv, "invalid flag-list item");
 		return SIEVE_EXEC_BIN_CORRUPT;
 	}
diff --git a/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.c b/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.c
index 44dae44b3..771455247 100644
--- a/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.c
+++ b/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.c
@@ -8,6 +8,7 @@
 #include "sieve-common.h"
 #include "sieve-commands.h"
 #include "sieve-code.h"
+#include "sieve-stringlist.h"
 #include "sieve-actions.h"
 #include "sieve-validator.h" 
 #include "sieve-generator.h"
@@ -256,12 +257,13 @@ const struct sieve_interpreter_extension imap4flags_interpreter_extension = {
 };
 
 /* 
- * Flag operations 
+ * Flag handling
  */
 
 /* FIXME: This currently accepts a potentially unlimited number of 
  * flags, making the internal or variable flag list indefinitely long
  */
+
 static bool flag_is_valid(const char *flag)
 {	
 	if (*flag == '\\') {
@@ -293,24 +295,35 @@ static bool flag_is_valid(const char *flag)
 	return TRUE;  
 }
 
+/* Flag iterator */
+
+static void ext_imap4flags_iter_clear
+(struct ext_imap4flags_iter *iter)
+{
+	memset(iter, 0, sizeof(*iter));
+} 
+
 void ext_imap4flags_iter_init
 (struct ext_imap4flags_iter *iter, string_t *flags_list) 
 {
+	ext_imap4flags_iter_clear(iter);
 	iter->flags_list = flags_list;
-	iter->offset = 0;
-	iter->last = 0;
 }
 
-const char *ext_imap4flags_iter_get_flag
+static string_t *ext_imap4flags_iter_get_flag_str
 (struct ext_imap4flags_iter *iter) 
 {
-	unsigned int len = str_len(iter->flags_list);
+	unsigned int len;
 	const unsigned char *fp;
 	const unsigned char *fbegin;
 	const unsigned char *fstart;
 	const unsigned char *fend;
 
+	/* Return if not initialized */
+	if ( iter->flags_list == NULL ) return NULL;
+
 	/* Return if no more flags are available */	
+	len = str_len(iter->flags_list);
 	if ( iter->offset >= len ) return NULL;
 	
 	/* Mark string boundries */
@@ -328,7 +341,8 @@ const char *ext_imap4flags_iter_get_flag
 			/* Did we scan more than nothing ? */
 			if ( fp > fstart ) {
 				/* Return flag */
-				const char *flag = t_strdup_until(fstart, fp);
+				string_t *flag = t_str_new(fp-fstart+1);
+				str_append_n(flag, fstart, fp-fstart);
 				
 				iter->last = fstart - fbegin;
 				iter->offset = fp - fbegin;
@@ -349,6 +363,16 @@ const char *ext_imap4flags_iter_get_flag
 	return NULL;
 }
 
+const char *ext_imap4flags_iter_get_flag
+(struct ext_imap4flags_iter *iter)
+{
+	string_t *flag = ext_imap4flags_iter_get_flag_str(iter);
+
+	if ( flag == NULL ) return NULL;
+
+	return str_c(flag);
+}
+
 static void ext_imap4flags_iter_delete_last
 (struct ext_imap4flags_iter *iter) 
 {
@@ -363,6 +387,8 @@ static void ext_imap4flags_iter_delete_last
 	iter->offset = iter->last;
 }
 
+/* Flag operations */
+
 static bool flags_list_flag_exists
 (string_t *flags_list, const char *flag)
 {
@@ -431,10 +457,6 @@ static void flags_list_set_flags
 	flags_list_add_flags(flags_list, flags);
 }
 
-/* 
- * Flag registration 
- */
-
 int ext_imap4flags_set_flags
 (const struct sieve_runtime_env *renv, struct sieve_variable_storage *storage,
 	unsigned int var_index, string_t *flags)
@@ -489,41 +511,119 @@ int ext_imap4flags_remove_flags
 	return SIEVE_EXEC_OK;
 }
 
-int ext_imap4flags_get_flags_string
-(const struct sieve_runtime_env *renv, struct sieve_variable_storage *storage,
-	unsigned int var_index, const char **flags)
+/* Flag stringlist */
+
+static int ext_imap4flags_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void ext_imap4flags_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+struct ext_imap4flags_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_stringlist *flags_list;
+	string_t *flags_string;
+	struct ext_imap4flags_iter flit;
+
+	unsigned int normalize:1;
+};
+
+static struct sieve_stringlist *ext_imap4flags_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *flags_list,
+	bool normalize)
 {
-	string_t *cur_flags;
-	
-	if ( storage != NULL ) {
-		if ( !sieve_variable_get_modifiable(storage, var_index, &cur_flags) )
-			return SIEVE_EXEC_BIN_CORRUPT;
-	} else
-		cur_flags = _get_flags_string(renv->oprtn->ext, renv->result);
-	
-	if ( cur_flags == NULL )
-		*flags = "";
-	else 
-		*flags = str_c(cur_flags);
+	struct ext_imap4flags_stringlist *strlist;
 
-	return SIEVE_EXEC_OK;
+	strlist = t_new(struct ext_imap4flags_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = ext_imap4flags_stringlist_next_item;
+	strlist->strlist.reset = ext_imap4flags_stringlist_reset;
+	strlist->normalize = normalize;
+
+	strlist->flags_list = flags_list;
+
+	return &strlist->strlist;
 }
 
-void ext_imap4flags_get_flags_init
-(struct ext_imap4flags_iter *iter, const struct sieve_runtime_env *renv, 
-	string_t *flags_list)
+static struct sieve_stringlist *ext_imap4flags_stringlist_create_single
+(const struct sieve_runtime_env *renv, string_t *flags_string, bool normalize)
 {
-	string_t *cur_flags;
-	
-	if ( flags_list != NULL ) {
-		cur_flags = t_str_new(256);
-		
-		flags_list_set_flags(cur_flags, flags_list);
+	struct ext_imap4flags_stringlist *strlist;
+
+	strlist = t_new(struct ext_imap4flags_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = ext_imap4flags_stringlist_next_item;
+	strlist->strlist.reset = ext_imap4flags_stringlist_reset;
+	strlist->normalize = normalize;
+
+	if ( normalize ) {
+		strlist->flags_string = t_str_new(256);
+		flags_list_set_flags(strlist->flags_string, flags_string);
+	} else {
+		strlist->flags_string = flags_string;
 	}
-	else
-		cur_flags = _get_flags_string(renv->oprtn->ext, renv->result);
+
+	ext_imap4flags_iter_init(&strlist->flit, strlist->flags_string);
+
+	return &strlist->strlist;
+}
+
+static int ext_imap4flags_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct ext_imap4flags_stringlist *strlist = 
+		(struct ext_imap4flags_stringlist *)_strlist;
 	
-	ext_imap4flags_iter_init(iter, cur_flags);		
+	while ( (*str_r=ext_imap4flags_iter_get_flag_str(&strlist->flit)) == NULL ) {
+		int ret;
+
+		if ( strlist->flags_list == NULL )
+			return 0;
+
+		if ( (ret=sieve_stringlist_next_item
+			(strlist->flags_list, &strlist->flags_string)) <= 0 )
+			return ret;
+
+		if ( strlist->flags_string == NULL )
+			return -1; 
+
+		if ( strlist->normalize ) {
+			string_t *flags_string = t_str_new(256);
+
+			flags_list_set_flags(flags_string, strlist->flags_string);
+			strlist->flags_string = flags_string;
+		}
+
+		ext_imap4flags_iter_init(&strlist->flit, strlist->flags_string);
+	}
+
+	return 1;
+}
+
+static void ext_imap4flags_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct ext_imap4flags_stringlist *strlist = 
+		(struct ext_imap4flags_stringlist *)_strlist;
+
+	if ( strlist->flags_list != NULL ) {
+		sieve_stringlist_reset(strlist->flags_list);
+		ext_imap4flags_iter_clear(&strlist->flit);
+	} else {
+		ext_imap4flags_iter_init(&strlist->flit, strlist->flags_string);
+	}
+}
+
+/* Flag access */
+
+struct sieve_stringlist *ext_imap4flags_get_flags
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *flags_list)
+{
+	if ( flags_list == NULL )
+		return ext_imap4flags_stringlist_create_single
+			(renv, _get_flags_string(renv->oprtn->ext, renv->result), FALSE);
+
+	return ext_imap4flags_stringlist_create(renv, flags_list, TRUE);
 }
 
 void ext_imap4flags_get_implicit_flags_init
diff --git a/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.h b/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.h
index 2a7dd0ef7..5e4140a36 100644
--- a/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.h
+++ b/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.h
@@ -86,6 +86,8 @@ void ext_imap4flags_iter_init
 const char *ext_imap4flags_iter_get_flag
 	(struct ext_imap4flags_iter *iter);
 
+/* Flag operations */
+
 typedef int (*ext_imapflag_flag_operation_t)
 	(const struct sieve_runtime_env *renv, struct sieve_variable_storage *storage,
 		unsigned int var_index, string_t *flags);
@@ -100,17 +102,11 @@ int ext_imap4flags_remove_flags
 	(const struct sieve_runtime_env *renv, struct sieve_variable_storage *storage,
 		unsigned int var_index, string_t *flags);
 
-/*
- * Flags access
- */
+/* Flags access */
 
-int ext_imap4flags_get_flags_string
-(const struct sieve_runtime_env *renv, struct sieve_variable_storage *storage, 
-	unsigned int var_index, const char **flags);
+struct sieve_stringlist *ext_imap4flags_get_flags
+	(const struct sieve_runtime_env *renv, struct sieve_stringlist *flags_list);
 
-void ext_imap4flags_get_flags_init
-	(struct ext_imap4flags_iter *iter, const struct sieve_runtime_env *renv,
-		string_t *flags_list);
 void ext_imap4flags_get_implicit_flags_init
 	(struct ext_imap4flags_iter *iter, const struct sieve_extension *this_ext,
 		struct sieve_result *result);
diff --git a/src/lib-sieve/plugins/imap4flags/tag-flags.c b/src/lib-sieve/plugins/imap4flags/tag-flags.c
index b7115d4db..2cf116e61 100644
--- a/src/lib-sieve/plugins/imap4flags/tag-flags.c
+++ b/src/lib-sieve/plugins/imap4flags/tag-flags.c
@@ -7,6 +7,7 @@
 #include "mail-storage.h"
 
 #include "sieve-code.h"
+#include "sieve-stringlist.h"
 #include "sieve-extensions.h"
 #include "sieve-commands.h"
 #include "sieve-result.h"
@@ -256,12 +257,12 @@ static bool seff_flags_read_context
 	const struct sieve_runtime_env *renv, sieve_size_t *address,
 	void **se_context)
 {
-	bool result = TRUE;
+	int ret;
 	struct sieve_operand operand;
 	pool_t pool = sieve_result_pool(renv->result);
 	struct seff_flags_context *ctx;
 	string_t *flags_item;
-	struct sieve_coded_stringlist *flag_list;
+	struct sieve_stringlist *flag_list;
 	
 	ctx = p_new(pool, struct seff_flags_context, 1);
 	p_array_init(&ctx->keywords, pool, 2);
@@ -293,8 +294,7 @@ static bool seff_flags_read_context
 	
 	/* Unpack */
 	flags_item = NULL;
-	while ( (result=sieve_coded_stringlist_next_item(flag_list, &flags_item)) && 
-		flags_item != NULL ) {
+	while ( (ret=sieve_stringlist_next_item(flag_list, &flags_item)) > 0 ) {
 		const char *flag;
 		struct ext_imap4flags_iter flit;
 
@@ -328,7 +328,7 @@ static bool seff_flags_read_context
 
 	t_pop();
 	
-	return result;
+	return ( ret >= 0 );
 }
 
 /* Result verification */
diff --git a/src/lib-sieve/plugins/imap4flags/tst-hasflag.c b/src/lib-sieve/plugins/imap4flags/tst-hasflag.c
index fd370e913..b6ea02ed5 100644
--- a/src/lib-sieve/plugins/imap4flags/tst-hasflag.c
+++ b/src/lib-sieve/plugins/imap4flags/tst-hasflag.c
@@ -4,6 +4,7 @@
 #include "lib.h"
 
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-comparators.h"
 #include "sieve-match-types.h"
@@ -170,52 +171,17 @@ static bool tst_hasflag_operation_dump
 /*
  * Interpretation
  */
- 
-static int _flag_key_extract_init
-(void **context, string_t *raw_key)
-{
-	struct ext_imap4flags_iter *iter = t_new(struct ext_imap4flags_iter, 1);
-	
-	ext_imap4flags_iter_init(iter, raw_key);
-	
-	*context = iter; 
-	
-	return TRUE;
-}
-
-static int _flag_key_extract
-(void *context, const char **key, size_t *size)
-{
-	struct ext_imap4flags_iter *iter = (struct ext_imap4flags_iter *) context;
-	
-	if ( (*key = ext_imap4flags_iter_get_flag(iter)) != NULL ) {
-		*size = strlen(*key); 
-		return TRUE;
-	}
-	
-	return FALSE;
-}
-
-static const struct sieve_match_key_extractor _flag_extractor = {
-	_flag_key_extract_init,
-	_flag_key_extract
-};
 
 static int tst_hasflag_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
-	int mret;
-	bool result = TRUE;
 	int opt_code = 0;
 	struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
-	struct sieve_match_type mtch = 
+	struct sieve_match_type mcht = 
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
-	struct sieve_match_context *mctx;
-	struct sieve_coded_stringlist *flag_list, *variables_list = NULL;
-	struct ext_imap4flags_iter iter;
-	const char *flag;
-	bool matched;
+	struct sieve_stringlist *flag_list, *variables_list, *value_list, *key_list;
+	int ret;
 	
 	/*
 	 * Read operands
@@ -223,12 +189,12 @@ static int tst_hasflag_operation_execute
 
 	/* Optional operands */
 
+	variables_list = NULL;
 	for (;;) {
 		bool opok = TRUE;
-		int ret;
 
 		if ( (ret=sieve_match_opr_optional_read
-			(renv, address, &opt_code, &cmp, &mtch)) < 0 )
+			(renv, address, &opt_code, &cmp, &mcht)) < 0 )
 			return SIEVE_EXEC_BIN_CORRUPT;
 
 		if ( ret == 0 ) break;
@@ -259,50 +225,19 @@ static int tst_hasflag_operation_execute
 
 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "hasflag test");
 
-	matched = FALSE;
-	mctx = sieve_match_begin
-		(renv, &mtch, &cmp, &_flag_extractor, flag_list); 	
-
-	matched = FALSE;
+	value_list = ext_imap4flags_get_flags(renv, variables_list);
 
-	if ( variables_list != NULL ) {
-		string_t *var_item = NULL;
-		
-		/* Iterate through all requested variables to match */
-		while ( result && !matched && 
-			(result=sieve_coded_stringlist_next_item(variables_list, &var_item)) 
-			&& var_item != NULL ) {
-		
-			ext_imap4flags_get_flags_init(&iter, renv, var_item);	
-			while ( !matched && (flag=ext_imap4flags_iter_get_flag(&iter)) != NULL ) {
-				if ( (mret=sieve_match_value(mctx, flag, strlen(flag))) < 0 ) {
-					result = FALSE;
-					break;
-				}
-
-				matched = ( mret > 0 ); 	
-			}
-		}
-	} else {
-		ext_imap4flags_get_flags_init(&iter, renv, NULL);	
-		while ( !matched && (flag=ext_imap4flags_iter_get_flag(&iter)) != NULL ) {
-			if ( (mret=sieve_match_value(mctx, flag, strlen(flag))) < 0 ) {
-				result = FALSE;
-				break;
-			}
-
-			matched = ( mret > 0 ); 	
-		}
-	}
+	if ( sieve_match_type_is(&mcht, is_match_type) ||
+		sieve_match_type_is(&mcht, contains_match_type) )
+		key_list = ext_imap4flags_get_flags(renv, flag_list);
+	else
+		key_list = flag_list;
 
-	if ( (mret=sieve_match_end(&mctx)) < 0 ) {
-		result = FALSE;
-	} else
-		matched = ( mret > 0 || matched ); 	
+	ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
 	
 	/* Assign test result */
-	if ( result ) {
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
 	}
 	
diff --git a/src/lib-sieve/plugins/mailbox/tst-mailboxexists.c b/src/lib-sieve/plugins/mailbox/tst-mailboxexists.c
index 9c57204c1..aead5b0d5 100644
--- a/src/lib-sieve/plugins/mailbox/tst-mailboxexists.c
+++ b/src/lib-sieve/plugins/mailbox/tst-mailboxexists.c
@@ -7,6 +7,7 @@
 
 #include "sieve-common.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-validator.h"
 #include "sieve-generator.h"
@@ -105,9 +106,9 @@ static bool tst_mailboxexists_operation_dump
 static int tst_mailboxexists_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
-	struct sieve_coded_stringlist *mailbox_names;
+	struct sieve_stringlist *mailbox_names;
 	string_t *mailbox_item;
-	bool result = TRUE, all_exist = TRUE;
+	bool all_exist = TRUE;
 
 	/*
 	 * Read operands 
@@ -125,10 +126,11 @@ static int tst_mailboxexists_operation_execute
 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "mailboxexists test");
 
 	if ( renv->scriptenv->user != NULL ) {
+		int ret;
+
 		mailbox_item = NULL;
-		while ( (result=sieve_coded_stringlist_next_item
-			(mailbox_names, &mailbox_item)) 
-			&& mailbox_item != NULL ) {
+		while ( (ret=sieve_stringlist_next_item(mailbox_names, &mailbox_item)) > 0 ) 
+			{
 			struct mail_namespace *ns;
 			const char *mailbox = str_c(mailbox_item);
 			struct mailbox *box;
@@ -157,11 +159,11 @@ static int tst_mailboxexists_operation_execute
 			/* Close mailbox */
 			mailbox_free(&box);
 		}
-	}
 	
-	if ( !result ) {
-		sieve_runtime_trace_error(renv, "invalid mailbox name item");
-		return SIEVE_EXEC_BIN_CORRUPT;
+		if ( ret < 0 ) {
+			sieve_runtime_trace_error(renv, "invalid mailbox name item");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}
 	}
 	
 	sieve_interpreter_set_test_result(renv->interp, all_exist);
diff --git a/src/lib-sieve/plugins/notify/cmd-denotify.c b/src/lib-sieve/plugins/notify/cmd-denotify.c
index 82c1baa0e..0a920006d 100644
--- a/src/lib-sieve/plugins/notify/cmd-denotify.c
+++ b/src/lib-sieve/plugins/notify/cmd-denotify.c
@@ -4,6 +4,7 @@
 #include "lib.h"
 
 #include "sieve-common.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-extensions.h"
 #include "sieve-ast.h"
@@ -279,7 +280,7 @@ static int cmd_denotify_operation_execute
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
 	const struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
-    struct sieve_coded_stringlist *match_key = NULL;
+	struct sieve_stringlist *match_key = NULL;
 	sieve_number_t importance = 0;
 	struct sieve_match_context *mctx;
 	struct sieve_result_iterate_context *rictx;
@@ -339,7 +340,7 @@ static int cmd_denotify_operation_execute
 	if ( match_key != NULL ) { 	
 
 		/* Initialize match */
-    	mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, match_key);
+		mctx = sieve_match_begin(renv, &mcht, &cmp);
 
 		/* Iterate through all actions */
 		rictx = sieve_result_iterate_init(renv->result);
@@ -351,8 +352,8 @@ static int cmd_denotify_operation_execute
 					(struct ext_notify_action *) action->context;
 	
 				if ( importance == 0 || nact->importance == importance ) {
-					if ( (ret=sieve_match_value(mctx, nact->id, strlen(nact->id)))
-						< 0 ) {
+					if ( (ret=sieve_match_value
+						(mctx, nact->id, strlen(nact->id), match_key)) < 0 ) {
 						result = FALSE;
 						break;
 					}
@@ -364,12 +365,11 @@ static int cmd_denotify_operation_execute
 		}
 	
 		/* Finish match */
-		if ( sieve_match_end(&mctx) < 0 )
-			result = FALSE;
+		(void)sieve_match_end(&mctx);
 
-	    if ( !result ) {
-		    sieve_runtime_trace_error(renv, "invalid string-list item");
-    		return SIEVE_EXEC_BIN_CORRUPT;
+		if ( !result ) {
+			sieve_runtime_trace_error(renv, "invalid string-list item");
+			return SIEVE_EXEC_BIN_CORRUPT;
 		}
 	} else {
 		/* Iterate through all actions */
diff --git a/src/lib-sieve/plugins/notify/cmd-notify.c b/src/lib-sieve/plugins/notify/cmd-notify.c
index 813d07d23..e0e03ba76 100644
--- a/src/lib-sieve/plugins/notify/cmd-notify.c
+++ b/src/lib-sieve/plugins/notify/cmd-notify.c
@@ -12,6 +12,7 @@
 #include "rfc2822.h"
 
 #include "sieve-common.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-extensions.h"
 #include "sieve-commands.h"
@@ -417,7 +418,7 @@ static int cmd_notify_operation_execute
 	pool_t pool;
 	int opt_code = 0;
 	sieve_number_t importance = 1;
-	struct sieve_coded_stringlist *options = NULL;
+	struct sieve_stringlist *options = NULL;
 	string_t *message = NULL, *id = NULL; 
 	unsigned int source_line;
 
@@ -481,7 +482,7 @@ static int cmd_notify_operation_execute
 	if ( options != NULL ) {
 		string_t *raw_address;
 		string_t *out_message;
-		bool result;	
+		int ret;
 
 		pool = sieve_result_pool(renv->result);
 		act = p_new(pool, struct ext_notify_action, 1);
@@ -498,13 +499,12 @@ static int cmd_notify_operation_execute
 		
 		/* Normalize and verify all :options addresses */					
 
-		sieve_coded_stringlist_reset(options);
+		sieve_stringlist_reset(options);
 			
 		p_array_init(&act->recipients, pool, 4);
 		
 		raw_address = NULL;
-		while ( (result=sieve_coded_stringlist_next_item(options, &raw_address))
-			&& raw_address != NULL ) {
+		while ( (ret=sieve_stringlist_next_item(options, &raw_address)) > 0 ) {
 			const char *error = NULL;
 			const char *addr_norm = sieve_address_normalize(raw_address, &error);
 			
@@ -557,7 +557,7 @@ static int cmd_notify_operation_execute
 			}
 		}
 		
-		if ( !result ) {
+		if ( ret < 0 ) {
 			sieve_runtime_trace_error(renv, "invalid options stringlist");
 			return SIEVE_EXEC_BIN_CORRUPT;
 		}
diff --git a/src/lib-sieve/plugins/regex/mcht-regex.c b/src/lib-sieve/plugins/regex/mcht-regex.c
index 2fda6940a..793edcbf3 100644
--- a/src/lib-sieve/plugins/regex/mcht-regex.c
+++ b/src/lib-sieve/plugins/regex/mcht-regex.c
@@ -9,10 +9,12 @@
 #include "buffer.h"
 #include "array.h"
 #include "str.h"
+#include "str-sanitize.h"
 
 #include "sieve-common.h"
 #include "sieve-limits.h"
 #include "sieve-ast.h"
+#include "sieve-stringlist.h"
 #include "sieve-commands.h"
 #include "sieve-validator.h"
 #include "sieve-comparators.h"
@@ -40,18 +42,19 @@ static bool mcht_regex_validate_context
     struct sieve_match_type_context *ctx, struct sieve_ast_argument *key_arg);
 
 static void mcht_regex_match_init(struct sieve_match_context *mctx);
-static int mcht_regex_match
+static int mcht_regex_match_keys
 	(struct sieve_match_context *mctx, const char *val, size_t val_size,
-    	const char *key, size_t key_size, int key_index);
-static int mcht_regex_match_deinit(struct sieve_match_context *mctx);
+    struct sieve_stringlist *key_list);
+static void mcht_regex_match_deinit(struct sieve_match_context *mctx);
 
 const struct sieve_match_type_def regex_match_type = {
 	SIEVE_OBJECT("regex", &regex_match_type_operand, 0),
-	TRUE, FALSE,
 	NULL,
 	mcht_regex_validate_context,
+	NULL,
 	mcht_regex_match_init,
-	mcht_regex_match,
+	mcht_regex_match_keys,
+	NULL, 
 	mcht_regex_match_deinit
 };
 
@@ -170,11 +173,16 @@ static bool mcht_regex_validate_context
  * Match type implementation 
  */
 
+struct mcht_regex_key {
+	regex_t regexp;
+	int status;
+};
+
 struct mcht_regex_context {
-	ARRAY_DEFINE(reg_expressions, regex_t);
-	int value_index;
+	ARRAY_DEFINE(reg_expressions, struct mcht_regex_key);
 	regmatch_t *pmatch;
 	size_t nmatch;
+	unsigned int all_compiled:1;
 };
 
 static void mcht_regex_match_init
@@ -185,8 +193,6 @@ static void mcht_regex_match_init
 
 	/* Create context */	
 	ctx = p_new(pool, struct mcht_regex_context, 1);
-	p_array_init(&ctx->reg_expressions, pool, 4);
-	ctx->value_index = -1;
 
 	/* Create storage for match values if match values are requested */
 	if ( sieve_match_values_are_enabled(mctx->runenv) ) {
@@ -201,72 +207,20 @@ static void mcht_regex_match_init
 	mctx->data = (void *) ctx;
 }
 
-static regex_t *mcht_regex_get
-(struct mcht_regex_context *ctx,
-	const struct sieve_comparator *cmp, 
-	const char *key, unsigned int key_index)
-{
-	int ret;
-	int cflags;
-	regex_t *regexp;
-	
-	/* If this is the first matched value, the regexes are not compiled
-	 * yet.
-	 */
-	if ( ctx->value_index <= 0 ) {
-		/* Allocate space */
-		array_idx_clear(&ctx->reg_expressions, key_index);
-		regexp = array_idx_modifiable(&ctx->reg_expressions, key_index);
-
-		/* Configure case-sensitivity according to comparator */
-		if ( sieve_comparator_is(cmp, i_octet_comparator) ) 
-			cflags =  REG_EXTENDED;
-		else if ( sieve_comparator_is(cmp, i_ascii_casemap_comparator) )
-			cflags =  REG_EXTENDED | REG_ICASE;
-		else
-			return NULL; /* Not supported */
-			
-		/* Indicate whether match values need to be produced */
-		if ( ctx->nmatch == 0 ) cflags |= REG_NOSUB;
-
-		/* Compile regular expression */
-		if ( (ret=regcomp(regexp, key, cflags)) != 0 ) {
-			/* FIXME: Do something useful, i.e. report error somewhere */
-			return NULL;
-		}
-	} else {
-		/* Get compiled regex from cache */
-		regexp = array_idx_modifiable(&ctx->reg_expressions, key_index);
-	}
-
-	return regexp;
-}
-
-static int mcht_regex_match
-(struct sieve_match_context *mctx, 
-	const char *val, size_t val_size ATTR_UNUSED, 
-	const char *key, size_t key_size ATTR_UNUSED, int key_index)
+static int mcht_regex_match_key
+(struct sieve_match_context *mctx, const char *val,
+	const regex_t *regexp)
 {
 	struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
-	regex_t *regexp;
-
-	if ( val == NULL ) {
-		val = "";
-		val_size = 0;
-	}
-
-	if ( key_index < 0 ) return FALSE;
+	int ret;
 
-	if ( key_index == 0 ) ctx->value_index++;
+	/* Execute regex */
 
-	/* Get compiled regex */
-	if ( (regexp=mcht_regex_get(ctx, mctx->comparator, key, key_index)) == NULL )
-		return FALSE;
+	ret = regexec(regexp, val, ctx->nmatch, ctx->pmatch, 0);
 
-	/* Execute regex */
-	if ( regexec(regexp, val, ctx->nmatch, ctx->pmatch, 0) == 0 ) {
+	/* Handle match values if necessary */
 
-		/* Handle match values if necessary */
+	if ( ret == 0 ) {
 		if ( ctx->nmatch > 0 ) {
 			struct sieve_match_values *mvalues;
 			size_t i;
@@ -281,13 +235,13 @@ static int mcht_regex_match
 			/* Add match values from regular expression */
 			for ( i = 0; i < ctx->nmatch; i++ ) {
 				str_truncate(subst, 0);
-			
+	
 				if ( ctx->pmatch[i].rm_so != -1 ) {
 					if ( skipped > 0 ) {
 						sieve_match_values_skip(mvalues, skipped);
 						skipped = 0;
 					}
-					
+			
 					str_append_n(subst, val + ctx->pmatch[i].rm_so, 
 						ctx->pmatch[i].rm_eo - ctx->pmatch[i].rm_so);
 					sieve_match_values_add(mvalues, subst);
@@ -299,25 +253,124 @@ static int mcht_regex_match
 			sieve_match_values_commit(mctx->runenv, &mvalues);
 		}
 
-		return TRUE;
+		return 1;
 	}
-	
-	return FALSE;
+
+	return 0;
 }
 
-int mcht_regex_match_deinit
+static int mcht_regex_match_keys
+(struct sieve_match_context *mctx, const char *val, size_t val_size ATTR_UNUSED, 
+	struct sieve_stringlist *key_list)
+{
+	const struct sieve_runtime_env *renv = mctx->runenv;
+	bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+	struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
+	const struct sieve_comparator *cmp = mctx->comparator;
+	int result = 0;
+
+	if ( !ctx->all_compiled ) {
+		string_t *key_item = NULL;
+		unsigned int i;
+		int ret;
+
+		/* Regular expressions still need to be compiled */
+
+		if ( !array_is_created(&ctx->reg_expressions) )
+			p_array_init(&ctx->reg_expressions, mctx->pool, 16);
+
+		i = 0;
+		while ( result == 0 &&
+			(ret=sieve_stringlist_next_item(key_list, &key_item)) > 0 ) {				
+
+			T_BEGIN {
+				struct mcht_regex_key *rkey;
+
+				if ( i >= array_count(&ctx->reg_expressions) ) {
+					int cflags;
+
+					rkey = array_append_space(&ctx->reg_expressions);
+
+					/* Configure case-sensitivity according to comparator */
+					if ( sieve_comparator_is(cmp, i_octet_comparator) ) 
+						cflags =  REG_EXTENDED;
+					else if ( sieve_comparator_is(cmp, i_ascii_casemap_comparator) )
+						cflags =  REG_EXTENDED | REG_ICASE;
+					else
+						rkey->status = -1; /* Not supported */
+		
+					if ( rkey->status >= 0 ) {
+						/* Indicate whether match values need to be produced */
+						if ( ctx->nmatch == 0 ) cflags |= REG_NOSUB;
+
+						/* Compile regular expression */
+						if ( regcomp(&rkey->regexp, str_c(key_item), cflags) != 0 ) {
+							/* FIXME: Do something useful, i.e. report error somewhere */
+							rkey->status = -1;
+						} else {
+							rkey->status = 1;
+						}
+					}
+				} else {
+					rkey = array_idx_modifiable(&ctx->reg_expressions, 1);
+				} 
+
+				if ( rkey->status > 0 ) {
+					result = mcht_regex_match_key(mctx, val, &rkey->regexp);
+
+					if ( trace ) {
+						sieve_runtime_trace(renv, 0,
+							"    with regex `%s' [id=%d] => %d", 
+							str_sanitize(str_c(key_item), 80),
+							array_count(&ctx->reg_expressions)-1, result);
+					}
+				}
+			} T_END;
+
+			i++;
+		}
+
+		if ( ret < 0 ) result = -1;
+
+	} else {
+		const struct mcht_regex_key *rkeys;
+		unsigned int i, count;
+
+		/* Regular expressions are compiled */
+
+		rkeys = array_get(&ctx->reg_expressions, &count);
+
+		i = 0; 
+		while ( result == 0 && i < count ) {
+			if ( rkeys[i].status > 0 ) {
+				result = mcht_regex_match_key(mctx, val, &rkeys[i].regexp);
+
+				if ( trace ) {
+					sieve_runtime_trace(renv, 0,
+						"    with compiled regex [id=%d] => %d", i, result);
+				}
+			}
+
+			i++;
+		}
+
+		if ( i == count ) ctx->all_compiled = TRUE;
+	}
+
+	return result;
+}
+
+void mcht_regex_match_deinit
 (struct sieve_match_context *mctx)
 {
 	struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
-	regex_t *regexps;
+	struct mcht_regex_key *rkeys;
 	unsigned int count, i;
 
 	/* Clean up compiled regular expressions */
-	regexps = array_get_modifiable(&ctx->reg_expressions, &count);
+	rkeys = array_get_modifiable(&ctx->reg_expressions, &count);
 	for ( i = 0; i < count; i++ ) {
-		regfree(&regexps[i]);
+		regfree(&rkeys[i].regexp);
 	}
-
-	return FALSE;
 }
 
diff --git a/src/lib-sieve/plugins/relational/ext-relational-common.h b/src/lib-sieve/plugins/relational/ext-relational-common.h
index 396c00230..17c03cb9d 100644
--- a/src/lib-sieve/plugins/relational/ext-relational-common.h
+++ b/src/lib-sieve/plugins/relational/ext-relational-common.h
@@ -85,9 +85,9 @@ bool mcht_relational_validate
  * Value match function (also used by :count)
  */
  
-int mcht_value_match
-    (struct sieve_match_context *mctx, const char *val, size_t val_size,
-        const char *key, size_t key_size, int key_index);
+int mcht_value_match_key
+	(struct sieve_match_context *mctx, const char *val, size_t val_size,
+		const char *key, size_t key_size);
 
 
 #endif /* __EXT_RELATIONAL_COMMON_H */
diff --git a/src/lib-sieve/plugins/relational/mcht-count.c b/src/lib-sieve/plugins/relational/mcht-count.c
index 27e50b263..9ec75274b 100644
--- a/src/lib-sieve/plugins/relational/mcht-count.c
+++ b/src/lib-sieve/plugins/relational/mcht-count.c
@@ -6,9 +6,11 @@
 
 #include "lib.h"
 #include "str.h"
+#include "str-sanitize.h"
 
 #include "sieve-common.h"
 #include "sieve-ast.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-extensions.h"
 #include "sieve-commands.h"
@@ -17,6 +19,7 @@
 #include "sieve-validator.h"
 #include "sieve-generator.h"
 #include "sieve-interpreter.h"
+#include "sieve-runtime-trace.h"
 #include "sieve-match.h"
 
 #include "ext-relational-common.h"
@@ -25,11 +28,9 @@
  * Forward declarations
  */
 
-static void mcht_count_match_init(struct sieve_match_context *mctx);
 static int mcht_count_match
-	(struct sieve_match_context *mctx, const char *val, size_t val_size, 
-		const char *key, size_t key_size, int key_index);
-static int mcht_count_match_deinit(struct sieve_match_context *mctx);
+	(struct sieve_match_context *mctx, struct sieve_stringlist *value_list,
+		struct sieve_stringlist *key_list);
 
 /* 
  * Match-type objects
@@ -37,21 +38,18 @@ static int mcht_count_match_deinit(struct sieve_match_context *mctx);
  
 const struct sieve_match_type_def count_match_type = {
 	SIEVE_OBJECT("count", &rel_match_type_operand, RELATIONAL_COUNT),
-	FALSE, FALSE,
 	mcht_relational_validate,
-	NULL, NULL, NULL, NULL
+	NULL, NULL, NULL, NULL, NULL, NULL
 };
 
-#define COUNT_MATCH_TYPE(name, rel_match)                     \
-const struct sieve_match_type_def rel_match_count_ ## name = {    \
-	SIEVE_OBJECT(                                             \
-		"count-" #name, &rel_match_type_operand,              \
-		REL_MATCH_INDEX(RELATIONAL_COUNT, rel_match)),        \
-	FALSE, FALSE,                                             \
-	NULL, NULL,                                               \
-	mcht_count_match_init,                                    \
-	mcht_count_match,                                         \
-	mcht_count_match_deinit                                   \
+#define COUNT_MATCH_TYPE(name, rel_match)                      \
+const struct sieve_match_type_def rel_match_count_ ## name = { \
+	SIEVE_OBJECT(                                                \
+		"count-" #name, &rel_match_type_operand,                   \
+		REL_MATCH_INDEX(RELATIONAL_COUNT, rel_match)),             \
+	NULL, NULL,                                                  \
+	mcht_count_match,                                            \
+	NULL, NULL, NULL, NULL                                       \
 }
 	
 COUNT_MATCH_TYPE(gt, REL_MATCH_GREATER);
@@ -65,72 +63,48 @@ COUNT_MATCH_TYPE(ne, REL_MATCH_NOT_EQUAL);
  * Match-type implementation 
  */
 
-struct mcht_count_context {
-	unsigned int count;
-};
-
-static void mcht_count_match_init(struct sieve_match_context *mctx)
-{
-	struct mcht_count_context *cctx = p_new(mctx->pool, struct mcht_count_context, 1);
-
-	cctx->count = 0;
-	mctx->data = (void *) cctx;
-}
-
 static int mcht_count_match
-(struct sieve_match_context *mctx, 
-	const char *val ATTR_UNUSED, size_t val_size ATTR_UNUSED, 
-	const char *key ATTR_UNUSED, size_t key_size ATTR_UNUSED,
-	int key_index) 
+(struct sieve_match_context *mctx, struct sieve_stringlist *value_list,
+	struct sieve_stringlist *key_list)
 {
-	if ( val == NULL )
-		return FALSE;
-
-	/* Count values */
-	if ( key_index == -1 ) {
-		struct mcht_count_context *cctx = 
-			(struct mcht_count_context *) mctx->data;
-
-		cctx->count++;
-	}
+	const struct sieve_runtime_env *renv = mctx->runenv;
+	bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+	int count;
+	string_t *key_item;
+	int ret;
 
-	return FALSE;
-}
+	if ( (count=sieve_stringlist_get_length(value_list)) < 0 )
+		return -1;
 
-static int mcht_count_match_deinit(struct sieve_match_context *mctx)
-{
-	struct mcht_count_context *cctx =
-            (struct mcht_count_context *) mctx->data;
-	int key_index;
-	string_t *key_item;
-    sieve_coded_stringlist_reset(mctx->key_list);
-	bool ok = TRUE;
+	sieve_stringlist_reset(key_list);
 
 	string_t *value = t_str_new(20);
-	str_printfa(value, "%d", cctx->count);
-
-    /* Match to all key values */
-    key_index = 0;
-    key_item = NULL;
-    while ( (ok=sieve_coded_stringlist_next_item(mctx->key_list, &key_item)) 
-		&& key_item != NULL )
-    {
-		int ret = mcht_value_match
-			(mctx, str_c(value), str_len(value), str_c(key_item), 
-				str_len(key_item), key_index);
-        
-		if ( ret > 0 )   
-			return TRUE;
-	
-		if ( ret < 0 )
-			return ret;
+	str_printfa(value, "%d", count);
 
-        key_index++;
-    }
+	if ( trace ) {
+		sieve_runtime_trace(renv, 0,
+			"  matching count value `%s'", str_sanitize(str_c(value), 80));
+	}
+
+  /* Match to all key values */
+  key_item = NULL;
+	ret = 0;
+  while ( ret == 0 && 
+		(ret=sieve_stringlist_next_item(key_list, &key_item)) > 0 )
+  {
+		ret = mcht_value_match_key
+			(mctx, str_c(value), str_len(value), str_c(key_item), str_len(key_item));
+
+		if ( trace ) {
+			sieve_runtime_trace(renv, 0,
+				"    with key `%s' => %d", str_sanitize(str_c(key_item), 80), ret);
+		}
+	}
 
-	return ( ok ? FALSE : -1 );
+	return ret;
 }
 
 
 
 
+
diff --git a/src/lib-sieve/plugins/relational/mcht-value.c b/src/lib-sieve/plugins/relational/mcht-value.c
index 625efb42a..fac3de9e0 100644
--- a/src/lib-sieve/plugins/relational/mcht-value.c
+++ b/src/lib-sieve/plugins/relational/mcht-value.c
@@ -25,20 +25,18 @@
 
 const struct sieve_match_type_def value_match_type = {
 	SIEVE_OBJECT("value", &rel_match_type_operand, RELATIONAL_VALUE), 
-	TRUE, TRUE,
 	mcht_relational_validate,
-	NULL, NULL, NULL, NULL
+	NULL, NULL, NULL, NULL, NULL, NULL
 };
 
-#define VALUE_MATCH_TYPE(name, rel_match)                   \
+#define VALUE_MATCH_TYPE(name, rel_match)                       \
 const struct sieve_match_type_def rel_match_value_ ## name = {  \
-	SIEVE_OBJECT(                                           \
-		"value-" #name, &rel_match_type_operand,            \
-		REL_MATCH_INDEX(RELATIONAL_VALUE, rel_match)),      \
-	TRUE, TRUE,                                             \
-	NULL, NULL, NULL,                                       \
-	mcht_value_match,                                       \
-	NULL                                                    \
+	SIEVE_OBJECT(                                                 \
+		"value-" #name, &rel_match_type_operand,                    \
+		REL_MATCH_INDEX(RELATIONAL_VALUE, rel_match)),              \
+	NULL, NULL, NULL, NULL, NULL,                                 \
+	mcht_value_match_key,                                         \
+	NULL                                                          \
 }
 
 VALUE_MATCH_TYPE(gt, REL_MATCH_GREATER);
@@ -52,19 +50,14 @@ VALUE_MATCH_TYPE(ne, REL_MATCH_NOT_EQUAL);
  * Match-type implementation 
  */
 
-int mcht_value_match
+int mcht_value_match_key
 (struct sieve_match_context *mctx, const char *val, size_t val_size, 
-	const char *key, size_t key_size, int key_index ATTR_UNUSED)
+	const char *key, size_t key_size)
 {
 	const struct sieve_match_type *mtch = mctx->match_type;
 	unsigned int rel_match = REL_MATCH(mtch->object.def->code);	
 	int cmp_result;
 
-	if ( val == NULL ) {
-		val = "";
-		val_size = 0;
-	}
-
 	cmp_result = mctx->comparator->def->
 		compare(mctx->comparator, val, val_size, key, key_size);
 
diff --git a/src/lib-sieve/plugins/spamvirustest/tst-spamvirustest.c b/src/lib-sieve/plugins/spamvirustest/tst-spamvirustest.c
index a93b5d224..cda0a3e3a 100644
--- a/src/lib-sieve/plugins/spamvirustest/tst-spamvirustest.c
+++ b/src/lib-sieve/plugins/spamvirustest/tst-spamvirustest.c
@@ -6,6 +6,7 @@
 #include "sieve-common.h"
 #include "sieve-extensions.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-comparators.h"
 #include "sieve-match-types.h"
@@ -241,16 +242,14 @@ static int tst_spamvirustest_operation_execute
 {	
 	const struct sieve_operation *op = renv->oprtn;
 	const struct sieve_extension *this_ext = op->ext;
-	bool result = TRUE, matched = FALSE;
 	int opt_code = 0;
 	struct sieve_match_type mcht = 
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
 	struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
 	bool percent = FALSE;
-	struct sieve_coded_stringlist *key_value;
-	struct sieve_match_context *mctx;
-	const char *value;
+	struct sieve_stringlist *value_list, *key_list;
+	const char *score_value;
 	int ret;
 	
 	/* Read optional operands */
@@ -274,7 +273,7 @@ static int tst_spamvirustest_operation_execute
 	}
 
 	/* Read value part */
-	if ( (key_value=sieve_opr_stringlist_read(renv, address, "value")) == NULL )
+	if ( (key_list=sieve_opr_stringlist_read(renv, address, "value")) == NULL )
 		return SIEVE_EXEC_BIN_CORRUPT;
 			
 	/* Perform test */
@@ -288,30 +287,18 @@ static int tst_spamvirustest_operation_execute
 			(renv, SIEVE_TRLVL_TESTS, "virustest test");
 	}
 
-	/* Initialize match */
-	mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, key_value); 	
+	/* Get score value */
+	score_value = ext_spamvirustest_get_value(renv, this_ext, percent);
 
-	/* Perform match */
-
-	matched = FALSE;
-
-	value = ext_spamvirustest_get_value(renv, this_ext, percent);
-
-	if ( (ret=sieve_match_value(mctx, value, strlen(value))) < 0 ) {
-		result = FALSE;
-	} else {
-		matched = ( ret > 0 );				
-	}
+	/* Construct value list */
+	value_list = sieve_single_stringlist_create_cstr(renv, score_value, TRUE);
 
-	/* Finish match */
-	if ( (ret=sieve_match_end(&mctx)) < 0 ) 
-		result = FALSE;
-	else
-		matched = ( ret > 0 || matched );
+	/* Perform match */
+	ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
 
 	/* Set test result for subsequent conditional jump */
-	if ( result ) {
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
 	}	
 
diff --git a/src/lib-sieve/plugins/vacation/cmd-vacation.c b/src/lib-sieve/plugins/vacation/cmd-vacation.c
index 6152eaf0c..01aaab836 100644
--- a/src/lib-sieve/plugins/vacation/cmd-vacation.c
+++ b/src/lib-sieve/plugins/vacation/cmd-vacation.c
@@ -14,6 +14,7 @@
 #include "rfc2822.h"
 
 #include "sieve-common.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-address.h"
 #include "sieve-extensions.h"
@@ -541,7 +542,7 @@ static int ext_vacation_operation_execute
 	int opt_code = 0;
 	sieve_number_t days = 7;
 	bool mime = FALSE;
-	struct sieve_coded_stringlist *addresses = NULL;
+	struct sieve_stringlist *addresses = NULL;
 	string_t *reason, *subject = NULL, *from = NULL, *handle = NULL; 
 	unsigned int source_line;
 	const char *from_normalized = NULL;
@@ -643,15 +644,14 @@ static int ext_vacation_operation_execute
 	if ( addresses != NULL ) {
 		ARRAY_DEFINE(norm_addresses, const char *);
 		string_t *raw_address;
-		bool result = FALSE;
-		
-		sieve_coded_stringlist_reset(addresses);
+		int ret;
+
+		sieve_stringlist_reset(addresses);
 			
 		p_array_init(&norm_addresses, pool, 4);
 		
 		raw_address = NULL;
-		while ( (result=sieve_coded_stringlist_next_item(addresses, &raw_address))
-			&& raw_address != NULL ) {
+		while ( (ret=sieve_stringlist_next_item(addresses, &raw_address)) > 0 ) {
 			const char *error;
 			const char *addr_norm = sieve_address_normalize(raw_address, &error);
 			
@@ -664,7 +664,7 @@ static int ext_vacation_operation_execute
 			}
 		}
 		
-		if ( !result ) {
+		if ( ret < 0 ) {
 			sieve_runtime_trace_error(renv, "invalid addresses stringlist");
 			return SIEVE_EXEC_BIN_CORRUPT;
 		}
diff --git a/src/lib-sieve/plugins/variables/tst-string.c b/src/lib-sieve/plugins/variables/tst-string.c
index 9e8977feb..b4184f1ec 100644
--- a/src/lib-sieve/plugins/variables/tst-string.c
+++ b/src/lib-sieve/plugins/variables/tst-string.c
@@ -3,6 +3,7 @@
 
 #include "sieve-common.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-comparators.h"
 #include "sieve-match-types.h"
@@ -160,21 +161,79 @@ static bool tst_string_operation_dump
  * Code execution 
  */
 
+static int tst_string_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void tst_string_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int tst_string_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+
+struct tst_string_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_stringlist *value_list;
+};
+
+static struct sieve_stringlist *tst_string_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *value_list)
+{
+	struct tst_string_stringlist *strlist;
+
+	strlist = t_new(struct tst_string_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = tst_string_stringlist_next_item;
+	strlist->strlist.reset = tst_string_stringlist_reset;
+	strlist->strlist.get_length = tst_string_stringlist_get_length;
+	strlist->value_list = value_list;
+
+	return &strlist->strlist;
+}
+
+static int tst_string_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct tst_string_stringlist *strlist = 
+		(struct tst_string_stringlist *)_strlist;
+
+	return sieve_stringlist_next_item(strlist->value_list, str_r);
+}
+
+static void tst_string_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct tst_string_stringlist *strlist = 
+		(struct tst_string_stringlist *)_strlist;
+
+	sieve_stringlist_reset(strlist->value_list);
+}
+
+static int tst_string_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+	struct tst_string_stringlist *strlist = 
+		(struct tst_string_stringlist *)_strlist;
+	string_t *item;
+	int length = 0;
+	int ret;
+
+	while ( (ret=sieve_stringlist_next_item(strlist->value_list, &item)) > 0 ) {
+		if ( str_len(item) > 0 )
+			length++;
+	}
+
+	return ( ret < 0 ? -1 : length );
+}
+
 static int tst_string_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
-	int ret, mret;
-	bool result = TRUE;
+	int ret;
 	int opt_code = 0;
 	struct sieve_match_type mcht = 
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
 	struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
-	struct sieve_match_context *mctx;
-	struct sieve_coded_stringlist *source;
-	struct sieve_coded_stringlist *key_list;
-	string_t *src_item;
-	bool matched;
+	struct sieve_stringlist *source, *value_list, *key_list;
 
 	/*
 	 * Read operands 
@@ -205,35 +264,18 @@ static int tst_string_operation_execute
 
 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "string test");
 
-	mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, key_list); 	
-
-	/* Iterate through all requested strings to match */
-	src_item = NULL;
-	matched = FALSE;
-	while ( result && !matched && 
-		(result=sieve_coded_stringlist_next_item(source, &src_item)) 
-		&& src_item != NULL ) {
-		const char *src = str_len(src_item) > 0 ? str_c(src_item) : NULL;
-
-		if ( (mret=sieve_match_value
-			(mctx, src, str_len(src_item))) < 0 ) {
-			result = FALSE;
-			break;
-		}
-		
-		matched = ( mret > 0 );				
-	}
+	/* Create wrapper string list wich does not count empty string items */
+	value_list = tst_string_stringlist_create(renv, source);
 
-	if ( (mret=sieve_match_end(&mctx)) < 0 ) 
-		result = FALSE;
-	else
-		matched = ( mret > 0 || matched ); 	
+	/* Perform match */
+	ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
 	
-	if ( result ) {
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	/* Set test result for subsequent conditional jump */
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
-	}
-	
-	sieve_runtime_trace_error(renv, "invalid string list item");
+	}	
+
+	sieve_runtime_trace_error(renv, "invalid string-list item");
 	return SIEVE_EXEC_BIN_CORRUPT;
 }
diff --git a/src/lib-sieve/sieve-address-parts.c b/src/lib-sieve/sieve-address-parts.c
index 9cae114ab..540faf4ff 100644
--- a/src/lib-sieve/sieve-address-parts.c
+++ b/src/lib-sieve/sieve-address-parts.c
@@ -6,7 +6,6 @@
 #include "mempool.h"
 #include "hash.h"
 #include "array.h"
-#include "message-address.h"
 #include "str-sanitize.h"
 
 #include "sieve-extensions.h"
@@ -228,67 +227,94 @@ const struct sieve_operand_def address_part_operand = {
 };
 
 /*
- * Address Matching
+ * Address-part string list
  */
- 
-int sieve_address_match
-(const struct sieve_address_part *addrp, struct sieve_match_context *mctx, 		
-	const char *data)
+
+static int sieve_address_part_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_address_part_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int sieve_address_part_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+
+struct sieve_address_part_stringlist {
+	struct sieve_stringlist strlist;
+
+	const struct sieve_address_part *addrp;
+	struct sieve_address_list *addresses;
+};
+
+struct sieve_stringlist *sieve_address_part_stringlist_create
+(const struct sieve_runtime_env *renv, const struct sieve_address_part *addrp,
+	struct sieve_address_list *addresses)
 {
-	const struct sieve_runtime_env *renv = mctx->runenv;
-	int result = FALSE;
-	const struct message_address *addr;
-
-	sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
-            "  matching addresses `%s'", str_sanitize(data, 80));
-
-	T_BEGIN {
-		bool valid = TRUE;
-		const struct message_address *aitem;
-
-		addr = message_address_parse
-			(pool_datastack_create(), (const unsigned char *) data, 
-				strlen(data), 256, FALSE);
-
-		/* Check validity of all addresses simultaneously. Unfortunately,
-		 * errorneous addresses cannot be extracted from the address list
-		 * and therefore :all will match against the whole header value
-		 * which is not entirely standard.
-		 */
-		aitem = addr;
-		while ( aitem != NULL) {
-			if ( aitem->invalid_syntax )
-				valid = FALSE;
-			aitem = aitem->next;
-		}
+	struct sieve_address_part_stringlist *strlist;
 
-		if ( !valid || addr == NULL ) {
-			if ( sieve_address_part_is(addrp, all_address_part) )
-				result = sieve_match_value(mctx, data, strlen(data));
-			else 
-				result = FALSE;
-		} else {
-			while ( result == 0 && addr != NULL) {
-				/* mailbox@domain */
-				struct sieve_address address;
-				const char *part = NULL;
-			
-				if ( addr->domain != NULL ) {
-					address.local_part = addr->mailbox;
-					address.domain = addr->domain;
-	
-					if ( addrp->def != NULL && addrp->def->extract_from ) 
-						part = addrp->def->extract_from(addrp, &address);
+	strlist = t_new(struct sieve_address_part_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = sieve_address_part_stringlist_next_item;
+	strlist->strlist.reset = sieve_address_part_stringlist_reset;
+	strlist->strlist.get_length = sieve_address_part_stringlist_get_length;
+
+	strlist->addrp = addrp;
+	strlist->addresses = addresses;
+
+	return &strlist->strlist;
+}
+
+static int sieve_address_part_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_address_part_stringlist *strlist = 
+		(struct sieve_address_part_stringlist *)_strlist;
+	struct sieve_address item;
+	string_t *item_unparsed;
+	int ret;
+
+	*str_r = NULL;
 
-					if ( part != NULL )
-						result = sieve_match_value(mctx, part, strlen(part));
-				}
-				addr = addr->next;
+	while ( *str_r == NULL ) {
+		if ( (ret=sieve_address_list_next_item
+			(strlist->addresses, &item, &item_unparsed)) <= 0 )
+			return ret;
+		
+		if ( item.local_part == NULL ) {
+			if ( item_unparsed != NULL ) {
+				if ( str_len(item_unparsed) == 0 ||
+					sieve_address_part_is(strlist->addrp, all_address_part) )
+					*str_r = item_unparsed;
 			}
+		} else {
+			const struct sieve_address_part *addrp = strlist->addrp;
+			const char *part = NULL;
+
+			if ( addrp->def != NULL && addrp->def->extract_from ) 
+				part = addrp->def->extract_from(addrp, &item);
+
+			if ( part != NULL )
+				*str_r = t_str_new_const(part, strlen(part));
 		}
-	} T_END;
-	
-	return result;
+	}
+		
+	return 1;
+}
+
+static void sieve_address_part_stringlist_reset
+	(struct sieve_stringlist *_strlist)
+{
+	struct sieve_address_part_stringlist *strlist = 
+		(struct sieve_address_part_stringlist *)_strlist;
+
+	sieve_address_list_reset(strlist->addresses);
+}
+
+static int sieve_address_part_stringlist_get_length
+	(struct sieve_stringlist *_strlist)
+{
+	struct sieve_address_part_stringlist *strlist = 
+		(struct sieve_address_part_stringlist *)_strlist;
+
+	return sieve_address_list_get_length(strlist->addresses);
 }
 
 /* 
diff --git a/src/lib-sieve/sieve-address-parts.h b/src/lib-sieve/sieve-address-parts.h
index 3df79c380..c72bb1a81 100644
--- a/src/lib-sieve/sieve-address-parts.h
+++ b/src/lib-sieve/sieve-address-parts.h
@@ -106,14 +106,18 @@ static inline bool sieve_opr_address_part_dump
 		(denv, &sieve_address_part_operand_class, address, NULL);
 }
 
+/*
+ * Address-part string list
+ */
+
+struct sieve_stringlist *sieve_address_part_stringlist_create
+	(const struct sieve_runtime_env *renv, const struct sieve_address_part *addrp,
+		struct sieve_address_list *addresses);
+
 /* 
  * Match utility 
  */
 
-int sieve_address_match
-(const struct sieve_address_part *addrp, struct sieve_match_context *mctx,
-    const char *data);
-
 enum sieve_addrmatch_opt_operand {
 	SIEVE_AM_OPT_END,
 	SIEVE_AM_OPT_COMPARATOR,
diff --git a/src/lib-sieve/sieve-address.c b/src/lib-sieve/sieve-address.c
index 4121ac3b7..ca7b70d59 100644
--- a/src/lib-sieve/sieve-address.c
+++ b/src/lib-sieve/sieve-address.c
@@ -5,12 +5,150 @@
 #include "str.h"
 #include "str-sanitize.h"
 #include "rfc822-parser.h"
+#include "message-address.h"
 
 #include "sieve-common.h"
+
 #include "sieve-address.h"
 
 #include <ctype.h>
 
+/*
+ * Header address list
+ */
+
+/* Forward declarations */
+
+static int sieve_header_address_list_next_string_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static int sieve_header_address_list_next_item
+	(struct sieve_address_list *_addrlist, struct sieve_address *addr_r, 
+		string_t **unparsed_r);
+static void sieve_header_address_list_reset
+	(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct sieve_header_address_list {
+	struct sieve_address_list addrlist;
+
+	struct sieve_stringlist *field_values;
+	const struct message_address *cur_address;
+};
+
+struct sieve_address_list *sieve_header_address_list_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values)
+{
+	struct sieve_header_address_list *addrlist;
+	    
+	addrlist = t_new(struct sieve_header_address_list, 1);
+	addrlist->addrlist.strlist.runenv = renv;
+	addrlist->addrlist.strlist.next_item = 
+		sieve_header_address_list_next_string_item;
+	addrlist->addrlist.strlist.reset = sieve_header_address_list_reset;
+	addrlist->addrlist.next_item = sieve_header_address_list_next_item;
+	addrlist->field_values = field_values;
+  
+	return &addrlist->addrlist;
+}
+
+static int sieve_header_address_list_next_item
+(struct sieve_address_list *_addrlist, struct sieve_address *addr_r, 
+	string_t **unparsed_r)
+{
+	struct sieve_header_address_list *addrlist = 
+		(struct sieve_header_address_list *) _addrlist;	
+	const struct message_address *aitem;
+	bool valid = TRUE;
+
+	if ( addr_r != NULL ) addr_r->local_part = NULL;
+	if ( unparsed_r != NULL ) *unparsed_r = NULL;
+
+	/* Parse next header field value if necessary */
+	while ( addrlist->cur_address == NULL ) {
+		string_t *value_item = NULL;
+		int ret;
+
+		/* Read next header value from source list */
+		if ( (ret=sieve_stringlist_next_item(addrlist->field_values, &value_item)) 
+			<= 0 )
+			return ret;
+
+		addrlist->cur_address = message_address_parse
+			(pool_datastack_create(), (const unsigned char *) str_data(value_item), 
+				str_len(value_item), 256, FALSE);
+
+		/* Check validity of all addresses simultaneously. Unfortunately,
+		 * errorneous addresses cannot be extracted from the address list.
+		 */
+		aitem = addrlist->cur_address;
+		while ( aitem != NULL) {
+			if ( aitem->invalid_syntax )
+				valid = FALSE;
+			aitem = aitem->next;
+		}
+
+		if ( addrlist->cur_address == NULL || !valid ) {
+			addrlist->cur_address = NULL;
+
+			if ( unparsed_r != NULL) *unparsed_r = value_item;
+			return 1;
+		}
+
+		/* Find first usable address */
+		aitem = addrlist->cur_address;
+		while ( aitem != NULL && aitem->domain == NULL ) {
+			aitem = aitem->next;
+		}
+
+		addrlist->cur_address = aitem;
+	}
+
+	/* Return next item */
+
+	if ( addr_r != NULL ) {
+		addr_r->local_part = addrlist->cur_address->mailbox;
+		addr_r->domain = addrlist->cur_address->domain;
+	}
+
+	/* Find next usable address */
+	aitem = addrlist->cur_address->next;
+	while ( aitem != NULL && aitem->domain == NULL ) {
+		aitem = aitem->next;
+	}
+	addrlist->cur_address = aitem;
+
+	return 1;
+}
+
+static int sieve_header_address_list_next_string_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_address_list *addrlist = (struct sieve_address_list *)_strlist;
+	struct sieve_address addr;
+	int ret;
+
+	if ( (ret=sieve_header_address_list_next_item(addrlist, &addr, str_r)) <= 0 )
+		return ret;
+
+	if ( addr.local_part != NULL ) {
+		const char *addr_str = sieve_address_to_string(&addr);
+		*str_r = t_str_new_const(addr_str, strlen(addr_str));
+	}
+
+	return 1;
+}
+
+static void sieve_header_address_list_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_header_address_list *addrlist = 
+		(struct sieve_header_address_list *)_strlist;
+
+	sieve_stringlist_reset(addrlist->field_values);
+	addrlist->cur_address = NULL;
+}
+
 /*
  * RFC 2822 addresses
  */
diff --git a/src/lib-sieve/sieve-address.h b/src/lib-sieve/sieve-address.h
index 830ef81c3..9ae4c2cc9 100644
--- a/src/lib-sieve/sieve-address.h
+++ b/src/lib-sieve/sieve-address.h
@@ -7,6 +7,9 @@
 #include "lib.h"
 #include "strfuncs.h"
 
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+
 /*
  * Generic address representation
  */ 
@@ -16,14 +19,54 @@ struct sieve_address {
 	const char *domain;
 };
 
-static inline const char *sieve_address_to_string(const struct sieve_address *address) 
+static inline const char *sieve_address_to_string
+(const struct sieve_address *address) 
+{
+	if ( address == NULL || address->local_part == NULL ||
+		address->domain == NULL )
+		return NULL;
+
+	return t_strconcat(address->local_part, "@", address->domain, NULL);
+}
+
+/*
+ * Address list API
+ */
+
+struct sieve_address_list {
+	struct sieve_stringlist strlist;
+
+	int (*next_item)
+		(struct sieve_address_list *_addrlist, struct sieve_address *addr_r, 
+			string_t **unparsed_r);
+};
+
+static inline int sieve_address_list_next_item
+(struct sieve_address_list *addrlist, struct sieve_address *addr_r, 
+	string_t **unparsed_r)
 {
-    if ( address == NULL || address->local_part == NULL || address->domain == NULL )
-        return NULL;
+	return addrlist->next_item(addrlist, addr_r, unparsed_r);
+}
+
+static inline void sieve_address_list_reset
+(struct sieve_address_list *addrlist) 
+{
+	return sieve_stringlist_reset(&addrlist->strlist);
+}
 
-    return t_strconcat(address->local_part, "@", address->domain, NULL);
+static inline int sieve_address_list_get_length
+(struct sieve_address_list *addrlist)
+{
+	return sieve_stringlist_get_length(&addrlist->strlist);
 }
 
+/*
+ * Header address list
+ */
+
+struct sieve_address_list *sieve_header_address_list_create
+	(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values);
+
 /* 
  * RFC 2822 addresses
  */ 
diff --git a/src/lib-sieve/sieve-code.c b/src/lib-sieve/sieve-code.c
index bccc4caa1..320a49f88 100644
--- a/src/lib-sieve/sieve-code.c
+++ b/src/lib-sieve/sieve-code.c
@@ -8,6 +8,7 @@
 #include "sieve-common.h"
 #include "sieve-limits.h"
 #include "sieve-extensions.h"
+#include "sieve-stringlist.h"
 #include "sieve-actions.h"
 #include "sieve-binary.h"
 #include "sieve-generator.h"
@@ -18,111 +19,99 @@
 
 #include <stdio.h>
 
-/* 		return opr_r->def != NULL;
- * Coded stringlist
+/* 
+ * Code stringlist
  */
 
-struct sieve_coded_stringlist {
-	const struct sieve_runtime_env *runenv;
+/* Forward declarations */
+
+static int sieve_code_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_code_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int sieve_code_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+
+/* Coded stringlist object */
+
+struct sieve_code_stringlist {
+	struct sieve_stringlist strlist;
+
 	sieve_size_t start_address;
 	sieve_size_t end_address;
 	sieve_size_t current_offset;
-	unsigned int length;
-	unsigned int index;
+	int length;
+	int index;
 };
 
-static struct sieve_coded_stringlist *sieve_coded_stringlist_create
+static struct sieve_stringlist *sieve_code_stringlist_create
 (const struct sieve_runtime_env *renv, 
 	 sieve_size_t start_address, unsigned int length, sieve_size_t end)
 {
-	struct sieve_coded_stringlist *strlist;
+	struct sieve_code_stringlist *strlist;
 	
 	if ( end > sieve_binary_block_get_size(renv->sblock) ) 
   		return NULL;
     
-	strlist = t_new(struct sieve_coded_stringlist, 1);
-	strlist->runenv = renv;
+	strlist = t_new(struct sieve_code_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = sieve_code_stringlist_next_item;
+	strlist->strlist.reset = sieve_code_stringlist_reset;
+	strlist->strlist.get_length = sieve_code_stringlist_get_length;
 	strlist->start_address = start_address;
 	strlist->current_offset = start_address;
 	strlist->end_address = end;
 	strlist->length = length;
 	strlist->index = 0;
   
-	return strlist;
+	return &strlist->strlist;
 }
 
-bool sieve_coded_stringlist_next_item
-(struct sieve_coded_stringlist *strlist, string_t **str_r) 
+/* Stringlist implementation */
+
+static int sieve_code_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r) 
 {
+	struct sieve_code_stringlist *strlist =
+		(struct sieve_code_stringlist *) _strlist;
 	sieve_size_t address;
 	*str_r = NULL;
   
+	/* Check for end of list */
 	if ( strlist->index >= strlist->length ) 
-		return TRUE;
-	else {
-		address = strlist->current_offset;
-  	
-		if ( sieve_opr_string_read(strlist->runenv, &address, NULL, str_r) ) {
-			strlist->index++;
-			strlist->current_offset = address;
-			return TRUE;
-		}
-	}  
+		return 0;
+
+	/* Read next item */
+	address = strlist->current_offset;	
+	if ( sieve_opr_string_read(_strlist->runenv, &address, NULL, str_r) ) {
+		strlist->index++;
+		strlist->current_offset = address;
+		return 1;
+	}
   
-	return FALSE;
-}
-
-void sieve_coded_stringlist_reset(struct sieve_coded_stringlist *strlist) 
-{  
-	strlist->current_offset = strlist->start_address;
-	strlist->index = 0;
-}
-
-unsigned int sieve_coded_stringlist_get_length
-(struct sieve_coded_stringlist *strlist)
-{
-	return strlist->length;
+	return -1;
 }
 
-sieve_size_t sieve_coded_stringlist_get_end_address
-(struct sieve_coded_stringlist *strlist)
+static void sieve_code_stringlist_reset
+(struct sieve_stringlist *_strlist) 
 {
-	return strlist->end_address;
-}
+	struct sieve_code_stringlist *strlist =
+		(struct sieve_code_stringlist *) _strlist;
 
-sieve_size_t sieve_coded_stringlist_get_current_offset
-(struct sieve_coded_stringlist *strlist)
-{
-	return strlist->current_offset;
+	strlist->current_offset = strlist->start_address;
+	strlist->index = 0;
 }
 
-bool sieve_coded_stringlist_read_all
-(struct sieve_coded_stringlist *strlist, pool_t pool,
-	const char * const **list_r)
+static int sieve_code_stringlist_get_length
+(struct sieve_stringlist *_strlist)
 {
-	bool result = FALSE;
-	ARRAY_DEFINE(items, const char *);
-	string_t *item;
-	
-	sieve_coded_stringlist_reset(strlist);
-	
-	p_array_init(&items, pool, 4);
-	
-	item = NULL;
-	while ( (result=sieve_coded_stringlist_next_item(strlist, &item)) && 
-		item != NULL ) {
-		const char *stritem = p_strdup(pool, str_c(item));
-		
-		array_append(&items, &stritem, 1);
-	}
-	
-	(void)array_append_space(&items);
-	*list_r = array_idx(&items, 0);
+	struct sieve_code_stringlist *strlist =
+		(struct sieve_code_stringlist *) _strlist;
 
-	return result;
+	return strlist->length;
 }
 
-static bool sieve_coded_stringlist_dump
+static bool sieve_code_stringlist_dump
 (const struct sieve_dumptime_env *denv, sieve_size_t *address, 
 	unsigned int length, sieve_size_t end, const char *field_name)
 {
@@ -328,7 +317,7 @@ const struct sieve_operand_def string_operand = {
 static bool opr_stringlist_dump
 	(const struct sieve_dumptime_env *denv, sieve_size_t *address,
 		const char *field_name);
-static struct sieve_coded_stringlist *opr_stringlist_read
+static struct sieve_stringlist *opr_stringlist_read
 	(const struct sieve_runtime_env *renv, sieve_size_t *address);
 
 const struct sieve_opr_stringlist_interface stringlist_interface = { 
@@ -715,7 +704,7 @@ bool sieve_opr_stringlist_dump
 	return sieve_opr_stringlist_dump_data(denv, &operand, address, field_name);
 }
 
-struct sieve_coded_stringlist *sieve_opr_stringlist_read_data
+struct sieve_stringlist *sieve_opr_stringlist_read_data
 (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
 	sieve_size_t *address, const char *field_name)
 {
@@ -725,7 +714,7 @@ struct sieve_coded_stringlist *sieve_opr_stringlist_read_data
 	if ( oprnd->def->class == &stringlist_class ) {
 		const struct sieve_opr_stringlist_interface *intf = 
 			(const struct sieve_opr_stringlist_interface *) oprnd->def->interface;
-		struct sieve_coded_stringlist *strlist;
+		struct sieve_stringlist *strlist;
 			
 		if ( intf->read == NULL ) 
 			return NULL;
@@ -748,7 +737,7 @@ struct sieve_coded_stringlist *sieve_opr_stringlist_read_data
 			return NULL;
 		}
 		
-		return sieve_coded_stringlist_create(renv, oprnd->address, 1, *address); 
+		return sieve_code_stringlist_create(renv, oprnd->address, 1, *address); 
 	}	
 
 	sieve_runtime_trace_operand_error(renv, oprnd, field_name,
@@ -757,7 +746,7 @@ struct sieve_coded_stringlist *sieve_opr_stringlist_read_data
 	return NULL;
 }
 
-struct sieve_coded_stringlist *sieve_opr_stringlist_read
+struct sieve_stringlist *sieve_opr_stringlist_read
 (const struct sieve_runtime_env *renv, sieve_size_t *address, 
 	const char *field_name)
 {
@@ -786,13 +775,13 @@ static bool opr_stringlist_dump
 	if ( !sieve_binary_read_unsigned(denv->sblock, address, &length) ) 
 		return FALSE;	
   	
-	return sieve_coded_stringlist_dump(denv, address, length, end, field_name); 
+	return sieve_code_stringlist_dump(denv, address, length, end, field_name); 
 }
 
-static struct sieve_coded_stringlist *opr_stringlist_read
+static struct sieve_stringlist *opr_stringlist_read
 (const struct sieve_runtime_env *renv, sieve_size_t *address )
 {
-	struct sieve_coded_stringlist *strlist;
+	struct sieve_stringlist *strlist;
 	sieve_size_t pc = *address;
 	sieve_size_t end; 
 	unsigned int length = 0;  
@@ -806,7 +795,8 @@ static struct sieve_coded_stringlist *opr_stringlist_read
 	if ( !sieve_binary_read_unsigned(renv->sblock, address, &length) ) 
 	  	return NULL;	
   	
-	strlist = sieve_coded_stringlist_create(renv, *address, (unsigned int) length, end); 
+	strlist = sieve_code_stringlist_create
+		(renv, *address, (unsigned int) length, end); 
 
 	/* Skip over the string list for now */
 	*address = end;
diff --git a/src/lib-sieve/sieve-code.h b/src/lib-sieve/sieve-code.h
index a350ecc5e..7f8ce700e 100644
--- a/src/lib-sieve/sieve-code.h
+++ b/src/lib-sieve/sieve-code.h
@@ -14,27 +14,6 @@
 #include "sieve-runtime-trace.h"
 #include "sieve-dump.h"
 
-/* 
- * Coded string list 
- */
-
-struct sieve_coded_stringlist;
-
-bool sieve_coded_stringlist_next_item
-	(struct sieve_coded_stringlist *strlist, string_t **str_r);
-void sieve_coded_stringlist_reset
-	(struct sieve_coded_stringlist *strlist);
-bool sieve_coded_stringlist_read_all
-	(struct sieve_coded_stringlist *strlist, pool_t pool,
-		const char * const **list_r);
-
-unsigned int sieve_coded_stringlist_get_length
-	(struct sieve_coded_stringlist *strlist);
-sieve_size_t sieve_coded_stringlist_get_end_address
-	(struct sieve_coded_stringlist *strlist);
-sieve_size_t sieve_coded_stringlist_get_current_offset
-	(struct sieve_coded_stringlist *strlist);
-
 /* 
  * Operand object
  */
@@ -178,7 +157,7 @@ struct sieve_opr_stringlist_interface {
 	bool (*dump)
 		(const struct sieve_dumptime_env *denv, sieve_size_t *address,
 			const char *field_name);
-	struct sieve_coded_stringlist *(*read)
+	struct sieve_stringlist *(*read)
 		(const struct sieve_runtime_env *renv, sieve_size_t *address);
 };
 
@@ -266,10 +245,10 @@ bool sieve_opr_stringlist_dump_data
 bool sieve_opr_stringlist_dump
 	(const struct sieve_dumptime_env *denv, sieve_size_t *address,
 		const char *field_name);
-struct sieve_coded_stringlist *sieve_opr_stringlist_read_data
+struct sieve_stringlist *sieve_opr_stringlist_read_data
 	(const struct sieve_runtime_env *renv, const struct sieve_operand *operand, 
 		sieve_size_t *address, const char *field_name);
-struct sieve_coded_stringlist *sieve_opr_stringlist_read
+struct sieve_stringlist *sieve_opr_stringlist_read
 	(const struct sieve_runtime_env *renv, sieve_size_t *address,
 		const char *field_name);
 
diff --git a/src/lib-sieve/sieve-common.h b/src/lib-sieve/sieve-common.h
index a2edd81ab..260d46110 100644
--- a/src/lib-sieve/sieve-common.h
+++ b/src/lib-sieve/sieve-common.h
@@ -44,6 +44,9 @@ struct sieve_command_def;
 struct sieve_command_context;
 struct sieve_command_registration;
 
+/* sieve-stringlist.h */
+struct sieve_stringlist;
+
 /* sieve-code.h */
 struct sieve_operation_extension;
 
@@ -107,6 +110,7 @@ struct sieve_match_context;
 
 /* sieve-address.h */
 struct sieve_address;
+struct sieve_address_list;
 
 /* sieve-address-parts.h */
 struct sieve_address_part_def;
diff --git a/src/lib-sieve/sieve-match-types.h b/src/lib-sieve/sieve-match-types.h
index dd040fb56..2a0e68f3c 100644
--- a/src/lib-sieve/sieve-match-types.h
+++ b/src/lib-sieve/sieve-match-types.h
@@ -37,16 +37,6 @@ extern const struct sieve_match_type_def matches_match_type;
  
 struct sieve_match_type_def {
 	struct sieve_object_def obj_def;
-
-	/* Match function called for every key value or should it be called once
-	 * for every tested value? (TRUE = first alternative)
-	 */
-	bool is_iterative;
-	
-	/* Is the key value allowed to contain formatting to extract multiple keys
-	 * out of the same string?
-	 */
-	bool allow_key_extract;
 		
 	bool (*validate)
 		(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, 
@@ -59,18 +49,24 @@ struct sieve_match_type_def {
 	 * Matching
  	 */
 
-	void (*match_init)(struct sieve_match_context *mctx);
+	/* Custom implementation */
 
-	/* WARNING: some tests may pass a val == NULL parameter indicating that the 
-	 * passed value has no significance. For string-type matches this should map 
-	 * to the empty string "", but for match types that consider the passed values 
-	 * as objects rather than strings (e.g. :count) this means that the passed 
-	 * value should be skipped. 
-	 */
 	int (*match)
+		(struct sieve_match_context *mctx, struct sieve_stringlist *value_list,
+			struct sieve_stringlist *key_list);
+
+	/* Default match loop */ 
+
+	void (*match_init)(struct sieve_match_context *mctx);
+
+	int (*match_keys)
+		(struct sieve_match_context *mctx, const char *val, size_t val_size, 
+			struct sieve_stringlist *key_list);
+	int (*match_key)
 		(struct sieve_match_context *mctx, const char *val, size_t val_size, 
-			const char *key, size_t key_size, int key_index);
-	int (*match_deinit)(struct sieve_match_context *mctx);
+			const char *key, size_t key_size);
+
+	void (*match_deinit)(struct sieve_match_context *mctx);
 };
 
 /* 
@@ -88,6 +84,8 @@ struct sieve_match_type {
 
 #define sieve_match_type_name(mcht) \
 	( (mcht)->object.def->identifier )
+#define sieve_match_type_is(mcht, definition) \
+	( (mcht)->def == &(definition) ) 
 
 static inline const struct sieve_match_type *sieve_match_type_copy
 (pool_t pool, const struct sieve_match_type *cmp_orig)
diff --git a/src/lib-sieve/sieve-match.c b/src/lib-sieve/sieve-match.c
index 19e5c7520..b0b2acda2 100644
--- a/src/lib-sieve/sieve-match.c
+++ b/src/lib-sieve/sieve-match.c
@@ -9,6 +9,7 @@
 
 #include "sieve-extensions.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-binary.h"
 #include "sieve-validator.h"
@@ -26,28 +27,35 @@
  */
 
 struct sieve_match_context *sieve_match_begin
-(const struct sieve_runtime_env *renv, const struct sieve_match_type *mcht, 
-	const struct sieve_comparator *cmp, 
-	const struct sieve_match_key_extractor *kextract,
-	struct sieve_coded_stringlist *key_list)
+(const struct sieve_runtime_env *renv,
+	const struct sieve_match_type *mcht, 
+	const struct sieve_comparator *cmp)
 {
 	struct sieve_match_context *mctx;
 	pool_t pool;
 
+	/* Reject unimplemented match-type */
+	if ( mcht->def == NULL || (mcht->def->match == NULL && 
+			mcht->def->match_keys == NULL && mcht->def->match_key == NULL) )
+			return NULL;
+
+	/* Create match context */
 	pool = pool_alloconly_create("sieve_match_context", 1024);
 	mctx = p_new(pool, struct sieve_match_context, 1);  
-
 	mctx->pool = pool;
 	mctx->runenv = renv;
 	mctx->match_type = mcht;
 	mctx->comparator = cmp;
-	mctx->kextract = kextract;
-	mctx->key_list = key_list;
+	mctx->trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
 
-	sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
-		"  starting `:%s' match with `%s' comparator:", sieve_match_type_name(mcht),
-		sieve_comparator_name(cmp));
+	/* Trace */
+	if ( mctx->trace ) {
+		sieve_runtime_trace(renv, 0,
+			"  starting `:%s' match with `%s' comparator:",
+			sieve_match_type_name(mcht), sieve_comparator_name(cmp));
+	}
 
+	/* Initialize match type */
 	if ( mcht->def != NULL && mcht->def->match_init != NULL ) {
 		mcht->def->match_init(mctx);
 	}
@@ -56,107 +64,110 @@ struct sieve_match_context *sieve_match_begin
 }
 
 int sieve_match_value
-(struct sieve_match_context *mctx, const char *value, size_t val_size)
+(struct sieve_match_context *mctx, const char *value, size_t value_size,
+	struct sieve_stringlist *key_list)
 {
-	const struct sieve_runtime_env *renv = mctx->runenv;
 	const struct sieve_match_type *mcht = mctx->match_type;
-	sieve_coded_stringlist_reset(mctx->key_list);
-	bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
-	bool ok = TRUE;
-	int ret = 0;
-
-	/* Reject unimplemented match-type */
-	if ( mcht->def == NULL || mcht->def->match == NULL ) {
-		mctx->status = FALSE;
-		return FALSE;
-	}
+	const struct sieve_runtime_env *renv = mctx->runenv;
+	int result = 0;
 
-	if ( trace ) {
+	if ( mctx->trace ) {
 		sieve_runtime_trace(renv, 0,
 			"  matching value `%s'", str_sanitize(value, 80));
 	}
 
-	/* Match to all key values */
-	if ( mcht->def->is_iterative ) {
-		unsigned int key_index = 0;
-		string_t *key_item = NULL;
+	/* Match to key values */
 	
-		while ( (ok=sieve_coded_stringlist_next_item(mctx->key_list, &key_item)) 
-			&& key_item != NULL ) {				
+	sieve_stringlist_reset(key_list);
+
+	if ( mcht->def->match_keys != NULL ) {
+		/* Call match-type's own key match handler */
+		result = mcht->def->match_keys(mctx, value, value_size, key_list);
+	} else {
+		string_t *key_item = NULL;
+		int ret;
+
+		/* Default key match loop */
+		while ( result == 0 && 
+			(ret=sieve_stringlist_next_item(key_list, &key_item)) > 0 ) {				
 			T_BEGIN {
-				if ( mctx->kextract != NULL && mcht->def->allow_key_extract ) {
-					const struct sieve_match_key_extractor *kext = mctx->kextract;
-					void *kctx;
-				
-					if ( (ret=kext->init(&kctx, key_item)) > 0 ) {
-						const char *key;
-						size_t key_size;
-					 			
-						while ( (ret=kext->extract_key(kctx, &key, &key_size)) > 0 ) {				
-							ret = mcht->def->match
-								(mctx, value, val_size, key, key_size, key_index);
-						
-							if ( trace ) {
-								sieve_runtime_trace(renv, 0,
-									"    with key `%s' => %d", str_sanitize(key, 80), ret);
-							}
-
-							if ( ret != 0 ) break;
-						}
-					}  
-				} else {
-					ret = mcht->def->match(mctx, value, val_size, str_c(key_item), 
-							str_len(key_item), key_index);
-
-					if ( trace ) {
-						sieve_runtime_trace(renv, 0,
-							"    with key `%s' => %d", str_sanitize(str_c(key_item), 80), ret);
-					}
+				result = mcht->def->match_key
+					(mctx, value, value_size, str_c(key_item), str_len(key_item));
+
+				if ( mctx->trace ) {
+					sieve_runtime_trace(renv, 0,
+						"    with key `%s' => %d", str_sanitize(str_c(key_item), 80),
+						result);
 				}
 			} T_END;
-			
-			if ( ret != 0 )
-				break;
-	
-			key_index++;
 		}
 
-		if ( !ok ) ret = -1;
-
-	} else {
-		T_BEGIN {
-			ret = mcht->def->match(mctx, value, val_size, NULL, 0, -1);
-		} T_END;
+		if ( ret < 0 ) result = -1;
 	}
 
-	mctx->status = ret;
-	return ret;
+	if ( mctx->status < 0 || result < 0 )
+		mctx->status = -1;
+	else 
+		mctx->status = ( mctx->status > result ? mctx->status : result );
+	return result;
 }
 
 int sieve_match_end(struct sieve_match_context **mctx)
 {
-	const struct sieve_runtime_env *renv = (*mctx)->runenv;
 	const struct sieve_match_type *mcht = (*mctx)->match_type;
-	int status = (*mctx)->status;
-	int ret = FALSE;
+	const struct sieve_runtime_env *renv = (*mctx)->runenv;
+	int result = (*mctx)->status;
 
-	if ( mcht->def != NULL && mcht->def->match_deinit != NULL ) {
-		ret = mcht->def->match_deinit(*mctx);
-	}
+	if ( mcht->def != NULL && mcht->def->match_deinit != NULL )
+		mcht->def->match_deinit(*mctx);
 
 	pool_unref(&(*mctx)->pool);
-	*mctx = NULL;
-
-	if ( ret < 0 || status < 0 )
-		status = ( ret <= status ? ret : status );
-	else
-		status = status || ret;
 
 	sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
 		"  finishing match with result: %s", 
-		( status > 0 ? "true" : ( status < 0 ? "error" : "false" ) ));
+		( result > 0 ? "matched" : ( result < 0 ? "error" : "not matched" ) ));
+
+	return result;
+}
+
+int sieve_match
+(const struct sieve_runtime_env *renv,
+	const struct sieve_match_type *mcht, 
+	const struct sieve_comparator *cmp, 
+	struct sieve_stringlist *value_list,
+	struct sieve_stringlist *key_list)
+{
+	struct sieve_match_context *mctx;
+	string_t *value_item = NULL;
+	int result, ret;
+
+	if ( (mctx=sieve_match_begin(renv, mcht, cmp)) == NULL )
+		return 0;
+
+	/* Match value to keys */
+
+	sieve_stringlist_reset(value_list);
+
+	if ( mcht->def->match != NULL ) {
+		/* Call match-type's match handler */
+		result = mcht->def->match(mctx, value_list, key_list); 
+
+	} else {
+		/* Default value match loop */
+
+		result = 0;
+		while ( result == 0 && 
+			(ret=sieve_stringlist_next_item(value_list, &value_item)) > 0 ) {
+
+			result = sieve_match_value
+				(mctx, str_c(value_item), str_len(value_item), key_list);
+		}
+
+		if ( ret < 0 ) result = -1;
+	}
 
-	return ret;
+	(void)sieve_match_end(&mctx);
+	return result;
 }
 
 /*
diff --git a/src/lib-sieve/sieve-match.h b/src/lib-sieve/sieve-match.h
index ed3c1d79c..e32bae1eb 100644
--- a/src/lib-sieve/sieve-match.h
+++ b/src/lib-sieve/sieve-match.h
@@ -9,40 +9,43 @@
 /*
  * Matching context
  */
- 
-struct sieve_match_key_extractor {
-	int (*init)(void **context, string_t *raw_key);
-	int (*extract_key)(void *context, const char **key, size_t *size);
-};
 
 struct sieve_match_context {
 	pool_t pool;
 
 	const struct sieve_runtime_env *runenv;
+
 	const struct sieve_match_type *match_type;
 	const struct sieve_comparator *comparator;
-	const struct sieve_match_key_extractor *kextract;
 
-	struct sieve_coded_stringlist *key_list;
+	void *data;
 
 	int status;
-	void *data;
+	unsigned int trace:1;
 };
 
 /*
  * Matching implementation
  */
 
+/* Manual value iteration (for when multiple matches are allowed) */
 struct sieve_match_context *sieve_match_begin
 	(const struct sieve_runtime_env *renv,
-		const struct sieve_match_type *mtch, 
-		const struct sieve_comparator *cmp, 
-		const struct sieve_match_key_extractor *kextract,
-		struct sieve_coded_stringlist *key_list);
+		const struct sieve_match_type *mcht, 
+		const struct sieve_comparator *cmp);
 int sieve_match_value
-	(struct sieve_match_context *mctx, const char *value, size_t val_size);
+	(struct sieve_match_context *mctx, const char *value, size_t value_size,
+		struct sieve_stringlist *key_list);
 int sieve_match_end(struct sieve_match_context **mctx);
 
+/* Default matching operation */
+int sieve_match
+	(const struct sieve_runtime_env *renv,
+		const struct sieve_match_type *mcht, 
+		const struct sieve_comparator *cmp, 
+		struct sieve_stringlist *value_list,
+		struct sieve_stringlist *key_list);
+
 /*
  * Read matching operands
  */
diff --git a/src/lib-sieve/sieve-message.c b/src/lib-sieve/sieve-message.c
index c54e867d4..a5f890c73 100644
--- a/src/lib-sieve/sieve-message.c
+++ b/src/lib-sieve/sieve-message.c
@@ -5,10 +5,14 @@
 #include "ioloop.h"
 #include "mempool.h"
 #include "array.h"
+#include "str.h"
+#include "mail-storage.h"
 
 #include "sieve-common.h"
+#include "sieve-stringlist.h"
 #include "sieve-error.h"
 #include "sieve-extensions.h"
+#include "sieve-runtime.h"
 #include "sieve-address.h"
 
 #include "sieve-message.h"
@@ -191,5 +195,107 @@ const char *sieve_message_get_sender
 	return sieve_address_to_string(msgctx->envelope_sender);
 } 
 
+/*
+ * Header stringlist
+ */
+
+/* Forward declarations */
+
+static int sieve_message_header_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_message_header_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+/* String list object */
+
+struct sieve_message_header_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_stringlist *field_names;
+
+	const char *const *headers;
+	int headers_index;
+};
+
+struct sieve_stringlist *sieve_message_header_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_names)
+{
+	struct sieve_message_header_stringlist *strlist;
+	    
+	strlist = t_new(struct sieve_message_header_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = sieve_message_header_stringlist_next_item;
+	strlist->strlist.reset = sieve_message_header_stringlist_reset;
+	strlist->field_names = field_names;
+  
+	return &strlist->strlist;
+}
+
+static inline string_t *_header_right_trim(const char *raw) 
+{
+	string_t *result;
+	int i;
+	
+	for ( i = strlen(raw)-1; i >= 0; i-- ) {
+		if ( raw[i] != ' ' && raw[i] != '\t' ) break;
+	}
+	
+	result = t_str_new(i+1);
+	str_append_n(result, raw, i + 1);
+	return result;
+}
+
+/* String list implementation */
+
+static int sieve_message_header_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_message_header_stringlist *strlist = 
+		(struct sieve_message_header_stringlist *) _strlist;
+	const struct sieve_runtime_env *renv = _strlist->runenv;
+	struct mail *mail = renv->msgdata->mail;
+
+	*str_r = NULL;
+
+	/* Check for end of current header list */
+	if ( strlist->headers == NULL ) {
+		strlist->headers_index = 0;
+ 	} else if ( strlist->headers[strlist->headers_index] == NULL ) {
+		strlist->headers = NULL;
+		strlist->headers_index = 0;
+	}
+
+	/* Fetch next header */
+	while ( strlist->headers == NULL ) {
+		string_t *hdr_item = NULL;
+		int ret;
+
+		/* Read next header name from source list */
+		if ( (ret=sieve_stringlist_next_item(strlist->field_names, &hdr_item)) 
+			<= 0 )
+			return ret;
+
+		/* Fetch all matching headers from the e-mail */
+		if ( mail_get_headers_utf8(mail, str_c(hdr_item), &strlist->headers) < 0 ||
+			( strlist->headers != NULL && strlist->headers[0] == NULL ) ) {
+			/* Try next item when this fails somehow */
+			strlist->headers = NULL;
+			continue;
+		}
+	}
 
+	/* Return next item */
+	*str_r = _header_right_trim(strlist->headers[strlist->headers_index++]);
+	return 1;
+}
 
+static void sieve_message_header_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_message_header_stringlist *strlist = 
+		(struct sieve_message_header_stringlist *) _strlist;
+
+	strlist->headers = NULL;
+	strlist->headers_index = 0;
+	sieve_stringlist_reset(strlist->field_names);
+}
diff --git a/src/lib-sieve/sieve-message.h b/src/lib-sieve/sieve-message.h
index 1e7555aa4..a7c145e57 100644
--- a/src/lib-sieve/sieve-message.h
+++ b/src/lib-sieve/sieve-message.h
@@ -48,5 +48,12 @@ const char *sieve_message_get_recipient
 
 const char *sieve_message_get_sender
 	(struct sieve_message_context *msgctx);
+
+/*
+ * Header stringlist
+ */
+
+struct sieve_stringlist *sieve_message_header_stringlist_create
+	(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_names);
 	
 #endif /* __SIEVE_MESSAGE_H */
diff --git a/src/lib-sieve/sieve-stringlist.c b/src/lib-sieve/sieve-stringlist.c
new file mode 100644
index 000000000..d3f7bc38f
--- /dev/null
+++ b/src/lib-sieve/sieve-stringlist.c
@@ -0,0 +1,139 @@
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+
+/*
+ * Default implementation
+ */
+
+bool sieve_stringlist_read_all
+(struct sieve_stringlist *strlist, pool_t pool,
+	const char * const **list_r)
+{
+	if ( strlist->read_all == NULL ) {
+		ARRAY_DEFINE(items, const char *);
+		string_t *item;
+		int ret;
+	
+		sieve_stringlist_reset(strlist);
+	
+		p_array_init(&items, pool, 4);
+	
+		item = NULL;
+		while ( (ret=sieve_stringlist_next_item(strlist, &item)) > 0 ) {
+			const char *stritem = p_strdup(pool, str_c(item));
+		
+			array_append(&items, &stritem, 1);
+		}
+	
+		(void)array_append_space(&items);
+		*list_r = array_idx(&items, 0);
+	
+		return ( ret >= 0 );
+	}
+
+	return strlist->read_all(strlist, pool, list_r);
+}
+
+int sieve_stringlist_get_length
+(struct sieve_stringlist *strlist)
+{
+	if ( strlist->get_length == NULL ) {
+		string_t *item;
+		int count = 0;
+		int ret;
+
+		while ( (ret=sieve_stringlist_next_item(strlist, &item)) > 0 ) {
+			count++;
+		}
+
+		return ( ret < 0 ? -1 : count );
+	}
+
+	return strlist->get_length(strlist);
+}		
+
+/*
+ * Single Stringlist
+ */
+
+/* Object */
+
+static int sieve_single_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_single_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+static int sieve_single_stringlist_get_length
+	(struct sieve_stringlist *_strlist);
+
+struct sieve_single_stringlist {
+	struct sieve_stringlist strlist;
+
+	string_t *value;
+
+	unsigned int end:1;
+	unsigned int count_empty:1;
+};
+
+struct sieve_stringlist *sieve_single_stringlist_create
+(const struct sieve_runtime_env *renv, string_t *str, bool count_empty)
+{
+	struct sieve_single_stringlist *strlist;
+
+	strlist = t_new(struct sieve_single_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = sieve_single_stringlist_next_item;
+	strlist->strlist.reset = sieve_single_stringlist_reset;
+	strlist->strlist.get_length = sieve_single_stringlist_get_length;
+	strlist->count_empty = count_empty;
+	strlist->value = str;
+
+	return &strlist->strlist;
+}
+
+struct sieve_stringlist *sieve_single_stringlist_create_cstr
+(const struct sieve_runtime_env *renv, const char *cstr, bool count_empty)
+{
+	string_t *str = t_str_new_const(cstr, strlen(cstr));
+
+	return sieve_single_stringlist_create(renv, str, count_empty);	
+}
+
+/* Implementation */
+
+static int sieve_single_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct sieve_single_stringlist *strlist =
+		(struct sieve_single_stringlist *)_strlist;
+
+	if ( strlist->end ) {
+		*str_r = NULL;
+		return 0;
+	}
+	
+	*str_r = strlist->value;
+	strlist->end = TRUE;
+	return 1;
+}
+
+static void sieve_single_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_single_stringlist *strlist =
+		(struct sieve_single_stringlist *)_strlist;
+
+	strlist->end = FALSE;
+}
+
+static int sieve_single_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+	struct sieve_single_stringlist *strlist =
+		(struct sieve_single_stringlist *)_strlist;
+
+	return ( strlist->count_empty || str_len(strlist->value) > 0 );
+}
diff --git a/src/lib-sieve/sieve-stringlist.h b/src/lib-sieve/sieve-stringlist.h
new file mode 100644
index 000000000..c9e1ebace
--- /dev/null
+++ b/src/lib-sieve/sieve-stringlist.h
@@ -0,0 +1,51 @@
+#ifndef __SIEVE_STRINGLIST_H
+#define __SIEVE_STRINGLIST_H
+
+/*
+ * Stringlist API
+ */
+
+struct sieve_stringlist {
+	const struct sieve_runtime_env *runenv;
+
+	int (*next_item)
+		(struct sieve_stringlist *strlist, string_t **str_r);
+	void (*reset)
+		(struct sieve_stringlist *strlist);
+	int (*get_length)
+		(struct sieve_stringlist *strlist);
+
+	bool (*read_all)
+		(struct sieve_stringlist *strlist, pool_t pool,
+			const char * const **list_r);
+};
+
+static inline int sieve_stringlist_next_item
+(struct sieve_stringlist *strlist, string_t **str_r) 
+{
+	return strlist->next_item(strlist, str_r);
+}
+
+static inline void sieve_stringlist_reset
+(struct sieve_stringlist *strlist) 
+{
+	return strlist->reset(strlist);
+}
+
+int sieve_stringlist_get_length
+	(struct sieve_stringlist *strlist);
+
+bool sieve_stringlist_read_all
+	(struct sieve_stringlist *strlist, pool_t pool,
+		const char * const **list_r);
+
+/*
+ * Single Stringlist
+ */
+
+struct sieve_stringlist *sieve_single_stringlist_create
+	(const struct sieve_runtime_env *renv, string_t *str, bool count_empty);
+struct sieve_stringlist *sieve_single_stringlist_create_cstr
+(const struct sieve_runtime_env *renv, const char *cstr, bool count_empty);
+
+#endif /* __SIEVE_STRINGLIST_H */
diff --git a/src/lib-sieve/tst-address.c b/src/lib-sieve/tst-address.c
index bf6d83feb..1e6567ce1 100644
--- a/src/lib-sieve/tst-address.c
+++ b/src/lib-sieve/tst-address.c
@@ -6,9 +6,12 @@
 
 #include "sieve-common.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-comparators.h"
 #include "sieve-match-types.h"
+#include "sieve-message.h"
+#include "sieve-address.h"
 #include "sieve-address-parts.h"
 #include "sieve-validator.h"
 #include "sieve-generator.h"
@@ -223,18 +226,14 @@ static bool tst_address_operation_dump
 static int tst_address_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {	
-	bool result = TRUE;
 	struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
 	struct sieve_match_type mcht = 
 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
 	struct sieve_address_part addrp = 
 		SIEVE_ADDRESS_PART_DEFAULT(all_address_part);
-	struct sieve_match_context *mctx;
-	struct sieve_coded_stringlist *hdr_list;
-	struct sieve_coded_stringlist *key_list;
-	string_t *hdr_item;
-	bool matched;
+	struct sieve_stringlist *hdr_list, *hdr_value_list, *value_list, *key_list;
+	struct sieve_address_list *addr_list;
 	int ret;
 	
 	/* Read optional operands */
@@ -254,48 +253,20 @@ static int tst_address_operation_execute
 
 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "address test");
 
-	/* Initialize match context */
-	mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, key_list);
-	
-	/* Iterate through all requested headers to match */
-	hdr_item = NULL;
-	matched = FALSE;
-	while ( result && !matched && 
-		(result=sieve_coded_stringlist_next_item(hdr_list, &hdr_item)) 
-		&& hdr_item != NULL ) {
-		const char *const *headers;
-
-		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
-            "  matching header `%s'", str_sanitize(str_c(hdr_item), 80));
-			
-		if ( mail_get_headers_utf8
-			(renv->msgdata->mail, str_c(hdr_item), &headers) >= 0 ) {	
-			int i;
-
-			for ( i = 0; !matched && headers[i] != NULL; i++ ) {
-				if ( (ret=sieve_address_match(&addrp, mctx, headers[i])) < 0 ) {
-					result = FALSE;
-					break;
-				}
-				
-				matched = ret > 0;				
-			} 
-		}
-	}
-	
-	/* Finish match */
+	/* Create value stringlist */
+	hdr_value_list = sieve_message_header_stringlist_create(renv, hdr_list);
+	addr_list = sieve_header_address_list_create(renv, hdr_value_list);
+	value_list = sieve_address_part_stringlist_create(renv, &addrp, addr_list);
 
-	if ( (ret=sieve_match_end(&mctx)) < 0 )
-		result = FALSE;
-	else
-		matched = ( ret > 0 || matched );
+	/* Perform match */
+	ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
 	
 	/* Set test result for subsequent conditional jump */
-	if ( result ) {
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
-	}
+	}	
 
-	sieve_runtime_trace_error(renv, "invalid string-list item");	
+	sieve_runtime_trace_error(renv, "invalid string-list item");
 	return SIEVE_EXEC_BIN_CORRUPT;
 }
diff --git a/src/lib-sieve/tst-exists.c b/src/lib-sieve/tst-exists.c
index 278faf80c..6231f4c35 100644
--- a/src/lib-sieve/tst-exists.c
+++ b/src/lib-sieve/tst-exists.c
@@ -6,6 +6,7 @@
 
 #include "sieve-common.h"
 #include "sieve-commands.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-validator.h"
 #include "sieve-generator.h"
@@ -105,8 +106,8 @@ static bool tst_exists_operation_dump
 static int tst_exists_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
-	bool result = TRUE;
-	struct sieve_coded_stringlist *hdr_list;
+	int ret;
+	struct sieve_stringlist *hdr_list;
 	string_t *hdr_item;
 	bool matched;
 	
@@ -129,8 +130,7 @@ static int tst_exists_operation_execute
 	hdr_item = NULL;
 	matched = TRUE;
 	while ( matched &&
-		(result=sieve_coded_stringlist_next_item(hdr_list, &hdr_item)) 
-		&& hdr_item != NULL ) {
+		(ret=sieve_stringlist_next_item(hdr_list, &hdr_item)) > 0 ) {
 		const char *const *headers;
 			
 		if ( mail_get_headers
@@ -145,7 +145,7 @@ static int tst_exists_operation_execute
 	}
 	
 	/* Set test result for subsequent conditional jump */
-	if ( result ) {
+	if ( ret >= 0 ) {
 		sieve_interpreter_set_test_result(renv->interp, matched);
 		return SIEVE_EXEC_OK;
 	}
diff --git a/src/lib-sieve/tst-header.c b/src/lib-sieve/tst-header.c
index 26ce9311f..9f1b85f8c 100644
--- a/src/lib-sieve/tst-header.c
+++ b/src/lib-sieve/tst-header.c
@@ -7,6 +7,7 @@
 #include "sieve-common.h"
 #include "sieve-commands.h"
 #include "sieve-code.h"
+#include "sieve-message.h"
 #include "sieve-comparators.h"
 #include "sieve-match-types.h"
 #include "sieve-validator.h"
@@ -151,34 +152,15 @@ static bool tst_header_operation_dump
  * Code execution 
  */
 
-static inline string_t *_header_right_trim(const char *raw) 
-{
-	string_t *result;
-	int i;
-	
-	for ( i = strlen(raw)-1; i >= 0; i-- ) {
-		if ( raw[i] != ' ' && raw[i] != '\t' ) break;
-	}
-	
-	result = t_str_new(i+1);
-	str_append_n(result, raw, i + 1);
-	return result;
-}
-
 static int tst_header_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
-	bool result = TRUE;
 	int opt_code = 0;
 	struct sieve_comparator cmp = 
 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
 	struct sieve_match_type mcht = 
 		SIEVE_COMPARATOR_DEFAULT(is_match_type);
-	struct sieve_match_context *mctx;
-	struct sieve_coded_stringlist *hdr_list;
-	struct sieve_coded_stringlist *key_list;
-	string_t *hdr_item;
-	bool matched;
+	struct sieve_stringlist *hdr_list, *key_list, *value_list;
 	int ret;
 	
 	/* 
@@ -212,48 +194,15 @@ static int tst_header_operation_execute
 
 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "header test");
 
-	/* Initialize match */
-	mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, key_list); 	
-
-	/* Iterate through all requested headers to match */
-	hdr_item = NULL;
-	matched = FALSE;
-	while ( result && !matched && 
-		(result=sieve_coded_stringlist_next_item(hdr_list, &hdr_item)) 
-		&& hdr_item != NULL ) {
-		const char *const *headers;
-
-		sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
-			"  matching header `%s'", str_sanitize(str_c(hdr_item), 80));
-			
-		if ( mail_get_headers_utf8
-			(renv->msgdata->mail, str_c(hdr_item), &headers) >= 0 ) {	
-			int i;
-
-			for ( i = 0; !matched && headers[i] != NULL; i++ ) {
-				string_t *theader = _header_right_trim(headers[i]);
-			
-				if ( (ret=sieve_match_value(mctx, str_c(theader), str_len(theader))) 
-					< 0 ) 
-				{
-					result = FALSE;
-					break;
-				}
-
-				matched = ret > 0;				
-			} 
-		}
-	}
+	/* Create header stringlist */
+	value_list = sieve_message_header_stringlist_create(renv, hdr_list);
 
-	/* Finish match */
-	if ( (ret=sieve_match_end(&mctx)) < 0 ) 
-		result = FALSE;
-	else
-		matched = ( ret > 0 || matched );
+	/* Perform match */
+	ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
 	
 	/* Set test result for subsequent conditional jump */
-	if ( result ) {
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
 	}	
 
diff --git a/src/testsuite/testsuite-log.c b/src/testsuite/testsuite-log.c
index c36a5b713..309572615 100644
--- a/src/testsuite/testsuite-log.c
+++ b/src/testsuite/testsuite-log.c
@@ -2,9 +2,11 @@
  */
 
 #include "lib.h"
+#include "str.h"
 #include "array.h"
 
 #include "sieve-common.h"
+#include "sieve-stringlist.h"
 #include "sieve-error-private.h"
 
 #include "testsuite-log.h"
@@ -145,6 +147,81 @@ void testsuite_log_deinit(void)
 	pool_unref(&_testsuite_logmsg_pool);
 }
 
+/*
+ * Result stringlist
+ */
+
+/* Forward declarations */
+
+static int testsuite_log_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void testsuite_log_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct testsuite_log_stringlist {
+	struct sieve_stringlist strlist;
+
+	int pos, index;
+};
+
+struct sieve_stringlist *testsuite_log_stringlist_create
+(const struct sieve_runtime_env *renv, int index)
+{
+	struct testsuite_log_stringlist *strlist;
+	    
+	strlist = t_new(struct testsuite_log_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = testsuite_log_stringlist_next_item;
+	strlist->strlist.reset = testsuite_log_stringlist_reset;
+
+ 	strlist->index = index;
+	strlist->pos = 0;
+ 
+	return &strlist->strlist;
+}
+
+static int testsuite_log_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct testsuite_log_stringlist *strlist = 
+		(struct testsuite_log_stringlist *) _strlist;
+	const struct _testsuite_log_message *msg;
+	int pos;
+
+	*str_r = NULL;
+
+	if ( strlist->pos < 0 )
+		return 0;
+
+	if ( strlist->index > 0 ) {
+		pos = strlist->index - 1;
+		strlist->pos = -1;
+	} else { 
+		pos = strlist->pos++;
+	}
+
+	if ( pos >= (int) array_count(&_testsuite_log_errors) ) {
+		strlist->pos = -1;
+		return 0;
+	}
+
+	msg = array_idx(&_testsuite_log_errors, (unsigned int) pos);
+
+	*str_r = t_str_new_const(msg->message, strlen(msg->message));
+	return 1;
+}
+
+static void testsuite_log_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct testsuite_log_stringlist *strlist = 
+		(struct testsuite_log_stringlist *) _strlist;
+
+	strlist->pos = 0;
+}
+
 
 
 
diff --git a/src/testsuite/testsuite-log.h b/src/testsuite/testsuite-log.h
index f89b54f5d..d5e8c6d59 100644
--- a/src/testsuite/testsuite-log.h
+++ b/src/testsuite/testsuite-log.h
@@ -15,4 +15,7 @@ void testsuite_log_clear_messages(void);
 void testsuite_log_get_error_init(void);
 const char *testsuite_log_get_error_next(bool location);
 
+struct sieve_stringlist *testsuite_log_stringlist_create
+	(const struct sieve_runtime_env *renv, int index);
+
 #endif /* __TESTSUITE_LOG_H */
diff --git a/src/testsuite/testsuite-result.c b/src/testsuite/testsuite-result.c
index df38ceb57..e73d31244 100644
--- a/src/testsuite/testsuite-result.c
+++ b/src/testsuite/testsuite-result.c
@@ -3,9 +3,11 @@
 
 #include "lib.h"
 #include "ostream.h"
+#include "str.h"
 
 #include "sieve-common.h"
 #include "sieve-error.h"
+#include "sieve-stringlist.h"
 #include "sieve-actions.h"
 #include "sieve-interpreter.h"
 #include "sieve-result.h"
@@ -92,4 +94,82 @@ void testsuite_result_print
 	o_stream_destroy(&out);	
 }
 
+/*
+ * Result stringlist
+ */
+
+/* Forward declarations */
+
+static int testsuite_result_stringlist_next_item
+	(struct sieve_stringlist *_strlist, string_t **str_r);
+static void testsuite_result_stringlist_reset
+	(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct testsuite_result_stringlist {
+	struct sieve_stringlist strlist;
+
+	struct sieve_result_iterate_context *result_iter;
+	int pos, index;
+};
+
+struct sieve_stringlist *testsuite_result_stringlist_create
+(const struct sieve_runtime_env *renv, int index)
+{
+	struct testsuite_result_stringlist *strlist;
+	    
+	strlist = t_new(struct testsuite_result_stringlist, 1);
+	strlist->strlist.runenv = renv;
+	strlist->strlist.next_item = testsuite_result_stringlist_next_item;
+	strlist->strlist.reset = testsuite_result_stringlist_reset;
+
+	strlist->result_iter = testsuite_result_iterate_init();
+ 	strlist->index = index;
+	strlist->pos = 0;
+ 
+	return &strlist->strlist;
+}
+
+static int testsuite_result_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+	struct testsuite_result_stringlist *strlist = 
+		(struct testsuite_result_stringlist *) _strlist;
+	const struct sieve_action *action;
+	const char *act_name;
+	bool keep;
+
+	*str_r = NULL;
+
+	if ( strlist->index > 0 && strlist->pos > 0 )
+		return 0;
+
+	do { 
+		if ( (action=sieve_result_iterate_next(strlist->result_iter, &keep))
+			== NULL )
+			return 0;
+		
+		strlist->pos++;
+	} while ( strlist->pos < strlist->index );
+	
+	if ( keep ) 
+		act_name = "keep";
+	else
+		act_name = ( action == NULL || action->def == NULL ||
+			action->def->name == NULL ) ? "" : action->def->name;
+
+	*str_r = t_str_new_const(act_name, strlen(act_name));
+	return 1;
+}
+
+static void testsuite_result_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+	struct testsuite_result_stringlist *strlist = 
+		(struct testsuite_result_stringlist *) _strlist;
+
+	strlist->result_iter = testsuite_result_iterate_init();
+	strlist->pos = 0;
+}
 
diff --git a/src/testsuite/testsuite-result.h b/src/testsuite/testsuite-result.h
index 60ad75de2..a74397d82 100644
--- a/src/testsuite/testsuite-result.h
+++ b/src/testsuite/testsuite-result.h
@@ -19,4 +19,7 @@ bool testsuite_result_execute(const struct sieve_runtime_env *renv);
 void testsuite_result_print
 	(const struct sieve_runtime_env *renv ATTR_UNUSED);
 
+struct sieve_stringlist *testsuite_result_stringlist_create
+	(const struct sieve_runtime_env *renv, int index);
+
 #endif /* __TESTSUITE_RESULT_H */
diff --git a/src/testsuite/tst-test-error.c b/src/testsuite/tst-test-error.c
index 52905b9da..f182203e0 100644
--- a/src/testsuite/tst-test-error.c
+++ b/src/testsuite/tst-test-error.c
@@ -210,14 +210,10 @@ static int tst_test_error_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {	
 	int opt_code = 0;
-	bool result = TRUE;
 	struct sieve_comparator cmp = SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
 	struct sieve_match_type mcht = SIEVE_COMPARATOR_DEFAULT(is_match_type);
-	struct sieve_match_context *mctx;
-	struct sieve_coded_stringlist *key_list;
-	bool matched;
-	const char *error;
-	int cur_index = 0, index = 0;
+	struct sieve_stringlist *value_list, *key_list;
+	int index = -1;
 	int ret;
 
 	/*
@@ -260,41 +256,17 @@ static int tst_test_error_operation_execute
 		sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
 			"test_error test");
 
-	testsuite_log_get_error_init();
-
-	/* Initialize match */
-	mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, key_list);
-
-	/* Iterate through all errors to match */
-	error = NULL;
-	matched = FALSE;
-	cur_index = 1;
-	ret = 0;
-	while ( result && !matched &&
-		(error=testsuite_log_get_error_next(FALSE)) != NULL ) {
-		
-		if ( index == 0 || index == cur_index ) {
-			if ( (ret=sieve_match_value(mctx, error, strlen(error))) < 0 ) {
-				result = FALSE;
-				break;
-			}
-		}
-
-		matched = ret > 0;
-		cur_index++;
-	}
-
-	/* Finish match */
-	if ( (ret=sieve_match_end(&mctx)) < 0 )
-		result = FALSE;
-	else
-		matched = ( ret > 0 || matched );
+	/* Create value stringlist */
+	value_list = testsuite_log_stringlist_create(renv, index);
 
+	/* Perform match */
+	ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
+	
 	/* Set test result for subsequent conditional jump */
-	if ( result ) {
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
-	}
+	}	
 
 	sieve_runtime_trace_error(renv, "invalid string-list item");
 	return SIEVE_EXEC_BIN_CORRUPT;
diff --git a/src/testsuite/tst-test-multiscript.c b/src/testsuite/tst-test-multiscript.c
index 2e678d3f9..555e13c6a 100644
--- a/src/testsuite/tst-test-multiscript.c
+++ b/src/testsuite/tst-test-multiscript.c
@@ -7,6 +7,7 @@
 #include "sieve-validator.h"
 #include "sieve-generator.h"
 #include "sieve-interpreter.h"
+#include "sieve-stringlist.h"
 #include "sieve-code.h"
 #include "sieve-binary.h"
 #include "sieve-dump.h"
@@ -107,17 +108,18 @@ static bool tst_test_multiscript_operation_dump
 static int tst_test_multiscript_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
-	struct sieve_coded_stringlist *scripts_list;
+	struct sieve_stringlist *scripts_list;
 	string_t *script_name;
 	const char *script_path;
 	ARRAY_TYPE (const_string) scriptfiles;
 	bool result = TRUE;
+	int ret;
 
 	/*
 	 * Read operands
 	 */
 
-	if ( (scripts_list=sieve_opr_stringlist_read(renv, address, "scripts")) 
+	if ( (scripts_list=sieve_opr_stringlist_read(renv, address, "scripts"))
 		== NULL )
 		return SIEVE_EXEC_BIN_CORRUPT;
 
@@ -130,22 +132,21 @@ static int tst_test_multiscript_operation_execute
 	t_array_init(&scriptfiles, 16);
 
 	script_path = sieve_script_dirpath(renv->script);
-	if ( script_path == NULL ) 
+	if ( script_path == NULL )
 		return SIEVE_EXEC_FAILURE;
 
 	script_name = NULL;
-	while ( result && 
-		(result=sieve_coded_stringlist_next_item(scripts_list, &script_name))
-		&& script_name != NULL ) {	
+	while ( result &&
+		(ret=sieve_stringlist_next_item(scripts_list, &script_name)) > 0 ) {
 
-		const char *path = 
+		const char *path =
 			t_strconcat(script_path, "/", str_c(script_name), NULL);
 
 		/* Attempt script compile */
-		array_append(&scriptfiles, &path, 1);	
+		array_append(&scriptfiles, &path, 1);
 	}
 
-	result = result && testsuite_script_multiscript(renv, &scriptfiles);
+	result = result && (ret >= 0) && testsuite_script_multiscript(renv, &scriptfiles);
 
 	/* Set result */
 	sieve_interpreter_set_test_result(renv->interp, result);
diff --git a/src/testsuite/tst-test-result.c b/src/testsuite/tst-test-result.c
index d684acc9c..1470454f7 100644
--- a/src/testsuite/tst-test-result.c
+++ b/src/testsuite/tst-test-result.c
@@ -215,16 +215,10 @@ static int tst_test_result_operation_execute
 (const struct sieve_runtime_env *renv, sieve_size_t *address)
 {	
 	int opt_code = 0;
-	bool result = TRUE;
 	struct sieve_comparator cmp = SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
-	struct sieve_match_type mcht = SIEVE_COMPARATOR_DEFAULT(is_match_type);
-	struct sieve_match_context *mctx;
-	struct sieve_coded_stringlist *key_list;
-	bool matched;
-	struct sieve_result_iterate_context *rictx;
-	const struct sieve_action *action;
-	bool keep;
-	int cur_index = 0, index = 0;
+	struct sieve_match_type mcht = SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+	struct sieve_stringlist *value_list, *key_list;
+	int index = 0;
 	int ret;
 
 	/*
@@ -261,49 +255,19 @@ static int tst_test_result_operation_execute
 	 */
 	
 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
-		"TEST_RESULT test (index: %d)", index);
-
-	rictx = testsuite_result_iterate_init();
-
-  /* Initialize match */
-  mctx = sieve_match_begin(renv, &mcht, &cmp, NULL, key_list);
-
-  /* Iterate through all errors to match */
-	matched = FALSE;
-	cur_index = 1;
-	ret = 0;
-	while ( result && !matched &&
-		(action=sieve_result_iterate_next(rictx, &keep)) != NULL ) {
-		const char *act_name;
-		
-		if ( keep ) 
-			act_name = "keep";
-		else
-			act_name = ( action == NULL || action->def == NULL ||
-				action->def->name == NULL ) ? "" : action->def->name;
-
-		if ( index == 0 || index == cur_index ) {
-			if ( (ret=sieve_match_value(mctx, act_name, strlen(act_name))) < 0 ) {
-				result = FALSE;
-				break;
-			}
-		}
-
-		matched = ret > 0;
-		cur_index++;
-	}
+		"test_result test (index: %d)", index);
 
-	/* Finish match */
-	if ( (ret=sieve_match_end(&mctx)) < 0 )
-		result = FALSE;
-	else
-		matched = ( ret > 0 || matched );
+	/* Create value stringlist */
+	value_list = testsuite_result_stringlist_create(renv, index);
 
+	/* Perform match */
+	ret = sieve_match(renv, &mcht, &cmp, value_list, key_list); 	
+	
 	/* Set test result for subsequent conditional jump */
-	if ( result ) {
-		sieve_interpreter_set_test_result(renv->interp, matched);
+	if ( ret >= 0 ) {
+		sieve_interpreter_set_test_result(renv->interp, ret > 0);
 		return SIEVE_EXEC_OK;
-	}
+	}	
 
 	sieve_runtime_trace_error(renv, "invalid string-list item");
 	return SIEVE_EXEC_BIN_CORRUPT;
@@ -311,4 +275,3 @@ static int tst_test_result_operation_execute
 
 
 
-
diff --git a/tests/extensions/envelope.svtest b/tests/extensions/envelope.svtest
index 32c46e03a..19ea5015c 100644
--- a/tests/extensions/envelope.svtest
+++ b/tests/extensions/envelope.svtest
@@ -6,6 +6,10 @@ require "envelope";
  * Empty envelope addresses
  */
 
+/* RFC 5228, Section 5.4: The null reverse-path is matched against as the empty
+ * string, regardless of the ADDRESS-PART argument specified.
+ */
+
 test "Envelope - from empty" {
 	/* Return_path: "" */
 
-- 
GitLab