]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: strfuncs - string match length and prefix checking helpers
authorPhil Carmody <phil@dovecot.fi>
Thu, 2 Feb 2017 12:27:58 +0000 (14:27 +0200)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Fri, 8 Jun 2018 08:36:42 +0000 (11:36 +0300)
strncmp(input, "literal", 7) is an idiom used everywhere, but leaves
room for human error in calculating the length.

strncmp(input, "literal", strlen("literal")) is an idiom also used
everywhere, but is both verbose and might be inefficient on some
legacy or ultralightweight compilers.

The old techniques are presumed to be optimal code-wise, but are
verbose (and, containing redundancy, they leave room for human error),
so make the macro fall back onto this operation, simply avoiding the
redundancy/verbosity.

The macro expansion does not multiply evaluate any of its parameters,
so should be safe even in the strangest of situations.

Signed-off-by: Phil Carmody <phil@dovecot.fi>
src/lib/strfuncs.c
src/lib/strfuncs.h
src/lib/test-strfuncs.c

index ac10605969547b02ddff0789fdfe0ff2a9fe171f..e2e082e63ac5a4faf6c9e52591241c92ef624e00 100644 (file)
@@ -524,6 +524,17 @@ bool mem_equals_timing_safe(const void *p1, const void *p2, size_t size)
        return ret == 0;
 }
 
+size_t
+str_match(const char *p1, const char *p2)
+{
+       size_t i = 0;
+
+       while(p1[i] != '\0' && p1[i] == p2[i])
+               i++;
+
+       return i;
+}
+
 static char **
 split_str_slow(pool_t pool, const char *data, const char *separators, bool spaces)
 {
index 222976dcd4f209c571a99f65cebdd6c741cecdbf..9bff5a25a92add2ba94ed7a854714196968f6100 100644 (file)
@@ -72,6 +72,17 @@ int i_strcasecmp_p(const char *const *p1, const char *const *p2) ATTR_PURE;
    against timing attacks, so it compares all the bytes every time. */
 bool mem_equals_timing_safe(const void *p1, const void *p2, size_t size);
 
+size_t str_match(const char *p1, const char *p2) ATTR_PURE;
+static inline ATTR_PURE bool str_begins(const char *haystack, const char *needle)
+{
+       return needle[str_match(haystack, needle)] == '\0';
+}
+#if defined(__GNUC__) && (__GNUC__ >= 2)
+/* GCC (and Clang) are known to have a compile-time strlen("literal") shortcut, and
+   an optimised strncmp(), so use that by default. Macro is multi-evaluation safe. */
+# define str_begins(h, n) (__builtin_constant_p(n) ? strncmp((h), (n), strlen(n))==0 : (str_begins)((h), (n)))
+#endif
+
 static inline char *i_strchr_to_next(const char *str, char chr)
 {
        char *tmp = (char *)strchr(str, chr);
index 2a532867be94b1163a1612da8d83eed0c9cff8ce..c8f2b2c68ea9a9e95e46ef5166259c463f579706 100644 (file)
@@ -368,6 +368,45 @@ static void test_dec2str_buf(void)
        test_end();
 }
 
+static void
+test_str_match(void)
+{
+       static const struct {
+               const char*s1, *s2; size_t match;
+       } tests[] = {
+#define MATCH_TEST(common, left, right) { common left, common right, sizeof(common)-1 }
+               MATCH_TEST("", "", ""),
+               MATCH_TEST("", "x", ""),
+               MATCH_TEST("", "", "x"),
+               MATCH_TEST("", "foo", "bar"),
+               MATCH_TEST("x", "", ""),
+               MATCH_TEST("x", "y", "z"),
+               MATCH_TEST("blahblahblah", "", ""),
+               MATCH_TEST("blahblahblah", "", "bar"),
+               MATCH_TEST("blahblahblah", "foo", ""),
+               MATCH_TEST("blahblahblah", "foo", "bar"),
+#undef MATCH_TEST
+       };
+
+       unsigned int i;
+
+       test_begin("str_match");
+       for (i = 0; i < N_ELEMENTS(tests); i++)
+               test_assert_idx(str_match(tests[i].s1, tests[i].s2) == tests[i].match, i);
+       test_end();
+
+       test_begin("i_strbegins");
+       for (i = 0; i < N_ELEMENTS(tests); i++) {
+               /* This is just 2 ways of wording the same test, but that also
+                  sanity tests the match values above. */
+               test_assert_idx(str_begins(tests[i].s1, tests[i].s2) ==
+                               (strncmp(tests[i].s1, tests[i].s2, strlen(tests[i].s2)) == 0), i);
+               test_assert_idx(str_begins(tests[i].s1, tests[i].s2) ==
+                               (strlen(tests[i].s2) == tests[i].match), i);
+       }
+       test_end();
+}
+
 void test_strfuncs(void)
 {
        test_p_strdup();
@@ -384,4 +423,5 @@ void test_strfuncs(void)
        test_p_array_const_string_join();
        test_mem_equals_timing_safe();
        test_dec2str_buf();
+       test_str_match();
 }