/* 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"

/**
 * 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;
}


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_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;
}

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
