/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */

/* @UNSAFE: whole file */

#include "lib.h"
#include "str.h"
#include "printf-format-fix.h"
#include "strfuncs.h"
#include "array.h"

#include <stdio.h>
#include <limits.h>
#include <ctype.h>

/* Disable our memcpy() safety wrapper. This file is very performance sensitive
   and it's been checked to work correctly with memcpy(). */
#undef memcpy

#define STRCONCAT_BUFSIZE 512

enum _str_trim_sides {
	STR_TRIM_LEFT = BIT(0),
	STR_TRIM_RIGHT = BIT(1),
};

const unsigned char uchar_nul = '\0';
const unsigned char *uchar_empty_ptr = &uchar_nul;
const char *const empty_str_array[] = { NULL };

volatile int timing_safety_unoptimization;

int i_snprintf(char *dest, size_t max_chars, const char *format, ...)
{
	va_list args;
	int ret;

	i_assert(max_chars < INT_MAX);

	va_start(args, format);
	ret = vsnprintf(dest, max_chars, printf_format_fix_unsafe(format),
			args);
	va_end(args);

	i_assert(ret >= 0);
	return (unsigned int)ret < max_chars ? 0 : -1;
}

char *p_strdup(pool_t pool, const char *str)
{
	void *mem;
	size_t len;

	if (str == NULL)
		return NULL;

	len = strlen(str) + 1;
	mem = p_malloc(pool, len);
	memcpy(mem, str, len);
	return mem;
}

void *p_memdup(pool_t pool, const void *data, size_t size)
{
	void *mem;

	mem = p_malloc(pool, size);
	memcpy(mem, data, size);
	return mem;
}

char *p_strdup_empty(pool_t pool, const char *str)
{
	if (str == NULL || *str == '\0')
		return NULL;

	return p_strdup(pool, str);
}

char *p_strdup_until(pool_t pool, const void *start, const void *end)
{
	size_t size;
	char *mem;

	i_assert((const char *) start <= (const char *) end);

	size = (size_t) ((const char *) end - (const char *) start);

	mem = p_malloc(pool, size + 1);
	memcpy(mem, start, size);
	return mem;
}

char *p_strndup(pool_t pool, const void *str, size_t max_chars)
{
	const char *p;
	char *mem;
	size_t len;

	i_assert(str != NULL);
	i_assert(max_chars != SIZE_MAX);

	p = memchr(str, '\0', max_chars);
	if (p == NULL)
		len = max_chars;
	else
		len = p - (const char *)str;

	mem = p_malloc(pool, len+1);
	memcpy(mem, str, len);
	return mem;
}

char *p_strdup_printf(pool_t pool, const char *format, ...)
{
	va_list args;
	char *ret;

	va_start(args, format);
	ret = p_strdup_vprintf(pool, format, args);
	va_end(args);

	return ret;
}

char *t_noalloc_strdup_vprintf(const char *format, va_list args,
			       unsigned int *size_r)
{
#define SNPRINTF_INITIAL_EXTRA_SIZE 256
	va_list args2;
	char *tmp;
	size_t init_size;
	int ret;
#ifdef DEBUG_FAST
	int old_errno = errno;
#endif

	VA_COPY(args2, args);

	/* the format string is modified only if %m exists in it. it happens
	   only in error conditions, so don't try to t_push() here since it'll
	   just slow down the normal code path. */
	format = printf_format_fix_get_len(format, &init_size);
	init_size += SNPRINTF_INITIAL_EXTRA_SIZE;

	tmp = t_buffer_get(init_size);
	ret = vsnprintf(tmp, init_size, format, args);
	i_assert(ret >= 0);

	*size_r = ret + 1;
	if ((unsigned int)ret >= init_size) {
		/* didn't fit with the first guess. now we know the size,
		   so try again. */
		tmp = t_buffer_get(*size_r);
		ret = vsnprintf(tmp, *size_r, format, args2);
		i_assert((unsigned int)ret == *size_r-1);
	}
#ifdef DEBUG_FAST
	/* we rely on errno not changing. it shouldn't. */
	i_assert(errno == old_errno);
#endif
	va_end(args2);
	return tmp;
}

char *p_strdup_vprintf(pool_t pool, const char *format, va_list args)
{
	char *tmp, *buf;
	unsigned int size;

	tmp = t_noalloc_strdup_vprintf(format, args, &size);
	if (pool->datastack_pool) {
		t_buffer_alloc(size);
		return tmp;
	} else {
		buf = p_malloc(pool, size);
		memcpy(buf, tmp, size - 1);
		return buf;
	}
}

char *vstrconcat(const char *str1, va_list args, size_t *ret_len)
{
	const char *str;
	char *temp;
	size_t bufsize, i, len;

	i_assert(str1 != NULL);

	str = str1;
	bufsize = STRCONCAT_BUFSIZE;
	temp = t_buffer_get(bufsize);

	i = 0;
	do {
		len = strlen(str);

		if (i + len >= bufsize) {
			/* need more memory */
			bufsize = nearest_power(i + len + 1);
			temp = t_buffer_reget(temp, bufsize);
		}

		memcpy(temp + i, str, len); i += len;

		/* next string */
		str = va_arg(args, const char *);
	} while (str != NULL);

	i_assert(i < bufsize);

	temp[i++] = '\0';
	*ret_len = i;
	return temp;
}

char *p_strconcat(pool_t pool, const char *str1, ...)
{
	va_list args;
	char *temp, *ret;
	size_t len;

	i_assert(str1 != NULL);

	va_start(args, str1);

	if (pool->datastack_pool) {
		ret = vstrconcat(str1, args, &len);
		t_buffer_alloc(len);
	} else {
		temp = vstrconcat(str1, args, &len);
		ret = p_malloc(pool, len);
		memcpy(ret, temp, len);
	}

	va_end(args);
	return ret;
}

void *t_memdup_noconst(const void *data, size_t size)
{
	void *mem = t_malloc_no0(size);
	memcpy(mem, data, size);
	return mem;
}

const void *t_memdup(const void *data, size_t size)
{
	return t_memdup_noconst(data, size);
}

const char *t_strdup(const char *str)
{
	return t_strdup_noconst(str);
}

char *t_strdup_noconst(const char *str)
{
	if (str == NULL)
		return NULL;
	return t_memdup_noconst(str, strlen(str) + 1);
}

const char *t_strdup_empty(const char *str)
{
	if (str == NULL || *str == '\0')
		return NULL;

	return t_strdup(str);
}

char *t_strdup_until_noconst(const void *start, const void *end)
{
	char *mem;
	size_t size;

	i_assert((const char *) start <= (const char *) end);

	size = (size_t)((const char *)end - (const char *)start);

	mem = t_malloc_no0(size + 1);
	memcpy(mem, start, size);
	mem[size] = '\0';
	return mem;
}

const char *t_strdup_until(const void *start, const void *end)
{
	return t_strdup_until_noconst(start, end);
}

const char *t_strndup(const void *str, size_t max_chars)
{
	i_assert(str != NULL);
	return p_strndup(unsafe_data_stack_pool, str, max_chars);
}

const char *t_strdup_printf(const char *format, ...)
{
	va_list args;
	const char *ret;

	va_start(args, format);
	ret = p_strdup_vprintf(unsafe_data_stack_pool, format, args);
	va_end(args);

	return ret;
}

const char *t_strdup_vprintf(const char *format, va_list args)
{
	return p_strdup_vprintf(unsafe_data_stack_pool, format, args);
}

const char *t_strconcat(const char *str1, ...)
{
	va_list args;
	const char *ret;
	size_t len;

	i_assert(str1 != NULL);

	va_start(args, str1);

	ret = vstrconcat(str1, args, &len);
	t_buffer_alloc(len);

	va_end(args);
	return ret;
}

const char *t_strcut(const char *str, char cutchar)
{
	const char *p;

	for (p = str; *p != '\0'; p++) {
		if (*p == cutchar)
			return t_strdup_until(str, p);
	}

	return str;
}

const char *t_str_replace(const char *str, char from, char to)
{
	char *out;
	size_t i, len;

	if (strchr(str, from) == NULL)
		return str;

	len = strlen(str);
	out = t_malloc_no0(len + 1);
	for (i = 0; i < len; i++) {
		if (str[i] == from)
			out[i] = to;
		else
			out[i] = str[i];
	}
	out[i] = '\0';
	return out;
}

const char *t_str_oneline(const char *str)
{
	string_t *out;
	size_t len;
	const char *p, *pend, *poff;
	bool new_line;

	if (strpbrk(str, "\r\n") == NULL)
		return str;

	len = strlen(str);
	out = t_str_new(len + 1);
	new_line = TRUE;
	p = poff = str;
	pend = str + len;
	while (p < pend) {
		switch (*p) {
		case '\r':
			if (p > poff)
				str_append_data(out,  poff, p - poff);
			/* just drop \r */
			poff = p + 1;
			break;
		case '\n':
			if (p > poff)
				str_append_data(out,  poff, p - poff);
			if (new_line) {
				/* coalesce multiple \n into a single space */
			} else {
				/* first \n after text */
				str_append_c(out, ' ');
				new_line = TRUE;
			}
			poff = p + 1;
			break;
		default:
			new_line = FALSE;
			break;
		}
		p++;
	}

	if (new_line && str_len(out) > 0)
		str_truncate(out, str_len(out) - 1);
	else if (p > poff)
		str_append_data(out,  poff, p - poff);
	return str_c(out);
}

int i_strocpy(char *dest, const char *src, size_t dstsize)
{
	if (dstsize == 0)
		return -1;

	while (*src != '\0' && dstsize > 1) {
		*dest++ = *src++;
		dstsize--;
	}

	*dest = '\0';
	return *src == '\0' ? 0 : -1;
}

char *str_ucase(char *str)
{
	char *p;

	for (p = str; *p != '\0'; p++)
		*p = i_toupper(*p);
	return str;
}

char *str_lcase(char *str)
{
	char *p;

	for (p = str; *p != '\0'; p++)
		*p = i_tolower(*p);
	return str;
}

const char *t_str_lcase(const char *str)
{
	i_assert(str != NULL);

	return str_lcase(t_strdup_noconst(str));
}

const char *t_str_ucase(const char *str)
{
	i_assert(str != NULL);

	return str_ucase(t_strdup_noconst(str));
}

const char *i_strstr_arr(const char *haystack, const char *const *needles)
{
	const char *ptr;
	for(; *needles != NULL; needles++)
		if ((ptr = strstr(haystack, *needles)) != NULL)
			return ptr;
	return NULL;
}

static void str_trim_parse(const char *str,
	const char *chars, enum _str_trim_sides sides,
	const char **begin_r, const char **end_r)
{
	const char *p, *pend, *begin, *end;

	*begin_r = *end_r = NULL;

	pend = str + strlen(str);
	if (pend == str)
		return;

	p = str;
	if ((sides & STR_TRIM_LEFT) != 0) {
		while (p < pend && strchr(chars, *p) != NULL)
			p++;
		if (p == pend)
			return;
	}
	begin = p;

	p = pend;
	if ((sides & STR_TRIM_RIGHT) != 0) {
		while (p > begin && strchr(chars, *(p-1)) != NULL)
			p--;
		if (p == begin)
			return;
	}
	end = p;

	*begin_r = begin;
	*end_r = end;
}

const char *t_str_trim(const char *str, const char *chars)
{
	const char *begin, *end;

	str_trim_parse(str, chars,
		STR_TRIM_LEFT | STR_TRIM_RIGHT, &begin, &end);
	if (begin == NULL)
		return "";
	return t_strdup_until(begin, end);
}

const char *p_str_trim(pool_t pool, const char *str, const char *chars)
{
	const char *begin, *end;

	str_trim_parse(str, chars,
		STR_TRIM_LEFT | STR_TRIM_RIGHT, &begin, &end);
	if (begin == NULL)
		return "";
	return p_strdup_until(pool, begin, end);
}

const char *str_ltrim(const char *str, const char *chars)
{
	const char *begin, *end;

	str_trim_parse(str, chars, STR_TRIM_LEFT, &begin, &end);
	if (begin == NULL)
		return "";
	return begin;
}

const char *t_str_ltrim(const char *str, const char *chars)
{
	return t_strdup(str_ltrim(str, chars));
}

const char *p_str_ltrim(pool_t pool, const char *str, const char *chars)
{
	return p_strdup(pool, str_ltrim(str, chars));
}

const char *t_str_rtrim(const char *str, const char *chars)
{
	const char *begin, *end;

	str_trim_parse(str, chars, STR_TRIM_RIGHT, &begin, &end);
	if (begin == NULL)
		return "";
	return t_strdup_until(begin, end);
}

const char *p_str_rtrim(pool_t pool, const char *str, const char *chars)
{
	const char *begin, *end;

	str_trim_parse(str, chars, STR_TRIM_RIGHT, &begin, &end);
	if (begin == NULL)
		return "";
	return p_strdup_until(pool, begin, end);
}

int null_strcmp(const char *s1, const char *s2)
{
	if (s1 == NULL)
		return s2 == NULL ? 0 : -1;
	if (s2 == NULL)
		return 1;

	return strcmp(s1, s2);
}

int null_strcasecmp(const char *s1, const char *s2)
{
	if (s1 == NULL)
		return s2 == NULL ? 0 : -1;
	if (s2 == NULL)
		return 1;

	return strcasecmp(s1, s2);
}

int i_memcasecmp(const void *p1, const void *p2, size_t size)
{
	const unsigned char *s1 = p1;
	const unsigned char *s2 = p2;
	int ret;

	while (size > 0) {
		ret = i_toupper(*s1) - i_toupper(*s2);
		if (ret != 0)
			return ret;

		s1++; s2++; size--;
	}

	return 0;
}

int i_strcmp_p(const char *const *p1, const char *const *p2)
{
	return strcmp(*p1, *p2);
}

int i_strcasecmp_p(const char *const *p1, const char *const *p2)
{
	return strcasecmp(*p1, *p2);
}

bool mem_equals_timing_safe(const void *p1, const void *p2, size_t size)
{
	const unsigned char *s1 = p1, *s2 = p2;
	size_t i;
	int ret = 0;

	for (i = 0; i < size; i++)
		ret |= s1[i] ^ s2[i];

	/* make sure the compiler optimizer doesn't try to break out of the
	   above loop early. */
	timing_safety_unoptimization = ret;
	return ret == 0;
}

bool str_equals_timing_almost_safe(const char *s1, const char *s2)
{
	size_t i;
	int ret = 0;

	for (i = 0; s1[i] != '\0' && s2[i] != '\0'; i++)
		ret |= s1[i] ^ s2[i];
	ret |= s1[i] ^ s2[i];

	/* make sure the compiler optimizer doesn't try to break out of the
	   above loop early. */
	timing_safety_unoptimization = ret;
	return ret == 0;
}

size_t
str_match(const char *p1, const char *p2)
{
	size_t i = 0;

	while(p1[i] != '\0' && p1[i] == p2[i])
		i++;

	return i;
}

size_t str_match_icase(const char *p1, const char *p2)
{
	size_t i = 0;

	while (p1[i] != '\0' && i_tolower(p1[i]) == i_tolower(p2[i]))
		i++;

	return i;
}

#undef str_begins
bool str_begins(const char *haystack, const char *needle, const char **suffix_r)
{
	size_t prefix_len = str_match(haystack, needle);
	if (needle[prefix_len] != '\0')
		return FALSE;
	*suffix_r = haystack + prefix_len;
	return TRUE;
}

#undef str_begins_icase
bool str_begins_icase(const char *haystack, const char *needle,
		      const char **suffix_r)
{
	size_t prefix_len = str_match_icase(haystack, needle);
	if (needle[prefix_len] != '\0')
		return FALSE;
	*suffix_r = haystack + prefix_len;
	return TRUE;
}

size_t i_memspn(const void *data, size_t data_len,
		const void *accept, size_t accept_len)
{
	const unsigned char *start = data;
	i_assert(data != NULL || data_len == 0);
	i_assert(accept != NULL || accept_len == 0);
	size_t pos = 0;
	/* nothing to accept */
	if (accept_len == 0)
		return 0;
	for (; pos < data_len; pos++) {
		if (memchr(accept, start[pos], accept_len) == NULL)
			break;
	}
	return pos;
}

size_t i_memcspn(const void *data, size_t data_len,
		 const void *reject, size_t reject_len)
{
	const unsigned char *start = data;
	const unsigned char *r = reject;
	const unsigned char *ptr = CONST_PTR_OFFSET(data, data_len);
	i_assert(data != NULL || data_len == 0);
	i_assert(reject != NULL || reject_len == 0);
	/* nothing to reject */
	if (reject_len == 0 || data_len == 0)
		return data_len;
	/* Doing repeated memchr's over the data is faster than
	   going over it once byte by byte, as long as reject
	   is reasonably short. */
	for (size_t i = 0; i < reject_len; i++) {
		const unsigned char *kand =
			memchr(start, r[i], data_len);
		if (kand != NULL && kand < ptr)
			ptr = kand;
	}
	return ptr - start;
}

bool t_split_key_value(const char *arg, char separator,
		       const char **key_r, const char **value_r)
{
	*value_r = arg == NULL ? NULL : strchr(arg, separator);
	if (*value_r != NULL) {
		*key_r = t_strdup_until(arg, (*value_r)++);
		return TRUE;
	}
	*value_r = "";
	*key_r = arg;
	return FALSE;
}

static char **
split_str_slow(pool_t pool, const char *data, const char *separators, bool spaces)
{
	char **array;
	char *str;
	unsigned int count, alloc_count, new_alloc_count;

	if (spaces) {
		/* skip leading separators */
		while (*data != '\0' && strchr(separators, *data) != NULL)
			data++;
	}
	if (*data == '\0')
		return p_new(pool, char *, 1);

	str = p_strdup(pool, data);

	alloc_count = 32;
	array = p_new(pool, char *, alloc_count);

	array[0] = str; count = 1;
	while (*str != '\0') {
		if (strchr(separators, *str) != NULL) {
			/* separator found */
			if (count+1 >= alloc_count) {
				new_alloc_count = nearest_power(alloc_count+1);
				array = p_realloc_type(pool, array, char *,
						       alloc_count, new_alloc_count);
				alloc_count = new_alloc_count;
			}

			*str = '\0';
			if (spaces) {
				while (str[1] != '\0' &&
				       strchr(separators, str[1]) != NULL)
					str++;

				/* ignore trailing separators */
				if (str[1] == '\0')
					break;
			}

			array[count++] = str+1;
		}

		str++;
	}

	i_assert(count < alloc_count);
	array[count] = NULL;

	return array;
}

static char **
split_str_fast(pool_t pool, const char *data, char sep)
{
	char **array, *str;
	unsigned int count, alloc_count, new_alloc_count;

	if (*data == '\0')
		return p_new(pool, char *, 1);

	str = p_strdup(pool, data);

	alloc_count = 32;
	array = p_new(pool, char *, alloc_count);

	array[0] = str; count = 1;
	while ((str = strchr(str, sep)) != NULL) {
		/* separator found */
		if (count+1 >= alloc_count) {
			new_alloc_count = nearest_power(alloc_count+1);
			array = p_realloc_type(pool, array, char *,
					       alloc_count, new_alloc_count);
			alloc_count = new_alloc_count;
		}
		*str++ = '\0';
		array[count++] = str;
	}
	i_assert(count < alloc_count);
	i_assert(array[count] == NULL);

	return array;
}

static char **
split_str(pool_t pool, const char *data, const char *separators, bool spaces)
{
	i_assert(*separators != '\0');

	if (separators[1] == '\0' && !spaces)
		return split_str_fast(pool, data, separators[0]);
	else
		return split_str_slow(pool, data, separators, spaces);
}

const char **t_strsplit(const char *data, const char *separators)
{
	return (const char **)split_str(unsafe_data_stack_pool, data,
					separators, FALSE);
}

const char **t_strsplit_spaces(const char *data, const char *separators)
{
	return (const char **)split_str(unsafe_data_stack_pool, data,
					separators, TRUE);
}

char **p_strsplit(pool_t pool, const char *data, const char *separators)
{
	return split_str(pool, data, separators, FALSE);
}

char **p_strsplit_spaces(pool_t pool, const char *data,
			 const char *separators)
{
	return split_str(pool, data, separators, TRUE);
}

void p_strsplit_free(pool_t pool, char **arr)
{
	p_free(pool, arr[0]);
	p_free(pool, arr);
}

unsigned int str_array_length(const char *const *arr)
{
	unsigned int count;

	if (arr == NULL)
		return 0;

	for (count = 0; *arr != NULL; arr++)
		count++;

	return count;
}

static char *
p_strarray_join_n(pool_t pool, const char *const *arr, unsigned int arr_len,
		  const char *separator)
{
	size_t alloc_len, sep_len, len, pos, needed_space;
	unsigned int i;
	char *str;

	sep_len = strlen(separator);
	alloc_len = 64;
	str = t_buffer_get(alloc_len);
	pos = 0;

	for (i = 0; i < arr_len; i++) {
		len = strlen(arr[i]);
		needed_space = pos + len + sep_len + 1;
		if (needed_space > alloc_len) {
			alloc_len = nearest_power(needed_space);
			str = t_buffer_reget(str, alloc_len);
		}

		if (i != 0) {
			memcpy(str + pos, separator, sep_len);
			pos += sep_len;
		}

		memcpy(str + pos, arr[i], len);
		pos += len;
	}
	str[pos] = '\0';
	if (!pool->datastack_pool)
		return p_memdup(pool, str, pos + 1);
	t_buffer_alloc(pos + 1);
	return str;
}

const char *t_strarray_join(const char *const *arr, const char *separator)
{
	return p_strarray_join_n(unsafe_data_stack_pool, arr,
				 str_array_length(arr), separator);
}

bool str_array_remove(const char **arr, const char *value)
{
	const char **dest;

	for (; *arr != NULL; arr++) {
		if (strcmp(*arr, value) == 0) {
			/* found it. now move the rest. */
			for (dest = arr, arr++; *arr != NULL; arr++, dest++)
				*dest = *arr;
			*dest = NULL;
			return TRUE;
		}
	}
	return FALSE;
}

bool str_array_find(const char *const *arr, const char *value)
{
	for (; *arr != NULL; arr++) {
		if (strcmp(*arr, value) == 0)
			return TRUE;
	}
	return FALSE;
}

bool str_array_icase_find(const char *const *arr, const char *value)
{
	for (; *arr != NULL; arr++) {
		if (strcasecmp(*arr, value) == 0)
			return TRUE;
	}
	return FALSE;
}

const char **p_strarray_dup(pool_t pool, const char *const *arr)
{
	unsigned int i;
	const char **ret;
	char *p;
	size_t len, size = sizeof(const char *);

	for (i = 0; arr[i] != NULL; i++) {
		size = MALLOC_ADD3(size, sizeof(const char *) + 1,
				   strlen(arr[i]));
	}

	ret = p_malloc(pool, size);
	p = PTR_OFFSET(ret, sizeof(const char *) * (i + 1));
	for (i = 0; arr[i] != NULL; i++) {
		len = strlen(arr[i]) + 1;
		memcpy(p, arr[i], len);
		ret[i] = p;
		p += len;
	}
	i_assert(PTR_OFFSET(ret, size) == (void *)p);
	return ret;
}

const char *dec2str(uintmax_t number)
{
	return dec2str_buf(t_malloc_no0(MAX_INT_STRLEN), number);
}

char *dec2str_buf(char buffer[STATIC_ARRAY MAX_INT_STRLEN], uintmax_t number)
{
	int pos;

	pos = MAX_INT_STRLEN;
	buffer[--pos] = '\0';
	do {
		buffer[--pos] = (number % 10) + '0';
		number /= 10;
	} while (number != 0 && pos >= 0);

	i_assert(pos >= 0);
	return buffer + pos;
}

char *p_array_const_string_join(pool_t pool, const ARRAY_TYPE(const_string) *arr,
				const char *separator)
{
	if (array_is_empty(arr))
		return "";
	return p_strarray_join_n(pool, array_front(arr), array_count(arr),
				 separator);
}
