/* SPDX-License-Identifier: MIT */ /* * Copyright © 2008 Kristian Høgsberg * Copyright © 2013-2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include "util-strings.h" /** * Return the next word in a string pointed to by state before the first * separator character. Call repeatedly to tokenize a whole string. * * @param state Current state * @param len String length of the word returned * @param separators List of separator characters * * @return The first word in *state, NOT null-terminated */ static const char * next_word(const char **state, size_t *len, const char *separators) { const char *next = *state; size_t l; if (!*next) return NULL; next += strspn(next, separators); if (!*next) { *state = next; return NULL; } l = strcspn(next, separators); *state = next + l; *len = l; return next; } /** * Return a null-terminated string array with the tokens in the input * string, e.g. "one two\tthree" with a separator list of " \t" will return * an array [ "one", "two", "three", NULL ]. * * Use strv_free() to free the array. * * @param in Input string * @param separators List of separator characters * * @return A null-terminated string array or NULL on errors */ char ** strv_from_string(const char *in, const char *separators) { const char *s, *word; char **strv = NULL; int nelems = 0, idx; size_t l; assert(in != NULL); s = in; while (next_word(&s, &l, separators) != NULL) nelems++; if (nelems == 0) return NULL; nelems++; /* NULL-terminated */ strv = xalloc(nelems * sizeof *strv); idx = 0; s = in; while ((word = next_word(&s, &l, separators)) != NULL) { char *copy = strndup(word, l); if (!copy) { strv_free(strv); return NULL; } strv[idx++] = copy; } return strv; } /** * Return a newly allocated string with all elements joined by the * joiner, same as Python's string.join() basically. * A strv of ["one", "two", "three", NULL] with a joiner of ", " results * in "one, two, three". * * An empty strv ([NULL]) returns NULL, same for passing NULL as either * argument. * * @param strv Input string arrray * @param joiner Joiner between the elements in the final string * * @return A null-terminated string joining all elements */ char * strv_join(char **strv, const char *joiner) { char **s; char *str; size_t slen = 0; size_t count = 0; if (!strv || !joiner) return NULL; if (strv[0] == NULL) return NULL; for (s = strv, count = 0; *s; s++, count++) { slen += strlen(*s); } assert(slen < 1000); assert(strlen(joiner) < 1000); assert(count > 0); assert(count < 100); slen += (count - 1) * strlen(joiner); str = xalloc(slen + 1); /* trailing \0 */ for (s = strv; *s; s++) { strcat(str, *s); --count; if (count > 0) strcat(str, joiner); } return str; } char * strreplace(const char *string, const char *separator, const char *replacement) { assert(string != NULL); assert(string[0] != '\0'); /* Enough to replace every character in the string with the * replacement. This will blow up on extremely long strings with long * replacements, but meh. It saves us having to write resizing code. */ size_t slen = strlen(string); size_t splen = strlen(separator); const char *current = string; const char *next; next = strstr(current, separator); if (!next) /* No separator found */ return xstrdup(string); size_t rlen = strlen(replacement); size_t max = slen * max(rlen, 1); char *r = calloc(max + 1, 1); /* the result, one extra for terminating \0 */ char *destptr = r; /* our strncpy calls truncate the second argument, we know... */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-truncation" while (next) { size_t len = next - current; /* silently truncate because we really don't care about this * case */ if (destptr + len > r + max) break; /* Copy the source string over, then append the separator */ strncpy(destptr, current, len); destptr += len; if (destptr + rlen > r + max) break; strncpy(destptr, replacement, rlen); destptr += rlen; current = next + splen; if (current > string + max) break; next = strstr(current, separator); } size_t len = strlen(current); /* silently truncate because we really don't care about this * case */ if (destptr + len <= r + max) { strncpy(destptr, current, len); destptr += len; } #pragma GCC diagnostic pop void *tmp = realloc(r, (destptr - r) + 1); assert(tmp); return tmp; } #if _enable_tests_ #include "util-munit.h" MUNIT_TEST(test_strsplit) { struct strsplit_test { const char *string; const char *delim; const char *results[10]; } tests[] = { { "one two three", " ", { "one", "two", "three", NULL } }, { "one", " ", { "one", NULL } }, { "one two ", " ", { "one", "two", NULL } }, { "one two", " ", { "one", "two", NULL } }, { " one two", " ", { "one", "two", NULL } }, { "one", "\t \r", { "one", NULL } }, { "one two three", " t", { "one", "wo", "hree", NULL } }, { " one two three", "te", { " on", " ", "wo ", "hr", NULL } }, { "one", "ne", { "o", NULL } }, { "onene", "ne", { "o", NULL } }, { NULL, NULL, { NULL }} }; struct strsplit_test *t = tests; while (t->string) { char **strv; int idx = 0; strv = strv_from_string(t->string, t->delim); while (t->results[idx]) { munit_assert_string_equal(t->results[idx], strv[idx]); idx++; } munit_assert_ptr_equal(strv[idx], NULL); strv_free(strv); t++; } /* Special cases */ munit_assert_ptr_equal(strv_from_string("", " "), NULL); munit_assert_ptr_equal(strv_from_string(" ", " "), NULL); munit_assert_ptr_equal(strv_from_string(" ", " "), NULL); munit_assert_ptr_equal(strv_from_string("oneoneone", "one"), NULL); return MUNIT_OK; } MUNIT_TEST(test_kvsplit_double) { struct kvsplit_dbl_test { const char *string; const char *psep; const char *kvsep; ssize_t nresults; struct { double a; double b; } results[32]; } tests[] = { { "1:2;3:4;5:6", ";", ":", 3, { {1, 2}, {3, 4}, {5, 6}}}, { "1.0x2.3 -3.2x4.5 8.090909x-6.00", " ", "x", 3, { {1.0, 2.3}, {-3.2, 4.5}, {8.090909, -6}}}, { "1:2", "x", ":", 1, {{1, 2}}}, { "1:2", ":", "x", -1, {}}, { "1:2", NULL, "x", -1, {}}, { "1:2", "", "x", -1, {}}, { "1:2", "x", NULL, -1, {}}, { "1:2", "x", "", -1, {}}, { "a:b", "x", ":", -1, {}}, { "", " ", "x", -1, {}}, { "1.2.3.4.5", ".", "", -1, {}}, { NULL } }; struct kvsplit_dbl_test *t = tests; while (t->string) { struct key_value_double *result = NULL; ssize_t npairs; npairs = kv_double_from_string(t->string, t->psep, t->kvsep, &result); munit_assert_int(npairs, ==, t->nresults); for (ssize_t i = 0; i < npairs; i++) { munit_assert_double(t->results[i].a, ==, result[i].key); munit_assert_double(t->results[i].b, ==, result[i].value); } free(result); t++; } return MUNIT_OK; } MUNIT_TEST(test_strjoin) { struct strjoin_test { char *strv[10]; const char *joiner; const char *result; } tests[] = { { { "one", "two", "three", NULL }, " ", "one two three" }, { { "one", NULL }, "x", "one" }, { { "one", "two", NULL }, "x", "onextwo" }, { { "one", "two", NULL }, ",", "one,two" }, { { "one", "two", NULL }, ", ", "one, two" }, { { "one", "two", NULL }, "one", "oneonetwo" }, { { "one", "two", NULL }, NULL, NULL }, { { "", "", "", NULL }, " ", " " }, { { "a", "b", "c", NULL }, "", "abc" }, { { "", "b", "c", NULL }, "x", "xbxc" }, { { "", "", "", NULL }, "", "" }, { { NULL }, NULL, NULL } }; struct strjoin_test *t = tests; struct strjoin_test nulltest = { {NULL}, "x", NULL }; while (t->strv[0]) { char *str; str = strv_join(t->strv, t->joiner); if (t->result == NULL) munit_assert(str == NULL); else munit_assert_string_equal(str, t->result); free(str); t++; } munit_assert(strv_join(nulltest.strv, "x") == NULL); return MUNIT_OK; } MUNIT_TEST(test_strstrip) { struct strstrip_test { const char *string; const char *expected; const char *what; } tests[] = { { "foo", "foo", "1234" }, { "\"bar\"", "bar", "\"" }, { "'bar'", "bar", "'" }, { "\"bar\"", "\"bar\"", "'" }, { "'bar'", "'bar'", "\"" }, { "\"bar\"", "bar", "\"" }, { "\"\"", "", "\"" }, { "\"foo\"bar\"", "foo\"bar", "\"" }, { "\"'foo\"bar\"", "foo\"bar", "\"'" }, { "abcfooabcbarbca", "fooabcbar", "abc" }, { "xxxxfoo", "foo", "x" }, { "fooyyyy", "foo", "y" }, { "xxxxfooyyyy", "foo", "xy" }, { "x xfooy y", " xfooy ", "xy" }, { " foo\n", "foo", " \n" }, { "", "", "abc" }, { "", "", "" }, { NULL , NULL, NULL } }; struct strstrip_test *t = tests; while (t->string) { char *str; str = strstrip(t->string, t->what); munit_assert_string_equal(str, t->expected); free(str); t++; } return MUNIT_OK; } MUNIT_TEST(test_strstartswith) { struct strstartswith_test { const char *string; const char *suffix; bool expected; } tests[] = { { "foobar", "foo", true }, { "foobar", "bar", false }, { "foobar", "foobar", true }, { "foo", "foobar", false }, { "foo", "", false }, { "", "", false }, { "foo", "", false }, { NULL, NULL, false }, }; for (struct strstartswith_test *t = tests; t->string; t++) { munit_assert_int(strstartswith(t->string, t->suffix), ==, t->expected); } return MUNIT_OK; } MUNIT_TEST(test_strendswith) { struct strendswith_test { const char *string; const char *suffix; bool expected; } tests[] = { { "foobar", "bar", true }, { "foobar", "foo", false }, { "foobar", "foobar", true }, { "foo", "foobar", false }, { "foobar", "", false }, { "", "", false }, { "", "foo", false }, { NULL, NULL, false }, }; for (struct strendswith_test *t = tests; t->string; t++) { munit_assert_int(strendswith(t->string, t->suffix), ==, t->expected); } return MUNIT_OK; } MUNIT_TEST(test_strreplace) { struct strtest { const char *string; const char *separator; const char *replacement; const char *expected; } tests[] = { { "teststring", "-", ".", "teststring" }, { "test-string", "-", ".", "test.string" }, { "test.string.", ".", "xyz", "testxyzstringxyz" }, { "ftestfstringf", "f", "", "teststring" }, { "xxx", "x", "y", "yyy" }, { "xyz", "x", "y", "yyz" }, { "xyz", "xy", "y", "yz" }, { .string = NULL }, }; for (struct strtest *t = tests; t->string; t++) { _cleanup_free_ char *s = strreplace(t->string, t->separator, t->replacement); munit_assert_string_equal(t->expected, s); } return MUNIT_OK; } MUNIT_TEST(test_cmdline_as_str) { _cleanup_free_ char *from_function = cmdline_as_str(); char cmdline[PATH_MAX]; xsnprintf(cmdline, sizeof(cmdline), "/proc/%i/cmdline", getpid()); int fd = open(cmdline, O_RDONLY); munit_assert_int(fd, >=, 0); int len = read(fd, cmdline, sizeof(cmdline) - 1); munit_assert_int(len, >=, 0); cmdline[len] = '\0'; munit_assert_string_equal(cmdline, from_function); return MUNIT_OK; } #endif