From: Florian Forster Date: Thu, 21 Dec 2023 10:38:20 +0000 (+0100) Subject: strbuf: Add `strbuf_print_restricted`. X-Git-Tag: 6.0.0-rc0~28^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fad8788bb357f9049044fb696bf55a3a93b81b8c;p=thirdparty%2Fcollectd.git strbuf: Add `strbuf_print_restricted`. This new function is similar to `strbuf_print_escaped` but differs in two important aspects: * `strbuf_print_restricted` expects a list of acceptable characters, i.e. an allow list. `strbuf_print_escaped` expects a deny list. * `strbuf_print_restricted` *replaces* characters not in the allow list. `strbuf_print_escaped` adds an escape character in front of the denied character. --- diff --git a/src/utils/strbuf/strbuf.c b/src/utils/strbuf/strbuf.c index 3e4db07a4..6f1a9f06a 100644 --- a/src/utils/strbuf/strbuf.c +++ b/src/utils/strbuf/strbuf.c @@ -289,3 +289,46 @@ int strbuf_print_escaped(strbuf_t *buf, char const *s, char const *need_escape, return 0; } + +int strbuf_print_restricted(strbuf_t *buf, char const *s, char const *accept, + char replace_char) { + if (buf == NULL || s == NULL || accept == NULL || accept[0] == 0 || replace_char == 0) { + return EINVAL; + } + if (strchr(accept, replace_char) == NULL) { + return EINVAL; + } + + size_t s_len = strlen(s); + if (s_len == 0) { + return strbuf_print(buf, s); + } + + while (s_len > 0) { + size_t valid_len = strspn(s, accept); + if (valid_len == s_len) { + return strbuf_print(buf, s); + } + if (valid_len != 0) { + int status = strbuf_printn(buf, s, valid_len); + if (status != 0) { + return status; + } + + s += valid_len; + s_len -= valid_len; + continue; + } + + char tmp[] = {replace_char, 0}; + int status = strbuf_print(buf, tmp); + if (status != 0) { + return status; + } + + s++; + s_len--; + } + + return 0; +} diff --git a/src/utils/strbuf/strbuf.h b/src/utils/strbuf/strbuf.h index c3a95fae2..f836287fe 100644 --- a/src/utils/strbuf/strbuf.h +++ b/src/utils/strbuf/strbuf.h @@ -104,4 +104,10 @@ int strbuf_printn(strbuf_t *buf, char const *s, size_t n); int strbuf_print_escaped(strbuf_t *buf, char const *s, char const *need_escape, char escape_char); +/* strbuf_print_restricted adds a copy of "s" to the buffer, that only consists + * of characters in "accept". All other characters are replaced with + * "replace_char". "replace_char" has to be in "accept". */ +int strbuf_print_restricted(strbuf_t *buf, char const *s, char const *accept, + char replace_char); + #endif diff --git a/src/utils/strbuf/strbuf_test.c b/src/utils/strbuf/strbuf_test.c index cd25b93cf..9078b4bcd 100644 --- a/src/utils/strbuf/strbuf_test.c +++ b/src/utils/strbuf/strbuf_test.c @@ -177,6 +177,100 @@ DEF_TEST(print_escaped) { return 0; } +DEF_TEST(print_restricted) { + struct { + char const *name; + char const *s; + char const *accept; + char replace_char; + char const *want; + int want_err; + } cases[] = { + { + .name = "no replacement", + .s = "normal string", + .accept = "abcdefghijklmnopqrstuvwxyz ", + .replace_char = ' ', + .want = "normal string", + }, + { + .name = "single replacement", + .s = "normal string", + .accept = "abcdefghijklmnopqrstuvwxyz_", + .replace_char = '_', + .want = "normal_string", + }, + { + .name = "double replacement", + .s = "normal, string", + .accept = "abcdefghijklmnopqrstuvwxyz_", + .replace_char = '_', + .want = "normal__string", + }, + { + .name = "empty string", + .s = "", + .accept = "abcdefghijklmnopqrstuvwxyz_", + .replace_char = '_', + .want = "", + }, + { + .name = "s is NULL", + .s = NULL, + .accept = "abcdefghijklmnopqrstuvwxyz_", + .replace_char = '_', + .want_err = EINVAL, + }, + { + .name = "accept is empty", + .s = "normal string", + .accept = "", + .replace_char = '_', + .want_err = EINVAL, + }, + { + .name = "accept is NULL", + .s = "normal string", + .accept = NULL, + .replace_char = '_', + .want_err = EINVAL, + }, + { + .name = "replace char is not in accept", + .s = "normal string", + .accept = "abcdefghijklmnopqrstuvwxyz_", + .replace_char = '@', + .want_err = EINVAL, + }, + { + .name = "replace char is zero", + .s = "normal string", + .accept = "abcdefghijklmnopqrstuvwxyz", + .replace_char = 0, + .want_err = EINVAL, + }, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(cases[0])); i++) { + printf("# Case %zu: %s\n", i, cases[i].name); + strbuf_t buf = STRBUF_CREATE; + + EXPECT_EQ_INT(cases[i].want_err, + strbuf_print_restricted(&buf, cases[i].s, cases[i].accept, + cases[i].replace_char)); + EXPECT_EQ_STR(cases[i].want, buf.ptr); + if (cases[i].want_err == 0) { + /* The string in buf has to have the same length as "s" and must entirely + * consist of characters in "accept". */ + EXPECT_EQ_UINT64(strlen(cases[i].s), strspn(buf.ptr, cases[i].accept)); + } + + STRBUF_DESTROY(buf); + } + + return 0; +} + int main(int argc, char **argv) /* {{{ */ { RUN_TEST(dynamic_heap); @@ -185,6 +279,7 @@ int main(int argc, char **argv) /* {{{ */ RUN_TEST(fixed_stack); RUN_TEST(static_stack); RUN_TEST(print_escaped); + RUN_TEST(print_restricted); END_TEST; } /* }}} int main */