From 5c77bc111bab0ea414754d3e8e75d96693cab9d1 Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan.bosch@dovecot.fi>
Date: Sat, 7 Jul 2018 17:51:42 +0200
Subject: [PATCH] lib-sieve: vacation extension: Make construction of default
 message subject configurable.

---
 src/lib-sieve/plugins/vacation/cmd-vacation.c |  41 ++++++-
 .../plugins/vacation/ext-vacation-common.c    |  10 ++
 .../plugins/vacation/ext-vacation-common.h    |   2 +
 tests/extensions/vacation/message.svtest      | 105 ++++++++++++++++++
 4 files changed, 154 insertions(+), 4 deletions(-)

diff --git a/src/lib-sieve/plugins/vacation/cmd-vacation.c b/src/lib-sieve/plugins/vacation/cmd-vacation.c
index bacb180ca..2f6806f27 100644
--- a/src/lib-sieve/plugins/vacation/cmd-vacation.c
+++ b/src/lib-sieve/plugins/vacation/cmd-vacation.c
@@ -10,6 +10,7 @@
 #include "ostream.h"
 #include "message-address.h"
 #include "message-date.h"
+#include "var-expand.h"
 #include "ioloop.h"
 #include "mail-storage.h"
 
@@ -947,23 +948,54 @@ static int _get_full_reply_recipient
 	return SIEVE_EXEC_OK;
 }
 
+static const struct var_expand_table *
+_get_var_expand_table(const struct sieve_action_exec_env *aenv ATTR_UNUSED,
+		      const char *subject)
+{
+	const struct var_expand_table stack_tab[] = {
+		{ '$', subject, "subject" },
+		{ '\0', NULL, NULL }
+	};
+	return p_memdup(unsafe_data_stack_pool, stack_tab, sizeof(stack_tab));
+}
+
 static int
 act_vacation_get_default_subject(const struct sieve_action_exec_env *aenv,
+				 const struct ext_vacation_config *config,
 				 const char **subject_r)
 {
 	const struct sieve_message_data *msgdata = aenv->msgdata;
-	const char *header;
+	const char *header, *error;
+	string_t *str;
+	const struct var_expand_table *tab;
 	int ret;
 
-	*subject_r = "Automated reply";
+	*subject_r = (config->default_subject == NULL ?
+		      "Automated reply" : config->default_subject);
 	if ((ret=mail_get_first_header_utf8(msgdata->mail, "subject",
 					    &header)) < 0 ) {
 		return sieve_result_mail_error(
 			aenv, msgdata->mail, "vacation action: "
 			"failed to read header field `subject'");
 	}
-	if (ret >= 0)
+	if (ret == 0)
+		return SIEVE_EXEC_OK;
+	if (config->default_subject_template == NULL) {
 		*subject_r = t_strconcat("Auto: ", header, NULL);
+		return SIEVE_EXEC_OK;
+	}
+
+	str = t_str_new(256);
+	tab = _get_var_expand_table(aenv, header);
+	if (var_expand(str, config->default_subject_template,
+		       tab, &error) <= 0) {
+		i_error("Failed to expand deliver_log_format=%s: %s",
+			config->default_subject_template, error);
+		*subject_r = t_strconcat("Auto: ", header, NULL);
+		return SIEVE_EXEC_OK;
+	}
+
+	*subject_r = str_c(str);
 	return SIEVE_EXEC_OK;
 }
 
@@ -995,7 +1027,8 @@ static int act_vacation_send
 	/* Make sure we have a subject for our reply */
 
 	if ( ctx->subject == NULL || *(ctx->subject) == '\0' ) {
-		if ((ret=act_vacation_get_default_subject(aenv, &subject)) <= 0)
+		if ((ret=act_vacation_get_default_subject(aenv, config,
+							  &subject)) <= 0)
 			return ret;
 	}	else {
 		subject = ctx->subject;
diff --git a/src/lib-sieve/plugins/vacation/ext-vacation-common.c b/src/lib-sieve/plugins/vacation/ext-vacation-common.c
index 67c2513bf..089d8481a 100644
--- a/src/lib-sieve/plugins/vacation/ext-vacation-common.c
+++ b/src/lib-sieve/plugins/vacation/ext-vacation-common.c
@@ -19,6 +19,7 @@ bool ext_vacation_load
 	bool use_original_recipient, dont_check_recipient, send_from_recipient,
 		to_header_ignore_envelope;
 	unsigned long long max_subject_codepoints;
+	const char *default_subject, *default_subject_template;
 
 	if ( *context != NULL ) {
 		ext_vacation_unload(ext);
@@ -52,6 +53,11 @@ bool ext_vacation_load
 			"sieve_vacation_max_period");
 	}
 
+	default_subject = sieve_setting_get(
+		svinst, "sieve_vacation_default_subject");
+	default_subject_template = sieve_setting_get(
+		svinst, "sieve_vacation_default_subject_template");
+
 	if ( !sieve_setting_get_uint_value
 		(svinst, "sieve_vacation_max_subject_codepoints", &max_subject_codepoints) ) {
 		max_subject_codepoints = EXT_VACATION_DEFAULT_MAX_SUBJECT_CODEPOINTS;
@@ -83,6 +89,8 @@ bool ext_vacation_load
 	config->max_period = max_period;
 	config->default_period = default_period;
 	config->max_subject_codepoints = max_subject_codepoints;
+	config->default_subject = i_strdup_empty(default_subject);
+	config->default_subject_template = i_strdup_empty(default_subject_template);
 	config->use_original_recipient = use_original_recipient;
 	config->dont_check_recipient = dont_check_recipient;
 	config->send_from_recipient = send_from_recipient;
@@ -99,5 +107,7 @@ void ext_vacation_unload
 	struct ext_vacation_config *config =
 		(struct ext_vacation_config *) ext->context;
 
+	i_free(config->default_subject);
+	i_free(config->default_subject_template);
 	i_free(config);
 }
diff --git a/src/lib-sieve/plugins/vacation/ext-vacation-common.h b/src/lib-sieve/plugins/vacation/ext-vacation-common.h
index e82c40c42..3a38cf631 100644
--- a/src/lib-sieve/plugins/vacation/ext-vacation-common.h
+++ b/src/lib-sieve/plugins/vacation/ext-vacation-common.h
@@ -17,6 +17,8 @@ struct ext_vacation_config {
 	unsigned int max_period;
 	unsigned int default_period;
 	unsigned long long max_subject_codepoints;
+	char *default_subject;
+	char *default_subject_template;
 	bool use_original_recipient;
 	bool dont_check_recipient;
 	bool send_from_recipient;
diff --git a/tests/extensions/vacation/message.svtest b/tests/extensions/vacation/message.svtest
index d062ed2f7..861605e7e 100644
--- a/tests/extensions/vacation/message.svtest
+++ b/tests/extensions/vacation/message.svtest
@@ -61,6 +61,100 @@ test "Subject - explicit" {
 	}
 }
 
+/*
+ * Subject - configured, no subject
+ */
+
+test_set "message" text:
+From: stephan@example.org
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_config_set "sieve_vacation_default_subject" "Something colorful";
+test_config_reload :extension "vacation";
+
+test_result_reset;
+test "Subject - configured, no subject" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not header :is "subject" "Something colorful" {
+		test_fail "Subject header is incorrect";
+	}
+}
+
+/*
+ * Subject - configured
+ */
+
+test_set "message" text:
+From: stephan@example.org
+Subject: Bloemetjes
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_config_set "sieve_vacation_default_subject_template"
+	"Automatisch bericht: %$";
+test_config_reload :extension "vacation";
+
+test_result_reset;
+test "Subject - configured" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not header :is "subject" "Automatisch bericht: Bloemetjes" {
+		test_fail "Subject header is incorrect";
+	}
+}
+
+/*
+ * Subject - configured, full variable
+ */
+
+test_set "message" text:
+From: stephan@example.org
+Subject: Bloemetjes
+To: nico@frop.example.org
+
+Frop
+.
+;
+
+test_config_set "sieve_vacation_default_subject_template"
+	"Automatisch bericht: %{subject}";
+test_config_reload :extension "vacation";
+
+test_result_reset;
+test "Subject - configured, full variable" {
+	vacation "I am not in today!";
+
+	if not test_result_execute {
+		test_fail "execution of result failed";
+	}
+
+	test_message :smtp 0;
+
+	if not header :is "subject" "Automatisch bericht: Bloemetjes" {
+		test_fail "Subject header is incorrect";
+	}
+}
+
 /*
  * No subject
  */
@@ -108,6 +202,10 @@ Frop
 .
 ;
 
+test_config_set "sieve_vacation_default_subject_template" "";
+test_config_set "sieve_vacation_default_subject" "";
+test_config_reload :extension "vacation";
+
 test_result_reset;
 test "Extremely long subject" {
 	vacation "I am not in today!";
@@ -167,6 +265,10 @@ Frop
 .
 ;
 
+test_config_set "sieve_vacation_default_subject_template" "";
+test_config_set "sieve_vacation_default_subject" "";
+test_config_reload :extension "vacation";
+
 test_result_reset;
 test "Extremely long japanese subject" {
 	vacation "I am not in today!";
@@ -227,6 +329,9 @@ Frop
 .
 ;
 
+
+test_config_set "sieve_vacation_default_subject_template" "";
+test_config_set "sieve_vacation_default_subject" "";
 test_config_set "sieve_vacation_max_subject_codepoints" "20";
 test_config_reload :extension "vacation";
 
-- 
GitLab