]> 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>
Thu, 26 Apr 2018 09:41:29 +0000 (09:41 +0000)
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 e1fd1f4fa0877694b8537a9f7fb571817a8d7326..f09782b14475db3210843bc402737792961285d3 100644 (file)
@@ -533,6 +533,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 7bff5d8fc41732046aa5fb6a9baae1304dc01266..974cd468ecf34b4839640d7f122782de9f93eecc 100644 (file)
@@ -75,6 +75,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();
 }