From: Phil Carmody Date: Thu, 2 Feb 2017 12:27:58 +0000 (+0200) Subject: lib: strfuncs - string match length and prefix checking helpers X-Git-Tag: 2.3.9~1924 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=dff3bf002d29ddd284e56b1293c516c211456802;p=thirdparty%2Fdovecot%2Fcore.git lib: strfuncs - string match length and prefix checking helpers 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 --- diff --git a/src/lib/strfuncs.c b/src/lib/strfuncs.c index e1fd1f4fa0..f09782b144 100644 --- a/src/lib/strfuncs.c +++ b/src/lib/strfuncs.c @@ -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) { diff --git a/src/lib/strfuncs.h b/src/lib/strfuncs.h index 7bff5d8fc4..974cd468ec 100644 --- a/src/lib/strfuncs.h +++ b/src/lib/strfuncs.h @@ -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); diff --git a/src/lib/test-strfuncs.c b/src/lib/test-strfuncs.c index 2a532867be..c8f2b2c68e 100644 --- a/src/lib/test-strfuncs.c +++ b/src/lib/test-strfuncs.c @@ -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(); }