diff --git a/src/libinput-util.c b/src/libinput-util.c index 4cf310b3..a0b423ba 100644 --- a/src/libinput-util.c +++ b/src/libinput-util.c @@ -285,3 +285,85 @@ parse_dimension_property(const char *prop, size_t *w, size_t *h) *h = (size_t)y; return true; } + +/** + * 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 ((word = next_word(&s, &l, separators)) != NULL) + nelems++; + + if (nelems == 0) + return NULL; + + nelems++; /* NULL-terminated */ + strv = zalloc(nelems * sizeof *strv); + if (!strv) + return NULL; + + 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; +} diff --git a/src/libinput-util.h b/src/libinput-util.h index 9b10e7dc..3fd37476 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -456,4 +456,22 @@ safe_atod(const char *str, double *val) return true; } +char **strv_from_string(const char *string, const char *separator); + +static inline void +strv_free(char **strv) { + char **s = strv; + + if (!strv) + return; + + while (*s != NULL) { + free(*s); + *s = (char*)0x1; /* detect use-after-free */ + s++; + } + + free (strv); +} + #endif /* LIBINPUT_UTIL_H */ diff --git a/test/misc.c b/test/misc.c index b514f90b..eadeae3a 100644 --- a/test/misc.c +++ b/test/misc.c @@ -951,6 +951,50 @@ START_TEST(safe_atod_test) } END_TEST +struct strsplit_test { + const char *string; + const char *delim; + const char *results[10]; +}; + +START_TEST(strsplit_test) +{ + struct strsplit_test 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]) { + ck_assert_str_eq(t->results[idx], strv[idx]); + idx++; + } + ck_assert_ptr_eq(strv[idx], NULL); + strv_free(strv); + t++; + } + + /* Special cases */ + ck_assert_ptr_eq(strv_from_string("", " "), NULL); + ck_assert_ptr_eq(strv_from_string(" ", " "), NULL); + ck_assert_ptr_eq(strv_from_string(" ", " "), NULL); + ck_assert_ptr_eq(strv_from_string("oneoneone", "one"), NULL); +} +END_TEST + static int open_restricted_leak(const char *path, int flags, void *data) { return *(int*)data; @@ -1080,6 +1124,7 @@ litest_setup_tests_misc(void) litest_add_no_device("misc:parser", dimension_prop_parser); litest_add_no_device("misc:parser", safe_atoi_test); litest_add_no_device("misc:parser", safe_atod_test); + litest_add_no_device("misc:parser", strsplit_test); litest_add_no_device("misc:time", time_conversion); litest_add_no_device("misc:fd", fd_no_event_leak);