From 34fd1a7b453e7e22319dec324701cf237298b4dd Mon Sep 17 00:00:00 2001
From: Stephan Bosch <stephan@rename-it.nl>
Date: Mon, 28 Jul 2008 12:48:14 +0200
Subject: [PATCH] Fixed bugs in the :matches match type.

---
 src/lib-sieve/mcht-matches.c                  |  83 +++++----
 src/lib-sieve/sieve-match-types.c             |  14 ++
 src/lib-sieve/sieve-match-types.h             |   2 +
 .../tests/extensions/variables/match.svtest   | 167 ++++++++++++++++++
 src/testsuite/tests/lexer.svtest              |  14 +-
 .../tests/match-types/matches.svtest          |   2 +
 6 files changed, 247 insertions(+), 35 deletions(-)
 create mode 100644 src/testsuite/tests/extensions/variables/match.svtest

diff --git a/src/lib-sieve/mcht-matches.c b/src/lib-sieve/mcht-matches.c
index db8863c9e..4d6a441fb 100644
--- a/src/lib-sieve/mcht-matches.c
+++ b/src/lib-sieve/mcht-matches.c
@@ -40,7 +40,7 @@ const struct sieve_match_type matches_match_type = {
 /* Quick 'n dirty debug */
 //#define MATCH_DEBUG
 #ifdef MATCH_DEBUG
-#define debug_printf(...) printf (__VA_ARGS__)
+#define debug_printf(...) printf ("match debug: " __VA_ARGS__)
 #else
 #define debug_printf(...) 
 #endif
@@ -116,8 +116,9 @@ static int mcht_matches_match
 	 * Escape sequences \? and \* need special attention. 
 	 */
 	 
-	debug_printf("MATCH key: %s\n", t_strdup_until(key, kend));
-	debug_printf("MATCH val: %s\n", t_strdup_until(val, vend));
+	debug_printf("=== Start ===\n");
+	debug_printf("  key:   %s\n", t_strdup_until(key, kend));
+	debug_printf("  value: %s\n", t_strdup_until(val, vend));
 
 	/* Loop until either key or value ends */
 	while (kp < kend && vp < vend && j < 40) {
@@ -145,29 +146,32 @@ static int mcht_matches_match
 				key_offset++;
 			}
 			
-			debug_printf("MATCH found wildcard '%c' at pos [%d]\n", 
+			debug_printf("found wildcard '%c' at pos [%d]\n", 
 				next_wcard, (int) (wp-key));
 				
 			str_truncate(mvalue, 0);
-		} else
+		} else {
+			debug_printf("backtracked");
 			backtrack = FALSE;
+		}
 		
 		needle = str_c(section);
 		nend = PTR_OFFSET(needle, str_len(section));		
 		 
-		debug_printf("MATCH sneedle: '%s'\n", t_strdup_until(needle, nend));
-		debug_printf("MATCH skey: '%s'\n", t_strdup_until(kp, kend));
-		debug_printf("MATCH swp: '%s'\n", t_strdup_until(wp, kend));
-		debug_printf("MATCH sval: '%s'\n", t_strdup_until(vp, vend));
+		debug_printf("  section needle:  '%s'\n", t_strdup_until(needle, nend));
+		debug_printf("  section key:     '%s'\n", t_strdup_until(kp, kend));
+		debug_printf("  section remnant: '%s'\n", t_strdup_until(wp, kend));
+		debug_printf("  value remnant:   '%s'\n", t_strdup_until(vp, vend));
 		
 		pvp = vp;
 		if ( next_wcard == '\0' ) {			
 			const char *qp, *qend;
 			
-			debug_printf("MATCH find end.\n");				 
+			debug_printf("next_wcard = NUL; must find needle at end\n");				 
 			
 			/* Find substring at the end of string */
 			if ( vend - str_len(section) < vp ) {
+				debug_printf("  wont match: value is too short\n");
 				break;
 			}
 
@@ -177,7 +181,7 @@ static int mcht_matches_match
 			str_append_n(mvalue, pvp, qp-pvp);
 					
 			if ( !cmp->char_match(cmp, &vp, vend, &needle, nend) ) {	
-				debug_printf("MATCH failed end\n");				 
+				debug_printf("  match at end failed\n");				 
 				break;
 			}
 			
@@ -187,32 +191,35 @@ static int mcht_matches_match
 
 			kp = kend;
 			vp = vend;
+			debug_printf("  matched end of value\n");
 			break;
 		} else {
 			const char *prv = NULL;
 			const char *prk = NULL;
 			const char *prw = NULL;
 			const char *chars;
-			debug_printf("MATCH find.\n");
 		
 			str_truncate(mchars, 0);
 							
 			if ( wcard == '\0' ) {
 				/* Match needs to happen right at the beginning */
-				debug_printf("MATCH bkey: '%s'\n", t_strdup_until(needle, nend));
-				debug_printf("MATCH bval: '%s'\n", t_strdup_until(vp, vend));
+				debug_printf("wcard = NUL; needle should be found at the beginning.\n");
+				debug_printf("  begin needle: '%s'\n", t_strdup_until(needle, nend));
+				debug_printf("  begin value:  '%s'\n", t_strdup_until(vp, vend));
 
 				if ( !cmp->char_match(cmp, &vp, vend, &needle, nend) ) {	
-					debug_printf("MATCH failed begin\n");				 
+					debug_printf("  failed to find needle at begin\n");				 
 					break;
 				}
 
 			} else {
 				const char *qp, *qend;
+
+				debug_printf("wcard != NUL; must find needle at an offset.\n");
 				
 				/* Match may happen at any offset: find substring */
 				if ( !_string_find(cmp, &vp, vend, &needle, nend)	) {
-					debug_printf("MATCH failed find\n"); 
+					debug_printf("  failed to find needle at an offset\n"); 
 					break;
 				}
 			
@@ -225,31 +232,32 @@ static int mcht_matches_match
 				str_append_n(mvalue, pvp, qp-pvp);
 				for ( ; qp < qend; qp++ )
 					str_append_c(mchars, *qp);
-				debug_printf("MATCH :: %s\n", str_c(mvalue));
 			}
 			
 			if ( wp < kend ) wp++;
 			kp = wp;
 		
 			while ( next_wcard == '?' ) {
-				debug_printf("MATCH ?\n");
+				debug_printf("next_wcard = '?'; need to match arbitrary character\n");
 				
 				/* Add match value */ 
 				str_append_c(mchars, *vp);
 				vp++;
 				
 				next_wcard = _scan_key_section(subsection, &wp, kend);
-				debug_printf("MATCH found next wildcard '%c' at pos [%d]\n", 
+				debug_printf("found next wildcard '%c' at pos [%d] (fixed match)\n", 
 					next_wcard, (int) (wp-key));
 					
 				needle = str_c(subsection);
 				nend = PTR_OFFSET(needle, str_len(subsection));
 
-				debug_printf("MATCH fkey: '%s'\n", t_strdup_until(needle, nend));
-				debug_printf("MATCH fval: '%s'\n", t_strdup_until(vp, vend));
+				debug_printf("  sub key:       '%s'\n", t_strdup_until(needle, nend));
+				debug_printf("  value remnant: '%s'\n", vp <= vend ? t_strdup_until(vp, vend) : "");
 
-				if ( (needle == nend && vp < vend ) || 
+				if ( (needle == nend && next_wcard == '\0' && vp < vend ) || 
 					!cmp->char_match(cmp, &vp, vend, &needle, nend) ) {	
+					debug_printf("  failed fixed match\n");
+
 					if ( prv != NULL && prv + 1 < vend ) {
 						vp = prv;
 						kp = prk;
@@ -262,13 +270,8 @@ static int mcht_matches_match
 						next_wcard = '?';
 				
 						backtrack = TRUE;				 
-						debug_printf("MATCH backtrack\n");
-					} else {
-						/* We are sure to have failed */
-						return FALSE;
+						debug_printf("  BACKTRACK\n");
 					}
-					
-					debug_printf("MATCH failed fixed\n");
 					break;
 				}
 				
@@ -279,8 +282,8 @@ static int mcht_matches_match
 			if ( !backtrack ) {
 				unsigned int i;
 				
-				if ( next_wcard != '*' && next_wcard != '\0' ) {
-					debug_printf("MATCH failed '?'\n");	
+				if ( next_wcard == '?' ) {
+					debug_printf("failed to match '?'\n");	
 					break;
 				}
 				
@@ -290,6 +293,11 @@ static int mcht_matches_match
 				for ( i = 0; i < str_len(mchars); i++ ) {
 					sieve_match_values_add_char(mvalues, chars[i]);
 				}
+
+				if ( next_wcard != '*' ) {
+					debug_printf("failed to match at end of string\n");
+					break;
+				}
 			}
 		}
 					
@@ -302,18 +310,27 @@ static int mcht_matches_match
 			sieve_match_values_add(mvalues, mvalue);
 			kp = kend;
 			vp = vend;
+			debug_printf("key ends with '*'\n");
 			break;
 		}			
 					
-		debug_printf("MATCH loop\n");
+		debug_printf("== Loop ==\n");
 		j++;
 	}
 	
-	debug_printf("MATCH loop ended\n");
+	debug_printf("=== Finish ===\n");
+	debug_printf("  result: %s\n", (kp == kend && vp == vend) ? "true" : "false");
 	
 	/* By definition, the match is only successful if both value and key pattern
 	 * are exhausted.
 	 */
-	return (kp == kend && vp == vend);
+	if (kp == kend && vp == vend) {
+		string_t *matched = str_new_const(pool_datastack_create(), val, val_size);
+		sieve_match_values_set(mvalues, 0, matched);
+
+		return TRUE;
+	}
+
+	return FALSE;
 }
 			 
diff --git a/src/lib-sieve/sieve-match-types.c b/src/lib-sieve/sieve-match-types.c
index b79f55e42..7e64310aa 100644
--- a/src/lib-sieve/sieve-match-types.c
+++ b/src/lib-sieve/sieve-match-types.c
@@ -194,6 +194,20 @@ static string_t *sieve_match_values_add_entry
 
 	return entry;
 }
+
+void sieve_match_values_set
+(struct sieve_match_values *mvalues, unsigned int index, string_t *value)
+{
+	if ( mvalues != NULL ) {
+		string_t * const *ep = array_idx(&mvalues->values, index);
+    	string_t *entry = *ep;
+
+	    if ( entry != NULL && value != NULL ) {
+			str_truncate(entry, 0);
+        	str_append_str(entry, value);
+		}
+	}
+}
 	
 void sieve_match_values_add
 	(struct sieve_match_values *mvalues, string_t *value) 
diff --git a/src/lib-sieve/sieve-match-types.h b/src/lib-sieve/sieve-match-types.h
index 83894f068..293b6eec7 100644
--- a/src/lib-sieve/sieve-match-types.h
+++ b/src/lib-sieve/sieve-match-types.h
@@ -70,6 +70,8 @@ bool sieve_match_values_set_enabled
 	(struct sieve_interpreter *interp, bool enable);
 struct sieve_match_values *sieve_match_values_start
 	(struct sieve_interpreter *interp);
+void sieve_match_values_set
+	(struct sieve_match_values *mvalues, unsigned int index, string_t *value);
 void sieve_match_values_add
 	(struct sieve_match_values *mvalues, string_t *value);
 void sieve_match_values_add_char
diff --git a/src/testsuite/tests/extensions/variables/match.svtest b/src/testsuite/tests/extensions/variables/match.svtest
new file mode 100644
index 000000000..67c734afc
--- /dev/null
+++ b/src/testsuite/tests/extensions/variables/match.svtest
@@ -0,0 +1,167 @@
+require "vnd.dovecot.testsuite";
+
+require "variables";
+
+set "match1" "Test of general stupidity";
+
+test "Begin" {
+	if not string :matches "${match1}" "Test of *" {
+		test_fail "should have matched";
+	} 
+
+	if not string :is "${1}" "general stupidity" {
+		test_fail "match value incorrect";
+	}
+}
+
+test "Begin no match" {
+	if string :matches "${match1}" "of *" {
+		test_fail "should not have matched";
+	}
+}
+
+set "match2" "toptoptop";
+
+test "End" {
+	if not string :matches "${match2}" "*top" {
+		test_fail "should have matched";
+	}
+
+	if not string :is "${1}" "toptop" {
+		test_fail "match value incorrect";
+	}
+}
+
+set "match3" "ik ben een tukker met grote oren en een lelijke broek.";
+
+test "Multiple" {
+	if not string :matches "${match3}" "ik ben * met * en *." {
+		test_fail "should have matched";
+	} 
+
+	set "line" "Hij is ${1} met ${2} en ${3}!";
+
+	if not string :is "${line}"
+		"Hij is een tukker met grote oren en een lelijke broek!" {
+		test_fail "match values incorrect: ${line}";
+	}
+}
+
+set "match4" "beter van niet?";
+
+test "Escape" {
+	if not string :matches "${match4}" "*\\?" {
+		test_fail "should have matched";
+	} 
+
+	if not string :is "${1}" "beter van niet" {
+		test_fail "match value incorrect: ${1}";
+	}
+}
+
+set "match5" "The quick brown fox jumps over the lazy dog.";
+
+test "Alphabet ?" {
+	if not string :matches "${match5}" "T?? ????? ????? ?o? ?u??? o?er ?he ???? ?o?." {
+		test_fail "should have matched";
+	} 
+
+	set "alphabet" "${22}${8}${6}${25}${2}${13}${26}${1}${5}${15}${7}${21}${16}${12}${10}${17}${3}${9}${18}${20}${4}${19}${11}${14}${24}${23}";
+
+	if not string :is "${alphabet}" "abcdefghijklmnopqrstuvwxyz" {
+		test_fail "match values incorrect: ${alphabet}";
+	}
+
+	if string :matches "${match5}" "T?? ????? ?w??? ?o? ?u??? o?er ?he ???? ?o?." {
+		test_fail "should not have matched";
+	}
+}
+
+set "match6" "zero:one:zero|three;one;zero/five";
+
+test "Words sep ?" {
+
+	if not string :matches "${match6}" "*one?zero?five" {
+		test_fail "should have matched";
+	}
+
+	if not string :is "${1}${2}${3}" "zero:one:zero|three;;/" {
+		test_fail "incorrect match values: ${1} ${2} ${3}";
+	}
+}
+
+set "match7" "frop";
+
+test "Letters begin ?" {
+	if not string :matches "${match7}" "??op" {
+		test_fail "should have matched";
+	}
+		
+	set "val" "${0}:${1}:${2}:${3}:";
+	
+	if not string :is "${val}" "frop:f:r::" {
+		test_fail "incorrect match values: ${val}";
+	}
+}
+
+test "Letters end ?" {
+    if not string :matches "${match7}" "fr??" {
+        test_fail "should have matched";
+    }
+
+    set "val" "${0}:${1}:${2}:${3}:";
+
+    if not string :is "${val}" "frop:o:p::" {
+        test_fail "incorrect match values: ${val}";
+    }
+}
+
+set "match8" "klopfropstroptop";
+
+test "Letters words *? - 1" {
+	if not string :matches "${match8}" "*fr??*top" {
+		test_fail "should have matched";
+	}
+
+	set "val" ":${0}:${1}:${2}:${3}:${4}:${5}:";
+	
+	if not string :is "${val}" ":klopfropstroptop:klop:o:p:strop::" {
+		test_fail "incorrect match values: ${val}";
+	}
+}
+
+test "Letters words *? - 2" {
+	if not string :matches "${match8}" "?*fr??*top" {
+		test_fail "should have matched";
+	}
+	
+	set "val" ":${0}:${1}:${2}:${3}:${4}:${5}:${6}:";
+
+	if not string :is "${val}" ":klopfropstroptop:k:lop:o:p:strop::" {
+		test_fail "incorrect match values: ${val}";
+	}
+}
+
+test "Letters words *? backtrack" {
+	if not string :matches "${match8}" "*?op" {
+		test_fail "should have matched";
+	}
+
+	set "val" ":${0}:${1}:${2}:${3}:${4}:";
+	
+	if not string :is "${val}" ":klopfropstroptop:klopfropstrop:t:::" {
+		test_fail "incorrect match values: ${val}";
+	}
+}
+
+test "Letters words *? first" {
+	if not string :matches "${match8}" "*?op*" {
+		test_fail "failed to match";
+	}
+
+	set "val" ":${0}:${1}:${2}:${3}:${4}:";
+
+	if not string :is "${val}" ":klopfropstroptop:k:l:fropstroptop::" {
+		test_fail "incorrect match values: ${val}";
+	}
+}
diff --git a/src/testsuite/tests/lexer.svtest b/src/testsuite/tests/lexer.svtest
index 95e668b6d..491309d0b 100644
--- a/src/testsuite/tests/lexer.svtest
+++ b/src/testsuite/tests/lexer.svtest
@@ -3,7 +3,7 @@ require "variables";
 
 /* Test conformance to RFC 5228 - 2.4.2. Strings */
 
-set "text" text:
+set "text" text: # Comment
 Line 1
 .Line 2
 ..Line 3
@@ -20,10 +20,20 @@ set "quoted"
 Line 5
 ";
 
-test "LEXER-string-literal" {
+test "String Literal" {
 	if not string :is "${text}" "${quoted}" {
 		test_fail "lexer messed-up dot stuffing";
 	}
+
+	if string :is "${text}" "" {
+		test_fail "variable substitution failed";
+	}
+}
+
+test "Unknown Escapes" {
+	if not string :is "\a\a\a\a\a" "aaaaa" {
+		test_fail "unknown quoted string escape sequences are handled inappropriately";
+	}
 }
 
 
diff --git a/src/testsuite/tests/match-types/matches.svtest b/src/testsuite/tests/match-types/matches.svtest
index a808520c5..32b4eb510 100644
--- a/src/testsuite/tests/match-types/matches.svtest
+++ b/src/testsuite/tests/match-types/matches.svtest
@@ -189,3 +189,5 @@ test "NO-MATCH-M" {
 		test_fail "should not have matched";
 	}
 }
+
+
-- 
GitLab