diff options
Diffstat (limited to 'src/lib/strfuncs.c')
-rw-r--r-- | src/lib/strfuncs.c | 945 |
1 files changed, 945 insertions, 0 deletions
diff --git a/src/lib/strfuncs.c b/src/lib/strfuncs.c new file mode 100644 index 0000000..56e4576 --- /dev/null +++ b/src/lib/strfuncs.c @@ -0,0 +1,945 @@ +/* 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> + +#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; + +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 + 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 + /* 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; +} + +static void *t_memdup(const void *data, size_t size) +{ + void *mem = t_malloc_no0(size); + memcpy(mem, data, size); + return mem; +} + +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(str, strlen(str) + 1); +} + +const char *t_strdup_empty(const char *str) +{ + if (str == NULL || *str == '\0') + return NULL; + + return t_strdup(str); +} + +const char *t_strdup_until(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_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 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; +} + +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(pool, array, + sizeof(char *) * alloc_count, + sizeof(char *) * + 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(pool, array, + sizeof(char *) * alloc_count, + sizeof(char *) * + 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 *); + + /* @UNSAFE: integer overflow checks are missing */ + for (i = 0; arr[i] != NULL; i++) + size += sizeof(const char *) + strlen(arr[i]) + 1; + + 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_count(arr) == 0) + return ""; + return p_strarray_join_n(pool, array_front(arr), array_count(arr), + separator); +} |