From 2c4e424954580ba9fdfc17a97b253686fab9394f Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan.bosch@open-xchange.com>
Date: Wed, 21 Aug 2024 23:07:55 +0200
Subject: [PATCH] lib-sieve: spamtest/virustest extension: Migrate settings to
 new config structure

---
 .../plugins/spamvirustest/Makefile.am         |   2 +
 .../spamvirustest/ext-spamvirustest-common.c  | 267 ++++--------------
 .../ext-spamvirustest-settings.c              | 244 ++++++++++++++++
 .../ext-spamvirustest-settings.h              |  40 +++
 .../extensions/spamvirustest/spamtest.svtest  |   4 +-
 .../extensions/spamvirustest/virustest.svtest |  14 +-
 6 files changed, 349 insertions(+), 222 deletions(-)
 create mode 100644 src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-settings.c
 create mode 100644 src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-settings.h

diff --git a/src/lib-sieve/plugins/spamvirustest/Makefile.am b/src/lib-sieve/plugins/spamvirustest/Makefile.am
index 6a15bdd14..ebb7143ff 100644
--- a/src/lib-sieve/plugins/spamvirustest/Makefile.am
+++ b/src/lib-sieve/plugins/spamvirustest/Makefile.am
@@ -9,8 +9,10 @@ tests = \
 
 libsieve_ext_spamvirustest_la_SOURCES = \
 	$(tests) \
+	ext-spamvirustest-settings.c \
 	ext-spamvirustest-common.c \
 	ext-spamvirustest.c
 
 noinst_HEADERS = \
+	ext-spamvirustest-settings.h \
 	ext-spamvirustest-common.h
diff --git a/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.c b/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.c
index a8a47daa5..c1a4b3a6e 100644
--- a/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.c
+++ b/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.c
@@ -4,31 +4,26 @@
 #include "lib.h"
 #include "strfuncs.h"
 #include "mail-storage.h"
+#include "settings.h"
 
 #include "sieve-common.h"
-#include "sieve-settings.old.h"
 #include "sieve-error.h"
 #include "sieve-extensions.h"
 #include "sieve-message.h"
 #include "sieve-interpreter.h"
 #include "sieve-runtime-trace.h"
 
+#include "ext-spamvirustest-settings.h"
 #include "ext-spamvirustest-common.h"
 
 #include <sys/types.h>
-#include <regex.h>
 #include <ctype.h>
+#include <regex.h>
 
 /*
  * Extension data
  */
 
-enum ext_spamvirustest_status_type {
-	EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE,
-	EXT_SPAMVIRUSTEST_STATUS_TYPE_STRLEN,
-	EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT,
-};
-
 struct ext_spamvirustest_header_spec {
 	const char *header_name;
 	regex_t regexp;
@@ -38,15 +33,10 @@ struct ext_spamvirustest_header_spec {
 struct ext_spamvirustest_context {
 	pool_t pool;
 	unsigned int reload_id;
+	const struct ext_spamvirustest_settings *set;
 
 	struct ext_spamvirustest_header_spec status_header;
 	struct ext_spamvirustest_header_spec max_header;
-
-	enum ext_spamvirustest_status_type status_type;
-
-	float max_value;
-
-	const char *text_values[11];
 };
 
 /*
@@ -96,9 +86,9 @@ _regexp_match_get_value(const char *string, int index, regmatch_t pmatch[],
  */
 
 static bool
-ext_spamvirustest_header_spec_parse(struct ext_spamvirustest_header_spec *spec,
-				    pool_t pool, const char *data,
-				    const char **error_r)
+ext_spamvirustest_header_spec_parse(
+	pool_t pool, const char *data,
+	struct ext_spamvirustest_header_spec *spec_r, const char **error_r)
 {
 	const char *p;
 	const char *regexp_error;
@@ -118,16 +108,16 @@ ext_spamvirustest_header_spec_parse(struct ext_spamvirustest_header_spec *spec,
 		p++;
 
 	if (*p == '\0') {
-		spec->header_name = p_strdup(pool, data);
+		spec_r->header_name = p_strdup(pool, data);
 		return TRUE;
 	}
 
-	spec->header_name = p_strdup_until(pool, data, p);
+	spec_r->header_name = p_strdup_until(pool, data, p);
 	while (*p == ' ' || *p == '\t')
 		p++;
 
 	if (*p == '\0') {
-		spec->regexp_match = FALSE;
+		spec_r->regexp_match = FALSE;
 		return TRUE;
 	}
 
@@ -140,8 +130,8 @@ ext_spamvirustest_header_spec_parse(struct ext_spamvirustest_header_spec *spec,
 	p++;
 	while (*p == ' ' || *p == '\t') p++;
 
-	spec->regexp_match = TRUE;
-	if (!_regexp_compile(&spec->regexp, p, &regexp_error)) {
+	spec_r->regexp_match = TRUE;
+	if (!_regexp_compile(&spec_r->regexp, p, &regexp_error)) {
 		*error_r = t_strdup_printf(
 			"failed to compile regular expression '%s': %s",
 			p, regexp_error);
@@ -182,68 +172,6 @@ ext_spamvirustest_parse_strlen_value(const char *str_value,
 	return TRUE;
 }
 
-static bool
-ext_spamvirustest_parse_decimal_value(const char *str_value,
-				      float *value_r, const char **error_r)
-{
-	const char *p = str_value;
-	float value;
-	float sign = 1;
-	int digits;
-
-	if (*p == '\0') {
-		*error_r = "empty value";
-		return FALSE;
-	}
-
-	if (*p == '+' || *p == '-') {
-		if (*p == '-')
-			sign = -1;
-		p++;
-	}
-
-	value = 0;
-	digits = 0;
-	while (i_isdigit(*p)) {
-		value = value*10 + (*p-'0');
-		if (digits++ > 4) {
-			*error_r = t_strdup_printf(
-				"decimal value has too many digits before radix point: %s",
-				str_value);
-			return FALSE;
-		}
-		p++;
-	}
-
-	if (*p == '.' || *p == ',') {
-		float radix = .1;
-		p++;
-
-		digits = 0;
-		while (i_isdigit(*p)) {
-			value = value + (*p-'0')*radix;
-
-			if (digits++ > 4) {
-				*error_r = t_strdup_printf(
-					"decimal value has too many digits after radix point: %s",
-					str_value);
-				return FALSE;
-			}
-			radix /= 10;
-			p++;
-		}
-	}
-
-	if (*p != '\0') {
-		*error_r = t_strdup_printf(
-			"invalid decimal point value: %s", str_value);
-		return FALSE;
-	}
-
-	*value_r = value * sign;
-	return TRUE;
-}
-
 /*
  * Extension initialization
  */
@@ -253,158 +181,69 @@ int ext_spamvirustest_load(const struct sieve_extension *ext, void **context_r)
 	static unsigned int reload_id = 0;
 	struct sieve_instance *svinst = ext->svinst;
 	struct ext_spamvirustest_context *extctx;
-	const char *ext_name, *status_header, *max_header, *status_type,
-		*max_value;
-	enum ext_spamvirustest_status_type type;
+	const struct setting_parser_info *set_info;
+	const struct ext_spamvirustest_settings *set;
 	const char *error;
 	pool_t pool;
 	int ret = 0;
 
-	/* FIXME: Prevent loading of both spamtest and spamtestplus:
-	   let these share contexts.
-	 */
-
-	if (sieve_extension_is(ext, spamtest_extension) ||
-	    sieve_extension_is(ext, spamtestplus_extension)) {
-		ext_name = spamtest_extension.name;
-	} else {
-		ext_name = sieve_extension_name(ext);
-	}
-
 	/* Get settings */
 
-	status_header = sieve_setting_get(
-		svinst, t_strconcat("sieve_", ext_name,
-				    "_status_header", NULL));
-	status_type = sieve_setting_get(
-		svinst, t_strconcat("sieve_", ext_name, "_status_type", NULL));
-	max_header = sieve_setting_get(
-		svinst, t_strconcat("sieve_", ext_name, "_max_header", NULL));
-	max_value = sieve_setting_get(
-		svinst, t_strconcat("sieve_", ext_name, "_max_value", NULL));
-
-	/* Base configuration */
-
-	if (status_header == NULL)
-		return 0;
+	if (sieve_extension_is(ext, spamtest_extension) ||
+	    sieve_extension_is(ext, spamtestplus_extension))
+		set_info = &ext_spamtest_setting_parser_info;
+	else if (sieve_extension_is(ext, virustest_extension))
+		set_info = &ext_virustest_setting_parser_info;
+	else
+		i_unreached();
 
-	if (status_type == NULL || strcmp(status_type, "score") == 0) {
-		type = EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE;
-	} else if (strcmp(status_type, "strlen") == 0) {
-		type = EXT_SPAMVIRUSTEST_STATUS_TYPE_STRLEN;
-	} else if (strcmp(status_type, "text") == 0) {
-		type = EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT;
-	} else {
-		e_error(svinst->event, "%s: "
-			"invalid status type '%s'", ext_name, status_type);
+	if (settings_get(svinst->event, set_info, 0, &set, &error) < 0) {
+		e_error(svinst->event, "%s", error);
 		return -1;
 	}
 
-	/* Verify settings */
-
-	if (type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT) {
-
-		if (max_header != NULL && max_value != NULL) {
-			e_error(svinst->event, "%s: "
-				"sieve_%s_max_header and sieve_%s_max_value "
-				"cannot both be configured",
-				ext_name, ext_name, ext_name);
-			return 0;
-		}
-
-		if (max_header == NULL && max_value == NULL) {
-			e_error(svinst->event, "%s: "
-				"none of sieve_%s_max_header or sieve_%s_max_value "
-				"is configured", ext_name, ext_name, ext_name);
-			return 0;
-		}
-	} else {
-		if (max_header != NULL) {
-			e_warning(svinst->event, "%s: "
-				  "setting sieve_%s_max_header has no meaning "
-				  "for sieve_%s_status_type=text",
-				  ext_name, ext_name, ext_name);
-		}
+	/* Base configuration */
 
-		if (max_value != NULL) {
-			e_warning(svinst->event, "%s: "
-				  "setting sieve_%s_max_value has no meaning "
-				  "for sieve_%s_status_type=text",
-				  ext_name, ext_name, ext_name);
-		}
+	if (*set->status_header == '\0') {
+		settings_free(set);
+		return 0;
 	}
 
 	pool = pool_alloconly_create("spamvirustest_data", 512);
 	extctx = p_new(pool, struct ext_spamvirustest_context, 1);
 	extctx->pool = pool;
 	extctx->reload_id = ++reload_id;
-	extctx->status_type = type;
+	extctx->set = set;
 
-	if (!ext_spamvirustest_header_spec_parse(
-		&extctx->status_header, extctx->pool, status_header,
-		&error)) {
+	if (!ext_spamvirustest_header_spec_parse(extctx->pool,
+						 set->status_header,
+						 &extctx->status_header,
+						 &error)) {
 		e_error(svinst->event, "%s: "
-			"invalid status header specification '%s': %s",
-			ext_name, status_header, error);
+			"Invalid status header specification '%s': %s",
+			sieve_extension_name(ext), set->status_header, error);
 		ret = -1;
 	}
 
-	if (ret == 0) {
-		if (type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT) {
-			/* Parse max header */
-
-			if (max_header != NULL &&
-			    !ext_spamvirustest_header_spec_parse(
-				&extctx->max_header, extctx->pool,
-				max_header, &error)) {
-				e_error(svinst->event, "%s: "
-					"invalid max header specification "
-					"'%s': %s", ext_name, max_header,
-					error);
-				ret = -1;
-			}
-
-			/* Parse max value */
-
-			if (ret == 0 && max_value != NULL) {
-				if (!ext_spamvirustest_parse_decimal_value(
-					max_value, &extctx->max_value, &error)) {
-					e_error(svinst->event, "%s: "
-						"invalid max value specification "
-						"'%s': %s", ext_name, max_value,
-						error);
-					ret = -1;
-				}
-			}
-
-		} else {
-			unsigned int i, max_text;
-
-			max_text = (sieve_extension_is(ext, virustest_extension) ?
-				    5 : 10);
-
-			/* Get text values */
-			for (i = 0; i <= max_text; i++) {
-				const char *value = sieve_setting_get(
-					svinst, t_strdup_printf("sieve_%s_text_value%d",
-								ext_name, i));
-
-				if (value != NULL && *value != '\0') {
-					extctx->text_values[i] =
-						p_strdup(extctx->pool, value);
-				}
-			}
-
-			extctx->max_value = 1;
-		}
+	if (ret == 0 &&
+	    set->parsed.status_type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT &&
+	    *set->max_header != '\0' &&
+	    !ext_spamvirustest_header_spec_parse(extctx->pool,
+						 set->max_header,
+						 &extctx->max_header,
+						 &error)) {
+		e_error(svinst->event, "%s: "
+			"Invalid max header specification '%s': %s",
+			sieve_extension_name(ext), set->max_header, error);
+		ret = -1;
 	}
 
 	*context_r = extctx;
 	if (ret < 0) {
 		e_warning(svinst->event, "%s: "
-			  "extension not configured, "
+			  "Extension not configured, "
 			  "tests will always match against \"0\"",
-			  ext_name);
+			  sieve_extension_name(ext));
 		ext_spamvirustest_unload(ext);
 		*context_r = NULL;
 	}
@@ -421,6 +260,7 @@ void ext_spamvirustest_unload(const struct sieve_extension *ext)
 
 	ext_spamvirustest_header_spec_free(&extctx->status_header);
 	ext_spamvirustest_header_spec_free(&extctx->max_header);
+	settings_free(extctx->set);
 	pool_unref(&extctx->pool);
 }
 
@@ -513,7 +353,7 @@ int ext_spamvirustest_get_value(const struct sieve_runtime_env *renv,
 	status_header = &extctx->status_header;
 	max_header = &extctx->max_header;
 
-	if (extctx->status_type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT) {
+	if (extctx->set->parsed.status_type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT) {
 		if (max_header->header_name != NULL) {
 			/* Get header from message */
 			if (mail_get_first_header_utf8(
@@ -569,7 +409,7 @@ int ext_spamvirustest_get_value(const struct sieve_runtime_env *renv,
 				goto failed;
 			}
 		} else {
-			max_value = extctx->max_value;
+			max_value = extctx->set->parsed.max_value;
 		}
 
 		if (max_value == 0) {
@@ -624,7 +464,7 @@ int ext_spamvirustest_get_value(const struct sieve_runtime_env *renv,
 		status = header_value;
 	}
 
-	switch (extctx->status_type) {
+	switch (extctx->set->parsed.status_type) {
 	case EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE:
 		if (!ext_spamvirustest_parse_decimal_value(
 			status, &status_value, &error)) {
@@ -652,8 +492,9 @@ int ext_spamvirustest_get_value(const struct sieve_runtime_env *renv,
 
 		i = 0;
 		while (i <= max_text) {
-			if (extctx->text_values[i] != NULL &&
-			    strcmp(status, extctx->text_values[i]) == 0) {
+			if (extctx->set->parsed.text_values[i] != NULL &&
+			    strcmp(status,
+				   extctx->set->parsed.text_values[i]) == 0) {
 				status_value = (float)i;
 				break;
 			}
diff --git a/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-settings.c b/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-settings.c
new file mode 100644
index 000000000..eb8d2f141
--- /dev/null
+++ b/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-settings.c
@@ -0,0 +1,244 @@
+/* Copyright (c) 2024 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "settings.h"
+#include "settings-parser.h"
+
+#include "ext-spamvirustest-settings.h"
+
+#include <ctype.h>
+
+static bool
+ext_spamtest_settings_check(void *_set, pool_t pool, const char **error_r);
+static bool
+ext_virustest_settings_check(void *_set, pool_t pool, const char **error_r);
+
+#undef DEF
+#define DEF(type, name) \
+	SETTING_DEFINE_STRUCT_##type("sieve_spamtest_"#name, name, \
+				     struct ext_spamvirustest_settings)
+
+static const struct setting_define ext_spamtest_setting_defines[] = {
+	DEF(STR, status_header),
+	DEF(STR, status_type),
+
+	DEF(STR, max_header),
+	DEF(STR, max_value),
+
+	{ .type = SET_STRLIST, .key = "sieve_spamtest_text_value",
+	  .offset = offsetof(struct ext_spamvirustest_settings,
+			     text_value) },
+
+	SETTING_DEFINE_LIST_END,
+};
+
+#undef DEF
+#define DEF(type, name) \
+	SETTING_DEFINE_STRUCT_##type("sieve_virustest_"#name, name, \
+				     struct ext_spamvirustest_settings)
+
+static const struct setting_define ext_virustest_setting_defines[] = {
+	DEF(STR, status_header),
+	DEF(STR, status_type),
+
+	DEF(STR, max_header),
+	DEF(STR, max_value),
+
+	{ .type = SET_STRLIST, .key = "sieve_virustest_text_value",
+	  .offset = offsetof(struct ext_spamvirustest_settings,
+			     text_value) },
+
+	SETTING_DEFINE_LIST_END,
+};
+
+static const struct ext_spamvirustest_settings ext_spamvirustest_default_settings = {
+	.status_header = "",
+	.status_type = "",
+
+	.max_header = "",
+	.max_value = "",
+
+	.text_value = ARRAY_INIT,
+};
+
+const struct setting_parser_info ext_spamtest_setting_parser_info = {
+	.name = "sieve_spamtest",
+
+	.defines = ext_spamtest_setting_defines,
+	.defaults = &ext_spamvirustest_default_settings,
+
+	.struct_size = sizeof(struct ext_spamvirustest_settings),
+
+	.check_func = ext_spamtest_settings_check,
+
+	.pool_offset1 = 1 + offsetof(struct ext_spamvirustest_settings, pool),
+};
+
+const struct setting_parser_info ext_virustest_setting_parser_info = {
+	.name = "sieve_virustest",
+
+	.defines = ext_virustest_setting_defines,
+	.defaults = &ext_spamvirustest_default_settings,
+
+	.struct_size = sizeof(struct ext_spamvirustest_settings),
+
+	.check_func = ext_virustest_settings_check,
+
+	.pool_offset1 = 1 + offsetof(struct ext_spamvirustest_settings, pool),
+};
+
+/* <settings checks> */
+bool ext_spamvirustest_parse_decimal_value(const char *str_value,
+					   float *value_r, const char **error_r)
+{
+	const char *p = str_value;
+	float value;
+	float sign = 1;
+	int digits;
+
+	if (*p == '\0') {
+		*error_r = "empty value";
+		return FALSE;
+	}
+
+	if (*p == '+' || *p == '-') {
+		if (*p == '-')
+			sign = -1;
+		p++;
+	}
+
+	value = 0;
+	digits = 0;
+	while (i_isdigit(*p)) {
+		value = value*10 + (*p-'0');
+		if (digits++ > 4) {
+			*error_r = t_strdup_printf(
+				"Decimal value has too many digits before radix point: %s",
+				str_value);
+			return FALSE;
+		}
+		p++;
+	}
+
+	if (*p == '.' || *p == ',') {
+		float radix = .1;
+		p++;
+
+		digits = 0;
+		while (i_isdigit(*p)) {
+			value = value + (*p-'0')*radix;
+
+			if (digits++ > 4) {
+				*error_r = t_strdup_printf(
+					"Decimal value has too many digits after radix point: %s",
+					str_value);
+				return FALSE;
+			}
+			radix /= 10;
+			p++;
+		}
+	}
+
+	if (*p != '\0') {
+		*error_r = t_strdup_printf(
+			"Invalid decimal point value: %s", str_value);
+		return FALSE;
+	}
+
+	*value_r = value * sign;
+	return TRUE;
+}
+
+static bool
+ext_spamvirustest_settings_check(void *_set, bool virustest,
+				 pool_t pool ATTR_UNUSED, const char **error_r)
+{
+	struct ext_spamvirustest_settings *set = _set;
+	const char *ext_name = (virustest ? "virustest" : "spamtest");
+	const char *error;
+
+	if (*set->status_header == '\0')
+		return TRUE;
+
+	if (*set->status_type == '\0' ||
+	    strcmp(set->status_type, "score") == 0)
+		set->parsed.status_type = EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE;
+	else if (strcmp(set->status_type, "strlen") == 0)
+		set->parsed.status_type = EXT_SPAMVIRUSTEST_STATUS_TYPE_STRLEN;
+	else if (strcmp(set->status_type, "text") == 0)
+		set->parsed.status_type = EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT;
+	else {
+		*error_r = t_strdup_printf("Invalid status type '%s'",
+					   set->status_type);
+		return FALSE;
+	}
+
+	if (set->parsed.status_type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT) {
+		if (*set->max_header != '\0' && *set->max_value != '\0') {
+			*error_r = t_strdup_printf(
+				"sieve_%s_max_header and sieve_%s_max_value "
+				"cannot both be configured",
+				ext_name, ext_name);
+			return FALSE;
+		}
+		if (*set->max_header == '\0' && *set->max_value == '\0') {
+			*error_r = t_strdup_printf(
+				"None of sieve_%s_max_header or sieve_%s_max_value "
+				"is configured", ext_name, ext_name);
+			return FALSE;
+		}
+		if (*set->max_value != '\0' &&
+		    !ext_spamvirustest_parse_decimal_value(
+			set->max_value, &set->parsed.max_value, &error)) {
+			*error_r = t_strdup_printf(
+				"Invalid max value specification "
+				"'%s': %s", set->max_value, error);
+			return FALSE;
+		}
+	} else {
+		const char *const *tvalues;
+		unsigned int tvalues_count, i;
+		unsigned int tv_index_max = (virustest ? 5 : 10);
+
+		tvalues = array_get(&set->text_value, &tvalues_count);
+		i_assert(tvalues_count % 2 == 0);
+		for (i = 0; i < tvalues_count; i += 2) {
+			unsigned int tv_index;
+
+			if (str_to_uint(tvalues[i], &tv_index) < 0) {
+				*error_r = t_strdup_printf(
+					"Invalid text value index '%s'",
+					tvalues[i]);
+				return FALSE;
+			}
+			if (tv_index > tv_index_max) {
+				*error_r = t_strdup_printf(
+					"Text value index out of range "
+					"(%u > %u)", tv_index, tv_index_max);
+				return FALSE;
+			}
+			set->parsed.text_values[tv_index] = tvalues[i + 1]; 
+		}
+		set->parsed.max_value = 1;
+	}
+
+	return TRUE;
+}
+
+static bool
+ext_spamtest_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+			    const char **error_r)
+{
+	return ext_spamvirustest_settings_check(_set, FALSE, pool, error_r);
+}
+
+static bool
+ext_virustest_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+			     const char **error_r)
+{
+	return ext_spamvirustest_settings_check(_set, TRUE, pool, error_r);
+}
+/* </settings checks> */
+
diff --git a/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-settings.h b/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-settings.h
new file mode 100644
index 000000000..ce1ba5f2d
--- /dev/null
+++ b/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-settings.h
@@ -0,0 +1,40 @@
+#ifndef EXT_SPAMVIRUSTEST_SETTINGS_H
+#define EXT_SPAMVIRUSTEST_SETTINGS_H
+
+/* <settings checks> */
+enum ext_spamvirustest_status_type {
+	EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE,
+	EXT_SPAMVIRUSTEST_STATUS_TYPE_STRLEN,
+	EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT,
+};
+/* </settings checks> */
+
+struct ext_spamvirustest_settings {
+	pool_t pool;
+
+	const char *status_header;
+	const char *status_type;
+	const char *max_header;
+	const char *max_value;
+
+	ARRAY_TYPE(const_string) text_value;
+
+	struct {
+		enum ext_spamvirustest_status_type status_type;
+
+		float max_value;
+
+		const char *text_values[11];
+	} parsed;
+};
+
+extern const struct setting_parser_info ext_spamtest_setting_parser_info;
+extern const struct setting_parser_info ext_virustest_setting_parser_info;
+
+/* <settings checks> */
+bool ext_spamvirustest_parse_decimal_value(const char *str_value,
+					   float *value_r,
+					   const char **error_r);
+/* </settings checks> */
+
+#endif
diff --git a/tests/extensions/spamvirustest/spamtest.svtest b/tests/extensions/spamvirustest/spamtest.svtest
index 11ffdeefd..9db1d4cf9 100644
--- a/tests/extensions/spamvirustest/spamtest.svtest
+++ b/tests/extensions/spamvirustest/spamtest.svtest
@@ -243,8 +243,8 @@ Test!
 
 test_config_set "sieve_spamtest_status_header" "X-Spam-Verdict";
 test_config_set "sieve_spamtest_status_type" "text";
-test_config_set "sieve_spamtest_text_value1" "Not Spam";
-test_config_set "sieve_spamtest_text_value10" "Spam";
+test_config_set "sieve_spamtest_text_value/1" "Not Spam";
+test_config_set "sieve_spamtest_text_value/10" "Spam";
 test_config_unset "sieve_spamtest_max_header";
 test_config_unset "sieve_spamtest_max_value";
 test_config_reload :extension "spamtest";
diff --git a/tests/extensions/spamvirustest/virustest.svtest b/tests/extensions/spamvirustest/virustest.svtest
index 03bb141c7..1a6c1d42f 100644
--- a/tests/extensions/spamvirustest/virustest.svtest
+++ b/tests/extensions/spamvirustest/virustest.svtest
@@ -27,11 +27,11 @@ Test!
 
 test_config_set "sieve_virustest_status_header" "X-VirusCheck";
 test_config_set "sieve_virustest_status_type" "text";
-test_config_set "sieve_virustest_text_value1" "Clean";
-test_config_set "sieve_virustest_text_value2" "Presumed Clean";
-test_config_set "sieve_virustest_text_value3" "Not sure";
-test_config_set "sieve_virustest_text_value4" "Almost Certain";
-test_config_set "sieve_virustest_text_value5" "Definitely";
+test_config_set "sieve_virustest_text_value/1" "Clean";
+test_config_set "sieve_virustest_text_value/2" "Presumed Clean";
+test_config_set "sieve_virustest_text_value/3" "Not sure";
+test_config_set "sieve_virustest_text_value/4" "Almost Certain";
+test_config_set "sieve_virustest_text_value/5" "Definitely";
 test_config_reload :extension "virustest";
 
 test "Text: 5" {
@@ -103,8 +103,8 @@ test "Text: 1" {
 
 test_config_set "sieve_virustest_status_header" "X-Virus-Scan:Found to be (.+)\.";
 test_config_set "sieve_virustest_status_type" "text";
-test_config_set "sieve_virustest_text_value1" "clean";
-test_config_set "sieve_virustest_text_value5" "infected";
+test_config_set "sieve_virustest_text_value/1" "clean";
+test_config_set "sieve_virustest_text_value/5" "infected";
 test_config_reload :extension "virustest";
 
 test "Text: regex: 1" {
-- 
GitLab