libei/src/util-strings.c

600 lines
15 KiB
C

/* 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 <string.h>
#include "util-strings.h"
#include "util-io.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 = xalloc(max + 1); /* the result, one extra for terminating \0 */
char *destptr = r;
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 */
memcpy(destptr, current, len);
destptr += len;
if (destptr + rlen > r + max)
break;
memcpy(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) {
memcpy(destptr, current, len);
destptr += len;
}
void *tmp = realloc(r, (destptr - r) + 1);
assert(tmp);
return tmp;
}
char **
strv_from_mem(const uint8_t *buffer, size_t sz, size_t stride)
{
assert(stride > 0);
assert(stride <= 16);
char **strv = xalloc(((sz / stride) + 2) * sizeof *strv);
#define hex(buffer_, sz, line_, offset_) \
(line_ + offset_ < sz) ? buffer_[line_ + offset_] : 0u
for (size_t i = 0; i < sz; i += stride) {
char *str = xaprintf(
"%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
hex(buffer, sz, i, 0),
hex(buffer, sz, i, 1),
hex(buffer, sz, i, 2),
hex(buffer, sz, i, 3),
hex(buffer, sz, i, 4),
hex(buffer, sz, i, 5),
hex(buffer, sz, i, 6),
hex(buffer, sz, i, 7),
hex(buffer, sz, i, 8),
hex(buffer, sz, i, 9),
hex(buffer, sz, i, 10),
hex(buffer, sz, i, 11),
hex(buffer, sz, i, 12),
hex(buffer, sz, i, 13),
hex(buffer, sz, i, 14),
hex(buffer, sz, i, 15));
/* Chop the string off at the last value or our stride, whichever applies */
if (i + stride >= sz)
str[(sz - i) * 3 - 1] = '\0';
else if (stride < 16)
str[stride * 3 - 1] = '\0';
strv[i / stride] = str;
}
return strv;
}
#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_ptr_not_null(str);
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());
_cleanup_close_ 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_ptr_not_null(from_function);
munit_assert_string_equal(cmdline, from_function);
return MUNIT_OK;
}
MUNIT_TEST(test_strlen0)
{
munit_assert_int(strlen0(NULL), ==, 0);
munit_assert_int(strlen0(""), ==, 1);
munit_assert_int(strlen0("foo"), ==, 4);
return MUNIT_OK;
}
MUNIT_TEST(test_strv_from_mem)
{
uint8_t buf[36];
for (size_t i = 0; i < sizeof(buf); i++)
buf[i] = i;
{
_cleanup_(strv_freep) char **strv = strv_from_mem(buf, 16, 16);
munit_assert(strv != NULL);
munit_assert(strv[0] != NULL);
munit_assert_null(strv[1]); /* we expect one line */
munit_assert_string_equal(strv[0], "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f");
}
{
_cleanup_(strv_freep) char **strv = strv_from_mem(buf, 8, 16);
munit_assert(strv != NULL);
munit_assert(strv[0] != NULL);
munit_assert_null(strv[1]); /* we expect one line */
munit_assert_string_equal(strv[0], "00 01 02 03 04 05 06 07");
}
{
_cleanup_(strv_freep) char **strv = strv_from_mem(buf, 8, 4);
munit_assert(strv != NULL);
munit_assert(strv[0] != NULL);
munit_assert(strv[1] != NULL);
munit_assert_null(strv[2]); /* we expect two lines */
munit_assert_string_equal(strv[0], "00 01 02 03");
munit_assert_string_equal(strv[1], "04 05 06 07");
}
{
_cleanup_(strv_freep) char **strv = strv_from_mem(buf, sizeof(buf), 5);
munit_assert(strv != NULL);
munit_assert_string_equal(strv[0], "00 01 02 03 04");
munit_assert_string_equal(strv[1], "05 06 07 08 09");
munit_assert_string_equal(strv[2], "0a 0b 0c 0d 0e");
munit_assert_string_equal(strv[3], "0f 10 11 12 13");
munit_assert_string_equal(strv[4], "14 15 16 17 18");
munit_assert_string_equal(strv[5], "19 1a 1b 1c 1d");
munit_assert_string_equal(strv[6], "1e 1f 20 21 22");
munit_assert_string_equal(strv[7], "23");
munit_assert_null(strv[8]);
}
{
uint8_t buffer[14];
memset(buffer, -1, sizeof(buffer));
_cleanup_(strv_freep) char **strv = strv_from_mem(buffer, sizeof(buffer), 8);
munit_assert(strv != NULL);
munit_assert_string_equal(strv[0], "ff ff ff ff ff ff ff ff");
munit_assert_string_equal(strv[1], "ff ff ff ff ff ff");
munit_assert_null(strv[2]);
}
return MUNIT_OK;
}
#endif