From f2a167f73b7421c8bf741e70196b56f52015f6c7 Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan@rename-it.nl>
Date: Tue, 20 Oct 2009 22:54:11 +0200
Subject: [PATCH] Notify (deprecated): added support for the $text$
 substitution.

---
 src/lib-sieve/plugins/notify/cmd-notify.c     |  75 +-----
 .../plugins/notify/ext-notify-common.c        | 228 ++++++++++++++++++
 .../plugins/notify/ext-notify-common.h        |   8 +
 tests/deprecated/notify/mailto.svtest         | 162 +++++++++++++
 4 files changed, 399 insertions(+), 74 deletions(-)

diff --git a/src/lib-sieve/plugins/notify/cmd-notify.c b/src/lib-sieve/plugins/notify/cmd-notify.c
index e5939ddd1..bb539a56e 100644
--- a/src/lib-sieve/plugins/notify/cmd-notify.c
+++ b/src/lib-sieve/plugins/notify/cmd-notify.c
@@ -418,79 +418,6 @@ static bool cmd_notify_operation_dump
  * Code execution
  */
 
-static void cmd_notify_construct_message
-(const struct sieve_runtime_env *renv, const char *msg_format, 
-	string_t *out_msg)
-{
-	const struct sieve_message_data *msgdata = renv->msgdata;
-	const char *p;
-
-	if ( msg_format == NULL )
-		msg_format = "$from$: $subject$";
- 
-	/* Scan message for substitutions */
-	p = msg_format;
-	while ( *p != '\0' ) {
-		const char *const *header;
-
-		if ( strncasecmp(p, "$from$", 6) == 0 ) {
-			p += 6;
-		
-			/* Fetch sender from oriinal message */
-			if ( mail_get_headers_utf8(msgdata->mail, "from", &header) >= 0 )
-				 str_append(out_msg, header[0]); 
-
-		} else if ( strncasecmp(p, "$env-from$", 10) == 0 ) {
-			p += 10;
-
-			if ( msgdata->return_path != NULL ) 
-				str_append(out_msg, msgdata->return_path);
-
-		} else if ( strncasecmp(p, "$subject$", 9) == 0 ) {	
-			p += 9;
-
-			/* Fetch sender from oriinal message */
-			if ( mail_get_headers_utf8(msgdata->mail, "subject", &header) >= 0 )
-				 str_append(out_msg, header[0]); 
-			
-		} else if ( strncasecmp(p, "$text", 5) == 0 
-			&& (p[5] == '[' || p[5] == '$') ) {
-			size_t num = 0;
-			const char *begin = p;
-			bool valid = TRUE;
-
-			p += 5;
-			if ( *p == '[' ) {
-				p += 1;
-
-				while ( i_isdigit(*p) ) {
-					num = num * 10 + (*p - '0');
-					p++;
-				}
-
-				if ( *p++ != ']' || *p++ != '$' ) {
-					str_append_n(out_msg, begin, p-begin);
-					valid = FALSE;										
-				}	    	
-			} else {
-				p += 1;			
-			}
-
-			if ( valid ) {
-				str_append(out_msg, "<body extraction not supported>");
-			}
-		} else {
-			size_t len;
-
-			/* Find next substitution */
-			len = strcspn(p + 1, "$") + 1; 
-
-			/* Copy normal text */
-			str_append_n(out_msg, p, len);
-			p += len;
-		}
-  }
-}
  
 static int cmd_notify_operation_execute
 (const struct sieve_operation *op ATTR_UNUSED,
@@ -584,7 +511,7 @@ static int cmd_notify_operation_execute
 		/* Process message */
 
 		out_message = t_str_new(1024);
-		cmd_notify_construct_message
+		ext_notify_construct_message
 			(renv, (message == NULL ? NULL : str_c(message)), out_message);
 		act->message = p_strdup(pool, str_c(out_message));
 		
diff --git a/src/lib-sieve/plugins/notify/ext-notify-common.c b/src/lib-sieve/plugins/notify/ext-notify-common.c
index 1c4f8de83..b4d48c7c8 100644
--- a/src/lib-sieve/plugins/notify/ext-notify-common.c
+++ b/src/lib-sieve/plugins/notify/ext-notify-common.c
@@ -2,9 +2,14 @@
  */
 
 #include "lib.h"
+#include "str.h"
+#include "rfc822-parser.h"
+#include "message-parser.h"
+#include "message-decoder.h"
 
 #include "sieve-common.h"
 #include "sieve-code.h"
+#include "sieve-message.h"
 #include "sieve-extensions.h"
 #include "sieve-commands.h"
 #include "sieve-actions.h"
@@ -16,6 +21,8 @@
 
 #include "ext-notify-common.h"
 
+#include <ctype.h>
+
 /*
  * Importance argument
  */
@@ -75,3 +82,224 @@ void ext_notify_register_importance_tags
 	sieve_validator_register_tag(valdtr, cmd_reg, &importance_high_tag, id_code);
 }
 
+/*
+ * Body extraction
+ */
+
+/* FIXME: overlaps somewhat with body extension */
+
+struct ext_notify_message_context {
+	pool_t pool;
+	buffer_t *body_text;
+};
+
+static struct ext_notify_message_context *ext_notify_get_message_context
+(struct sieve_message_context *msgctx)
+{
+	struct ext_notify_message_context *ctx;
+	
+	/* Get message context (contains cached message body information) */
+	ctx = (struct ext_notify_message_context *)
+		sieve_message_context_extension_get(msgctx, &notify_extension);
+	
+	/* Create it if it does not exist already */
+	if ( ctx == NULL ) {
+		pool_t pool = sieve_message_context_pool(msgctx);
+		ctx = p_new(pool, struct ext_notify_message_context, 1);	
+		ctx->pool = pool;
+		ctx->body_text = NULL;
+
+		/* Register context */
+		sieve_message_context_extension_set
+			(msgctx, &notify_extension, (void *) ctx);
+	}
+	
+	return ctx;
+}
+
+static bool _is_text_content(const struct message_header_line *hdr)
+{
+	struct rfc822_parser_context parser;
+	string_t *content_type;
+	const char *data;
+
+	/* Initialize parsing */
+	rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+	(void)rfc822_skip_lwsp(&parser);
+
+	/* Parse content type */
+	content_type = t_str_new(64);
+	if (rfc822_parse_content_type(&parser, content_type) < 0)
+		return "";
+
+	/* Content-type value must end here, otherwise it is invalid after all */
+	(void)rfc822_skip_lwsp(&parser);
+	if ( parser.data != parser.end && *parser.data != ';' )
+		return "";
+
+	/* Success */
+	data = str_c(content_type);
+	if ( strncmp(data, "text", 4) == 0 && data[4] == '/' ) {
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static buffer_t *cmd_notify_extract_body_text
+(const struct sieve_runtime_env *renv)
+{ 
+	struct ext_notify_message_context *mctx;
+	struct message_parser_ctx *parser;
+	struct message_decoder_context *decoder;
+	struct message_part *parts;
+	struct message_block block, decoded;
+	struct istream *input;
+	bool is_text, save_body;
+	int ret;
+	
+	/* Return cached result if available */
+	mctx = ext_notify_get_message_context(renv->msgctx);
+	if ( mctx->body_text != NULL ) {
+		return mctx->body_text;	
+	}
+
+	/* Create buffer */
+	mctx->body_text = buffer_create_dynamic(mctx->pool, 1024*64);
+
+	/* Get the message stream */
+	if ( mail_get_stream(renv->msgdata->mail, NULL, NULL, &input) < 0 )
+		return FALSE;
+			
+	/* Initialize body decoder */
+	decoder = message_decoder_init(FALSE);
+	
+	parser = message_parser_init(mctx->pool, input, 0, 0);
+	is_text = TRUE;
+	while ( (ret = message_parser_parse_next_block(parser, &block)) > 0 ) {		
+		if ( block.hdr != NULL || block.size == 0 ) {
+			/* Decode block */
+			(void)message_decoder_decode_next_block(decoder, &block, &decoded);
+
+			/* Check for end of headers */
+			if ( block.hdr == NULL ) {
+				save_body = is_text;
+				continue;
+			}
+							
+			/* We're interested of only Content-Type: header */
+			if ( strcasecmp(block.hdr->name, "Content-Type" ) != 0)
+				continue;
+
+			/* Header can have folding whitespace. Acquire the full value before 
+			 * continuing
+			 */
+			if ( block.hdr->continues ) {
+				block.hdr->use_full_value = TRUE;
+				continue;
+			}
+		
+			/* Is it a text part? */
+			T_BEGIN {
+				is_text = _is_text_content(block.hdr);
+			} T_END;
+
+			continue;
+		}
+
+		/* Read text body */
+		if ( save_body ) {
+			(void)message_decoder_decode_next_block(decoder, &block, &decoded);
+			buffer_append(mctx->body_text, decoded.data, decoded.size);
+			is_text = TRUE;			
+		}
+	}
+
+	/* Cleanup */
+	(void)message_parser_deinit(&parser, &parts);
+	message_decoder_deinit(&decoder);
+	
+	/* Return status */
+	return mctx->body_text;
+}
+
+void ext_notify_construct_message
+(const struct sieve_runtime_env *renv, const char *msg_format, 
+	string_t *out_msg)
+{
+	const struct sieve_message_data *msgdata = renv->msgdata;
+	const char *p;
+
+	if ( msg_format == NULL )
+		msg_format = "$from$: $subject$";
+ 
+	/* Scan message for substitutions */
+	p = msg_format;
+	while ( *p != '\0' ) {
+		const char *const *header;
+
+		if ( strncasecmp(p, "$from$", 6) == 0 ) {
+			p += 6;
+		
+			/* Fetch sender from oriinal message */
+			if ( mail_get_headers_utf8(msgdata->mail, "from", &header) >= 0 )
+				 str_append(out_msg, header[0]); 
+
+		} else if ( strncasecmp(p, "$env-from$", 10) == 0 ) {
+			p += 10;
+
+			if ( msgdata->return_path != NULL ) 
+				str_append(out_msg, msgdata->return_path);
+
+		} else if ( strncasecmp(p, "$subject$", 9) == 0 ) {	
+			p += 9;
+
+			/* Fetch sender from oriinal message */
+			if ( mail_get_headers_utf8(msgdata->mail, "subject", &header) >= 0 )
+				 str_append(out_msg, header[0]); 
+			
+		} else if ( strncasecmp(p, "$text", 5) == 0 
+			&& (p[5] == '[' || p[5] == '$') ) {
+			size_t num = 0;
+			const char *begin = p;
+			bool valid = TRUE;
+
+			p += 5;
+			if ( *p == '[' ) {
+				p += 1;
+
+				while ( i_isdigit(*p) ) {
+					num = num * 10 + (*p - '0');
+					p++;
+				}
+
+				if ( *p++ != ']' || *p++ != '$' ) {
+					str_append_n(out_msg, begin, p-begin);
+					valid = FALSE;										
+				}	    	
+			} else {
+				p += 1;			
+			}
+
+			if ( valid ) {
+				size_t body_size;
+				const char *body_text = (const char *)
+					buffer_get_data(cmd_notify_extract_body_text(renv), &body_size);
+
+				if ( num > 0 && num < body_size) 
+					str_append_n(out_msg, body_text, num);
+				else						
+					str_append_n(out_msg, body_text, body_size);
+			}
+		} else {
+			size_t len;
+
+			/* Find next substitution */
+			len = strcspn(p + 1, "$") + 1; 
+
+			/* Copy normal text */
+			str_append_n(out_msg, p, len);
+			p += len;
+		}
+  }
+}
diff --git a/src/lib-sieve/plugins/notify/ext-notify-common.h b/src/lib-sieve/plugins/notify/ext-notify-common.h
index de00e3442..146d7c6d9 100644
--- a/src/lib-sieve/plugins/notify/ext-notify-common.h
+++ b/src/lib-sieve/plugins/notify/ext-notify-common.h
@@ -54,4 +54,12 @@ struct ext_notify_action {
 	ARRAY_TYPE(recipients) recipients;
 };
 
+/*
+ * Message construct
+ */
+
+void ext_notify_construct_message
+	(const struct sieve_runtime_env *renv, const char *msg_format, 
+		string_t *out_msg);
+
 #endif /* __EXT_NOTIFY_COMMON_H */
diff --git a/tests/deprecated/notify/mailto.svtest b/tests/deprecated/notify/mailto.svtest
index 37b83c975..ac63c9bd8 100644
--- a/tests/deprecated/notify/mailto.svtest
+++ b/tests/deprecated/notify/mailto.svtest
@@ -1,5 +1,7 @@
 require "vnd.dovecot.testsuite";
+
 require "notify";
+require "body";
 require "relational";
 require "comparator-i;ascii-numeric";
 
@@ -153,3 +155,163 @@ test "Notifying on automated messages" {
 	}
 }
 
+test_result_reset;
+
+test_set "message" text:
+To: nico@vestingbar.nl
+From: stephan@rename-it.nl
+Subject: Test
+
+Test. Test
+Frop!
+.
+;
+
+test "Body; Singular Message" {
+	notify :low :id "frop" :options "stephan@rename-it.nl"
+    	:message text:
+Received interesting message:
+
+$text$
+
+You have been notified.
+.
+;	
+	
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not body :raw :contains "Received interesting message" {
+		test_fail "notification has no heading";
+	}
+
+	if not body :raw :contains "You have been notified" {
+		test_fail "notification has no footer";
+	}	
+
+	if not allof(
+		body :raw :contains "Test. Test",
+		body :raw :contains "Frop" ) {
+		test_fail "notification has no original message";
+	}
+}
+
+test_result_reset;
+
+test_set "message" text:
+To: nico@vestingbar.nl
+From: stephan@rename-it.nl
+Subject: Test
+
+Test. Test
+Frop!
+.
+;
+
+test "Body; $text[maxsize]$" {
+	notify :low :id "frop" :options "sirius@rename-it.nl"
+    	:message text:
+Received interesting message:
+
+$text[5]$
+
+You have been notified.
+.
+;	
+	
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not body :raw :contains "Received interesting message" {
+		test_fail "notification has no heading";
+	}
+
+	if not body :raw :contains "You have been notified" {
+		test_fail "notification has no footer";
+	}	
+
+	if anyof(
+		body :raw :contains "Test. Test",
+		body :raw :contains "Frop" ) {
+		test_fail "original message in notification is not truncated";
+	}
+
+	if not body :raw :contains "Test." {
+		test_fail "notification does not contain the required message";
+	}	
+}
+
+test_result_reset;
+
+test_set "message" text:
+From: Whomever <whoever@example.com>
+To: Someone <someone@example.com>
+Date: Sat, 10 Oct 2009 00:30:04 +0200
+Subject: whatever
+Content-Type: multipart/mixed; boundary=outer
+
+This is a multi-part message in MIME format.
+
+--outer
+Content-Type: multipart/alternative; boundary=inner
+
+This is a nested multi-part message in MIME format.
+
+--inner
+Content-Type: application/sieve; charset="us-ascii"
+
+keep;
+        
+--inner
+Content-Type: text/plain; charset="us-ascii"
+
+Friep!
+
+--inner--
+
+This is the end of the inner MIME multipart.
+
+--outer
+Content-Type: message/rfc822
+
+From: Someone Else
+Subject: hello request
+
+Please say Hello
+
+--outer--
+
+This is the end of the outer MIME multipart.
+.
+;
+
+test "Body; Multipart Message" {
+	notify :low :id "frop" :options "stephan@rename-it.nl"
+    	:message text:
+Received interesting message:
+
+$text$
+
+You have been notified.
+.
+;	
+	
+	if not test_result_execute {
+		test_fail "failed to execute notify";
+	}
+
+	test_message :smtp 0;
+
+	if not body :raw :contains "Friep!" {
+		test_fail "notification has incorrect content";
+	}
+}
+
+
+
-- 
GitLab