From ddfd461e67872fda34cc8cc02e004d12cd82cafd Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan@rename-it.nl>
Date: Tue, 18 Dec 2007 21:08:06 +0100
Subject: [PATCH] Implemented evaluation for the body test introduced by the
 body extension.

---
 AUTHORS                                       |   6 +-
 src/lib-sieve/plugins/body/Makefile.am        |   5 +
 src/lib-sieve/plugins/body/body.sieve         |   5 +
 src/lib-sieve/plugins/body/ext-body-common.c  | 282 +++++++++++++
 src/lib-sieve/plugins/body/ext-body-common.h  |  16 +
 src/lib-sieve/plugins/body/ext-body.c         | 374 +----------------
 src/lib-sieve/plugins/body/tst-body.c         | 382 ++++++++++++++++++
 src/lib-sieve/plugins/imapflags/tst-hasflag.c |   4 +-
 src/lib-sieve/sieve-address-parts.c           |   2 +-
 src/lib-sieve/sieve-match-types.c             |   4 +-
 src/lib-sieve/sieve-match-types.h             |   2 +-
 src/lib-sieve/tst-header.c                    |   2 +-
 12 files changed, 716 insertions(+), 368 deletions(-)
 create mode 100644 src/lib-sieve/plugins/body/body.sieve
 create mode 100644 src/lib-sieve/plugins/body/ext-body-common.c
 create mode 100644 src/lib-sieve/plugins/body/ext-body-common.h
 create mode 100644 src/lib-sieve/plugins/body/tst-body.c

diff --git a/AUTHORS b/AUTHORS
index 47c4a042f..59c9d4031 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,6 +1,10 @@
 Stephan Bosch <stephan@rename-it.nl>
 
-For and using the Dovecot secure IMAP server by:
+This plugin is partly based on the original cmusieve plugin for the Dovecot 
+secure IMAP server. Both cmusieve plugin and IMAP server are primarily 
+written by:
 
 Timo Sirainen <tss@iki.fi>
 
+View the AUTHORS files in the Dovecot and Dovecot-Sieve distributions for 
+other authors of Dovecot or the cmusieve plugin.
diff --git a/src/lib-sieve/plugins/body/Makefile.am b/src/lib-sieve/plugins/body/Makefile.am
index 8077860de..641c32e94 100644
--- a/src/lib-sieve/plugins/body/Makefile.am
+++ b/src/lib-sieve/plugins/body/Makefile.am
@@ -7,6 +7,11 @@ AM_CPPFLAGS = \
 	-I$(dovecot_incdir)/src/lib-mail \
 	-I$(dovecot_incdir)/src/lib-storage 
 
+tsts = \
+	tst-body.c
+
 libsieve_ext_body_la_SOURCES = \
+	ext-body-common.c \
+	$(tsts) \
 	ext-body.c
 
diff --git a/src/lib-sieve/plugins/body/body.sieve b/src/lib-sieve/plugins/body/body.sieve
new file mode 100644
index 000000000..d9708cda0
--- /dev/null
+++ b/src/lib-sieve/plugins/body/body.sieve
@@ -0,0 +1,5 @@
+require ["body", "fileinto"];
+
+if body :content "text/plain" :comparator "i;ascii-casemap" :contains "WERKT" {
+	fileinto "TEST";
+}
diff --git a/src/lib-sieve/plugins/body/ext-body-common.c b/src/lib-sieve/plugins/body/ext-body-common.c
new file mode 100644
index 000000000..a4ac27991
--- /dev/null
+++ b/src/lib-sieve/plugins/body/ext-body-common.c
@@ -0,0 +1,282 @@
+#include "lib.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "array.h"
+#include "str.h"
+#include "istream.h"
+#include "rfc822-parser.h"
+#include "message-date.h"
+#include "message-parser.h"
+#include "message-decoder.h"
+
+#include "sieve-common.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.
+ */
+ 
+struct ext_body_part_cached {
+	const char *content_type;
+
+	const char *raw_body;
+	const char *decoded_body;
+	size_t raw_body_size;
+	size_t decoded_body_size;
+	bool have_body; /* there's the empty end-of-headers line */
+};
+
+struct ext_body_message_context {
+	pool_t pool;
+	ARRAY_DEFINE(cached_body_parts, struct ext_body_part_cached);
+	ARRAY_DEFINE(return_body_parts, struct ext_body_part);
+	buffer_t *tmp_buffer;
+};
+
+static bool _is_wanted_content_type
+(const char * const *wanted_types, const char *content_type)
+{
+	const char *subtype = strchr(content_type, '/');
+	size_t type_len;
+
+	type_len = subtype == NULL ? strlen(content_type) :
+		(size_t)(subtype - content_type);
+
+	for (; *wanted_types != NULL; wanted_types++) {
+		const char *wanted_subtype = strchr(*wanted_types, '/');
+
+		if (**wanted_types == '\0') {
+			/* empty string matches everything */
+			return TRUE;
+		}
+		if (wanted_subtype == NULL) {
+			/* match only main type */
+			if (strlen(*wanted_types) == type_len &&
+			    strncasecmp(*wanted_types, content_type,
+					type_len) == 0)
+				return TRUE;
+		} else {
+			/* match whole type/subtype */
+			if (strcasecmp(*wanted_types, content_type) == 0)
+				return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static bool ext_body_get_return_parts
+(struct ext_body_message_context *ctx, const char * const *wanted_types,
+	bool decode_to_plain)
+{
+	const struct ext_body_part_cached *body_parts;
+	unsigned int i, count;
+	struct ext_body_part *return_part;
+
+	body_parts = array_get(&ctx->cached_body_parts, &count);
+	if (count == 0)
+		return FALSE;
+
+	array_clear(&ctx->return_body_parts);
+	for (i = 0; i < count; i++) {
+		if (!body_parts[i].have_body) {
+			/* doesn't match anything */
+			continue;
+		}
+
+		if (!_is_wanted_content_type(wanted_types, body_parts[i].content_type))
+			continue;
+
+		return_part = array_append_space(&ctx->return_body_parts);
+		if (decode_to_plain) {
+			if (body_parts[i].decoded_body == NULL)
+				return FALSE;
+			return_part->content = body_parts[i].decoded_body;
+			return_part->size = body_parts[i].decoded_body_size;
+		} else {
+			if (body_parts[i].raw_body == NULL)
+				return FALSE;
+			return_part->content = body_parts[i].raw_body;
+			return_part->size = body_parts[i].raw_body_size;
+		}
+	}
+	return TRUE;
+}
+
+static void ext_body_part_save
+(struct ext_body_message_context *ctx, struct message_part *part,
+	struct ext_body_part_cached *body_part, bool decoded)
+{
+	buffer_t *buf = ctx->tmp_buffer;
+
+	buffer_append_c(buf, '\0');
+	if ( !decoded ) {
+		body_part->raw_body = p_strdup(ctx->pool, buf->data);
+		body_part->raw_body_size = buf->used - 1;
+		i_assert(buf->used - 1 == part->body_size.physical_size);
+	} else {
+		body_part->decoded_body = p_strdup(ctx->pool, buf->data);
+		body_part->decoded_body_size = buf->used - 1;
+	}
+	buffer_set_used_size(buf, 0);
+}
+
+static const char *_parse_content_type(const struct message_header_line *hdr)
+{
+	struct rfc822_parser_context parser;
+	string_t *content_type;
+
+	rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+	(void)rfc822_skip_lwsp(&parser);
+
+	content_type = t_str_new(64);
+	if (rfc822_parse_content_type(&parser, content_type) < 0)
+		return "";
+	return str_c(content_type);
+}
+
+static bool ext_body_parts_add_missing
+(const struct sieve_message_data *msgdata, struct ext_body_message_context *ctx, 
+	const char * const *content_types, bool decode_to_plain)
+{
+	struct ext_body_part_cached *body_part = NULL;
+	struct message_parser_ctx *parser;
+	struct message_decoder_context *decoder;
+	struct message_block block, decoded;
+	const struct message_part *const_parts;
+	struct message_part *parts, *prev_part = NULL;
+	struct istream *input;
+	unsigned int idx = 0;
+	bool save_body = FALSE, have_all;
+	int ret;
+
+	if (ext_body_get_return_parts(ctx, content_types, decode_to_plain))
+		return 0;
+
+	if (mail_get_stream(msgdata->mail, NULL, NULL, &input) < 0)
+		return -1;
+	if (mail_get_parts(msgdata->mail, &const_parts) < 0)
+		return -1;
+	parts = (struct message_part *)const_parts;
+
+	buffer_set_used_size(ctx->tmp_buffer, 0);
+	decoder = decode_to_plain ? message_decoder_init(FALSE) : NULL;
+	parser = message_parser_init_from_parts(parts, input, 0, 0);
+	while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
+		if (block.part != prev_part) {
+			if (body_part != NULL && save_body) {
+				ext_body_part_save(ctx, prev_part, body_part, decoder != NULL);
+			}
+			prev_part = block.part;
+			body_part = array_idx_modifiable(&ctx->cached_body_parts, idx);
+			idx++;
+			body_part->content_type = "text/plain";
+		}
+		if (block.hdr != NULL || block.size == 0) {
+			/* reading headers */
+			if (decoder != NULL) {
+				(void)message_decoder_decode_next_block(decoder,
+					&block, &decoded);
+			}
+
+			if (block.hdr == NULL) {
+				/* save bodies only if we have a wanted
+				   content-type */
+				save_body = _is_wanted_content_type
+					(content_types, body_part->content_type);
+				continue;
+			}
+			
+			if (block.hdr->eoh)
+				body_part->have_body = TRUE;
+				
+			/* We're interested of only Content-Type: header */
+			if (strcasecmp(block.hdr->name, "Content-Type") != 0)
+				continue;
+
+			if (block.hdr->continues) {
+				block.hdr->use_full_value = TRUE;
+				continue;
+			}
+		
+			T_FRAME(
+				body_part->content_type =
+					p_strdup(ctx->pool, _parse_content_type(block.hdr));
+			);
+			continue;
+		}
+
+		/* reading body */
+		if (save_body) {
+			if (decoder != NULL) {
+				(void)message_decoder_decode_next_block(decoder,
+							&block, &decoded);
+				buffer_append(ctx->tmp_buffer,
+					      decoded.data, decoded.size);
+			} else {
+				buffer_append(ctx->tmp_buffer,
+					      block.data, block.size);
+			}
+		}
+	}
+
+	if (body_part != NULL && save_body)
+		ext_body_part_save(ctx, prev_part, body_part, decoder != NULL);
+
+	have_all = ext_body_get_return_parts(ctx, content_types, decode_to_plain);
+	i_assert(have_all);
+
+	(void)message_parser_deinit(&parser);
+	if (decoder != NULL)
+		message_decoder_deinit(&decoder);
+	return ( input->stream_errno == 0 );
+}
+
+/* FIXME: This will be inefficient in combination with include extension, 
+ * creating a new context for each sub-interpreter. We should provide a means to 
+ * associate context data with the message itself. 
+ */
+static struct ext_body_message_context *ext_body_get_context
+(struct sieve_interpreter *interp)
+{
+	pool_t pool = sieve_interpreter_pool(interp);
+	struct ext_body_message_context *ctx;
+	
+	ctx = (struct ext_body_message_context *)
+		sieve_interpreter_extension_get_context(interp, ext_body_my_id);
+	
+	if ( ctx == NULL ) {
+		ctx = p_new(pool, struct ext_body_message_context, 1);	
+		ctx->pool = pool;
+		p_array_init(&ctx->cached_body_parts, pool, 8);
+		p_array_init(&ctx->return_body_parts, pool, 8);
+		ctx->tmp_buffer = buffer_create_dynamic(pool, 1024*64);
+		
+		sieve_interpreter_extension_set_context
+			(interp, ext_body_my_id, (void *) ctx);
+	}
+	
+	return ctx;
+}
+
+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 result = TRUE;
+	struct ext_body_message_context *ctx = ext_body_get_context(renv->interp);
+
+	T_FRAME(
+		if ( !ext_body_parts_add_missing
+			(renv->msgdata, ctx, content_types, decode_to_plain != 0) )
+			result = FALSE;
+	);
+	
+	if ( !result ) return FALSE;
+
+	(void)array_append_space(&ctx->return_body_parts); /* NULL-terminate */
+	*parts_r = array_idx_modifiable(&ctx->return_body_parts, 0);
+
+	return result;
+}
diff --git a/src/lib-sieve/plugins/body/ext-body-common.h b/src/lib-sieve/plugins/body/ext-body-common.h
new file mode 100644
index 000000000..66b417577
--- /dev/null
+++ b/src/lib-sieve/plugins/body/ext-body-common.h
@@ -0,0 +1,16 @@
+#ifndef __EXT_BODY_COMMON_H
+#define __EXT_BODY_COMMON_H
+
+extern int ext_body_my_id;
+extern const struct sieve_extension body_extension;
+
+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);
+
+#endif /* __EXT_BODY_COMMON_H */
diff --git a/src/lib-sieve/plugins/body/ext-body.c b/src/lib-sieve/plugins/body/ext-body.c
index b12f16ab9..24aa2eed4 100644
--- a/src/lib-sieve/plugins/body/ext-body.c
+++ b/src/lib-sieve/plugins/body/ext-body.c
@@ -7,9 +7,7 @@
  * Status: under development
  *
  */
-
-#include <stdio.h>
-
+ 
 #include "lib.h"
 #include "array.h"
 
@@ -25,34 +23,28 @@
 #include "sieve-interpreter.h"
 #include "sieve-code-dumper.h"
 
+#include "ext-body-common.h"
+
 /* 
- * Forward declarations 
+ * Commands
  */
 
-static bool ext_body_load(int ext_id);
-static bool ext_body_validator_load(struct sieve_validator *validator);
-
-static bool ext_body_opcode_dump
-	(const struct sieve_opcode *opcode, 
-		const struct sieve_dumptime_env *denv, sieve_size_t *address);
-static bool ext_body_opcode_execute
-	(const struct sieve_opcode *opcode,
-		const struct sieve_runtime_env *renv, sieve_size_t *address);
+extern const struct sieve_command body_test;
+ 
+/*
+ * Opcodes
+ */
 
-static bool tst_body_registered
-	(struct sieve_validator *validator, struct sieve_command_registration *cmd_reg);
-static bool tst_body_validate
-	(struct sieve_validator *validator, struct sieve_command_context *tst);
-static bool tst_body_generate
-	(struct sieve_generator *generator,	struct sieve_command_context *ctx);
+extern const struct sieve_opcode body_opcode;
 
 /* 
  * Extension definitions 
  */
 
-static int ext_my_id;
+int ext_body_my_id;
 
-const struct sieve_opcode body_opcode;
+static bool ext_body_load(int ext_id);
+static bool ext_body_validator_load(struct sieve_validator *validator);
 
 const struct sieve_extension body_extension = { 
 	"body", 
@@ -65,176 +57,7 @@ const struct sieve_extension body_extension = {
 
 static bool ext_body_load(int ext_id) 
 {
-	ext_my_id = ext_id;
-	return TRUE;
-}
-
-/* body test 
- *
- * Syntax
- *   body [COMPARATOR] [MATCH-TYPE] [BODY-TRANSFORM]
- *     <key-list: string-list>
- */
-static const struct sieve_command body_test = { 
-	"body", 
-	SCT_TEST, 
-	1, 0, FALSE, FALSE,
-	tst_body_registered, 
-	NULL,
-	tst_body_validate, 
-	tst_body_generate, 
-	NULL 
-};
-
-/* body opcode */
-
-const struct sieve_opcode body_opcode = { 
-	"body",
-	SIEVE_OPCODE_CUSTOM,
-	&body_extension,
-	0,
-	ext_body_opcode_dump, 
-	ext_body_opcode_execute 
-};
-
-enum tst_body_optional {	
-	OPT_END,
-	OPT_COMPARATOR,
-	OPT_MATCH_TYPE,
-	OPT_BODY_TRANSFORM
-};
-
-enum tst_body_transform {
-	TST_BODY_TRANSFORM_RAW,
-	TST_BODY_TRANSFORM_CONTENT,
-	TST_BODY_TRANSFORM_TEXT
-};
-
-/* 
- * Custom command tags 
- */
-
-static bool tag_body_transform_validate
-	(struct sieve_validator *validator, struct sieve_ast_argument **arg, 
-		struct sieve_command_context *cmd);
-static bool tag_body_transform_generate	
-	(struct sieve_generator *gentr, struct sieve_ast_argument *arg, 
-		struct sieve_command_context *cmd);
- 
-static const struct sieve_argument body_raw_tag = { 
-	"raw", NULL, 
-	tag_body_transform_validate, 
-	NULL, 
-	tag_body_transform_generate 
-};
-
-static const struct sieve_argument body_content_tag = { 
-	"content", NULL, 
-	tag_body_transform_validate, 
-	NULL, 
-	tag_body_transform_generate 
-};
-
-static const struct sieve_argument body_text_tag = { 
-	"text", NULL, 
-	tag_body_transform_validate, 
-	NULL, 
-	tag_body_transform_generate
-};
- 
-static bool tag_body_transform_validate
-(struct sieve_validator *validator, struct sieve_ast_argument **arg, 
-	struct sieve_command_context *cmd)
-{
-	enum tst_body_transform transform;
-	struct sieve_ast_argument *tag = *arg;
-
-	/* BODY-TRANSFORM:
-	 *   :raw
-   *     / :content <content-types: string-list>
-   *     / :text
-   */
-	if ( (bool) cmd->data ) {
-		sieve_command_validate_error(validator, cmd, 
-			"the :raw, :content and :text arguments for the body test are mutually "
-			"exclusive, but more than one was specified");
-		return FALSE;
-	}
-
-	/* Skip tag */
-	*arg = sieve_ast_argument_next(*arg);
-
-	/* :content tag has a string-list argument */
-	if ( tag->argument == &body_raw_tag ) 
-		transform = TST_BODY_TRANSFORM_RAW;
-		
-	else if ( tag->argument == &body_text_tag )
-		transform = TST_BODY_TRANSFORM_TEXT;
-		
-	else if ( tag->argument == &body_content_tag ) {
-		/* Check syntax:
-		 *   :content <content-types: string-list>
-		 */
-		if ( !sieve_validate_tag_parameter
-			(validator, cmd, tag, *arg, SAAT_STRING_LIST) ) {
-			return FALSE;
-		}
-		sieve_validator_argument_activate(validator, *arg);
-		
-		/* Assign tag parameters */
-		tag->parameters = *arg;
-		*arg = sieve_ast_arguments_detach(*arg,1);
-		
-		transform = TST_BODY_TRANSFORM_CONTENT;
-	} else 
-		return FALSE;
-	
-	/* Signal the presence of this tag */
-	cmd->data = (void *) TRUE;
-		
-	/* Assign context data */
-	tag->context = (void *) transform;	
-		
-	return TRUE;
-}
-
-/* 
- * Command Registration 
- */
-static bool tst_body_registered
-(struct sieve_validator *validator, struct sieve_command_registration *cmd_reg) 
-{
-	/* The order of these is not significant */
-	sieve_comparators_link_tag(validator, cmd_reg, OPT_COMPARATOR);
-	sieve_match_types_link_tags(validator, cmd_reg, OPT_MATCH_TYPE);
-	
-	sieve_validator_register_tag
-		(validator, cmd_reg, &body_raw_tag, OPT_BODY_TRANSFORM); 	
-	sieve_validator_register_tag
-		(validator, cmd_reg, &body_content_tag, OPT_BODY_TRANSFORM); 	
-	sieve_validator_register_tag
-		(validator, cmd_reg, &body_text_tag, OPT_BODY_TRANSFORM); 	
-	
-	return TRUE;
-}
-
-/* 
- * Validation 
- */
- 
-static bool tst_body_validate(struct sieve_validator *validator, struct sieve_command_context *tst) 
-{ 		
-	struct sieve_ast_argument *arg = tst->first_positional;
-					
-	if ( !sieve_validate_positional_argument
-		(validator, tst, arg, "key list", 1, SAAT_STRING_LIST) ) {
-		return FALSE;
-	}
-	sieve_validator_argument_activate(validator, arg);
-
-	/* Validate the key argument to a specified match type */
-	sieve_match_type_validate(validator, tst, arg);
-	
+	ext_body_my_id = ext_id;
 	return TRUE;
 }
 
@@ -248,173 +71,4 @@ static bool ext_body_validator_load(struct sieve_validator *validator)
 	return TRUE;
 }
 
-/*
- * Generation
- */
- 
-static bool tst_body_generate
-	(struct sieve_generator *gentr,	struct sieve_command_context *ctx) 
-{
-	(void)sieve_generator_emit_opcode_ext(gentr, &body_opcode, ext_my_id);
-
-	/* Generate arguments */
-	if ( !sieve_generate_arguments(gentr, ctx, NULL) )
-		return FALSE;
-
-	return TRUE;
-}
-
-static bool tag_body_transform_generate
-(struct sieve_generator *gentr, struct sieve_ast_argument *arg, 
-	struct sieve_command_context *cmd ATTR_UNUSED)
-{
-	struct sieve_binary *sbin = sieve_generator_get_binary(gentr);
-	enum tst_body_transform transform =	(enum tst_body_transform) arg->context;
-	
-	sieve_binary_emit_byte(sbin, transform);
-	sieve_generate_argument_parameters(gentr, cmd, arg); 
-			
-	return TRUE;
-}
-
-/* 
- * Code dump 
- */
- 
-static bool ext_body_opcode_dump
-(const struct sieve_opcode *opcode ATTR_UNUSED, 
-	const struct sieve_dumptime_env *denv, sieve_size_t *address)
-{
-	int opt_code = 1;
-	enum tst_body_transform transform;
-
-	sieve_code_dumpf(denv, "BODY");
-	sieve_code_descend(denv);
-
-	/* Handle any optional arguments */
-	if ( sieve_operand_optional_present(denv->sbin, address) ) {
-		while ( opt_code != 0 ) {
-			if ( !sieve_operand_optional_read(denv->sbin, address, &opt_code) ) 
-				return FALSE;
-
-			switch ( opt_code ) {
-			case 0:
-				break;
-			case OPT_COMPARATOR:
-				sieve_opr_comparator_dump(denv, address);
-				break;
-			case OPT_MATCH_TYPE:
-				sieve_opr_match_type_dump(denv, address);
-				break;
-			case OPT_BODY_TRANSFORM:
-				if ( !sieve_binary_read_byte(denv->sbin, address, &transform) )
-					return FALSE;
-				
-				switch ( transform ) {
-				case TST_BODY_TRANSFORM_RAW:
-					sieve_code_dumpf(denv, "BODY-TRANSFORM: RAW");
-					break;
-				case TST_BODY_TRANSFORM_TEXT:
-					sieve_code_dumpf(denv, "BODY-TRANSFORM: TEXT");
-					break;
-				case TST_BODY_TRANSFORM_CONTENT:
-					sieve_code_dumpf(denv, "BODY-TRANSFORM: CONTENT");
-					
-					sieve_code_descend(denv);
-					if ( !sieve_opr_stringlist_dump(denv, address) )
-						return FALSE;
-					sieve_code_ascend(denv);
-					break;
-				default:
-					return FALSE;
-				}
-				break;
-			default: 
-				return FALSE;
-			}
- 		}
-	}
 
-	return
-		sieve_opr_stringlist_dump(denv, address);
-}
-
-static bool ext_body_opcode_execute
-(const struct sieve_opcode *opcode ATTR_UNUSED,
-	const struct sieve_runtime_env *renv, sieve_size_t *address)
-{
-	bool result = TRUE;
-	int opt_code = 1;
-	const struct sieve_comparator *cmp = &i_octet_comparator;
-	const struct sieve_match_type *mtch = &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;
-	
-	printf("?? HEADER\n");
-
-	/* Handle any optional arguments */
-	if ( sieve_operand_optional_present(renv->sbin, address) ) {
-		while ( opt_code != 0 ) {
-			if ( !sieve_operand_optional_read(renv->sbin, address, &opt_code) )
-				return FALSE;
-
-			switch ( opt_code ) {
-			case 0: 
-				break;
-			case OPT_COMPARATOR:
-				cmp = sieve_opr_comparator_read(renv->sbin, address);
-				break;
-			case OPT_MATCH_TYPE:
-				mtch = sieve_opr_match_type_read(renv->sbin, address);
-				break;
-			default:
-				return FALSE;
-			}
-		}
-	}
-
-	t_push();
-		
-	/* Read header-list */
-	if ( (hdr_list=sieve_opr_stringlist_read(renv->sbin, address)) == NULL ) {
-		t_pop();
-		return FALSE;
-	}
-	
-	/* Read key-list */
-	if ( (key_list=sieve_opr_stringlist_read(renv->sbin, address)) == NULL ) {
-		t_pop();
-		return FALSE;
-	}
-
-	mctx = sieve_match_begin(mtch, cmp, key_list); 	
-
-	/* Iterate through all requested headers to match */
-	hdr_item = NULL;
-	matched = FALSE;
-	while ( !matched && (result=sieve_coded_stringlist_next_item(hdr_list, &hdr_item)) 
-		&& hdr_item != NULL ) {
-		const char *const *headers;
-			
-		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 ( sieve_match_value(mctx, headers[i]) )
-					matched = TRUE;				
-			} 
-		}
-	}
-
-	matched = sieve_match_end(mctx) || matched; 	
-	
-	t_pop();
-	
-	if ( result )
-		sieve_interpreter_set_test_result(renv->interp, matched);
-	
-	return result;
-}
diff --git a/src/lib-sieve/plugins/body/tst-body.c b/src/lib-sieve/plugins/body/tst-body.c
new file mode 100644
index 000000000..5bf063524
--- /dev/null
+++ b/src/lib-sieve/plugins/body/tst-body.c
@@ -0,0 +1,382 @@
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-code-dumper.h"
+
+#include "ext-body-common.h"
+
+/* 
+ * Forward declarations 
+ */
+
+static bool ext_body_opcode_dump
+	(const struct sieve_opcode *opcode, 
+		const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static bool ext_body_opcode_execute
+	(const struct sieve_opcode *opcode,
+		const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+static bool tst_body_registered
+	(struct sieve_validator *validator, struct sieve_command_registration *cmd_reg);
+static bool tst_body_validate
+	(struct sieve_validator *validator, struct sieve_command_context *tst);
+static bool tst_body_generate
+	(struct sieve_generator *generator,	struct sieve_command_context *ctx);
+
+/* body test 
+ *
+ * Syntax
+ *   body [COMPARATOR] [MATCH-TYPE] [BODY-TRANSFORM]
+ *     <key-list: string-list>
+ */
+const struct sieve_command body_test = { 
+	"body", 
+	SCT_TEST, 
+	1, 0, FALSE, FALSE,
+	tst_body_registered, 
+	NULL,
+	tst_body_validate, 
+	tst_body_generate, 
+	NULL 
+};
+
+/* body opcode */
+
+const struct sieve_opcode body_opcode = { 
+	"body",
+	SIEVE_OPCODE_CUSTOM,
+	&body_extension,
+	0,
+	ext_body_opcode_dump, 
+	ext_body_opcode_execute 
+};
+
+enum tst_body_optional {	
+	OPT_END,
+	OPT_COMPARATOR,
+	OPT_MATCH_TYPE,
+	OPT_BODY_TRANSFORM
+};
+
+enum tst_body_transform {
+	TST_BODY_TRANSFORM_RAW,
+	TST_BODY_TRANSFORM_CONTENT,
+	TST_BODY_TRANSFORM_TEXT
+};
+
+/* 
+ * Custom command tags 
+ */
+
+static bool tag_body_transform_validate
+	(struct sieve_validator *validator, struct sieve_ast_argument **arg, 
+		struct sieve_command_context *cmd);
+static bool tag_body_transform_generate	
+	(struct sieve_generator *gentr, struct sieve_ast_argument *arg, 
+		struct sieve_command_context *cmd);
+ 
+static const struct sieve_argument body_raw_tag = { 
+	"raw", NULL, 
+	tag_body_transform_validate, 
+	NULL, 
+	tag_body_transform_generate 
+};
+
+static const struct sieve_argument body_content_tag = { 
+	"content", NULL, 
+	tag_body_transform_validate, 
+	NULL, 
+	tag_body_transform_generate 
+};
+
+static const struct sieve_argument body_text_tag = { 
+	"text", NULL, 
+	tag_body_transform_validate, 
+	NULL, 
+	tag_body_transform_generate
+};
+ 
+static bool tag_body_transform_validate
+(struct sieve_validator *validator, struct sieve_ast_argument **arg, 
+	struct sieve_command_context *cmd)
+{
+	enum tst_body_transform transform;
+	struct sieve_ast_argument *tag = *arg;
+
+	/* BODY-TRANSFORM:
+	 *   :raw
+   *     / :content <content-types: string-list>
+   *     / :text
+   */
+	if ( (bool) cmd->data ) {
+		sieve_command_validate_error(validator, cmd, 
+			"the :raw, :content and :text arguments for the body test are mutually "
+			"exclusive, but more than one was specified");
+		return FALSE;
+	}
+
+	/* Skip tag */
+	*arg = sieve_ast_argument_next(*arg);
+
+	/* :content tag has a string-list argument */
+	if ( tag->argument == &body_raw_tag ) 
+		transform = TST_BODY_TRANSFORM_RAW;
+		
+	else if ( tag->argument == &body_text_tag )
+		transform = TST_BODY_TRANSFORM_TEXT;
+		
+	else if ( tag->argument == &body_content_tag ) {
+		/* Check syntax:
+		 *   :content <content-types: string-list>
+		 */
+		if ( !sieve_validate_tag_parameter
+			(validator, cmd, tag, *arg, SAAT_STRING_LIST) ) {
+			return FALSE;
+		}
+		sieve_validator_argument_activate(validator, *arg);
+		
+		/* Assign tag parameters */
+		tag->parameters = *arg;
+		*arg = sieve_ast_arguments_detach(*arg,1);
+		
+		transform = TST_BODY_TRANSFORM_CONTENT;
+	} else 
+		return FALSE;
+	
+	/* Signal the presence of this tag */
+	cmd->data = (void *) TRUE;
+		
+	/* Assign context data */
+	tag->context = (void *) transform;	
+		
+	return TRUE;
+}
+
+/* 
+ * Command Registration 
+ */
+static bool tst_body_registered
+(struct sieve_validator *validator, struct sieve_command_registration *cmd_reg) 
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(validator, cmd_reg, OPT_COMPARATOR);
+	sieve_match_types_link_tags(validator, cmd_reg, OPT_MATCH_TYPE);
+	
+	sieve_validator_register_tag
+		(validator, cmd_reg, &body_raw_tag, OPT_BODY_TRANSFORM); 	
+	sieve_validator_register_tag
+		(validator, cmd_reg, &body_content_tag, OPT_BODY_TRANSFORM); 	
+	sieve_validator_register_tag
+		(validator, cmd_reg, &body_text_tag, OPT_BODY_TRANSFORM); 	
+	
+	return TRUE;
+}
+
+/* 
+ * Validation 
+ */
+ 
+static bool tst_body_validate
+(struct sieve_validator *validator, struct sieve_command_context *tst) 
+{ 		
+	struct sieve_ast_argument *arg = tst->first_positional;
+					
+	if ( !sieve_validate_positional_argument
+		(validator, tst, arg, "key list", 1, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+	sieve_validator_argument_activate(validator, arg);
+
+	/* Validate the key argument to a specified match type */
+	sieve_match_type_validate(validator, tst, arg);
+	
+	return TRUE;
+}
+
+/*
+ * Generation
+ */
+ 
+static bool tst_body_generate
+	(struct sieve_generator *gentr,	struct sieve_command_context *ctx) 
+{
+	(void)sieve_generator_emit_opcode_ext(gentr, &body_opcode, ext_body_my_id);
+
+	/* Generate arguments */
+	if ( !sieve_generate_arguments(gentr, ctx, NULL) )
+		return FALSE;
+
+	return TRUE;
+}
+
+static bool tag_body_transform_generate
+(struct sieve_generator *gentr, struct sieve_ast_argument *arg, 
+	struct sieve_command_context *cmd ATTR_UNUSED)
+{
+	struct sieve_binary *sbin = sieve_generator_get_binary(gentr);
+	enum tst_body_transform transform =	(enum tst_body_transform) arg->context;
+	
+	sieve_binary_emit_byte(sbin, transform);
+	sieve_generate_argument_parameters(gentr, cmd, arg); 
+			
+	return TRUE;
+}
+
+/* 
+ * Code dump 
+ */
+ 
+static bool ext_body_opcode_dump
+(const struct sieve_opcode *opcode ATTR_UNUSED, 
+	const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 1;
+	enum tst_body_transform transform;
+
+	sieve_code_dumpf(denv, "BODY");
+	sieve_code_descend(denv);
+
+	/* Handle any optional arguments */
+	if ( sieve_operand_optional_present(denv->sbin, address) ) {
+		while ( opt_code != 0 ) {
+			if ( !sieve_operand_optional_read(denv->sbin, address, &opt_code) ) 
+				return FALSE;
+
+			switch ( opt_code ) {
+			case 0:
+				break;
+			case OPT_COMPARATOR:
+				sieve_opr_comparator_dump(denv, address);
+				break;
+			case OPT_MATCH_TYPE:
+				sieve_opr_match_type_dump(denv, address);
+				break;
+			case OPT_BODY_TRANSFORM:
+				if ( !sieve_binary_read_byte(denv->sbin, address, &transform) )
+					return FALSE;
+				
+				switch ( transform ) {
+				case TST_BODY_TRANSFORM_RAW:
+					sieve_code_dumpf(denv, "BODY-TRANSFORM: RAW");
+					break;
+				case TST_BODY_TRANSFORM_TEXT:
+					sieve_code_dumpf(denv, "BODY-TRANSFORM: TEXT");
+					break;
+				case TST_BODY_TRANSFORM_CONTENT:
+					sieve_code_dumpf(denv, "BODY-TRANSFORM: CONTENT");
+					
+					sieve_code_descend(denv);
+					if ( !sieve_opr_stringlist_dump(denv, address) )
+						return FALSE;
+					sieve_code_ascend(denv);
+					break;
+				default:
+					return FALSE;
+				}
+				break;
+			default: 
+				return FALSE;
+			}
+ 		}
+	}
+
+	return
+		sieve_opr_stringlist_dump(denv, address);
+}
+
+static bool ext_body_opcode_execute
+(const struct sieve_opcode *opcode ATTR_UNUSED,
+	const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+	bool result = TRUE;
+	int opt_code = 1;
+	const struct sieve_comparator *cmp = &i_octet_comparator;
+	const struct sieve_match_type *mtch = &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 = { NULL };
+	struct ext_body_part *body_parts;
+	bool matched;
+	
+	printf("?? BODY\n");
+
+	/* Handle any optional arguments */
+	if ( sieve_operand_optional_present(renv->sbin, address) ) {
+		while ( opt_code != 0 ) {
+			if ( !sieve_operand_optional_read(renv->sbin, address, &opt_code) )
+				return FALSE;
+
+			switch ( opt_code ) {
+			case 0: 
+				break;
+			case OPT_COMPARATOR:
+				cmp = sieve_opr_comparator_read(renv->sbin, address);
+				break;
+			case OPT_MATCH_TYPE:
+				mtch = sieve_opr_match_type_read(renv->sbin, address);
+				break;
+			case OPT_BODY_TRANSFORM:
+				if ( !sieve_binary_read_byte(renv->sbin, address, &transform) ||
+					transform > TST_BODY_TRANSFORM_TEXT )
+					return FALSE;
+				
+				if ( transform == TST_BODY_TRANSFORM_CONTENT ) {				
+					if ( (ctype_list=sieve_opr_stringlist_read(renv->sbin, address)) 
+						== NULL )
+						return FALSE;
+				}
+				break;
+
+			default:
+				return FALSE;
+			}
+		}
+	}
+
+	t_push();
+		
+	/* Read key-list */
+	if ( (key_list=sieve_opr_stringlist_read(renv->sbin, address)) == NULL ) {
+		t_pop();
+		return FALSE;
+	}
+	
+	if ( ctype_list != NULL && !sieve_coded_stringlist_read_all
+    (ctype_list, pool_datastack_create(), &content_types) ) {
+  	t_pop();
+  	return FALSE;
+  }
+
+	if ( !ext_body_get_content
+		(renv, content_types, transform == TST_BODY_TRANSFORM_RAW, &body_parts) ) {
+		t_pop();
+		return FALSE;
+	}
+
+	mctx = sieve_match_begin(mtch, cmp, key_list); 	
+
+	/* Iterate through all requested body parts to match */
+	matched = FALSE;
+	while ( !matched && body_parts->content != NULL ) {	
+		if ( sieve_match_value(mctx, body_parts->content, body_parts->size) )
+			matched = TRUE;			
+		body_parts++;	
+	}
+
+	matched = sieve_match_end(mctx) || matched; 	
+	
+	t_pop();
+	
+	if ( result )
+		sieve_interpreter_set_test_result(renv->interp, matched);
+	
+	return result;
+}
diff --git a/src/lib-sieve/plugins/imapflags/tst-hasflag.c b/src/lib-sieve/plugins/imapflags/tst-hasflag.c
index 5b1f02fea..d2055428a 100644
--- a/src/lib-sieve/plugins/imapflags/tst-hasflag.c
+++ b/src/lib-sieve/plugins/imapflags/tst-hasflag.c
@@ -136,7 +136,7 @@ static bool tst_hasflag_validate
 	
 	/* Validate the key argument to a specified match type */
 	
-  sieve_match_type_validate(validator, tst, arg2);
+	sieve_match_type_validate(validator, tst, arg2);
 	
 	return TRUE;
 }
@@ -249,7 +249,7 @@ static bool tst_hasflag_opcode_execute
 	ext_imapflags_get_flags_init(&iter, renv->interp);
 	
 	while ( (flag=ext_imapflags_iter_get_flag(&iter)) != NULL ) {
-		if ( sieve_match_value(mctx, flag) )
+		if ( sieve_match_value(mctx, flag, strlen(flag)) )
 			matched = TRUE; 	
 	}
 
diff --git a/src/lib-sieve/sieve-address-parts.c b/src/lib-sieve/sieve-address-parts.c
index 1bf2c80b5..ec007f62e 100644
--- a/src/lib-sieve/sieve-address-parts.c
+++ b/src/lib-sieve/sieve-address-parts.c
@@ -360,7 +360,7 @@ bool sieve_address_match
 
 				part = addrp->extract_from(addr);
 			
-				if ( part != NULL && sieve_match_value(mctx, part) )
+				if ( part != NULL && sieve_match_value(mctx, part, strlen(part)) )
 					matched = TRUE;				
 			} 
 
diff --git a/src/lib-sieve/sieve-match-types.c b/src/lib-sieve/sieve-match-types.c
index e2ab77367..b2e013c43 100644
--- a/src/lib-sieve/sieve-match-types.c
+++ b/src/lib-sieve/sieve-match-types.c
@@ -405,7 +405,7 @@ struct sieve_match_context *sieve_match_begin
 }
 
 bool sieve_match_value
-	(struct sieve_match_context *mctx, const char *value)
+	(struct sieve_match_context *mctx, const char *value, size_t val_size)
 {
 	const struct sieve_match_type *mtch = mctx->match_type;
 	sieve_coded_stringlist_reset(mctx->key_list);
@@ -423,7 +423,7 @@ bool sieve_match_value
 			key_item != NULL ) 
 		{
 			if ( mtch->match
-				(mctx, value, strlen(value), str_c(key_item), 
+				(mctx, value, val_size, str_c(key_item), 
 					str_len(key_item), key_index) ) {
 				return TRUE;  
 			}
diff --git a/src/lib-sieve/sieve-match-types.h b/src/lib-sieve/sieve-match-types.h
index a68677e11..df976a090 100644
--- a/src/lib-sieve/sieve-match-types.h
+++ b/src/lib-sieve/sieve-match-types.h
@@ -111,7 +111,7 @@ struct sieve_match_context *sieve_match_begin
 (const struct sieve_match_type *mtch, const struct sieve_comparator *cmp,
     struct sieve_coded_stringlist *key_list);
 bool sieve_match_value
-    (struct sieve_match_context *mctx, const char *value);
+    (struct sieve_match_context *mctx, const char *value, size_t val_size);
 bool sieve_match_end(struct sieve_match_context *mctx);
 		
 #endif /* __SIEVE_MATCH_TYPES_H */
diff --git a/src/lib-sieve/tst-header.c b/src/lib-sieve/tst-header.c
index 340b90e39..8e9d6d4ec 100644
--- a/src/lib-sieve/tst-header.c
+++ b/src/lib-sieve/tst-header.c
@@ -215,7 +215,7 @@ static bool tst_header_opcode_execute
 			
 			int i;
 			for ( i = 0; !matched && headers[i] != NULL; i++ ) {
-				if ( sieve_match_value(mctx, headers[i]) )
+				if ( sieve_match_value(mctx, headers[i], strlen(headers[i])) )
 					matched = TRUE;				
 			} 
 		}
-- 
GitLab