From: Timo Sirainen Date: Sun, 19 Apr 2020 12:31:35 +0000 (+0300) Subject: lib: Add str_begins_icase*() and str_match_icase() X-Git-Tag: 2.4.0~4233 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ec8b78cabca7380e104db8f7ab7500c811cb9d29;p=thirdparty%2Fdovecot%2Fcore.git lib: Add str_begins_icase*() and str_match_icase() --- diff --git a/src/lib/strfuncs.c b/src/lib/strfuncs.c index 2f72ea74bf..68dc4378b3 100644 --- a/src/lib/strfuncs.c +++ b/src/lib/strfuncs.c @@ -633,6 +633,16 @@ str_match(const char *p1, const char *p2) return i; } +size_t str_match_icase(const char *p1, const char *p2) +{ + size_t i = 0; + + while (p1[i] != '\0' && i_tolower(p1[i]) == i_tolower(p2[i])) + i++; + + return i; +} + #undef str_begins bool str_begins(const char *haystack, const char *needle, const char **suffix_r) { @@ -643,6 +653,17 @@ bool str_begins(const char *haystack, const char *needle, const char **suffix_r) return TRUE; } +#undef str_begins_icase +bool str_begins_icase(const char *haystack, const char *needle, + const char **suffix_r) +{ + size_t prefix_len = str_match_icase(haystack, needle); + if (needle[prefix_len] != '\0') + return FALSE; + *suffix_r = haystack + prefix_len; + return TRUE; +} + size_t i_memspn(const void *data, size_t data_len, const void *accept, size_t accept_len) { diff --git a/src/lib/strfuncs.h b/src/lib/strfuncs.h index 99baa28064..32450dffaf 100644 --- a/src/lib/strfuncs.h +++ b/src/lib/strfuncs.h @@ -87,13 +87,21 @@ bool mem_equals_timing_safe(const void *p1, const void *p2, size_t size); bool str_equals_timing_almost_safe(const char *s1, const char *s2); size_t str_match(const char *p1, const char *p2) ATTR_PURE; +size_t str_match_icase(const char *p1, const char *p2) ATTR_PURE; bool str_begins(const char *haystack, const char *needle, const char **suffix_r); +bool str_begins_icase(const char *haystack, const char *needle, + const char **suffix_r); static inline ATTR_PURE bool str_begins_with(const char *haystack, const char *needle) { return needle[str_match(haystack, needle)] == '\0'; } +static inline ATTR_PURE bool +str_begins_icase_with(const char *haystack, const char *needle) +{ + return needle[str_match_icase(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. */ @@ -111,6 +119,14 @@ str_begins_builtin_success(const char *haystack, size_t needle_len, (!__builtin_constant_p(n) ? (str_begins)((h), (n), (suffix_r)) : \ (strncmp((h), (n), strlen(n)) != 0 ? FALSE : \ str_begins_builtin_success((h), strlen(n), suffix_r))) + +# define str_begins_icase_with(h, n) \ + (__builtin_constant_p(n) ? strncasecmp((h), (n), strlen(n))==0 : \ + (str_begins_icase_with)((h), (n))) +# define str_begins_icase(h, n, suffix_r) \ + (!__builtin_constant_p(n) ? (str_begins_icase)((h), (n), (suffix_r)) : \ + (strncasecmp((h), (n), strlen(n)) != 0 ? FALSE : \ + str_begins_builtin_success((h), strlen(n), suffix_r))) #endif /* Get length of a prefix segment. diff --git a/src/lib/test-strfuncs.c b/src/lib/test-strfuncs.c index 0100e733ec..bbc5c3a5d1 100644 --- a/src/lib/test-strfuncs.c +++ b/src/lib/test-strfuncs.c @@ -462,6 +462,13 @@ test_str_match(void) static const struct { const char*s1, *s2; size_t match; } tests[] = { + { "", "a", 0 }, + { "a", "a", 1 }, + { "a", "A", 0 }, + { "ab", "a", 1 }, + { "ab", "A", 0 }, + { "B", "AB", 0 }, + { "ab", "AB", 0 }, #define MATCH_TEST(common, left, right) { common left, common right, sizeof(common)-1 } MATCH_TEST("", "", ""), MATCH_TEST("", "x", ""), @@ -514,6 +521,61 @@ test_str_match(void) test_end(); } +static void +test_str_match_icase(void) +{ + const struct { + const char *s1, *s2; + size_t match; + } tests[] = { + { "", "a", 0 }, + { "a", "a", 1 }, + { "a", "A", 1 }, + { "ab", "a", 1 }, + { "ab", "A", 1 }, + { "B", "AB", 0 }, + { "ab", "AB", 2 }, + }; + const char *suffix; + unsigned int i; + + test_begin("str_match_icase"); + for (i = 0; i < N_ELEMENTS(tests); i++) + test_assert_idx(str_match_icase(tests[i].s1, tests[i].s2) == tests[i].match, i); + test_end(); + + test_begin("str_begins_icase"); + 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. */ + bool equals = strncasecmp(tests[i].s1, tests[i].s2, strlen(tests[i].s2)) == 0; + test_assert_idx(str_begins_icase_with(tests[i].s1, tests[i].s2) == equals, i); + test_assert_idx(str_begins_icase(tests[i].s1, tests[i].s2, &suffix) == equals && + (!equals || suffix == tests[i].s1 + strlen(tests[i].s2)), i); + test_assert_idx(str_begins_icase(tests[i].s1, tests[i].s2, &suffix) == + (strlen(tests[i].s2) == tests[i].match), i); + } + /* test literal-optimized versions of these */ + test_assert(str_begins_icase("", "", &suffix) && suffix[0] == '\0'); + test_assert(str_begins_icase("aBc", "", &suffix) && strcmp(suffix, "aBc") == 0); + test_assert(str_begins_icase("aBc", "a", &suffix) && strcmp(suffix, "Bc") == 0); + test_assert(str_begins_icase("aBc", "A", &suffix) && strcmp(suffix, "Bc") == 0); + test_assert(str_begins_icase("aBc", "AbC", &suffix) && suffix[0] == '\0'); + suffix = NULL; + test_assert(!str_begins_icase("aBc", "AbCd", &suffix) && suffix == NULL); + test_assert(!str_begins_icase("", "aBc", &suffix) && suffix == NULL); + test_assert(!str_begins_icase("aB", "AbC", &suffix) && suffix == NULL); + + test_assert(str_begins_icase_with("", "")); + test_assert(str_begins_icase_with("aBc", "")); + test_assert(str_begins_icase_with("aBc", "A")); + test_assert(str_begins_icase_with("aBc", "AbC")); + test_assert(!str_begins_icase_with("aBc", "aBcD")); + test_assert(!str_begins_icase_with("", "aBc")); + test_assert(!str_begins_icase_with("aB", "AbC")); + test_end(); +} + static void test_memspn(void) { #undef TEST_CASE @@ -637,6 +699,7 @@ void test_strfuncs(void) test_str_equals_timing_almost_safe(); test_dec2str_buf(); test_str_match(); + test_str_match_icase(); test_memspn(); test_memcspn(); }