/* .IP ext_delimiters
/* The set of address extension delimiters.
/* .IP null_sender
-/* If a sender pattern equals the null_sender pattern, then
+/* If a sender pattern equals the null_sender pattern, then
/* the empty address is matched.
/* .IP wildcard
/* Null pointer, or non-empty string with a wildcard pattern.
/* flags= */ 0)) != 0) {
/*
- * Match the sender. TODO: don't break a sender pattern on a
- * comma/space inside a quoted localpart.
+ * Match the sender. Don't break a sender pattern between double
+ * quotes.
*/
cp = saved_sender_patterns = mystrdup(sender_patterns);
while (found_or_error == LSM_STAT_NOTFOUND
- && (sender_pattern = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+ && (sender_pattern = mystrtokdq(&cp, CHARS_COMMA_SP)) != 0) {
/* Special pattern: @domain. */
if (*sender_pattern == '@') {
if ((at_sender_domain = strrchr(sender_addr, '@')) != 0
- && strcasecmp_utf8(sender_pattern, at_sender_domain) == 0)
+ && strcasecmp_utf8(sender_pattern, at_sender_domain) == 0)
found_or_error = LSM_STAT_FOUND;
}
/* Special pattern: wildcard. */
found_or_error = LSM_STAT_FOUND;
}
/* Literal pattern: match the stripped and externalized sender. */
- if (ext_stripped_sender == 0)
- ext_stripped_sender =
- STR(strip_externalize_addr(lsm->ext_stripped_sender,
- sender_addr,
- lsm->ext_delimiters));
- if (strcasecmp_utf8(sender_pattern, ext_stripped_sender) == 0)
- found_or_error = LSM_STAT_FOUND;
+ else {
+ if (ext_stripped_sender == 0)
+ ext_stripped_sender =
+ STR(strip_externalize_addr(lsm->ext_stripped_sender,
+ sender_addr,
+ lsm->ext_delimiters));
+ if (strcasecmp_utf8(sender_pattern, ext_stripped_sender) == 0)
+ found_or_error = LSM_STAT_FOUND;
+ }
}
myfree(saved_sender_patterns);
} else {
"inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}",
"+-", "<>", "*", "uid:12345", "foo", LSM_STAT_FOUND
},
+ {"unknown \"other last\"",
+ "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "other last", LSM_STAT_NOTFOUND
+ },
+ {"bare \"first last\"",
+ "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "first last", LSM_STAT_FOUND
+ },
+ {"\"first last\"@domain",
+ "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+ "+-", "<>", "*", "foo", "first last@example.com", LSM_STAT_FOUND
+ },
};
struct testcase *tp;
int act_return;
/* char **bufp;
/* const char *delimiters;
/* const char *parens;
+/*
+/* char *mystrtokdq(bufp, delimiters)
+/* char **bufp;
+/* const char *delimiters;
/* DESCRIPTION
/* mystrtok() splits a buffer on the specified \fIdelimiters\fR.
/* Tokens are delimited by runs of delimiters, so this routine
/* opening and closing parenthesis (one of each). The set of
/* \fIparens\fR must be distinct from the set of \fIdelimiters\fR.
/*
+/* mystrtokdq() is like mystrtok() but will not split text
+/* between double quotes. The backslash character may be used
+/* to escape characters. The double quote and backslash
+/* character must not appear in the set of \fIdelimiters\fR.
+/*
/* The \fIbufp\fR argument specifies the start of the search; it
/* is updated with each call. The input is destroyed.
/*
char *mystrtokq(char **src, const char *sep, const char *parens)
{
char *start = *src;
- static char *cp;
+ static char *cp;
int ch;
int level;
for (level = 0, cp = start; (ch = *(unsigned char *) cp) != 0; cp++) {
if (ch == parens[0]) {
level++;
- } else if (level > 0 && ch == parens[1]) {
+ } else if (level > 0 && ch == parens[1]) {
level--;
} else if (level == 0 && strchr(sep, ch) != 0) {
*cp++ = 0;
return (start);
}
+/* mystrtokdq - safe tokenizer, double quote and backslash support */
+
+char *mystrtokdq(char **src, const char *sep)
+{
+ char *cp = *src;
+ char *start;
+
+ /*
+ * Skip leading delimiters.
+ */
+ cp += strspn(cp, sep);
+
+ /*
+ * Skip to next unquoted space or comma.
+ */
+ if (*cp == 0) {
+ start = 0;
+ } else {
+ int in_quotes;
+
+ for (in_quotes = 0, start = cp; *cp; cp++) {
+ if (*cp == '\\') {
+ if (*++cp == 0)
+ break;
+ } else if (*cp == '"') {
+ in_quotes = !in_quotes;
+ } else if (!in_quotes && strchr(sep, *(unsigned char *) cp) != 0) {
+ *cp++ = 0;
+ break;
+ }
+ }
+ }
+ *src = cp;
+ return (start);
+}
+
#ifdef TEST
/*
- * Test program: read lines from stdin, split on whitespace.
+ * Test program.
*/
-#include "vstring.h"
-#include "vstream.h"
-#include "vstring_vstream.h"
+#include "msg.h"
+#include "mymalloc.h"
+
+ /*
+ * The following needs to be large enough to include a null terminator in
+ * every testcase.expected field.
+ */
+#define EXPECT_SIZE 5
+
+struct testcase {
+ const char *action;
+ const char *input;
+ const char *expected[EXPECT_SIZE];
+};
+static const struct testcase testcases[] = {
+ {"mystrtok", ""},
+ {"mystrtok", " foo ", {"foo"}},
+ {"mystrtok", " foo bar ", {"foo", "bar"}},
+ {"mystrtokq", ""},
+ {"mystrtokq", "foo bar", {"foo", "bar"}},
+ {"mystrtokq", "{ bar } ", {"{ bar }"}},
+ {"mystrtokq", "foo { bar } baz", {"foo", "{ bar }", "baz"}},
+ {"mystrtokq", "foo{ bar } baz", {"foo{ bar }", "baz"}},
+ {"mystrtokq", "foo { bar }baz", {"foo", "{ bar }baz"}},
+ {"mystrtokdq", ""},
+ {"mystrtokdq", " foo ", {"foo"}},
+ {"mystrtokdq", " foo bar ", {"foo", "bar"}},
+ {"mystrtokdq", " foo\\ bar ", {"foo\\ bar"}},
+ {"mystrtokdq", " foo \\\" bar", {"foo", "\\\"", "bar"}},
+ {"mystrtokdq", " foo \" bar baz\" ", {"foo", "\" bar baz\""}},
+};
int main(void)
{
- VSTRING *vp = vstring_alloc(100);
- char *start;
- char *str;
-
- while (vstring_fgets(vp, VSTREAM_IN) && VSTRING_LEN(vp) > 0) {
- start = vstring_str(vp);
- if (strchr(start, CHARS_BRACE[0]) == 0) {
- while ((str = mystrtok(&start, CHARS_SPACE)) != 0)
- vstream_printf(">%s<\n", str);
- } else {
- while ((str = mystrtokq(&start, CHARS_SPACE, CHARS_BRACE)) != 0)
- vstream_printf(">%s<\n", str);
+ const struct testcase *tp;
+ char *actual;
+ int pass;
+ int fail;
+ int match;
+ int n;
+
+#define NUM_TESTS sizeof(testcases)/sizeof(testcases[0])
+#define STR_OR_NULL(s) ((s) ? (s) : "null")
+
+ for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
+ char *saved_input = mystrdup(tp->input);
+ char *cp = saved_input;
+
+ msg_info("RUN test case %ld %s >%s<",
+ (long) (tp - testcases), tp->action, tp->input);
+#if 0
+ msg_info("action=%s", tp->action);
+ msg_info("input=%s", tp->input);
+ for (n = 0; tp->expected[n]; tp++)
+ msg_info("expected[%d]=%s", n, tp->expected[n]);
+#endif
+
+ for (n = 0; n < EXPECT_SIZE; n++) {
+ if (strcmp(tp->action, "mystrtok") == 0) {
+ actual = mystrtok(&cp, CHARS_SPACE);
+ } else if (strcmp(tp->action, "mystrtokq") == 0) {
+ actual = mystrtokq(&cp, CHARS_SPACE, CHARS_BRACE);
+ } else if (strcmp(tp->action, "mystrtokdq") == 0) {
+ actual = mystrtokdq(&cp, CHARS_SPACE);
+ } else {
+ msg_panic("invalid command: %s", tp->action);
+ }
+ if ((match = (actual && tp->expected[n]) ?
+ (strcmp(actual, tp->expected[n]) == 0) :
+ (actual == tp->expected[n])) != 0) {
+ if (actual == 0) {
+ msg_info("PASS test %ld", (long) (tp - testcases));
+ pass++;
+ break;
+ }
+ } else {
+ msg_warn("expected: >%s<, got: >%s<",
+ STR_OR_NULL(tp->expected[n]), STR_OR_NULL(actual));
+ msg_info("FAIL test %ld", (long) (tp - testcases));
+ fail++;
+ break;
+ }
}
- vstream_fflush(VSTREAM_OUT);
+ if (n >= EXPECT_SIZE)
+ msg_panic("need to increase EXPECT_SIZE");
+ myfree(saved_input);
}
- vstring_free(vp);
- return (0);
+ return (fail > 0);
}
#endif