]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: Add str_begins_icase*() and str_match_icase()
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Sun, 19 Apr 2020 12:31:35 +0000 (15:31 +0300)
committertimo.sirainen <timo.sirainen@open-xchange.com>
Wed, 23 Mar 2022 10:25:06 +0000 (10:25 +0000)
src/lib/strfuncs.c
src/lib/strfuncs.h
src/lib/test-strfuncs.c

index 2f72ea74bfc4540a209c1b71b31a60bdd196e90f..68dc4378b32d76cf32033cb800f759b3089d3d8e 100644 (file)
@@ -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)
 {
index 99baa28064de34ea334858302fd3bb18c77d80e2..32450dffaf40dd00f4fdb4cdaba925b0d2df1654 100644 (file)
@@ -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.
index 0100e733ecbdfed50a0a3ace69bc3daf7eb49fb5..bbc5c3a5d18fb1937acc1e7f77c061f6242d031c 100644 (file)
@@ -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();
 }