From 4e78793dc6be6364a392cb77501ccb8bb458952b Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan@rename-it.nl>
Date: Mon, 14 Jul 2008 19:57:37 +0200
Subject: [PATCH] Built result print functions thus removing various printf()s.

---
 TODO                                          | 10 +--
 src/lib-sieve/cmd-discard.c                   |  6 +-
 src/lib-sieve/cmd-redirect.c                  |  6 +-
 src/lib-sieve/ext-reject.c                    |  6 +-
 src/lib-sieve/plugins/copy/ext-copy.c         |  7 +-
 .../imapflags/imapflags-implicit.sieve        | 29 +++++++
 src/lib-sieve/plugins/imapflags/tag-flags.c   | 61 ++++++++-------
 src/lib-sieve/plugins/vacation/cmd-vacation.c | 19 ++---
 src/lib-sieve/sieve-actions.c                 |  6 +-
 src/lib-sieve/sieve-actions.h                 |  6 +-
 src/lib-sieve/sieve-ast.h                     |  2 +-
 src/lib-sieve/sieve-common.h                  |  1 +
 src/lib-sieve/sieve-result.c                  | 75 ++++++++++++++++---
 src/lib-sieve/sieve-result.h                  | 18 ++++-
 src/lib-sieve/sieve-validator.c               |  6 +-
 src/lib-sieve/sieve.c                         |  4 +-
 src/lib-sieve/sieve.h                         |  4 +-
 src/sieve-bin/sieve-test.c                    |  9 ++-
 src/testsuite/testsuite.c                     |  7 +-
 19 files changed, 199 insertions(+), 83 deletions(-)
 create mode 100644 src/lib-sieve/plugins/imapflags/imapflags-implicit.sieve

diff --git a/TODO b/TODO
index 4aa084eff..abf1cf31e 100644
--- a/TODO
+++ b/TODO
@@ -1,18 +1,18 @@
 Next (in order of descending priority/precedence):
 * Get rid of all <stdio.h> printf()s in the library; use trace macro instead
+* Emit line numbers for certain action commands to provide useful runtime error
+  messages.
 * Revise extension support for comparators, match-types, address-parts and 
   side-effects. Current implementation emits redundant bytes and associated code
   is not very neat.
-* Emit line numbers for certain action commands to provide useful runtime error
-  messages.
 * Improve handling of old/corrupt binaries.
 
-* Full security review. Enforce limits on number of created objects, script 
-  size, execution time, etc...
 * Full standards compliance review for the engine and all fully implemented 
-  sieve extensions.
+  sieve extensions.\
 * Code cleanup 
 * Make sure cmusieve can be replaced seamlessly with the new plugin.
+* Full security review. Enforce limits on number of created objects, script 
+  size, execution time, etc...
 * Make simple test suite for the base functionality
 
 * ## MAKE A FIRST RELEASE ##
diff --git a/src/lib-sieve/cmd-discard.c b/src/lib-sieve/cmd-discard.c
index 42af9c156..f972c35d8 100644
--- a/src/lib-sieve/cmd-discard.c
+++ b/src/lib-sieve/cmd-discard.c
@@ -46,7 +46,7 @@ const struct sieve_operation cmd_discard_operation = {
 /* discard action */
 
 static void act_discard_print
-	(const struct sieve_action *action, struct sieve_result *result,
+	(const struct sieve_action *action, const struct sieve_result_print_env *rpenv,
 		void *context, bool *keep);	
 static bool act_discard_commit
 (const struct sieve_action *action, 
@@ -96,10 +96,10 @@ static bool cmd_discard_operation_execute
  
 static void act_discard_print
 (const struct sieve_action *action ATTR_UNUSED, 
-	struct sieve_result *result ATTR_UNUSED, void *context ATTR_UNUSED, 
+	const struct sieve_result_print_env *rpenv, void *context ATTR_UNUSED, 
 	bool *keep)	
 {
-	printf("* discard\n");
+	sieve_result_action_printf(rpenv, "discard");
 	
 	*keep = FALSE;
 }
diff --git a/src/lib-sieve/cmd-redirect.c b/src/lib-sieve/cmd-redirect.c
index 342bb87a4..46831a2a8 100644
--- a/src/lib-sieve/cmd-redirect.c
+++ b/src/lib-sieve/cmd-redirect.c
@@ -67,7 +67,7 @@ static int act_redirect_check_duplicate
 	(const struct sieve_runtime_env *renv,
 		const struct sieve_action *action1, void *context1, void *context2);
 static void act_redirect_print
-	(const struct sieve_action *action, struct sieve_result *result,
+	(const struct sieve_action *action, const struct sieve_result_print_env *rpenv,
 		void *context, bool *keep);	
 static bool act_redirect_commit
 	(const struct sieve_action *action ATTR_UNUSED, 
@@ -210,11 +210,11 @@ static int act_redirect_check_duplicate
 
 static void act_redirect_print
 (const struct sieve_action *action ATTR_UNUSED, 
-	struct sieve_result *result ATTR_UNUSED, void *context, bool *keep)	
+	const struct sieve_result_print_env *rpenv, void *context, bool *keep)	
 {
 	struct act_redirect_context *ctx = (struct act_redirect_context *) context;
 	
-	printf("* redirect message to: %s\n", ctx->to_address);
+	sieve_result_action_printf(rpenv, "redirect message to: %s", ctx->to_address);
 	
 	*keep = FALSE;
 }
diff --git a/src/lib-sieve/ext-reject.c b/src/lib-sieve/ext-reject.c
index bd044cbbc..69118395a 100644
--- a/src/lib-sieve/ext-reject.c
+++ b/src/lib-sieve/ext-reject.c
@@ -99,7 +99,7 @@ int act_reject_check_conflict
 	(const struct sieve_runtime_env *renv, const struct sieve_action *action,
     	const struct sieve_action *other_action, void *context);
 static void act_reject_print
-	(const struct sieve_action *action, struct sieve_result *result,
+	(const struct sieve_action *action, const struct sieve_result_print_env *rpenv,
 		void *context, bool *keep);	
 static bool act_reject_commit
 	(const struct sieve_action *action ATTR_UNUSED, 
@@ -256,11 +256,11 @@ int act_reject_check_conflict
  
 static void act_reject_print
 (const struct sieve_action *action ATTR_UNUSED, 
-	struct sieve_result *result ATTR_UNUSED, void *context, bool *keep)	
+	const struct sieve_result_print_env *rpenv, void *context, bool *keep)	
 {
 	struct act_reject_context *ctx = (struct act_reject_context *) context;
 	
-	printf("* reject message with reason: %s\n", ctx->reason);
+	sieve_result_action_printf(rpenv, "reject message with reason: %s", ctx->reason);
 	
 	*keep = FALSE;
 }
diff --git a/src/lib-sieve/plugins/copy/ext-copy.c b/src/lib-sieve/plugins/copy/ext-copy.c
index a7e659dee..27daac377 100644
--- a/src/lib-sieve/plugins/copy/ext-copy.c
+++ b/src/lib-sieve/plugins/copy/ext-copy.c
@@ -55,7 +55,7 @@ const struct sieve_side_effect_extension ext_copy_side_effect;
 
 static void seff_copy_print
 	(const struct sieve_side_effect *seffect, const struct sieve_action *action, 
-		struct sieve_result *result, void *se_context, bool *keep);
+		const struct sieve_result_print_env *rpenv, void *se_context, bool *keep);
 static bool seff_copy_post_commit
 	(const struct sieve_side_effect *seffect, const struct sieve_action *action, 
 		const struct sieve_action_exec_env *aenv, void *se_context,
@@ -126,10 +126,10 @@ static const struct sieve_argument copy_tag = {
 static void seff_copy_print
 (const struct sieve_side_effect *seffect ATTR_UNUSED, 
 	const struct sieve_action *action ATTR_UNUSED, 
-	struct sieve_result *result ATTR_UNUSED,
+	const struct sieve_result_print_env *rpenv,
 	void *se_context ATTR_UNUSED, bool *keep)
 {
-	printf("        + preserve implicit keep\n");
+	sieve_result_seffect_printf(rpenv, "preserve implicit keep");
 
 	*keep = TRUE;
 }
@@ -140,7 +140,6 @@ static bool seff_copy_post_commit
 	const struct sieve_action_exec_env *aenv ATTR_UNUSED, 
 		void *se_context ATTR_UNUSED,	void *tr_context ATTR_UNUSED, bool *keep)
 {	
-	sieve_result_log(aenv, "implicit keep preserved after %s action.", action->name);
 	*keep = TRUE;
 	return TRUE;
 }
diff --git a/src/lib-sieve/plugins/imapflags/imapflags-implicit.sieve b/src/lib-sieve/plugins/imapflags/imapflags-implicit.sieve
new file mode 100644
index 000000000..8754fad5c
--- /dev/null
+++ b/src/lib-sieve/plugins/imapflags/imapflags-implicit.sieve
@@ -0,0 +1,29 @@
+require "imap4flags";
+require "fileinto";
+require "relational";
+require "comparator-i;ascii-numeric";
+require "copy";
+
+setflag "\\Seen";
+addflag "$DSNRequired";
+removeflag "$DSNRequired";
+
+if header :contains "from" "boss@frobnitzm.example.edu" {
+	setflag "\\Flagged";
+	fileinto :copy "From Boss";
+}
+
+if header :contains "Disposition-Notification-To" "mel@example.com" {
+	addflag "$MDNRequired";
+}
+
+if header :contains "from" "imap@cac.washington.example.edu" {
+	removeflag "$MDNRequired \\Flagged \\Seen \\Deleted";
+	fileinto :copy "imap-list";
+}
+
+if hasflag :count "ge" :comparator "i;ascii-numeric" "2" {
+	fileinto :copy "imap-twoflags";
+}
+
+fileinto :copy :flags "MDNRequired \\Draft" "imap-draft";
diff --git a/src/lib-sieve/plugins/imapflags/tag-flags.c b/src/lib-sieve/plugins/imapflags/tag-flags.c
index 5f1e6c5b8..dd062ab28 100644
--- a/src/lib-sieve/plugins/imapflags/tag-flags.c
+++ b/src/lib-sieve/plugins/imapflags/tag-flags.c
@@ -42,8 +42,8 @@ static bool seff_flags_read_context
 		const struct sieve_runtime_env *renv, sieve_size_t *address,
 		void **se_context);
 static void seff_flags_print
-	(const struct sieve_side_effect *seffect,	const struct sieve_action *action,
-		struct sieve_result *result, void *se_context, bool *keep);
+	(const struct sieve_side_effect *seffect, const struct sieve_action *action,
+		const struct sieve_result_print_env *rpenv, void *se_context, bool *keep);
 static bool seff_flags_pre_execute
 	(const struct sieve_side_effect *seffect, const struct sieve_action *action, 
 		const struct sieve_action_exec_env *aenv, 
@@ -168,7 +168,10 @@ static bool seff_flags_read_context
 		while ( (flag=ext_imapflags_iter_get_flag(&flit)) != NULL ) {		
 			if (flag != NULL && *flag != '\\') {
 				/* keyword */
-				array_append(&ctx->keywords, &flag, 1);
+				const char *keyword = p_strdup(pool, flag);
+
+				array_append(&ctx->keywords, &keyword, 1);
+
 			} else {
 				/* system flag */
 				if (flag == NULL || strcasecmp(flag, "\\flagged") == 0)
@@ -210,7 +213,8 @@ static struct seff_flags_context *seff_flags_get_implicit_context
 	while ( (flag=ext_imapflags_iter_get_flag(&flit)) != NULL ) {		
 		if (flag != NULL && *flag != '\\') {
 			/* keyword */
-			array_append(&ctx->keywords, &flag, 1);
+			const char *keyword = p_strdup(pool, flag);
+			array_append(&ctx->keywords, &keyword, 1);
 		} else {
 			/* system flag */
 			if (flag == NULL || strcasecmp(flag, "\\flagged") == 0)
@@ -234,40 +238,43 @@ static struct seff_flags_context *seff_flags_get_implicit_context
 static void seff_flags_print
 (const struct sieve_side_effect *seffect ATTR_UNUSED, 
 	const struct sieve_action *action ATTR_UNUSED, 
-	struct sieve_result *result,
-	void *se_context ATTR_UNUSED, bool *keep ATTR_UNUSED)
+	const struct sieve_result_print_env *rpenv,
+	void *se_context, bool *keep ATTR_UNUSED)
 {
+	struct sieve_result *result = rpenv->result;
 	struct seff_flags_context *ctx = (struct seff_flags_context *) se_context;
 	unsigned int i;
 	
 	if ( ctx == NULL )
 		ctx = seff_flags_get_implicit_context(result);
 	
-	if ( ctx->flags != 0 || array_count(&ctx->keywords) ) { 
-		printf("        + add flags:");
-
-		if ( (ctx->flags & MAIL_FLAGGED) > 0 )
-			printf(" \\flagged\n");
-
-		if ( (ctx->flags & MAIL_ANSWERED) > 0 )
-			printf(" \\answered");
+	if ( ctx->flags != 0 || array_count(&ctx->keywords) > 0 ) {
+		T_BEGIN {
+			string_t *flags = t_str_new(128);
+ 
+			if ( (ctx->flags & MAIL_FLAGGED) > 0 )
+				str_printfa(flags, " \\flagged\n");
+
+			if ( (ctx->flags & MAIL_ANSWERED) > 0 )
+				str_printfa(flags, " \\answered");
 		
-		if ( (ctx->flags & MAIL_DELETED) > 0 )
-			printf(" \\deleted");
+			if ( (ctx->flags & MAIL_DELETED) > 0 )
+				str_printfa(flags, " \\deleted");
 					
-		if ( (ctx->flags & MAIL_SEEN) > 0 )
-			printf(" \\seen");
-		
-		if ( (ctx->flags & MAIL_DRAFT) > 0 )
-			printf(" \\draft");
+			if ( (ctx->flags & MAIL_SEEN) > 0 )
+				str_printfa(flags, " \\seen");
+			
+			if ( (ctx->flags & MAIL_DRAFT) > 0 )
+				str_printfa(flags, " \\draft");
+
+			for ( i = 0; i < array_count(&ctx->keywords); i++ ) {
+				const char *const *keyword = array_idx(&ctx->keywords, i);
+				str_printfa(flags, " %s", *keyword);
+			}
 
-		for ( i = 0; i < array_count(&ctx->keywords); i++ ) {
-			const char *const *keyword = array_idx(&ctx->keywords, i);
-			printf(" %s", *keyword);
-		};
+			sieve_result_seffect_printf(rpenv, "add flags:%s", str_c(flags));
+		} T_END;
 	}
-	
-	printf("\n");
 }
 
 static bool seff_flags_pre_execute
diff --git a/src/lib-sieve/plugins/vacation/cmd-vacation.c b/src/lib-sieve/plugins/vacation/cmd-vacation.c
index ac7b0c30f..7fee60230 100644
--- a/src/lib-sieve/plugins/vacation/cmd-vacation.c
+++ b/src/lib-sieve/plugins/vacation/cmd-vacation.c
@@ -56,7 +56,7 @@ int act_vacation_check_conflict
 	(const struct sieve_runtime_env *renv, const struct sieve_action *action,
 		const struct sieve_action *other_action, void *context);
 static void act_vacation_print
-	(const struct sieve_action *action, struct sieve_result *result,
+	(const struct sieve_action *action, const struct sieve_result_print_env *rpenv,
 		void *context, bool *keep);	
 static bool act_vacation_commit
 	(const struct sieve_action *action,	const struct sieve_action_exec_env *aenv, 
@@ -469,20 +469,21 @@ int act_vacation_check_conflict
 }
  
 static void act_vacation_print
-(const struct sieve_action *action ATTR_UNUSED, struct sieve_result *result ATTR_UNUSED,
-	void *context, bool *keep ATTR_UNUSED)	
+(const struct sieve_action *action ATTR_UNUSED, 
+	const struct sieve_result_print_env *rpenv, void *context, 
+	bool *keep ATTR_UNUSED)	
 {
 	struct act_vacation_context *ctx = (struct act_vacation_context *) context;
 	
-	printf( 	"* send vacation message:\n"
-						"    => days   : %d\n", ctx->days);
+	sieve_result_action_printf( rpenv, "send vacation message:");
+	sieve_result_printf(rpenv, "    => days   : %d\n", ctx->days);
 	if ( ctx->subject != NULL )
-		printf(	"    => subject: %s\n", ctx->subject);
+		sieve_result_printf(rpenv, "    => subject: %s\n", ctx->subject);
 	if ( ctx->from != NULL )
-		printf(	"    => from   : %s\n", ctx->from);
+		sieve_result_printf(rpenv, "    => from   : %s\n", ctx->from);
 	if ( ctx->handle != NULL )
-		printf(	"    => handle : %s\n", ctx->handle);
-	printf(		"\nSTART MESSAGE\n%s\nEND MESSAGE\n", ctx->reason);
+		sieve_result_printf(rpenv, "    => handle : %s\n", ctx->handle);
+	sieve_result_printf(rpenv, "\nSTART MESSAGE\n%s\nEND MESSAGE\n", ctx->reason);
 }
 
 static const char * const _list_headers[] = {
diff --git a/src/lib-sieve/sieve-actions.c b/src/lib-sieve/sieve-actions.c
index c5cb0f7e7..cae579a3d 100644
--- a/src/lib-sieve/sieve-actions.c
+++ b/src/lib-sieve/sieve-actions.c
@@ -183,7 +183,7 @@ static int act_store_check_duplicate
 	(const struct sieve_runtime_env *renv, const struct sieve_action *action1, 
 		void *context1, void *context2);
 static void act_store_print
-	(const struct sieve_action *action, struct sieve_result *result,
+	(const struct sieve_action *action, const struct sieve_result_print_env *rpenv,
 		void *context, bool *keep);
 
 static bool act_store_start
@@ -244,11 +244,11 @@ static int act_store_check_duplicate
 
 static void act_store_print
 (const struct sieve_action *action ATTR_UNUSED, 
-	struct sieve_result *result ATTR_UNUSED, void *context, bool *keep)	
+	const struct sieve_result_print_env *rpenv, void *context, bool *keep)	
 {
 	struct act_store_context *ctx = (struct act_store_context *) context;
 	
-	printf("* store message in folder: %s\n", ctx->folder);
+	sieve_result_action_printf(rpenv, "store message in folder: %s", ctx->folder);
 	
 	*keep = FALSE;
 }
diff --git a/src/lib-sieve/sieve-actions.h b/src/lib-sieve/sieve-actions.h
index b17ffe838..2f40d5a37 100644
--- a/src/lib-sieve/sieve-actions.h
+++ b/src/lib-sieve/sieve-actions.h
@@ -34,8 +34,8 @@ struct sieve_action {
 			void *context);
 
 	void (*print)
-		(const struct sieve_action *action, struct sieve_result *result, 
-			void *context, bool *keep);	
+		(const struct sieve_action *action, 
+			const struct sieve_result_print_env *penv, void *context, bool *keep);	
 		
 	bool (*start)
 		(const struct sieve_action *action, 
@@ -73,7 +73,7 @@ struct sieve_side_effect {
 			
 	void (*print)
 		(const struct sieve_side_effect *seffect, const struct sieve_action *action, 
-			struct sieve_result *result, void *se_context, bool *keep);
+			const struct sieve_result_print_env *penv, void *se_context, bool *keep);
 
 	bool (*pre_execute)
 		(const struct sieve_side_effect *seffect, const struct sieve_action *action, 
diff --git a/src/lib-sieve/sieve-ast.h b/src/lib-sieve/sieve-ast.h
index ca76da300..0e0b5758a 100644
--- a/src/lib-sieve/sieve-ast.h
+++ b/src/lib-sieve/sieve-ast.h
@@ -82,7 +82,7 @@ struct sieve_ast_argument {
 
 	/* Argument associated with this ast element  */
 	const struct sieve_argument *argument;
-	unsigned int arg_id_code;
+	int arg_id_code;
 
 	/* Parameters to this (tag) argument */
 	struct sieve_ast_argument *parameters;
diff --git a/src/lib-sieve/sieve-common.h b/src/lib-sieve/sieve-common.h
index 7ce1f2360..a8a49af02 100644
--- a/src/lib-sieve/sieve-common.h
+++ b/src/lib-sieve/sieve-common.h
@@ -84,6 +84,7 @@ struct sieve_address_part;
 /* sieve-result.h */
 struct sieve_result;
 struct sieve_side_effects_list;
+struct sieve_result_print_env;
 
 /* sieve-actions.h */
 struct sieve_action_exec_env;
diff --git a/src/lib-sieve/sieve-result.c b/src/lib-sieve/sieve-result.c
index a27dd83f7..8eab847c3 100644
--- a/src/lib-sieve/sieve-result.c
+++ b/src/lib-sieve/sieve-result.c
@@ -1,6 +1,8 @@
 #include "lib.h"
 #include "mempool.h"
+#include "ostream.h"
 #include "hash.h"
+#include "str.h"
 #include "strfuncs.h"
 #include "str-sanitize.h"
 
@@ -289,25 +291,77 @@ int sieve_result_add_action
 	}
 	
 	return 0;
-}	
+}
+
+/*
+ * Result printing
+ */
+
+void sieve_result_printf
+(const struct sieve_result_print_env *penv, const char *fmt, ...)
+{	
+	string_t *outbuf = t_str_new(128);
+	va_list args;
+	
+	va_start(args, fmt);	
+	str_vprintfa(outbuf, fmt, args);
+	va_end(args);
+	
+	o_stream_send(penv->stream, str_data(outbuf), str_len(outbuf));
+}
 
-bool sieve_result_print(struct sieve_result *result)
+void sieve_result_action_printf
+(const struct sieve_result_print_env *penv, const char *fmt, ...)
+{	
+	string_t *outbuf = t_str_new(128);
+	va_list args;
+	
+	va_start(args, fmt);	
+	str_append(outbuf, "\n * ");
+	str_vprintfa(outbuf, fmt, args);
+	str_append_c(outbuf, '\n');
+	va_end(args);
+	
+	o_stream_send(penv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+void sieve_result_seffect_printf
+(const struct sieve_result_print_env *penv, const char *fmt, ...)
+{	
+	string_t *outbuf = t_str_new(128);
+	va_list args;
+	
+	va_start(args, fmt);	
+	str_append(outbuf, "        + ");
+	str_vprintfa(outbuf, fmt, args);
+	str_append_c(outbuf, '\n');
+	va_end(args);
+	
+	o_stream_send(penv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+bool sieve_result_print
+(struct sieve_result *result, struct ostream *stream)
 {
+	struct sieve_result_print_env penv;
 	bool implicit_keep = TRUE;
-	struct sieve_result_action *rac = result->first_action;
+	struct sieve_result_action *rac;
+	
+	penv.result = result;
+	penv.stream = stream;
 	
-	printf("\nPerformed actions:\n");
+	sieve_result_printf(&penv, "\nPerformed actions:\n");
+	rac = result->first_action;
 	while ( rac != NULL ) {		
 		bool keep = TRUE;
 		const struct sieve_action *act = rac->action;
 		struct sieve_result_side_effect *rsef;
 		const struct sieve_side_effect *sef;
 
-	
 		if ( act->print != NULL ) {
-			act->print(act, result, rac->context, &keep);
+			act->print(act, &penv, rac->context, &keep);
 		} else {
-			printf("* %s\n", act->name); 
+			sieve_result_action_printf(&penv, act->name); 
 		}
 	
 		/* Print side effects */
@@ -316,15 +370,16 @@ bool sieve_result_print(struct sieve_result *result)
 			sef = rsef->seffect;
 			if ( sef->print != NULL ) 
 				sef->print
-					(sef, act, result, rsef->context, &keep);
+					(sef, act, &penv, rsef->context, &keep);
 			rsef = rsef->next;
 		}
-
+			
 		implicit_keep = implicit_keep && keep;		
 		rac = rac->next;	
 	}
 	
-	printf("\nImplicit keep: %s\n", implicit_keep ? "yes" : "no");
+	sieve_result_printf
+		(&penv, "\nImplicit keep: %s\n", implicit_keep ? "yes" : "no");
 	
 	return TRUE;
 }
diff --git a/src/lib-sieve/sieve-result.h b/src/lib-sieve/sieve-result.h
index f5917cc8a..d476326ec 100644
--- a/src/lib-sieve/sieve-result.h
+++ b/src/lib-sieve/sieve-result.h
@@ -18,6 +18,22 @@ void sieve_result_extension_set_context
 const void *sieve_result_extension_get_context
 	(struct sieve_result *result, int ext_id); 
 
+/* Printing */
+
+struct sieve_result_print_env {
+	struct sieve_result *result;
+	struct ostream *stream;
+};
+
+void sieve_result_printf
+	(const struct sieve_result_print_env *penv, const char *fmt, ...);
+void sieve_result_action_printf
+	(const struct sieve_result_print_env *penv, const char *fmt, ...);
+void sieve_result_seffect_printf
+	(const struct sieve_result_print_env *penv, const char *fmt, ...);
+
+bool sieve_result_print(struct sieve_result *result, struct ostream *stream);
+
 /* Error handling */
 
 void sieve_result_log
@@ -36,8 +52,6 @@ int sieve_result_add_action
 	const struct sieve_action *action, struct sieve_side_effects_list *seffects,
 	void *context);
 
-bool sieve_result_print(struct sieve_result *result);
-
 void sieve_result_cancel_implicit_keep(struct sieve_result *result);
 
 int sieve_result_execute
diff --git a/src/lib-sieve/sieve-validator.c b/src/lib-sieve/sieve-validator.c
index 505d9703a..aaa3caa7b 100644
--- a/src/lib-sieve/sieve-validator.c
+++ b/src/lib-sieve/sieve-validator.c
@@ -371,7 +371,7 @@ static void sieve_validator_register_unknown_tag
 
 static const struct sieve_argument *sieve_validator_find_tag
 (struct sieve_validator *validator, struct sieve_command_context *cmd, 
-	struct sieve_ast_argument *arg, unsigned int *id_code) 
+	struct sieve_ast_argument *arg, int *id_code) 
 {
 	const char *tag;
 	unsigned int i;
@@ -609,7 +609,7 @@ static bool sieve_validate_command_arguments
 		
 	/* Visit tagged and optional arguments */
 	while ( sieve_ast_argument_type(arg) == SAAT_TAG ) {
-		unsigned int id_code;
+		int id_code;
 		struct sieve_ast_argument *parg; 
 		const struct sieve_argument *tag = 
 			sieve_validator_find_tag(validator, cmd, arg, &id_code);
@@ -639,7 +639,7 @@ static bool sieve_validate_command_arguments
 		parg = sieve_ast_argument_prev(arg);
 		while ( parg != NULL ) {
 			if ( (sieve_ast_argument_type(parg) == SAAT_TAG && parg->argument == tag) 
-				|| parg->arg_id_code == id_code ) 
+				|| (id_code > 0 && parg->arg_id_code == id_code) ) 
 			{
 				const char *tag_id = sieve_ast_argument_tag(arg);
 				const char *tag_desc =
diff --git a/src/lib-sieve/sieve.c b/src/lib-sieve/sieve.c
index 15304adf2..688d1211f 100644
--- a/src/lib-sieve/sieve.c
+++ b/src/lib-sieve/sieve.c
@@ -189,7 +189,7 @@ void sieve_dump(struct sieve_binary *sbin, struct ostream *stream)
 
 int sieve_test
 	(struct sieve_binary *sbin, const struct sieve_message_data *msgdata,
-		const struct sieve_script_env *senv, 
+		const struct sieve_script_env *senv, struct ostream *stream,
 		struct sieve_error_handler *ehandler) 	
 {
 	struct sieve_result *sres = sieve_result_create(ehandler);
@@ -200,7 +200,7 @@ int sieve_test
 	ret = sieve_interpreter_run(interp, msgdata, senv, &sres);
 	
 	if ( ret > 0 ) 
-		ret = sieve_result_print(sres);
+		ret = sieve_result_print(sres, stream);
 	
 	sieve_interpreter_free(&interp);
 	sieve_result_unref(&sres);
diff --git a/src/lib-sieve/sieve.h b/src/lib-sieve/sieve.h
index a81c79a7a..444d9f6e5 100644
--- a/src/lib-sieve/sieve.h
+++ b/src/lib-sieve/sieve.h
@@ -88,11 +88,11 @@ bool sieve_save
 
 /* sieve_test:
  *
- *   Executes the bytecode, but only prints the result to stdout.
+ *   Executes the bytecode, but only prints the result to the given stream.
  */ 
 int sieve_test
 	(struct sieve_binary *sbin, const struct sieve_message_data *msgdata, 
-		const struct sieve_script_env *senv, 
+		const struct sieve_script_env *senv, struct ostream *stream,
 		struct sieve_error_handler *ehandler);
 
 /* sieve_execute:
diff --git a/src/sieve-bin/sieve-test.c b/src/sieve-bin/sieve-test.c
index 35ac5a398..d6b63f5f5 100644
--- a/src/sieve-bin/sieve-test.c
+++ b/src/sieve-bin/sieve-test.c
@@ -37,6 +37,7 @@ int main(int argc, char **argv)
 	struct sieve_message_data msgdata;
 	struct sieve_script_env scriptenv;
 	struct sieve_error_handler *ehandler;
+	struct ostream *teststream;
 	bool force_compile = FALSE;
 
 	bin_init();
@@ -128,9 +129,13 @@ int main(int argc, char **argv)
 	scriptenv.username = user;
 
 	ehandler = sieve_stderr_ehandler_create(0);	
-	
+
+	teststream = o_stream_create_fd(1, 0, FALSE);	
+
 	/* Run the test */
-	(void) sieve_test(sbin, &msgdata, &scriptenv, ehandler);
+	(void) sieve_test(sbin, &msgdata, &scriptenv, teststream, ehandler);
+
+	o_stream_destroy(&teststream);
 
 	sieve_close(&sbin);
 	sieve_error_handler_unref(&ehandler);
diff --git a/src/testsuite/testsuite.c b/src/testsuite/testsuite.c
index 51b5e0116..df45c84d6 100644
--- a/src/testsuite/testsuite.c
+++ b/src/testsuite/testsuite.c
@@ -143,6 +143,7 @@ int main(int argc, char **argv)
 	struct sieve_binary *sbin;
 	struct sieve_script_env scriptenv;
 	struct sieve_error_handler *ehandler;
+	struct ostream *teststream;
 
 	testsuite_init();
 
@@ -183,9 +184,13 @@ int main(int argc, char **argv)
 	scriptenv.username = user;
 
 	ehandler = sieve_stderr_ehandler_create(0);	
+
+	teststream = o_stream_create_fd(1, 0, FALSE);
 	
 	/* Run the test */
-	(void) sieve_test(sbin, &testsuite_msgdata, &scriptenv, ehandler);
+	(void) sieve_test(sbin, &testsuite_msgdata, &scriptenv, teststream, ehandler);
+
+	o_stream_destroy(&teststream);
 
 	sieve_close(&sbin);
 	sieve_error_handler_unref(&ehandler);
-- 
GitLab