/* radare - LGPL - Copyright 2007-2026 - pancake */

#include <limits.h>
#include <r_bin.h>
#include <r_userconf.h>

/* stable code */
static const char *const rwxstr[] = {
	[0] = "---",
	[1] = "--x",
	[2] = "-w-",
	[3] = "-wx",
	[4] = "r--",
	[5] = "r-x",
	[6] = "rw-",
	[7] = "rwx",

	[8] = "s---",
	[9] = "s--x",
	[10] = "sw--",
	[11] = "sw-x",
	[12] = "sr--",
	[13] = "sr-x",
	[14] = "srw-",
	[15] = "srwx",
};

static const char *const srwxstr[] = {
	[0] = "----",
	[1] = "---x",
	[2] = "--w-",
	[3] = "--wx",
	[4] = "-r--",
	[5] = "-r-x",
	[6] = "-rw-",
	[7] = "-rwx",

	[8] = "s---",
	[9] = "s--x",
	[10] = "sw--",
	[11] = "sw-x",
	[12] = "sr--",
	[13] = "sr-x",
	[14] = "srw-",
	[15] = "srwx",
};

// equal string, same case
R_API bool r_str_eq(const char *s1, const char *s2) {
	return s1 && s2 && !strcmp (s1, s2);
}

// equal string, ignoring case
R_API bool r_str_eqi(const char *s1, const char *s2) {
	return s1 && s2 && !r_str_casecmp (s1, s2);
}

R_API int r_str_casecmp(const char *s1, const char *s2) {
	if (!s1) {
		return -1;
	}
	if (R_UNLIKELY (!s2)) {
		return 1;
	}
#ifdef _MSC_VER
	return stricmp (s1, s2);
#else
	return strcasecmp (s1, s2);
#endif
}

R_API int r_str_ncasecmp(const char *s1, const char *s2, size_t n) {
#ifdef _MSC_VER
	return _strnicmp (s1, s2, n);
#else
	return strncasecmp (s1, s2, n);
#endif
}

// GOOD
// In-place replace the first instance of the character a, with the character b.
R_API int r_str_replace_ch(char *s, char a, char b, bool global) {
	int ret = 0;
	char *o = s;
	if (!s || a == b) {
		return 0;
	}
	for (; *o; s++, o++) {
		if (*o == a) {
			ret++;
			if (b) {
				*s = b;
			} else {
				/* remove char */
				s--;
			}
			if (!global) {
				return 1;
			}
		} else {
			*s = *o;
		}
	}
	*s = 0;
	return ret;
}

R_API int r_str_replace_char_once(char *s, int a, int b) {
	return r_str_replace_ch (s, a, b, false);
}

R_API int r_str_replace_char(char *s, int a, int b) {
	return r_str_replace_ch (s, a, b, true);
}

R_API void r_str_normalize_newlines(char *str) {
	R_RETURN_IF_FAIL (str);
	char *dst = str;
	for (; *str; str++, dst++) {
		const bool newline = *str == '\r' || *str == '\n';
		if (newline && str[1] && str[1] != *str && strchr ("\r\n", str[1])) {
			str++;
		}
		*dst = newline? '\n': *str;
	}
	*dst = 0;
}

R_API void r_str_remove_char(char *str, char c) {
	while (*str) {
		if (*str == c) {
			memmove (str, str + 1, strlen (str + 1) + 1);
			continue;
		}
		str++;
	}
}

R_API void r_str_reverse(char *str) {
	int i, len = strlen (str);
	int half = len / 2;
	for (i = 0; i < half; i++) {
		char ch = str[i];
		str[i] = str[len - i - 1];
		str[len - i - 1] = ch;
	}
}

// TODO: do not use toupper.. must support modes to also append lowercase chars like in r1
// TODO: this function doesnt specify the size of strout, so it can overflow by accident
R_API int r_str_bits(char *strout, const ut8 *buf, int len, const char *bitz) {
	int i, j, idx;
	if (bitz) {
		for (i = j = 0; i < len && bitz[i]; i++) {
			if (i > 0 && (i % 8) == 0) {
				buf++;
			}
			if (*buf &(1 << (i % 8))) {
				strout[j++] = toupper ((const ut8)bitz[i]);
			}
		}
	} else {
		for (i = j = 0; i < len; i++) {
			idx = (i / 8);
			int bit = 7 - (i % 8);
			strout[j++] = (buf[idx] &(1 << bit))? '1': '0';
		}
	}
	strout[j] = 0;
	return j;
}

R_API const char *r_str_sysbits(const RSysBits v) {
	switch (v) {
	case R_SYS_BITS_PACK (4): return "4";
	case R_SYS_BITS_PACK (8): return "8";
	case R_SYS_BITS_PACK (16): return "16";
	case R_SYS_BITS_PACK (32): return "32";
	case R_SYS_BITS_PACK (64): return "64";
	case R_SYS_BITS_PACK2 (4, 8): return "4,8";
	case R_SYS_BITS_PACK2 (16, 64): return "16,32";
	case R_SYS_BITS_PACK2 (32, 64): return "32,64";
	case R_SYS_BITS_PACK3 (16, 32, 64): return "16,32,64";
	}
	return "?";
}

// In-place trims a bitstring to groups of 8 bits.
// For example, the bitstring 1000000000000000 will not be modified, but the
// bitstring 0000000001000000 will be changed to 01000000.
static void trimbits(char *b) {
	const int len = strlen (b);
	char *one = strchr (b, '1');
	int pos = one? (int) (size_t) (one - b): len - 1;
	pos = (pos / 8) * 8;
	memmove (b, b + pos, len - pos + 1);
}

// Set 'strout' to the binary representation of the input value.
// strout must be a char array of 65 or greater.
// The string is then trimmed using the "trimbits" function above.
R_API int r_str_bits64(char *strout, ut64 in) {
	int i, bit, count = 0;
	for (i = (sizeof (in) * 8) - 1; i >= 0; i--) {
		bit = in >> i;
		strout[count] = (bit & 1)? '1': '0';
		count++;
	}
	strout[count] = '\0';
	/* trim by 8 bits */
	trimbits (strout);
	return count;
}

/**
 * function: r_str_bits_from_num
 *
 */
R_API ut64 r_str_bits_from_string(const char *buf, const char *bitz) {
	R_RETURN_VAL_IF_FAIL (buf && bitz, UT64_MAX);
	ut64 out = 0LL;
	/* return the numeric value associated to a string (rflags) */
	for (; *buf; buf++) {
		const char *ch = strchr (bitz, toupper ((int) (ut8)*buf));
		if (!ch) {
			ch = strchr (bitz, tolower ((int) (ut8)*buf));
			if (!ch) {
				return UT64_MAX;
			}
		}
		const int bit = (int) (size_t) (ch - bitz);
		out |= (ut64) (1LL << bit);
	}
	return out;
}

// Returns the permissions as in integer given an input in the form of rwx, rx,
// etc.
R_API int r_str_rwx(const char *str) {
	R_RETURN_VAL_IF_FAIL (str, -1);
	int ret = atoi (str);
	if (!ret) {
		ret |= strchr (str, 's')? 8: 0;
		ret |= strchr (str, 'm')? 16: 0;
		ret |= strchr (str, 'r')? 4: 0;
		ret |= strchr (str, 'w')? 2: 0;
		ret |= strchr (str, 'x')? 1: 0;
		if (!ret && strcmp (str, "---") && strcmp (str, "s---")) {
			ret = -1;
		}
	} else if (ret < 0 || ret >= R_ARRAY_SIZE (rwxstr)) {
		ret = -1;
	}
	return ret;
}

// Returns the string representation of permissions.
// with_dash=false: 3-4 chars "rwx" "s--x" (compact)
// with_dash=true:  4 chars "-rwx" "s--x" (Unix-style)
// If SHAR bit is set, returns 4 characters with 's' prefix instead of dash.
R_API const char *r_str_perm(int perm, bool with_dash) {
	if (perm < 0 || perm >= 16) {
		return with_dash ? srwxstr[0] : rwxstr[0];
	}
	int idx = (perm & R_PERM_RWX) | ((perm & R_PERM_SHAR) ? 8 : 0);
	return with_dash ? srwxstr[idx] : rwxstr[idx];
}

// Deprecated: use r_str_perm(perm, false) instead
R_API const char *r_str_rwx_i(int rwx) {
	return r_str_perm (rwx, false);
}

// Deprecated: use r_str_perm(perm, true) instead
R_API const char *r_str_srwx_i(int rwx) {
	return r_str_perm (rwx, true);
}

// If up is true, upcase all characters in the string, otherwise downcase all
// characters in the string.
R_API void r_str_case(char *str, bool up) {
	if (up) {
		char oc = 0;
		for (; *str; oc = *str++) {
			*str = (*str == 'x' && oc == '0')? 'x': toupper ((int) (ut8)*str);
		}
	} else {
		for (; *str; str++) {
			*str = tolower ((int) (ut8)*str);
		}
	}
}

R_API R_MUSTUSE char *r_str_r2_prefix(const char *str) {
	char *pfx = r_sys_prefix (NULL);
	char *res = r_str_newf ("%s%s%s", pfx, R_SYS_DIR, str);
	free (pfx);
	return res;
}

static inline ut64 str_hash_tail(const char *s, size_t len) {
	if (len >= 4) {
		return r_read_le32 (s) | ((ut64)r_read_le32 (s + len - 4) << ((len - 4) * 8));
	}
	if (len >= 2) {
		return r_read_le16 (s) | ((ut64)(ut8)s[len - 1] << ((len - 1) * 8));
	}
	return len? (ut8)*s: 0;
}

// Read little-endian words without accessing bytes beyond the supplied length.
static inline FUNC_ATTR_ALWAYS_INLINE ut64 str_hash64_seed(const char *s, size_t len, ut64 seed) {
	ut64 h = seed ^ ((ut64)len * 0x9e3779b97f4a7c15ULL);
	while (len >= 8) {
		h = (h ^ r_read_le64 (s)) * 0xff51afd7ed558ccdULL;
		h ^= h >> 32;
		s += 8;
		len -= 8;
	}
	h = (h ^ str_hash_tail (s, len)) * 0xc4ceb9fe1a85ec53ULL;
	h ^= h >> 29;
	return h;
}

R_API ut64 r_str_hash64_seed(const char *s, size_t len, ut64 seed) {
	return str_hash64_seed (s, len, seed);
}

R_API ut64 r_str_hash64(const char *s) {
	if (!s || !*s) {
		return 0;
	}
	if (!s[1]) {
		return str_hash64_seed (s, 1, 0);
	}
	if (!s[2]) {
		return str_hash64_seed (s, 2, 0);
	}
	if (!s[3]) {
		return str_hash64_seed (s, 3, 0);
	}
	return str_hash64_seed (s, strlen (s), 0);
}

R_API ut32 r_str_hash(const char *s) {
	return (ut32)r_str_hash64 (s);
}

R_API int r_str_delta(char *p, char a, char b) {
	const char *_a = strchr (p, a);
	const char *_b = strchr (p, b);
	return (!_a || !_b)? 0: (_a - _b);
}

// In-place split string using ch as a delimiter. Replaces all instances of ch
// with a null byte. Returns the number of split strings. For example
// r_str_split ("hello world", ' ') will replace the space with '\0' and
// return 2.
R_API int r_str_split(char *str, char ch) {
	int i;
	char *p;
	if (R_STR_ISEMPTY (str)) {
		return 0;
	}
	/* TODO: sync with r1 code */
	for (i = 1, p = str; *p; p++) {
		if (*p == ch) {
			i++;
			*p = '\0';
		} // s/ /\0/g
	}
	return i;
}

// Convert a string into an array of string separated by \0
// And the last by \0\0
// Separates by words and skip spaces.
// Returns the number of tokens that the string is tokenized into.
R_API int r_str_word_set0(char *str) {
	int i, quote = 0;
	char *p;
	if (R_STR_ISEMPTY (str)) {
		return 0;
	}
	for (i = 0; str[i] && str[i + 1]; i++) {
		if (i > 0 && str[i - 1] == ' ' && str[i] == ' ') {
			int len = strlen (str + i);
			memmove (str + i, str + i + 1, len);
			i--;
		}
	}
	if (str[i] == ' ') {
		str[i] = 0;
	}
	for (i = 1, p = str; *p; p++) {
		if (*p == '\"') {
			if (quote) {
				quote = 0;
				*p = '\0';
				// FIX: i++;
				continue;
			}
			quote = 1;
			memmove (p, p + 1, strlen (p + 1) + 1);
		}
		if (quote) {
			continue;
		}
		if (*p == ' ') {
			char *q = p - 1;
			if (p > str && (*q == '\\' || !*q)) {
				memmove (p, p + 1, strlen (p + 1) + 1);
				if (*q == '\\') {
					*q = ' ';
					continue;
				}
				p--;
			}
			i++;
			*p = '\0';
		} // s/ /\0/g
	}
	return i;
}

R_API int r_str_word_set0_stack(char *str) {
	int i;
	char *p, *q;
	RStack *s;
	void *pop;
	if (!str || !*str) {
		return 0;
	}
	for (i = 0; str[i] && str[i + 1]; i++) {
		if (i > 0 && str[i - 1] == ' ' && str[i] == ' ') {
			memmove (str + i, str + i + 1, strlen (str + i));
			i--;
		}
		if (i == 0 && str[i] == ' ') {
			memmove (str + i, str + i + 1, strlen (str + i));
		}
	}
	if (str[i] == ' ') {
		str[i] = 0;
	}
	s = r_stack_new (5); // Some random number
	for (i = 1, p = str; *p; p++) {
		q = p - 1;
		if (p > str && (*q == '\\')) {
			memmove (q, p, strlen (p) + 1);
			p--;
			continue;
		}
		switch (*p) {
		case '(':
		case '{':
		case '[':
			r_stack_push (s, (void *)p);
			continue;
		case '\'':
		case '"':
			pop = r_stack_pop (s);
			if (pop && *(char *)pop != *p) {
				r_stack_push (s, pop);
				r_stack_push (s, (void *)p);
			} else if (!pop) {
				r_stack_push (s, (void *)p);
			}
			continue;
		case ')':
		case '}':
		case ']':
			pop = r_stack_pop (s);
			if (pop) {
				if ((*(char *)pop == '(' && *p == ')') ||
					(*(char *)pop == '{' && *p == '}') ||
					(*(char *)pop == '[' && *p == ']')) {
					continue;
				}
			}
			break;
		case ' ':
			if (p > str && !*q) {
				memmove (p, p + 1, strlen (p + 1) + 1);
				if (*q == '\\') {
					*q = ' ';
					continue;
				}
				p--;
			}
			if (r_stack_is_empty (s)) {
				i++;
				*p = '\0';
			}
		default:
			break;
		}
	}
	r_stack_free (s);
	return i;
}

R_API char *r_str_word_get0set(char *stra, int stralen, int idx, const char *newstr, int *newlen) {
	char *p = NULL;
	char *out;
	if (!stra && !newstr) {
		return NULL;
	}
	if (stra) {
		p = (char *)r_str_word_get0 (stra, idx);
	}
	if (!p) {
		int nslen = strlen (newstr);
		out = malloc (nslen + 1);
		if (!out) {
			return NULL;
		}
		strcpy (out, newstr);
		out[nslen] = 0;
		if (newlen) {
			*newlen = nslen;
		}
		return out;
	}
	if (stralen < 0) {
		return NULL;
	}
	size_t alen = (size_t) (p - stra);
	size_t plen = strlen (p);
	size_t slen = strlen (newstr);
	size_t head = alen + plen + 1;
	size_t blen = ((size_t) stralen > head)? (size_t) stralen - head : 0;
	size_t nlen = alen + blen + slen;
	if (nlen + 2 >= ST32_MAX) {
		return NULL;
	}
	out = malloc (nlen + 2);
	if (!out) {
		return NULL;
	}
	if (alen > 0) {
		memcpy (out, stra, alen);
	}
	memcpy (out + alen, newstr, slen + 1);
	if (blen > 0) {
		memcpy (out + alen + slen + 1, p + plen + 1, blen);
	}
	out[nlen + 1] = 0;
	if (newlen) {
		*newlen = nlen + 1;
	}
	return out;
}

// Get the idx'th entry of a tokenized string.
// XXX: Warning! this function is UNSAFE, check that the string has, at least,
// idx+1 tokens.
R_API const char *r_str_word_get0(const char *str, int idx) {
	int i;
	const char *ptr = str;
	if (!ptr || idx < 0 /* prevent crashes with negative index */) {
		return (char *)"";
	}
	for (i = 0; i != idx; i++) {
		ptr += strlen (ptr) + 1;
	}
	return ptr;
}

// Return the number of times that the character ch appears in the string.
R_API size_t r_str_char_count(const char *string, char ch) {
	R_RETURN_VAL_IF_FAIL (string, 0);
	size_t i, count = 0;
	for (i = 0; string[i]; i++) {
		if (string[i] == ch) {
			count++;
		}
	}
	return count;
}

// Counts the number of words (separated by separator characters: newlines, tabs,
// return, space). See r_util.h for more details of the IS_SEPARATOR macro.
R_API int r_str_word_count(const char *string) {
	const char *text;
	int word;

	for (text = string; *text && IS_SEPARATOR (*text); text++) {
		;
	}
	for (word = 0; *text; word++) {
		for (; *text && !IS_SEPARATOR (*text); text++) {
			;
		}
		for (; *text && IS_SEPARATOR (*text); text++) {
			;
		}
	}
	return R_MAX (0, word);
}

// Returns a pointer to the first instance of a character that isn't chr in a
// string.
// XXX if the string is only made up of chr, then the pointer will just point to
// a null byte!
R_API const char *r_str_ichr(const char *str, char chr) {
	while (*str == chr) {
		str++;
	}
	return str;
}

// Returns a pointer to the last instance of the character chr in the input
// string.
R_API const char *r_str_lchr(const char *str, char chr) {
	if (str) {
		int len = strlen (str);
		for (; len >= 0; len--) {
			if (str[len] == chr) {
				return str + len;
			}
		}
	}
	return NULL;
}

R_API char *r_str_lstr(const char *s, const char *sub) {
	if (!s || !sub) {
		return NULL; // Handle null input
	}

	const size_t sub_len = strlen (sub);
	const size_t s_len = strlen (s);

	if (sub_len == 0 || s_len < sub_len) {
		return NULL; // No valid substring can be found
	}

	const char *p = s + s_len - sub_len;

	while (p >= s) {
		if (strncmp (p, sub, sub_len) == 0) {
			return (char *)p;
		}
		p--;
	}
	return NULL;
}

/* find the last char chr in the substring str[start:end] with end not included */
R_API const char *r_sub_str_lchr(const char *str, int start, int end, char chr) {
	do {
		end--;
	} while (str[end] != chr && end >= start);
	return str[end] == chr? &str[end]: NULL;
}

/* find the first char chr in the substring str[start:end] with end not included */
R_API const char *r_sub_str_rchr(const char *str, int start, int end, char chr) {
	while (str[start] != chr && start < end) {
		start++;
	}
	return str[start] == chr? str + start: NULL;
}

R_API const char *r_str_nchr(const char *str, char chr, int maxlen) {
	int count = 0;
	while (*str) {
		if (maxlen > 0 && count++ >= maxlen) {
			return NULL;
		}
		if (*str == chr) {
			return str;
		}
		str++;
	}
	return *str? str: NULL;
}

R_API const char *r_str_sep(const char *base, const char *sep) {
	int i;
	while (*base) {
		for (i = 0; sep[i]; i++) {
			if (*base == sep[i]) {
				return base;
			}
		}
		base++;
	}
	return NULL;
}

R_API const char *r_str_rsep(const char *base, const char *p, const char *sep) {
	int i;
	for (; p >= base; p--) {
		for (i = 0; sep[i]; i++) {
			if (*p == sep[i]) {
				return p;
			}
		}
	}
	return NULL;
}

R_API const char *r_str_rstr(const char *base, const char *p) {
	char *s = strdup (base);
	char *k = strdup (p);
	r_str_reverse (s);
	r_str_reverse (k);
	char *q = strstr (s, k);
	const char *r = NULL;
	if (q) {
		r = base + strlen (base) - (q - s) - strlen (p);
	}
	free (s);
	free (k);
	return r;
}

R_API const char *r_str_rchr(const char *base, const char *p, int ch) {
	R_RETURN_VAL_IF_FAIL (base, NULL);
	if (!p) {
		return strrchr (base, ch);
	}
	if (p < base) {
		return NULL;
	}
	for (; p >= base; p--) {
		if (ch == *p) {
			return p;
		}
	}
	return NULL;
}

R_API const char *r_str_nstr(const char *s, const char *find, int slen) {
	char c, sc;
	size_t len;

	if ((c = *find++) != '\0') {
		len = strlen (find);
		do {
			do {
				if (slen-- < 1 || ! (sc = *s++)) {
					return NULL;
				}
			} while (sc != c);
			if (len > slen) {
				return NULL;
			}
		} while (strncmp (s, find, len) != 0);
		s--;
	}
	return (char *)s;
}

// Returns a new heap-allocated copy of str.
// XXX what's the diff with strdup ?
R_API char *r_str_new(const char *str) {
	return str? strdup (str): NULL;
}

// Returns a new heap-allocated copy of str, sets str[len] to '\0'.
// If the input str is longer than len, it will be truncated.
R_API char *r_str_newlen(const char *str, int len) {
	if (len < 1) {
		return NULL;
	}
	char *buf = malloc (len + 1);
	if (buf) {
		memcpy (buf, str, len);
		buf[len] = 0;
	}
	return buf;
}

R_API char *r_str_trunc_ellipsis(const char *str, int len) {
	if (!str) {
		return NULL;
	}
	if (strlen (str) < len) {
		return strdup (str);
	}
	char *buf = r_str_ndup (str, len);
	if (buf && len > 4) {
		strcpy (buf + len - 4, "...");
	}
	return buf;
}

R_API char *r_str_newvf(const char *fmt, va_list ap) {
	// format into a stack buffer first, most strings fit and this avoids
	// running the formatter twice (once to measure, once to fill)
	char tmp[256];
	va_list ap2;
	va_copy (ap2, ap);
	int ret = vsnprintf (tmp, sizeof (tmp), fmt, ap2);
	va_end (ap2);
	if (ret < 0) {
		return NULL;
	}
	const size_t len = (size_t)ret;
	char *p = malloc (len + 1);
	if (p) {
		if (len < sizeof (tmp)) {
			memcpy (p, tmp, len + 1);
		} else {
			(void)vsnprintf (p, len + 1, fmt, ap);
		}
	}
	return p;
}

R_API char *r_str_newf(const char *fmt, ...) {
	if (!strchr (fmt, '%')) {
		return strdup (fmt);
	}
	va_list ap;
	va_start (ap, fmt);
	char *p = r_str_newvf (fmt, ap);
	va_end (ap);
	return p;
}

#if 0
// Secure string copy with null terminator (like strlcpy or strscpy but ours
R_API size_t r_str_ncpy(char *dst, const char *src, size_t n) {
	size_t i;

	// do not do anything if n is 0
	if (n == 0) {
		return 0;
	}

	n--;
	for (i = 0; src[i] && n > 0; i++, n--) {
		dst[i] = src[i];
	}
	dst[i] = 0;
	return i;
}
#endif

/* memccmp ("foo.bar", "foo.cow, '.') == 0 */
// Returns 1 if src and dst are equal up until the first instance of ch in src.
R_API bool r_str_ccmp(const char *dst, const char *src, int ch) {
	int i;
	for (i = 0; src[i] && src[i] != ch; i++) {
		if (dst[i] != src[i]) {
			return true;
		}
	}
	return false;
}

// Returns true if item is in sep-separated list
R_API bool r_str_cmp_list(const char *list, const char *item, char sep) {
	if (!list || !item) {
		return false;
	}
	const size_t item_len = strlen (item);
	while (list && *list) {
		const char *next = sep? strchr (list, sep): NULL;
		const size_t list_len = next? (size_t)(next - list): strlen (list);
		if (list_len == item_len && !strncmp (list, item, item_len)) {
			return true;
		}
		list = next? next + 1: NULL;
	}
	return false;
}

R_API char *r_str_word_get_first(const char *text) {
	for (; *text && IS_SEPARATOR (*text); text++) {
		;
	}
	return strdup (text);
}

R_API const char *r_str_get(const char *str) {
	return str? str: "";
}

R_API const char *r_str_get_fail(const char *str, const char *failstr) {
	return str? str: failstr;
}

R_API const char *r_str_getf(const char *str) {
	return str? str: "(null)";
}

R_API char *r_str_ndup(const char *ptr, int len) {
	if (!ptr || len < 0) {
		return NULL;
	}
	const char *nul = memchr (ptr, 0, len);
	const size_t olen = nul? (size_t)(nul - ptr): (size_t)len;
	char *out = malloc (olen + 1);
	if (!out) {
		return NULL;
	}
	memcpy (out, ptr, olen);
	out[olen] = 0;
	return out;
}

R_API char *r_str_prepend(char *ptr, const char *string) {
	if (!ptr) {
		return strdup (string);
	}
	size_t plen = strlen (ptr);
	size_t slen = strlen (string);
	char *newptr = realloc (ptr, slen + plen + 1);
	if (!newptr) {
		free (ptr);
		return NULL;
	}
	ptr = newptr;
	memmove (ptr + slen, ptr, plen + 1);
	memmove (ptr, string, slen);
	return ptr;
}

R_API char *r_str_appendlen(char *ptr, const char *string, int slen) {
	R_RETURN_VAL_IF_FAIL (string, NULL);
	char *msg = R_STR_NDUP (string, slen); // only fails for token.c
	char *ret = r_str_append (ptr, msg);
	free (msg);
	return ret;
}

R_API char *r_str_append_owned(char *ptr, char *string) {
	if (!ptr) {
		return string;
	}
	char *r = r_str_append (ptr, string);
	free (string);
	return r;
}
/*
 * first argument must be allocated
 * return: the pointer ptr resized to string size.
 */
R_API char *r_str_append(char *ptr, const char *string) {
	if (string && !ptr) {
		return strdup (string);
	}
	if (R_STR_ISEMPTY (string)) {
		return ptr;
	}
	size_t plen = strlen (ptr);
	size_t slen = strlen (string);
	char *newptr = realloc (ptr, slen + plen + 1);
	if (!newptr) {
		free (ptr);
		return NULL;
	}
	ptr = newptr;
	memcpy (ptr + plen, string, slen + 1);
	return ptr;
}

R_API char *r_str_appendf(char *ptr, const char *fmt, ...) {
	va_list ap, ap2;

	va_start (ap, fmt);
	if (!strchr (fmt, '%')) {
		va_end (ap);
		return r_str_append (ptr, fmt);
	}
	va_copy (ap2, ap);
	int ret = vsnprintf (NULL, 0, fmt, ap2);
	ret++;
	char *p = calloc (1, ret);
	if (p) {
		(void)vsnprintf (p, ret, fmt, ap);
		ptr = r_str_append (ptr, p);
		free (p);
	}
	va_end (ap2);
	va_end (ap);
	return ptr;
}

R_API char *r_str_appendch(char *x, char y) {
	char b[2] = { y, 0 };
	return r_str_append (x, b);
}

R_API R_MUSTUSE char *r_str_replace_all(char *str, const char *key, const char *val) {
	R_RETURN_VAL_IF_FAIL (str && key, NULL);
	if (strstr (val, key)) {
		// XXX value cant contain the key otherwise we go into infinite loop
		R_LOG_ERROR ("RStr.replaceAll() value can't contain key");
		free (str);
		return NULL;
	}
	char *res = str;
	while (strstr (str, key)) {
		res = r_str_replace (str, key, val, true);
		if (!res) {
			return NULL;
		}
		str = res;
	}
	return res;
}

R_API R_MUSTUSE char *r_str_replace(char *str, const char *key, const char *val, int g) {
	if (g == 'i') {
		return r_str_replace_icase (str, key, val, g, true);
	}
	R_RETURN_VAL_IF_FAIL (str && key && val, NULL);

	char *newstr, *p = str;
	size_t klen = strlen (key);
	size_t vlen = strlen (val);
	if (klen == 1 && vlen < 2) {
		r_str_replace_char (str, *key, *val);
		return str;
	}
	if (klen == 0) {
		return str;
	}
	if (klen == vlen && !strcmp (key, val)) {
		return str;
	}
	size_t slen = strlen (str);
	char *q = str;
	for (;;) {
		p = strstr (q, key);
		if (!p) {
			break;
		}
		size_t off = (size_t) (p - str);
		if (vlen != klen) {
			size_t tlen = slen - (off + klen);
			slen += vlen - klen;
			if (vlen > klen) {
				newstr = realloc (str, slen + 1);
				if (!newstr) {
					R_FREE (str);
					return NULL;
				}
				str = newstr;
			}
			p = str + off;
			memmove (p + vlen, p + klen, tlen + 1);
		}
		memcpy (p, val, vlen);
		q = str + off + vlen;
		if (!g) {
			break;
		}
	}
	return str;
}

R_API R_MUSTUSE char *r_str_replace_icase(char *str, const char *key, const char *val, int g, int keep_case) {
	R_RETURN_VAL_IF_FAIL (str && key && val, NULL);
	char *newstr, *p = str;
	size_t off, i;
	size_t klen = strlen (key);
	size_t vlen = strlen (val);
	char *q = str;
	for (;;) {
		p = (char *)r_str_casestr (q, key);
		if (!p) {
			break;
		}
		off = (size_t) (p - str);
		if (vlen != klen) {
			size_t tlen = strlen (p + klen);
			if (vlen > klen) {
				size_t nslen, nlen;
				if (r_add_overflow (off, vlen, &nslen) ||
						r_add_overflow (nslen, tlen, &nslen) ||
						r_add_overflow (nslen, (size_t)1, &nlen)) {
					free (str);
					return NULL;
				}
				newstr = realloc (str, nlen);
				if (!newstr) {
					free (str);
					return NULL;
				}
				str = newstr;
			}
			p = str + off;
			memmove (p + vlen, p + klen, tlen + 1);
		}

		if (keep_case) {
			char *tmp_val = strdup (val);
			char *str_case = r_str_ndup (p, klen);
			char *tmp_val_replaced;
			if (!tmp_val || !str_case) {
				free (tmp_val);
				free (str_case);
				return NULL;
			}
			tmp_val_replaced = r_str_replace_icase (tmp_val, key, str_case, 0, 0);
			free (str_case);
			if (!tmp_val_replaced) {
				// tmp_val is already freed by r_str_replace_icase on failure
				return NULL;
			}
			tmp_val = tmp_val_replaced;
			memcpy (p, tmp_val, vlen);
			free (tmp_val);
		} else {
			memcpy (p, val, vlen);
		}

		i = off + vlen;
		q = str + i;
		if (!g) {
			break;
		}
	}
	return str;
}

/* replace the key in str with val.
 *
 * str - input string
 * clean - input string cleaned of ANSI chars
 * thunk - array of integers that map each char of the clean string into the
 *         position in the str string
 * clen  - number of elements in thunk
 * key   - string to find in the clean string
 * val   - string that replaces key in the str string
 * g     - if true, replace all occurrences of key
 *
 * It returns a pointer to the modified string */
R_API R_MUSTUSE char *r_str_replace_thunked(char *str, char *clean, int *thunk, int clen, const char *key, const char *val, int g) {
	int i, klen, vlen, slen, delta = 0, bias;
	char *newstr, *scnd, *p = clean, *str_p;

	if (!str || !key || !val || !clean || !thunk) {
		return NULL;
	}
	klen = strlen (key);
	vlen = strlen (val);
	if (klen == vlen && !strcmp (key, val)) {
		return str;
	}
	slen = strlen (str) + 1;

	for (i = 0; i < clen;) {
		p = (char *)r_mem_mem (
			(const ut8 *)clean + i, clen - i,
			(const ut8 *)key, klen);
		if (!p) {
			break;
		}
		i = (int) (size_t) (p - clean);
		/* as the original string changes size during replacement
		 * we need delta to keep track of it*/
		str_p = str + thunk[i] + delta;

		int newo = thunk[i + klen] - thunk[i];
		r_str_ansi_filter (str_p, NULL, NULL, newo);
		scnd = strdup (str_p + newo);
		bias = vlen - newo;

		slen += bias;
		// HACK: this 32 avoids overwrites wtf
		newstr = realloc (str, slen + klen);
		if (!newstr) {
			R_FREE (str);
			free (scnd);
			break;
		}
		str = newstr;
		str_p = str + thunk[i] + delta;
		memcpy (str_p, val, vlen);
		memcpy (str_p + vlen, scnd, strlen (scnd) + 1);
		i += klen;
		delta += bias;
		free (scnd);
		if (!g) {
			break;
		}
	}
	return str;
}

R_API void r_str_replace_in(char *str, ut32 sz, const char *key, const char *val, int g) {
	R_RETURN_IF_FAIL (str && key && val);
	char *heaped = r_str_replace (strdup (str), key, val, g);
	if (heaped) {
		r_str_ncpy (str, heaped, sz);
		free (heaped);
	}
}

R_API int r_str_unescape(char *buf) {
	unsigned char ch = 0, ch2 = 0;
	int err = 0;
	int i;

	for (i = 0; buf[i]; i++) {
		if (buf[i] != '\\') {
			continue;
		}
		int esc_seq_len = 2;
		switch (buf[i + 1]) {
		case 'e':
			buf[i] = 0x1b;
			break;
		case ' ':
		case 's':
			buf[i] = ' ';
			break;
		case '\\':
			buf[i] = '\\';
			break;
		case 'r':
			buf[i] = 0x0d;
			break;
		case 'n':
			buf[i] = 0x0a;
			break;
		case 'a':
			buf[i] = 0x07;
			break;
		case 'b':
			buf[i] = 0x08;
			break;
		case 't':
			buf[i] = 0x09;
			break;
		case 'v':
			buf[i] = 0x0b;
			break;
		case 'f':
			buf[i] = 0x0c;
			break;
		case '"':
			buf[i] = '"';
			break;
		case '\'':
			buf[i] = '\'';
			break;
		case '`':
			buf[i] = '`';
			break;
		case 'x':
			err = ch2 = ch = 0;
			if (!buf[i + 2] || !buf[i + 3]) {
				R_LOG_ERROR ("Unexpected end of string");
				return 0;
			}
			err |= !r_hex_to_byte (&ch, buf[i + 2]);
			err |= !r_hex_to_byte (&ch2, buf[i + 3]);
			if (err) {
				R_LOG_ERROR ("Non-hexadecimal chars in input");
				return 0; // -1?
			}
			buf[i] = (ch << 4) + ch2;
			esc_seq_len = 4;
			break;
		case '$':
			buf[i] = '$';
			break;
		default:
			if (IS_OCTAL (buf[i + 1])) {
				int num_digits = 1;
				buf[i] = buf[i + 1] - '0';
				if (IS_OCTAL (buf[i + 2])) {
					num_digits++;
					buf[i] = (ut8)buf[i] * 8 + (buf[i + 2] - '0');
					if (IS_OCTAL (buf[i + 3])) {
						num_digits++;
						buf[i] = (ut8)buf[i] * 8 + (buf[i + 3] - '0');
					}
				}
				esc_seq_len = 1 + num_digits;
			} else {
				esc_seq_len = 1;
			}
		}
		memmove (buf + i + 1, buf + i + esc_seq_len, strlen (buf + i + esc_seq_len) + 1);
	}
	return i;
}

R_API void r_str_sanitize(char *c) {
	if (c) {
		for (; *c; c++) {
			switch (*c) {
			case '`':
			case '$':
			case '{':
			case '}':
			case '~':
			case '|':
			case ';':
			case '#':
			case '@':
			case '&':
			case '\\':
			case '"':
			case '\n':
			case '\r':
			case '<':
			case '>':
				*c = '_';
				break;
			}
		}
	}
}

R_API void r_str_sanitize_space(char *c) {
	R_RETURN_IF_FAIL (c);
	char *p;
	bool maytrim = false;
	for (p = c; *p; p++) {
		const char ch = *p;
		if (ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ') {
			*p = ' ';
			maytrim = true;
		}
	}
	if (maytrim) {
		r_str_trim (c);
	}
}

R_API char *r_str_sanitize_sdb_key(const char *s) {
	if (!s || !*s) {
		return NULL;
	}
	size_t len = strlen (s);
	char *ret = malloc (len + 1);
	if (!ret) {
		return NULL;
	}
	char *cur = ret;
	while (len > 0) {
		if ((ut8)*s >= 0x80) {
			RRune ch;
			int size = r_utf8_decode ((const ut8 *)s, R_MIN (len, 4), &ch);
			if (size > 1 && ch >= 0xa0 && ch != 0x2028 && ch != 0x2029) {
				memcpy (cur, s, size);
				cur += size;
				s += size;
				len -= size;
				continue;
			}
		}
		char c = *s;
		if (! (c >= 'a' && c <= 'z') && ! (c >= 'A' && c <= 'Z') && ! (c >= '0' && c <= '9') && c != '_' && c != ':') {
			c = '_';
		}
		*cur = c;
		s++;
		cur++;
		len--;
	}
	*cur = '\0';
	return ret;
}

R_API void r_str_byte_escape(const char *p, char **dst, int dot_nl, bool default_dot, bool esc_bslash) {
	R_RETURN_IF_FAIL (p && dst);
	char *q = *dst;
	switch (*p) {
	case '\n':
		*q++ = '\\';
		*q++ = dot_nl? 'l': 'n';
		break;
	case '\r':
		*q++ = '\\';
		*q++ = 'r';
		break;
	case '\\':
		*q++ = '\\';
		if (esc_bslash) {
			*q++ = '\\';
		}
		break;
	case '\t':
		*q++ = '\\';
		*q++ = 't';
		break;
	case '"':
		*q++ = '\\';
		*q++ = '"';
		break;
	case '\f':
		*q++ = '\\';
		*q++ = 'f';
		break;
	case '\b':
		*q++ = '\\';
		*q++ = 'b';
		break;
	case '\v':
		*q++ = '\\';
		*q++ = 'v';
		break;
	case '\a':
		*q++ = '\\';
		*q++ = 'a';
		break;
	default:
		/* Outside the ASCII printable range */
		if (!IS_PRINTABLE (*p)) {
			if (default_dot) {
				*q++ = '.';
			} else {
				*q++ = '\\';
				*q++ = 'x';
				*q++ = "0123456789abcdef"[*p >> 4 & 0xf];
				*q++ = "0123456789abcdef"[*p & 0xf];
			}
		} else {
			*q++ = *p;
		}
	}
	*dst = q;
}

/* Internal function. dot_nl specifies whether to convert \n into the
 * graphiz-compatible newline \l */
static char *r_str_escape_(const char *buf, int dot_nl, bool parse_esc_seq, bool ign_esc_seq, bool show_asciidot, bool esc_bslash) {
	R_RETURN_VAL_IF_FAIL (buf, NULL);

	/* Worst case scenario, we convert every byte to a single-char escape
	 *(e.g. \n) if show_asciidot, or \xhh if !show_asciidot */
	char *new_buf = malloc (1 + strlen (buf) *(show_asciidot? 2: 4));
	if (!new_buf) {
		return NULL;
	}
	const char *p = buf;
	char *q = new_buf;
	while (*p) {
		switch (*p) {
		case 0x1b: // ESC
			if (parse_esc_seq) {
				const char *start_seq = p;
				p++;
				/* Parse the ANSI code (only the graphic mode
				 * set ones are supported) */
				if (*p == '\0') {
					goto out;
				}
				if (*p == '[') {
					for (p++; *p != 'm'; p++) {
						if (*p == '\0') {
							goto out;
						}
					}
					if (!ign_esc_seq) {
						memcpy (q, start_seq, p - start_seq + 1);
						q += (p - start_seq + 1);
					}
				}
				break;
			}
			/* fallthrough */
		default:
			r_str_byte_escape (p, &q, dot_nl, show_asciidot, esc_bslash);
		}
		p++;
	}
out:
	*q = '\0';
	return new_buf;
}

/* hex-escape unprintable characters in a raw buffer (null-safe) */
R_API char *r_str_escape_raw(const ut8 *buf, int sz) {
	R_RETURN_VAL_IF_FAIL (buf, NULL);
	if (sz < 0) {
		return NULL;
	}
	/* Worst case scenario, we convert every byte to a \xhh escape */
	char *new_buf = malloc (1 + (size_t) sz * 4);
	if (!new_buf) {
		return NULL;
	}
	char *q = new_buf;
	int i;
	for (i = 0; i < sz; i++) {
		r_str_byte_escape ((char *)&buf[i], &q, false, false, true);
	}
	*q = '\0';
	return new_buf;
}

R_API char *r_str_escape(const char *buf) {
	return r_str_escape_(buf, false, true, true, false, true);
}

R_API char *r_str_sanitize_r2(const char *buf) {
	R_RETURN_VAL_IF_FAIL (buf, NULL);
	char *new_buf = strdup (buf);
	if (new_buf) {
		r_str_sanitize (new_buf);
	}
	return new_buf;
}

// the argument itself when an r2 script can replay it verbatim, base64:<b64> otherwise
R_API char *r_str_r2arg(const char *arg) {
	R_RETURN_VAL_IF_FAIL (arg, NULL);
	if (*arg && r_str_is_printable (arg) && !r_str_startswith (arg, "base64:")) {
		char *safe = r_str_sanitize_r2 (arg);
		if (!strcmp (safe, arg)) {
			return safe;
		}
		free (safe);
	}
	char *b64 = r_base64_encode_dyn ((const ut8 *)arg, -1);
	char *res = r_str_newf ("base64:%s", r_str_get (b64));
	free (b64);
	return res;
}

R_API char *r_str_escape_sql(const char *buf) {
	R_RETURN_VAL_IF_FAIL (buf, NULL);
	char *res = r_str_replace (strdup (buf), "'", "\\'", true);
	return res;
}

// Return MUST BE surrounded by double-quotes
R_API char *r_str_escape_sh(const char *buf) {
	R_RETURN_VAL_IF_FAIL (buf, NULL);
	char *new_buf = malloc (1 + strlen (buf) * 2);
	if (!new_buf) {
		return NULL;
	}
	const char *p = buf;
	char *q = new_buf;
	while (*p) {
		switch (*p) {
#if R2__UNIX__
		case '!':
		case '$':
		case '`':
#endif
		case '\\':
		case '"':
			*q++ = '\\';
			*q++ = *p++;
			break;
#if R2__WINDOWS__
		case '%':
			*q++ = '%';
			*q++ = *p++;
			break;
		case '!':
			*q++ = '^';
			*q++ = *p++;
			break;
#endif
		default:
			*q++ = *p++;
			break;
		}
	}
	*q = '\0';
	return new_buf;
}

R_API char *r_str_escape_dot(const char *buf) {
	return r_str_escape_(buf, true, true, true, false, true);
}

R_API char *r_str_escape_latin1(const char *buf, bool show_asciidot, bool esc_bslash, bool colors) {
	return r_str_escape_(buf, false, colors, !colors, show_asciidot, esc_bslash);
}

static char *r_str_escape_utf(const char *buf, int buf_size, RStrEnc enc, bool show_asciidot, bool esc_bslash, bool keep_printable) {
	char *new_buf, *q;
	const char *p, *end;
	RRune ch;
	int i, len, ch_bytes;

	if (!buf) {
		return NULL;
	}
	switch (enc) {
	case R_STRING_ENC_UTF16LE:
	case R_STRING_ENC_UTF16BE:
	case R_STRING_ENC_UTF32LE:
	case R_STRING_ENC_UTF32BE:
		if (buf_size < 0) {
			return NULL;
		}
		if (enc == R_STRING_ENC_UTF16LE || enc == R_STRING_ENC_UTF16BE) {
			end = (char *)r_mem_mem_aligned ((ut8 *)buf, buf_size, (ut8 *)"\0\0", 2, 2);
		} else {
			end = (char *)r_mem_mem_aligned ((ut8 *)buf, buf_size, (ut8 *)"\0\0\0\0", 4, 4);
		}
		if (!end) {
			end = buf + buf_size - 1; /* TODO: handle overlong strings properly */
		}
		len = end - buf;
		break;
	default:
		len = strlen (buf);
		end = buf + len;
	}
	/* Worst case scenario, we convert every byte to \xhh */
	new_buf = malloc (1 + (size_t) len * 4);
	if (!new_buf) {
		return NULL;
	}
	p = buf;
	q = new_buf;
	while (p < end) {
		switch (enc) {
		case R_STRING_ENC_UTF16LE:
		case R_STRING_ENC_UTF16BE:
		case R_STRING_ENC_UTF32LE:
		case R_STRING_ENC_UTF32BE:
			if (enc == R_STRING_ENC_UTF16LE || enc == R_STRING_ENC_UTF16BE) {
				ch_bytes = r_utf16_decode ((ut8 *)p, end - p, &ch, enc == R_STRING_ENC_UTF16BE);
			} else {
				ch_bytes = r_utf32_decode ((ut8 *)p, end - p, &ch, enc == R_STRING_ENC_UTF32BE);
			}
			if (ch_bytes == 0) {
				p++;
				continue;
			}
			break;
		default:
			ch_bytes = r_utf8_decode ((ut8 *)p, end - p, &ch);
			if (ch_bytes == 0) {
				ch_bytes = 1;
			}
		}
		if (show_asciidot && !IS_PRINTABLE (ch)) {
			*q++ = '.';
		} else if (ch_bytes > 1) {
			if (keep_printable && r_isprint (ch)) {
				q += r_utf8_encode ((ut8 *)q, ch);
			} else {
				*q++ = '\\';
				*q++ = ch_bytes == 4? 'U': 'u';
				for (i = ch_bytes == 4? 6: 2; i >= 0; i -= 2) {
					*q++ = "0123456789abcdef"[ch >> 4 *(i + 1) & 0xf];
					*q++ = "0123456789abcdef"[ch >> 4 * i & 0xf];
				}
			}
		} else {
			int offset = enc == R_STRING_ENC_UTF16BE? 1: enc == R_STRING_ENC_UTF32BE? 3
												: 0;
			r_str_byte_escape (p + offset, &q, false, false, esc_bslash);
		}
		switch (enc) {
		case R_STRING_ENC_UTF16LE:
		case R_STRING_ENC_UTF16BE:
			p += ch_bytes < 2? 2: ch_bytes;
			break;
		case R_STRING_ENC_UTF32LE:
		case R_STRING_ENC_UTF32BE:
			p += 4;
			break;
		default:
			p += ch_bytes;
		}
	}
	*q = '\0';
	return new_buf;
}

R_API char *r_str_escape_utf8(const char *buf, bool show_asciidot, bool esc_bslash) {
	return r_str_escape_utf (buf, -1, R_STRING_ENC_UTF8, show_asciidot, esc_bslash, false);
}

R_API char *r_str_escape_utf8_keep_printable(const char *buf, bool show_asciidot, bool esc_bslash) {
	return r_str_escape_utf (buf, -1, R_STRING_ENC_UTF8, show_asciidot, esc_bslash, true);
}

R_API char *r_str_escape_utf16le(const char *buf, int buf_size, bool show_asciidot, bool esc_bslash) {
	return r_str_escape_utf (buf, buf_size, R_STRING_ENC_UTF16LE, show_asciidot, esc_bslash, false);
}

R_API char *r_str_escape_utf32le(const char *buf, int buf_size, bool show_asciidot, bool esc_bslash) {
	return r_str_escape_utf (buf, buf_size, R_STRING_ENC_UTF32LE, show_asciidot, esc_bslash, false);
}

R_API char *r_str_escape_utf16be(const char *buf, int buf_size, bool show_asciidot, bool esc_bslash) {
	return r_str_escape_utf (buf, buf_size, R_STRING_ENC_UTF16BE, show_asciidot, esc_bslash, false);
}

R_API char *r_str_escape_utf32be(const char *buf, int buf_size, bool show_asciidot, bool esc_bslash) {
	return r_str_escape_utf (buf, buf_size, R_STRING_ENC_UTF32BE, show_asciidot, esc_bslash, false);
}

static char *escape_utf8(const char *buf, int buf_size, bool escape_non_printable) {
	char *new_buf, *q;
	const char *p, *end;
	RRune ch;
	int i, len, ch_bytes;

	if (!buf) {
		return NULL;
	}
	len = buf_size < 0? strlen (buf): buf_size;
	end = buf + len;
	/* Worst case scenario, we convert every byte to \u00hh */
	new_buf = malloc (1 + (size_t) len * 6);
	if (!new_buf) {
		return NULL;
	}
	p = buf;
	q = new_buf;
	while (p < end) {
		ch_bytes = r_utf8_decode ((ut8 *)p, end - p, &ch);
		if (ch_bytes == 1) {
			switch (*p) {
			case '\n':
				*q++ = '\\';
				*q++ = 'n';
				break;
			case '\r':
				*q++ = '\\';
				*q++ = 'r';
				break;
			case '\\':
				*q++ = '\\';
				*q++ = '\\';
				break;
			case '\t':
				*q++ = '\\';
				*q++ = 't';
				break;
			case '"':
				*q++ = '\\';
				*q++ = '"';
				break;
			case '\f':
				*q++ = '\\';
				*q++ = 'f';
				break;
			case '\b':
				*q++ = '\\';
				*q++ = 'b';
				break;
			default:
				if (IS_PRINTABLE (*p)) {
					*q++ = *p;
				} else if (escape_non_printable) {
					*q++ = '\\';
					*q++ = 'u';
					*q++ = '0';
					*q++ = '0';
					*q++ = "0123456789abcdef"[*p >> 4 & 0xf];
					*q++ = "0123456789abcdef"[*p & 0xf];
				}
				// else skip non-printable
			}
		} else if (ch_bytes == 4) {
			if (r_isprint (ch)) {
				// Assumes buf is UTF8-encoded
				for (i = 0; i < ch_bytes; i++) {
					*q++ = *(p + i);
				}
			} else {
				RRune high, low;
				ch -= 0x10000;
				high = 0xd800 + (ch >> 10 & 0x3ff);
				low = 0xdc00 + (ch & 0x3ff);
				*q++ = '\\';
				*q++ = 'u';
				for (i = 2; i >= 0; i -= 2) {
					*q++ = "0123456789abcdef"[high >> 4 *(i + 1) & 0xf];
					*q++ = "0123456789abcdef"[high >> 4 * i & 0xf];
				}
				*q++ = '\\';
				*q++ = 'u';
				for (i = 2; i >= 0; i -= 2) {
					*q++ = "0123456789abcdef"[low >> 4 *(i + 1) & 0xf];
					*q++ = "0123456789abcdef"[low >> 4 * i & 0xf];
				}
			}
		} else if (ch_bytes > 1) {
			if (r_isprint (ch)) {
				// Assumes buf is UTF8-encoded
				for (i = 0; i < ch_bytes; i++) {
					*q++ = *(p + i);
				}
			} else {
				*q++ = '\\';
				*q++ = 'u';
				for (i = 2; i >= 0; i -= 2) {
					*q++ = "0123456789abcdef"[ch >> 4 *(i + 1) & 0xf];
					*q++ = "0123456789abcdef"[ch >> 4 * i & 0xf];
				}
			}
		} else { // ch_bytes == 0
			if (escape_non_printable) {
				// Outside JSON spec, but apparently no better
				// alternative if need to reconstruct the original string
				*q++ = '\\';
				*q++ = 'u';
				*q++ = '0';
				*q++ = '0';
				*q++ = "0123456789abcdef"[*p >> 4 & 0xf];
				*q++ = "0123456789abcdef"[*p & 0xf];
			}
			ch_bytes = 1;
		}
		p += ch_bytes;
	}
	*q = '\0';
	return new_buf;
}

static char *escape_and_strip(const char *buf, int buf_size) {
	return escape_utf8 (buf, buf_size, false);
}

R_API char *r_str_encoded_json(const char *buf, int buf_size, int encoding) {
	R_RETURN_VAL_IF_FAIL (buf, NULL);
	size_t buf_sz = buf_size < 0? strlen (buf): buf_size;
	char *encoded_str;

	if (encoding == PJ_ENCODING_STR_BASE64) {
		encoded_str = r_base64_encode_dyn ((const ut8 *)buf, buf_sz);
	} else if (encoding == PJ_ENCODING_STR_HEX || encoding == PJ_ENCODING_STR_ARRAY) {
		size_t loop = 0;
		size_t i = 0;
		size_t increment = encoding == PJ_ENCODING_STR_ARRAY? 4: 2;

		size_t new_sz;
		if (r_mul_overflow (buf_sz, increment, &new_sz) || r_add_overflow (new_sz, (size_t)1, &new_sz)) {
			return NULL;
		}

		encoded_str = malloc (new_sz);
		if (!encoded_str) {
			return NULL;
		}

		const char *format = encoding == PJ_ENCODING_STR_ARRAY? "%03u,": "%02X";
		while (buf[loop] != '\0' && i < (new_sz - 1)) {
			snprintf (encoded_str + i, new_sz - i, format, (ut8)buf[loop]);
			loop++;
			i += increment;
		}
		if (encoding == PJ_ENCODING_STR_ARRAY && i) {
			// get rid of the trailing comma
			encoded_str[i - 1] = '\0';
		} else {
			encoded_str[i] = '\0';
		}
	} else if (encoding == PJ_ENCODING_STR_STRIP) {
		encoded_str = escape_and_strip (buf, buf_sz);
	} else {
		encoded_str = r_str_escape_json (buf, buf_sz);
	}
	return encoded_str;
}

// TODO: very long and bad name here
R_API char *r_str_escape_json(const char *buf, int buf_size) {
	return escape_utf8 (buf, buf_size, true);
}

// https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES
// https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args? redirectedfrom=MSDN&view=vs-2019#parsing-c-command-line-arguments
R_API char *r_str_format_msvc_argv(size_t argc, const char **argv) {
	RStrBuf sb;
	r_strbuf_init (&sb);

	size_t i;
	for (i = 0; i < argc; i++) {
		if (i > 0) {
			r_strbuf_append (&sb, " ");
		}
		const char *arg = argv[i];
		bool must_escape = strchr (arg, '\"');
		bool must_quote = strpbrk (arg, " \t") || !*arg;
		if (!must_escape && must_quote && *arg && arg[strlen (arg) - 1] == '\\') {
			// if the last char is a bs and we would quote it, we must also escape
			must_escape = true;
		}
		if (must_quote) {
			r_strbuf_append (&sb, "\"");
		}
		if (must_escape) {
			size_t bs_count = 0; // bullshit counter
			for (; *arg; arg++) {
				switch (*arg) {
				case '\"':
					for (; bs_count; bs_count--) {
						// backslashes must be escaped iff they precede a "
						// so just duplicate the number of backslashes already printed
						r_strbuf_append (&sb, "\\");
					}
					r_strbuf_append (&sb, "\\\"");
					break;
				case '\\':
					bs_count++;
					r_strbuf_append (&sb, "\\");
					break;
				default:
					bs_count = 0;
					r_strbuf_append_n (&sb, arg, 1);
					break;
				}
			}
			if (must_quote) {
				// there will be a quote after this so we have to escape bs here as well
				for (; bs_count; bs_count--) {
					r_strbuf_append (&sb, "\\");
				}
			}
		} else {
			r_strbuf_append (&sb, arg);
		}
		if (must_quote) {
			r_strbuf_append (&sb, "\"");
		}
	}

	return r_strbuf_drain_nofree (&sb);
}

enum {
	RUNE_WIDTH_MASK = 3,
	RUNE_VARIATION = 4,
	RUNE_PICTOGRAPHIC = 8,
	RUNE_MODIFIER_BASE = 16,
	RUNE_REGIONAL = 32,
	RUNE_KEYCAP = 64,
};

#include "str_utf8_width.inc.c"

#define RUNE_PROPS(c) (utf8_width_pages[(utf8_width_index[(c) >> 7] << 7) | ((c) & 0x7f)])

static size_t str_ansi_length(const char *s, size_t left) {
	size_t i = 1;
	if (*s == 0x1b) {
		if (i < left && s[i] == '[') {
			i++;
			while (i < left && s[i] && s[i] != 'J' && s[i] != 'm' && s[i] != 'H' && s[i] != 'K') {
				i++;
			}
		} else if (i < left && s[i] == '#') {
			while (i < left && s[i] && s[i] != 'q') {
				i++;
			}
		}
		if (i < left && s[i]) {
			i++;
		}
	} else if ((ut8)*s >= 0x80) {
		int n = r_utf8_decode ((const ut8 *)s, left == SIZE_MAX? -1: R_MIN (left, 4), NULL);
		i = n > 0? n: 1;
	}
	return i;
}

static size_t display_skip_ansi(const char *s, size_t i, size_t left, bool ansi) {
	while (ansi && i < left && s[i] == 0x1b) {
		i += str_ansi_length (s + i, left - i);
	}
	return i;
}

static inline FUNC_ATTR_ALWAYS_INLINE size_t display_cluster(const char *s, size_t left, bool ansi, int *w) {
	RRune ch = (ut8)*s;
	int n = ch < 0x80? 1: r_utf8_decode ((const ut8 *)s, R_MIN (left, 4), &ch);
	if (n < 1) {
		*w = ((ut8)*s & 0xc0) == 0x80? 0: 1;
		return 1;
	}
	ut8 p = R_BETWEEN (0x4e00, ch, 0x9fff)? 2: RUNE_PROPS (ch);
	*w = p & RUNE_WIDTH_MASK;
	size_t len = n;
	if (!(p & ~RUNE_WIDTH_MASK) || len == left || ((ut8)s[len] < 0x80 && (!ansi || s[len] != 0x1b))) {
		return len;
	}
	ut8 flags = p;
	for (;;) {
		size_t i = display_skip_ansi (s, len, left, ansi);
		if (i == left || (ut8)s[i] < 0x80) {
			break;
		}
		RRune f;
		n = r_utf8_decode ((const ut8 *)s + i, R_MIN (left - i, 4), &f);
		if (n < 1) {
			break;
		}
		ut8 fprops = RUNE_PROPS (f);
		if ((p & RUNE_VARIATION) && R_BETWEEN (0xfe0e, f, 0xfe0f)) {
			*w = f == 0xfe0f? 2: p & RUNE_WIDTH_MASK;
			if (f == 0xfe0e) {
				flags &= ~RUNE_PICTOGRAPHIC;
			}
		} else if (((flags & RUNE_KEYCAP) && f == 0x20e3)
				|| (flags & fprops & RUNE_REGIONAL)) {
			*w = 2;
			flags &= ~(RUNE_KEYCAP | RUNE_REGIONAL);
		} else if ((flags & RUNE_MODIFIER_BASE) && R_BETWEEN (0x1f3fb, f, 0x1f3ff)) {
			flags &= ~RUNE_MODIFIER_BASE;
		} else if (ch == 0x200d && (flags & fprops & RUNE_PICTOGRAPHIC)) {
			size_t end = display_skip_ansi (s, i + n, left, ansi);
			if ((fprops & RUNE_VARIATION) && left - end >= 3 && !strncmp (s + end, "\xef\xb8\x8e", 3)) {
				break;
			}
			*w = 2;
			flags = fprops;
		} else if (fprops & RUNE_WIDTH_MASK) {
			break;
		}
		ch = f;
		p = fprops;
		len = i + n;
	}
	return len;
}

static size_t str_dwidth(const char *s, size_t slen, bool ansi) {
	size_t i = 0, len = 0;
	const char *end = slen? memchr (s, 0, slen): s + strlen (s);
	const size_t max = end? end - s: slen;
	while (i < max) {
		const ut8 c = (ut8)s[i];
		if (ansi && c == 0x1b) {
			i += str_ansi_length (s + i, max - i);
			continue;
		}
		// Keep plain ASCII runs out of Unicode property lookups.
		if (c < 0x80 && (!R_BETWEEN ('#', c, '9') || i + 1 == max || ((ut8)s[i + 1] < 0x80 && (!ansi || s[i + 1] != 0x1b)))) {
			len++;
			i++;
			continue;
		}
		int w;
		i += display_cluster (s + i, max - i, ansi, &w);
		len += w;
	}
	return (ansi && slen > 0 && len < 1)? 1: len;
}

/* ansi helpers */
R_API size_t r_str_ansi_nlen(const char *str, size_t slen) {
	return str_dwidth (str, slen, true);
}

static size_t __str_ansi_sanitize_length(char const *str) {
	const signed char str0 = (const signed char)str[0];
	const signed char str1 = (const signed char)str[1];
	size_t i = 0;
	if (str0 == 0x1b || str0 == 0x07 || str0 == 0x05 || str0 == 0x7f) { // ESC, BEL, ENQ, DEL
		i++;
	} else if (str0 == -0x3e && str1 >= -0x80 && str1 <= -0x61) { // C1 control codes U+0080 - U+009F
		i += 2;
	}
	return i;
}

// remove ansi escape codes from string in place, decolorizing it
R_API size_t r_str_ansi_strip(char *str) {
	size_t r = 0, w = 0;
	while (str[r]) {
		const size_t chlen = str_ansi_length (str + r, SIZE_MAX);
		const size_t sanitize_len = __str_ansi_sanitize_length (str + r);
		if (str[r] == 0x1b && chlen > 1) {
			r += chlen;
		} else if (sanitize_len > 0) {
			r += sanitize_len;
		} else if (w > 0 && (ut8)str[w - 1] == 0xc2 && (ut8)str[r] >= 0x80 && (ut8)str[r] <= 0x9f) {
			// a cut joined a stray 0xc2 with the second byte of a C1 control code
			w--;
			r++;
		} else {
			memmove (str + w, str + r, chlen);
			w += chlen;
			r += chlen;
		}
	}
	str[w] = 0;
	return w;
}

#if 0
// unused
// insert a string into another string, supports ansi control chars
R_API char *r_str_insert(R_OWNED char *src, int pos, const char *str) {
	r_str_var (a, pos, src);
	char *b = strdup (src + pos + r_str_ansi_len (str));
	char *r = r_str_newf ("%s%s%s", a, str, b);
	free (b);
	free (src);
	return r;
}
#endif

R_API size_t r_str_ansi_len(const char *str) {
	R_RETURN_VAL_IF_FAIL (str, 0);
	return r_str_ansi_nlen (str, 0);
}

R_API int r_str_display_width(const char *str) {
	size_t len = r_str_ansi_len (str);
	return (len > INT_MAX)? INT_MAX: (int)len;
}

R_API size_t r_str_nlen(const char *str, int n) {
	R_RETURN_VAL_IF_FAIL (str && n >= 0, 0);
	size_t len = 0;
	while (n > 0 && *str) {
		len++;
		str++;
		n--;
	}
	return len;
}

R_API size_t r_str_pnlen(const char *str, int n) {
	R_RETURN_VAL_IF_FAIL (str && n >= 0, 0);
	size_t len = 0;
	while (n > 0 && IS_PRINTABLE ((ut8)*str)) {
		len++;
		str++;
		n--;
	}
	return len;
}

// to handle wide string as well
// XXX can be error prone
R_API size_t r_str_nlen_w(const char *str, int n) {
	size_t len = 0;
	if (str) {
		while (*str && n > 0) {
			len++;
			str++;
			if (!*str) {
				// handle wide strings
				// xx00yy00bb00
				if (n - 2 > 0) {
					if (str[2]) {
						break;
					}
				}
				str++;
			}
			n--;
		}
	}
	return len;
}

R_API bool r_str_is_ascii(const char *str) {
	const ut8 *ptr;
	for (ptr = (const ut8 *)str; *ptr; ptr++) {
		if (*ptr > 0x7f) {
			return false;
		}
	}
	return true;
}

R_API bool r_str_is_printable(const char *str) {
	while (*str) {
		int ulen = r_utf8_decode ((const ut8 *)str, strlen (str), NULL);
		if (ulen > 1) {
			str += ulen;
			continue;
		}
		if (!IS_PRINTABLE (*str)) {
			return false;
		}
		str++;
	}
	return true;
}

R_API bool r_str_is_printable_limited(const char *str, int size) {
	int left = size;

	while (size > 0 && *str) {
		int ulen = r_utf8_decode ((const ut8 *)str, left, NULL);
		if (ulen > 1) {
			str += ulen;
			continue;
		}
		if (!IS_PRINTABLE (*str)) {
			break;
		}
		str++;
		size--;
		left -= ulen;
	}

	return size == 0;
}

R_API bool r_str_is_printable_incl_newlines(const char *str) {
	while (*str) {
		int ulen = r_utf8_decode ((const ut8 *)str, strlen (str), NULL);
		if (ulen > 1) {
			str += ulen;
			continue;
		}
		if (!IS_PRINTABLE (*str)) {
			if (*str != '\r' && *str != '\n' && *str != '\t') {
				return false;
			}
		}
		str++;
	}
	return true;
}

// Length in chars of a wide string and find a better name
R_API size_t r_wstr_clen(const char *s) {
	size_t len = 0;
	if (!*s++) {
		return 0;
	}
	while (*s++ || *s++) {
		len++;
	}
	return len + 1;
}

// TODO: rename to r_str_ansi_at ()? or find better name?
R_API const char *r_str_ansi_chrn(const char *str, size_t n) {
	int len, i, li;
	for (li = i = len = 0; str[i] && (n != len); i++) {
		size_t chlen = str_ansi_length (str + i, SIZE_MAX);
		if (chlen > 1) {
			if (str[i] == 0x1b) {
				// ANSI escape sequence, skip without incrementing len
				i += chlen - 1;
			} else {
				// UTF-8 multibyte character
				RRune ch;
				int ulen = r_utf8_decode ((const ut8 *)str + i, chlen, &ch);
				if (ulen > 0) {
					len += (RUNE_PROPS (ch) & RUNE_WIDTH_MASK);
				} else {
					len += 1; // invalid, assume 1
				}
				i += chlen - 1;
				li = i;
			}
		} else {
			if ((str[i] & 0xc0) != 0x80) {
				len++;
			}
			li = i;
		}
	}
	return str + li;
}

#if 0
// modify string in place removing escape chars that modify the background color
R_API void r_str_ansi_filterbg(char *str) {
	char *d = str;
	while (*str) {
		if (*str == 0x1b && str[1] == '[') {
			if (str[2] == '4') { // \x1b[4#m
				str += 4;
			} else if (str[2] == '1' && str[3] == '0') { // \x1b[104#m
				str += 5;
			} else if (str[2] == '4' && str[3] == '8' && str[4] == ';') { // 256 bgcolor
				char *end = strchr (str + 4, 'm');
				if (end) {
					str = end;
				}
			}
		}
		str++;
	}
}
#endif

// replace \x1b[0m with \x[0m + bgcolor
R_API char *r_str_ansi_resetbg(const char *str, const char *bgcolor) {
	R_RETURN_VAL_IF_FAIL (str && bgcolor, NULL);
	char *resetbg = r_str_newf (Color_RESET "%s", bgcolor);
	if (!resetbg) {
		return NULL;
	}
	char *res = strdup (str);
	if (res) {
		res = r_str_replace (res, Color_RESET, resetbg, true);
	}
	free (resetbg);
	return res;
}

/*
 * filter out ansi CSI shit in-place!.
 * str - input string,
 * out - if not NULL write a pointer to the original string there,
 * cposs - if not NULL write a pointer to thunk array there
 *(*cposs)[i] is the offset of the out[i] in str
 * len - length of str
 *
 * it returns the number of normal characters found in str
 */
R_API int r_str_ansi_filter(char *str, char **R_NULLABLE out, int **R_NULLABLE cposs, int len) {
	R_RETURN_VAL_IF_FAIL (str, -1);
	int i, j;

	if (len == 0) {
		return 0;
	}
	if (len < 0) {
		const size_t slen = strlen (str);
		if (slen > INT_MAX - 1) {
			return -1;
		}
		len = slen;
	}
	int *cps = NULL;
	if (cposs) {
		cps = calloc (len + 1, sizeof (int));
		if (!cps) {
			return -1;
		}
	}
	if (out) {
		*out = r_str_ndup (str, len);
	}
	// compact in place, the write cursor never passes the read cursor
	for (i = j = 0; i < len; i++) {
		if (str[i] == 0x1b) {
			const size_t chlen = str_ansi_length (str + i, len - i);
			if (chlen > 1) {
				i += chlen - 1;
			}
			continue;
		}
		if (cps) {
			cps[j] = i;
		}
		str[j++] = str[i];
	}
	if (cps) {
		cps[j] = i;
		*cposs = cps;
	}
	str[j] = str[i];
	return j;
}

static char *str_crop(const char *str, ut32 x, ut32 y, ut32 x2, ut32 y2, bool ansi) {
	if (!str || !x2 || y2 <= y || (ansi && x2 <= x)) {
		return strdup ("");
	}
	ut32 ch = 0, cw = 0;
	while (ch < y) {
		str = strchr (str, '\n');
		if (!str) {
			return strdup ("");
		}
		str++;
		ch++;
	}
	size_t str_len = strlen (str), nr_of_lines = 0, r_len;
	const char *s = str, *str_end = str + str_len;
	while (ansi && nr_of_lines < y2 - y && (s = strchr (s, '\n'))) {
		nr_of_lines++;
		s++;
	}
	if (r_mul_overflow_size_t (nr_of_lines, ansi? sizeof (Color_RESET) - 1: 0, &r_len)
		|| r_add_overflow_size_t (r_len, str_len, &r_len)
		|| r_add_overflow_size_t (r_len, 1, &r_len)) {
		return NULL;
	}
	char *ret = malloc (r_len);
	if (!ret) {
		return NULL;
	}
	char *r = ret;
	while (*str) {
		if (*str == '\n') {
			if (ansi) {
				memcpy (r, Color_RESET, sizeof (Color_RESET) - 1);
				r += sizeof (Color_RESET) - 1;
			}
			*r++ = '\n';
			str++;
			ch++;
			if (ch == y2 && *str) {
				r--;
				break;
			}
			cw = 0;
		} else {
			if (ansi && (*str & 0xc0) == 0x80) {
				if (cw > x) {
					*r++ = *str;
				}
				str++;
				continue;
			}
			if (ansi && (*str & 0x80) && r_str_char_fullwidth (str, str_end - str)) {
				cw++;
				if (cw == x) {
					*r++ = ' ';
					str++;
					continue;
				}
			}
			if (ansi && *str == 0x1b && str[1] == '[') {
				const size_t len = str_ansi_length (str, str_end - str);
				memcpy (r, str, len);
				r += len;
				str += len;
				continue;
			}
			if (cw >= x && cw < x2) {
				*r++ = *str;
			}
			if (cw >= x2) {
				str = strchr (str, '\n');
				if (!str) {
					break;
				}
			} else {
				str++;
			}
			cw++;
		}
	}
	*r = 0;
	return ret;
}

R_API char *r_str_ansi_crop(const char *str, ut32 x, ut32 y, ut32 x2, ut32 y2) {
	return str_crop (str, x, y, x2, y2, true);
}

R_API size_t r_str_utf8_codepoint(const char *s, size_t left) {
	if (!s || !*s || left <= 0) {
		return 0;
	}
	if ((*s & 0x80) != 0x80) {
		return 0;
	} else if ((*s & 0xe0) == 0xc0 && left > 1) {
		return ((*s & 0x1f) << 6) + (*(s + 1) & 0x3f);
	} else if ((*s & 0xf0) == 0xe0 && left > 2) {
		return ((*s & 0xf) << 12) + ((*(s + 1) & 0x3f) << 6) + (*(s + 2) & 0x3f);
	} else if ((*s & 0xf8) == 0xf0 && left > 3) {
		return ((*s & 0x7) << 18) + ((*(s + 1) & 0x3f) << 12) + ((*(s + 2) & 0x3f) << 6) + (*(s + 3) & 0x3f);
	}
	return 0;
}

R_API bool r_str_char_fullwidth(const char *s, size_t left) {
	if (!s || !*s || left <= 0) {
		return false;
	}
	size_t codepoint = r_str_utf8_codepoint (s, left);
	if (codepoint >= 0x1100) {
		return codepoint <= 0x115f || codepoint == 0x2329 || codepoint == 0x232a || /* Hangul Jamo init. consonants */
			(R_BETWEEN (0x2e80, codepoint, 0xa4cf) && codepoint != 0x303f) || /* CJK ... Yi */
			R_BETWEEN (0xac00, codepoint, 0xd7a3) || /* Hangul Syllables */
			R_BETWEEN (0xf900, codepoint, 0xfaff) || /* CJK Compatibility Ideographs */
			R_BETWEEN (0xfe10, codepoint, 0xfe19) || /* Vertical forms */
			R_BETWEEN (0xfe30, codepoint, 0xfe6f) || /* CJK Compatibility Forms */
			R_BETWEEN (0xff00, codepoint, 0xff60) || /* Fullwidth Forms */
			R_BETWEEN (0xffe0, codepoint, 0xffe6) ||
			R_BETWEEN (0x20000, codepoint, 0x2fffd) ||
			R_BETWEEN (0x30000, codepoint, 0x3fffd);
	}
	return false;
}

/**
 * Returns size in bytes of the utf8 char
 * Returns 1 in case of ASCII
 * str - Pointer to buffer
 */
R_API size_t r_str_utf8_charsize(const char *str) {
	R_RETURN_VAL_IF_FAIL (str, 0);
	size_t size = 0;
	while (size < 5 && str[size]) {
		size++;
		if ((str[size] & 0xc0) != 0x80) {
			break;
		}
	}
	return size < 5? size: 0;
}

/**
 * Returns size in bytes of the utf8 char previous to str
 * Returns 1 in case of ASCII
 * str - Pointer to leading utf8 char
 * prev_len - Length in bytes of the buffer until str
 */
R_API size_t r_str_utf8_charsize_prev(const char *str, int prev_len) {
	R_RETURN_VAL_IF_FAIL (str, 0);
	int pos = 0;
	size_t size = 0, minsize = R_MIN (5, prev_len);
	while (size < minsize) {
		size++;
		if ((str[--pos] & 0xc0) != 0x80) {
			break;
		}
	}
	return size < 5? size: 0;
}

/**
 * Returns size in bytes of the last utf8 char of the string
 * Returns 1 in case of ASCII
 * str - Pointer to buffer
 */
R_API size_t r_str_utf8_charsize_last(const char *str) {
	R_RETURN_VAL_IF_FAIL (str, 0);
	size_t len = strlen (str);
	return r_str_utf8_charsize_prev (str + len, len);
}

R_API void r_str_filter_zeroline(char *str, int len) {
	R_RETURN_IF_FAIL (str);
	str[r_str_pnlen (str, len)] = 0;
}

R_API void r_str_filter(char *str, int len) {
	size_t i;
	if (len < 1) {
		len = strlen (str);
	}
	for (i = 0; i < len; i++) {
		if (!IS_PRINTABLE (str[i])) {
			str[i] = '.';
		}
	}
}

R_API bool r_str_filter_file(char *str) {
	R_RETURN_VAL_IF_FAIL (str, false);
	bool changed = false;
	char *p;
	for (p = str; *p; p++) {
		if (!IS_PRINTABLE (*p) || *p == '/' || *p == '\\') {
			*p = '_';
			changed = true;
		}
		if (p > str && p[-1] == '.' && *p == '.') {
			*p = '_';
			changed = true;
		}
	}
	return changed;
}

R_API bool r_str_glob(const char *str, const char *glob) {
	if (!glob) {
		return true;
	}
	const char *begin = strchr (glob, '^');
	if (begin) {
		begin++;
		glob = begin;
	}
	while (*str) {
		if (!*glob) {
			return false;
		}
		switch (*glob) {
		case '*':
			glob++;
			if (!*glob) {
				return true;
			}
			// Advance glob an additional time if it is a '**'
			if (*glob == '*') {
				glob++;
				if (!*glob) {
					return true;
				}
			}
			// Check if there are additional wildcards
			// if so, we need to search for the substring in between the wildcards
			const char *needle_end = glob;
			while (*needle_end != '*' &&
				*needle_end != '?' &&
				*needle_end != '$' &&
				*needle_end != '^' &&
				*needle_end != '\0') {
				needle_end++;
			}
			// Find the pattern in between wildcards
			char *needle = r_str_ndup (glob, needle_end - glob);
			const char *advance_to = strstr (str, needle);
			free (needle);
			if (!advance_to) {
				return false;
			}
			// Advance str to found pattern
			while (*str) {
				if (str == advance_to) {
					break;
				}
				str++;
			}
			break;
		case '?':
			str++;
			glob++;
			break;
		default:
			if (*glob != *str) {
				return false;
			}
			str++;
			glob++;
		}
	}
	while (*glob == '*') {
		glob++;
	}
	return ((*glob == '$' && !*glob++) || !*glob);
}

// Escape the string arg so that it is parsed as a single argument by r_str_argv
R_API char *r_str_arg_escape(const char *arg) {
	char *str;
	int dest_i = 0, src_i = 0;
	if (!arg) {
		return NULL;
	}
	str = malloc ((2 * strlen (arg) + 1) * sizeof (char)); // Worse case when every character need to be escaped
	if (!str) {
		return NULL;
	}
	for (src_i = 0; arg[src_i] != '\0'; src_i++) {
		char c = arg[src_i];
		switch (c) {
		case '\'':
		case '"':
		case '\\':
		case ' ':
			str[dest_i++] = '\\';
			str[dest_i++] = c;
			break;
		default:
			str[dest_i++] = c;
			break;
		}
	}
	str[dest_i] = '\0';
	return realloc (str, (strlen (str) + 1) * sizeof (char));
}

// Unescape the string arg to its original format
R_API int r_str_arg_unescape(char *arg) {
	int dest_i = 0, src_i = 0;
	if (!arg) {
		return 0;
	}
	for (src_i = 0; arg[src_i] != '\0'; src_i++) {
		char c = arg[src_i];
		if (c == '\\') {
			if (arg[++src_i] == '\0') {
				break;
			}
			arg[dest_i++] = arg[src_i];
		} else {
			arg[dest_i++] = c;
		}
	}
	arg[dest_i] = '\0';
	return dest_i;
}

R_API char *r_str_path_escape(const char *path) {
	char *str;
	int dest_i = 0, src_i = 0;

	if (!path) {
		return NULL;
	}
	// Worst case when every character need to be escaped
	str = malloc ((2 * strlen (path) + 1) * sizeof (char));
	if (!str) {
		return NULL;
	}

	for (src_i = 0; path[src_i] != '\0'; src_i++) {
		char c = path[src_i];
		switch (c) {
		case ' ':
			str[dest_i++] = '\\';
			str[dest_i++] = c;
			break;
		default:
			str[dest_i++] = c;
			break;
		}
	}

	str[dest_i] = '\0';
	return realloc (str, (strlen (str) + 1) * sizeof (char));
}

R_API int r_str_path_unescape(char *path) {
	int i;

	for (i = 0; path[i]; i++) {
		if (path[i] != '\\') {
			continue;
		}
		if (path[i + 1] == ' ') {
			path[i] = ' ';
			memmove (path + i + 1, path + i + 2, strlen (path + i + 2) + 1);
		}
	}

	return i;
}

R_API char **r_str_argv(const char *cmdline, int *_argc) {
	int argc = 0;
	int argv_len = 128; // Begin with that, argv will reallocated if necessary
	char *args; // Working buffer for writing unescaped args
	int cmdline_current = 0; // Current character index in _cmdline
	int args_current = 0; // Current character index in  args
	int arg_begin = 0; // Index of the first character of the current argument in args

	if (!cmdline) {
		return NULL;
	}

	char **argv = R_NEWS (char *, argv_len);
	if (!argv) {
		return NULL;
	}
	args = malloc (128 + strlen (cmdline) * sizeof (char)); // Unescaped args will be shorter, so strlen (cmdline) will be enough
	if (!args) {
		free (argv);
		return NULL;
	}
	do {
		// States for parsing args
		int escaped = 0;
		int singlequoted = 0;
		int doublequoted = 0;

		// Seek the beginning of next argument (skip whitespaces)
		while (cmdline[cmdline_current] != '\0' && IS_WHITECHAR (cmdline[cmdline_current])) {
			cmdline_current++;
		}

		if (cmdline[cmdline_current] == '\0') {
			break; // No more arguments
		}
		// Read the argument
		while (1) {
			char c = cmdline[cmdline_current];
			int end_of_current_arg = 0;
			if (escaped) {
				switch (c) {
				case '\'':
				case '"':
				case ' ':
				case '\\':
					args[args_current++] = '\\';
					args[args_current++] = c;
					break;
				case '\0':
					args[args_current++] = '\\';
					end_of_current_arg = 1;
					break;
				default:
					args[args_current++] = '\\';
					args[args_current++] = c;
				}
				escaped = 0;
			} else {
				switch (c) {
				case '\'':
					if (doublequoted) {
						args[args_current++] = c;
					} else {
						singlequoted = !singlequoted;
					}
					break;
				case '"':
					if (singlequoted) {
						args[args_current++] = c;
					} else {
						doublequoted = !doublequoted;
					}
					break;
				case '\\':
					escaped = 1;
					break;
				case ' ':
					if (singlequoted || doublequoted) {
						args[args_current++] = c;
					} else {
						end_of_current_arg = 1;
					}
					break;
				case '\0':
					end_of_current_arg = 1;
					break;
				default:
					args[args_current++] = c;
				}
			}
			if (end_of_current_arg) {
				break;
			}
			cmdline_current++;
		}
		args[args_current++] = '\0';
		argv[argc++] = strdup (&args[arg_begin]);
		if (argc >= argv_len) {
			argv_len *= 2;
			char **tmp = r_array_resize (argv, argv_len, sizeof (char *));
			if (!tmp) {
				free (args);
				free (argv);
				return NULL;
			}
			argv = tmp;
		}
		arg_begin = args_current;
	} while (cmdline[cmdline_current++] != '\0');
	argv[argc] = NULL;
	char **tmp = r_array_resize (argv, argc + 1, sizeof (char *));
	if (tmp) {
		argv = tmp;
	} else {
		free (argv);
		argv = NULL;
	}
	if (_argc) {
		*_argc = argc;
	}
	free (args);
	return argv;
}

R_API void r_str_argv_free(char **argv) {
	int argc = 0;
	if (!argv) {
		return;
	}
	while (argv[argc]) {
		free (argv[argc++]);
	}
	free (argv);
}

R_API const char *r_str_firstbut(const char *s, char ch, const char *but) {
	if (!but) {
		return strchr (s, ch);
	}
	char quote = 0;
	const char *p;
	for (p = s; *p; p++) {
		if (*p == '\\') {
			p++;
			if (!*p) {
				break;
			}
			if (strchr (but, *p)) {
				continue;
			}
		}
		if (strchr (but, *p)) {
			if (!quote) {
				quote = *p;
			} else if (quote == *p) {
				quote = 0;
			}
			continue;
		}
		if (*p == ch && !quote) {
			return p;
		}
	}
	return NULL;
}

R_API const char *r_str_firstbut_escape(const char *s, char ch, const char *but) {
	if (!but) {
		return strchr (s, ch);
	}
	const char *p = s;
	while ((p = r_str_firstbut (p, ch, but))) {
		const char *escape = p;
		while (escape > s && escape[-1] == '\\') {
			escape--;
		}
		if (!((p - escape) & 1)) {
			return p;
		}
		p++;
	}
	return NULL;
}

R_API const char *r_str_lastbut(const char *s, char ch, const char *but) {
	if (!but) {
		return r_str_lchr (s, ch);
	}
	const char *last = NULL;
	while ((s = r_str_firstbut (s, ch, but))) {
		last = s++;
	}
	return last;
}

// Must be merged inside strlen
R_API size_t r_str_len_utf8char(const char *s, int left) {
	size_t i = 1;
	while (s[i] && (!left || i < left)) {
		if ((s[i] & 0xc0) != 0x80) {
			i++;
		} else {
			break;
		}
	}
	return i;
}

R_API size_t r_str_len_utf8(const char *s) {
	return str_dwidth (s, 0, false);
}

R_API size_t r_str_len_utf8_ansi(const char *str) {
	return str_dwidth (str, 0, true);
}

// XXX must find across the ansi tags, as well as support utf8
R_API const char *r_strstr_ansi(const char *a, const char *b) {
	// XXX not working well if "\x1b[32mF\x1b[32mOO" -> FOO not found
	const char *ch, *p = a;
	do {
		ch = strchr (p, '\x1b');
		if (ch) {
			const char *v = r_str_nstr (p, b, ch - p);
			if (v) {
				return v;
			}
			p = ch + str_ansi_length (ch, SIZE_MAX);
		}
	} while (ch);
	return strstr (p, b);
}

R_API const char *r_str_casestr(const char *a, const char *b) {
	// That's a GNUism that works in many places.. but we don't want it
	// return strcasestr (a, b);
	size_t hay_len = strlen (a);
	size_t needle_len = strlen (b);
	if (!hay_len || !needle_len) {
		return NULL;
	}
	while (hay_len >= needle_len) {
		if (!r_str_ncasecmp (a, b, needle_len)) {
			return (const char *)a;
		}
		a++;
		hay_len--;
	}
	return NULL;
}

R_API int r_str_write(int fd, const char *b) {
	return write (fd, b, strlen (b));
}

R_API void r_str_range_foreach(const char *r, RStrRangeCallback cb, void *u) {
	const char *p = r;
	for (; *r; r++) {
		if (*r == ',') {
			cb (u, atoi (p));
			p = r + 1;
		}
		if (*r == '-') {
			if (p != r) {
				int from = atoi (p);
				int to = atoi (r + 1);
				for (; from <= to; from++) {
					cb (u, from);
				}
			} else {
				R_LOG_ERROR ("Invalid range");
			}
			for (r++; *r && *r != ',' && *r != '-'; r++) {
				;
			}
			p = r;
		}
	}
	if (*p) {
		cb (u, atoi (p));
	}
}

R_API bool r_str_range_in(const char *r, ut64 addr) {
	const char *p = r;
	ut64 min = UT64_MAX;
	ut64 max = 0;
	if (!r) {
		return false;
	}
	for (; *r; r++) {
		if (*r == ',') {
			if (max == 0) {
				if (addr == r_num_get (NULL, p)) {
					return true;
				}
			} else {
				if (addr >= min && addr <= r_num_get (NULL, p)) {
					return true;
				}
			}
			p = r + 1;
		}
		if (*r == '-') {
			if (p != r) {
				ut64 from = r_num_get (NULL, p);
				ut64 to = r_num_get (NULL, r + 1);
				if (addr >= from && addr <= to) {
					return true;
				}
			} else {
				R_LOG_ERROR ("Invalid range");
			}
			for (r++; *r && *r != ',' && *r != '-'; r++) {
				;
			}
			p = r;
		}
	}
	if (*p) {
		if (addr == r_num_get (NULL, p)) {
			return true;
		}
	}
	return false;
}

// Percent-decode a string in place and return its possibly binary length.
R_API int r_str_uri_decode(char *s) {
	R_RETURN_VAL_IF_FAIL (s, -1);
	char *src = s;
	char *dst = s;
	for (; *src; src++, dst++) {
		if (*src == '%') {
			ut8 ch = 0;
			if (!src[1] || !src[2] || !r_hex_to_byte (&ch, src[1]) || !r_hex_to_byte (&ch, src[2])) {
				return -1;
			}
			*dst = ch;
			src += 2;
		} else {
			*dst = *src;
		}
	}
	*dst = 0;
	return dst - s;
}

R_API char *r_str_uri_encode(const char *s) {
	char ch[4], *d, *od;
	if (!s) {
		return NULL;
	}
	od = d = malloc (1 + (strlen (s) * 4));
	if (!d) {
		return NULL;
	}
	for (; *s; s++) {
		if ((*s >= '0' && *s <= '9') || (*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z')) {
			*d++ = *s;
		} else {
			*d++ = '%';
			snprintf (ch, sizeof (ch), "%02x", 0xff &((ut8)*s));
			*d++ = ch[0];
			*d++ = ch[1];
		}
	}
	*d = 0;
	char *trimDown = realloc (od, strlen (od) + 1); // FIT
	return trimDown? trimDown: od;
}

R_API int r_str_utf16_to_utf8(ut8 *dst, int len_dst, const ut8 *src, int len_src, bool big_endian) {
	R_RETURN_VAL_IF_FAIL (dst && src, -1);
	ut8 *outstart = dst;
	const ut8 *in = src;
	const ut8 *inend;

	if (len_dst < 1 || len_src < 1) {
		if (len_dst > 0) {
			*dst = 0;
		}
		return 0;
	}
	if ((len_src % 2) == 1) {
		len_src--;
	}
	inend = in + len_src;
	while (in < inend) {
		ut32 c = big_endian? r_read_be16 (in): r_read_le16 (in);
		in += 2;
		if (!c) {
			break;
		}
		if ((c & 0xFC00) == 0xD800) { /* surrogates */
			if (in >= inend) { /*(in > inend) shouldn't happens */
				break;
			}
			ut32 d = big_endian? r_read_be16 (in): r_read_le16 (in);
			in += 2;
			if ((d & 0xFC00) == 0xDC00) {
				c &= 0x03FF;
				c <<= 10;
				c |= d & 0x03FF;
				c += 0x10000;
			} else {
				*dst = 0;
				return -2;
			}
		}

		ut8 buf[4];
		int len = r_utf8_encode (buf, c);
		if (len < 1 || dst - outstart + len >= len_dst) {
			break;
		}
		memcpy (dst, buf, len);
		dst += len;
	}
	len_dst = dst - outstart;
	*dst = 0;
	return len_dst;
}

R_API char *r_str_utf16_decode(const ut8 *s, int len) {
	int i = 0;
	int j = 0;
	char *result = NULL;
	int count_unicode = 0;
	int count_ascii = 0;
	int lenresult = 0;
	if (!s) {
		return NULL;
	}
	for (i = 0; i < len && (s[i] || s[i + 1]); i += 2) {
		if (!s[i + 1] && 0x20 <= s[i] && s[i] <= 0x7E) {
			++count_ascii;
		} else {
			++count_unicode;
		}
	}
	lenresult = 1 + count_ascii + count_unicode * 6; // len ("\\uXXXX") = 6
	if (! (result = calloc (1 + count_ascii + count_unicode * 6, 1))) {
		return NULL;
	}
	for (i = 0; i < len && j < lenresult && (s[i] || s[i + 1]); i += 2) {
		if (!s[i + 1] && IS_PRINTABLE (s[i])) {
			result[j++] = s[i];
		} else {
			j += snprintf (&result[j], lenresult - j, "\\u%.2" HHXFMT "%.2" HHXFMT "", s[i], s[i + 1]);
		}
	}
	return result;
}

// TODO: kill this completely, it makes no sense:
R_API char *r_str_utf16_encode(const char *s, int len) {
	int i;
	char ch[4], *d, *od, *tmp;
	if (!s) {
		return NULL;
	}
	if (len < 0) {
		len = strlen (s);
	}
	od = d = malloc (1 + (size_t) len * 7);
	if (!d) {
		return NULL;
	}
	for (i = 0; i < len; s++, i++) {
		if (*s == '\\') {
			*d++ = '\\';
			*d++ = '\\';
		} else if (*s == '"') {
			*d++ = '\\';
			*d++ = '"';
		} else if ((*s >= 0x20) && (*s <= 126)) {
			*d++ = *s;
		} else {
			*d++ = '\\';
			//	*d++ = '\\';
			*d++ = 'u';
			*d++ = '0';
			*d++ = '0';
			snprintf (ch, sizeof (ch), "%02x", 0xff &((ut8)*s));
			*d++ = ch[0];
			*d++ = ch[1];
		}
	}
	*d = 0;
	tmp = realloc (od, strlen (od) + 1); // FIT
	if (!tmp) {
		free (od);
		return NULL;
	}
	return tmp;
}

R_API char *r_str_prefix_all(const char *s, const char *pfx) {
	const char *os = s;
	char *p;
	int newlines = 1;
	int len = 0;
	int pfx_len = 0;

	if (!s) {
		return strdup (pfx);
	}
	if (!pfx) {
		return strdup (s);
	}
	len = strlen (s);
	pfx_len = strlen (pfx);
	for (os = s; *os; os++) {
		if (*os == '\n') {
			newlines++;
		}
	}
	char *o = malloc (len + (pfx_len * newlines) + 1);
	if (!o) {
		return NULL;
	}
	memcpy (o, pfx, pfx_len);
	for (p = o + pfx_len; *s; s++) {
		*p++ = *s;
		if (*s == '\n' && s[1]) {
			memcpy (p, pfx, pfx_len);
			p += pfx_len;
		}
	}
	*p = 0;
	return o;
}

#define HASCH(x) strchr(input_value, x)
R_API ut8 r_str_contains_macro(const char *input_value) {
	const char *has_tilde = input_value? HASCH ('~'): NULL,
	*has_bang = input_value? HASCH ('!'): NULL,
	*has_brace = input_value && (HASCH ('[') || HASCH (']'))? input_value: NULL,
	*has_paren = input_value && (HASCH ('(') || HASCH (')'))? input_value: NULL,
	*has_cbrace = input_value && (HASCH ('{') || HASCH ('}'))? input_value: NULL,
	*has_qmark = input_value? HASCH ('?'): NULL,
	*has_colon = input_value? HASCH (':'): NULL,
	*has_at = input_value? strchr (input_value, '@'): NULL;

	return has_tilde || has_bang || has_brace || has_cbrace || has_qmark || has_paren || has_colon || has_at;
}

R_API void r_str_truncate_cmd(char *string) {
	ut32 pos = 0;
	if (string && *string) {
		ut32 sz = strlen (string);
		for (pos = 0; pos < sz; pos++) {
			switch (string[pos]) {
			case '!':
			case ':':
			case ';':
			case '@':
			case '~':
			case '(':
			case '[':
			case '{':
			case '?':
				string[pos] = '\0';
				return;
			}
		}
	}
}

R_API const char *r_str_closer_chr(const char *b, const char *s) {
	const char *a;
	while (*b) {
		for (a = s; *a; a++) {
			if (*b == *a) {
				return b;
			}
		}
		b++;
	}
	return NULL;
}

R_API int r_str_bounds(const char *_str, int *h) {
	const char *str, *ptr;
	int W = 0, H = 0;
	int cw = 0;

	if (_str) {
		ptr = str = _str;
		while (*str) {
			if (*str == '\n') {
				H++;
				cw = r_str_ansi_nlen (ptr, (size_t) (str - ptr));
				if (cw > W) {
					W = cw;
				}
				cw = 0;
				ptr = str + 1;
			}
			str++;
			cw++;
		}
		if (*str == '\n') { // skip last newline
			H--;
		}
		if (h) {
			*h = H;
		}
	}
	return W;
}

/* crop a string like it is in a rectangle with the upper-left corner at (x, y)
 * coordinates and the bottom-right corner at (x2, y2) coordinates. The result
 * is a newly allocated string, that should be deallocated by the user */
R_API char *r_str_crop(const char *str, unsigned int x, unsigned int y, unsigned int x2, unsigned int y2) {
	R_RETURN_VAL_IF_FAIL (str, NULL);
	return str_crop (str, x, y, x2, y2, false);
}

R_API char *r_str_wrap(const char *str, int w) {
	if (w < 1 || !str) {
		return strdup ("");
	}
	size_t r_size = 0;
	size_t str_len = strlen (str);
	if (r_mul_overflow (str_len, (size_t)8, &r_size) || r_add_overflow (r_size, (size_t)1, &r_size)) {
		return NULL;
	}
	char *r = malloc (r_size);
	if (!r) {
		return NULL;
	}
	char *ret = r;
	char *line = r;
	char *last = NULL;
	char *end = r + r_size;
	const char *send = str + str_len;
	int cw = 0;
	int lastw = 0;
	while (*str && r + 1 < end) {
		int dw = 0;
		size_t len = *str == 0x1b? str_ansi_length (str, send - str): display_cluster (str, send - str, true, &dw);
		if (*str == '\t') {
			str++;
			continue;
		}
		if (*str == '\r') {
			str++;
			continue;
		}
		if (*str == '\n') {
			*r++ = *str++;
			line = r;
			last = NULL;
			cw = 0;
			continue;
		}
		if (dw > 0 && cw + dw > w && r > line) {
			if (last) {
				int tail = cw - lastw;
				*last = '\n';
				line = last + 1;
				last = NULL;
				cw = tail;
			}
			if (cw + dw > w && r > line) {
				*r++ = '\n';
				line = r;
				cw = 0;
			}
		}
		if (*str == ' ' && r == line) {
			str++;
			continue;
		}
		if (len >= (size_t)(end - r)) {
			break;
		}
		if (*str == ' ') {
			last = r;
			lastw = cw + dw;
		}
		memcpy (r, str, len);
		r += len;
		str += len;
		cw += dw;
	}
	*r = 0;
	return ret;
}

R_API int r_str_do_until_token(str_operation op, char *str, const char tok) {
	int ret = -1;
	if (str) {
		if (op) {
			for (ret = 0; (str[ret] != tok) && str[ret]; ret++) {
				op (str + ret);
			}
		} else {
			for (ret = 0; (str[ret] != tok) && str[ret]; ret++) {
				// empty body
			}
		}
	}
	return ret;
}

R_API char *r_str_pad(char *pad, size_t padsz, const char ch, int sz) {
	if (sz < 0) {
		sz = 0;
	}
	if (!pad) {
		padsz = sz + 1;
		pad = malloc (padsz);
		if (!pad) {
			return NULL;
		}
	}
	if (padsz > 0) {
		size_t fill = R_MIN ((size_t)sz, padsz - 1);
		memset (pad, ch, fill);
		pad[fill] = 0;
	}
	return pad;
}

R_API char *r_str_repeat(const char *ch, int sz) {
	int i;
	if (sz < 0) {
		sz = 0;
	}
	if (sz == 0) {
		return strdup ("");
	}
	RStrBuf *buf = r_strbuf_new (ch);
	for (i = 1; i < sz; i++) {
		r_strbuf_append (buf, ch);
	}
	return r_strbuf_drain (buf);
}

R_API char *r_str_between(const char *cmt, const char *prefix, const char *suffix) {
	R_RETURN_VAL_IF_FAIL (cmt && prefix && suffix, NULL);
	const char *c0 = strstr (cmt, prefix);
	if (c0) {
		const char *c1 = strstr (c0 + strlen (prefix), suffix);
		if (c1) {
			return r_str_ndup (c0 + strlen (prefix), (c1 - c0 - strlen (prefix)));
		}
	}
	return NULL;
}

R_API bool r_str_endswith(const char *str, const char *needle) {
	R_RETURN_VAL_IF_FAIL (str && needle, false);
	if (!*needle) {
		return true;
	}
	size_t slen = strlen (str);
	size_t nlen = strlen (needle);
	if (!slen || !nlen || slen < nlen) {
		return false;
	}
	return !strcmp (str + (slen - nlen), needle);
}

R_API bool r_str_iendswith(const char *str, const char *needle) {
	R_RETURN_VAL_IF_FAIL (str && needle, false);
	if (!*needle) {
		return true;
	}
	size_t slen = strlen (str);
	size_t nlen = strlen (needle);
	if (!slen || !nlen || slen < nlen) {
		return false;
	}
	return !r_str_casecmp (str + (slen - nlen), needle);
}

R_API char *r_str_slice(const char *str, RStringSlice s) {
	size_t len = s.to - s.from;
	return r_str_ndup (str + s.from, len);
}

R_API RVecStringSlice *r_str_split_vec(const char *str, const char *c, int n) {
	R_RETURN_VAL_IF_FAIL (str && c, NULL);
	size_t b = 0;
	// TODO honor 'c' and 'n'. not just split by ' '
	RVecStringSlice *vs = RVecStringSlice_new ();
	while (str[b]) {
		const char ch = str[b];
		if (isspace (ch)) {
			b++;
			continue;
		}
		RStringSlice slice = { b, 0 };
		b++;
		while (str[b]) {
			const char ch = str[b];
			if (isspace (ch)) {
				break;
			}
			b++;
		}
		slice.to = b;
		RVecStringSlice_push_back (vs, &slice);
	}
	return vs;
}

// Avoid ASan's full suffix scan in strstr for single-character delimiters.
static inline char *split_next(char *s, const char *c, size_t clen) {
	return clen == 1? strchr (s, *c): strstr (s, c);
}

R_API RList *r_str_split_list_dup(char *str, const char *c, int n) {
	R_RETURN_VAL_IF_FAIL (str && c, NULL);
	RList *lst = r_list_newf (NULL);
	char *aux = str; // R2_600 - XXX should be an strdup to not modify the string passed
	int i = 0;
	char *e = aux;
	const size_t clen = strlen (c);
	for (; e;) {
		e = split_next (aux, c, clen);
#if 0
		if (e == aux) {
			aux++;
			continue;
		}
#endif
		if (n > 0) {
			if (++i >= n) {
				r_list_append (lst, aux);
				break;
			}
		}
		if (e) {
			*e = 0;
			e += clen;
		}
		// TODO: make string trim optional
		r_str_trim (aux);
		r_list_append (lst, aux);
		aux = e;
	}
	return lst;
}

// Splits the string <str> by string <c> and returns the result in a list.
// R2_600 - char *arg must be const!!
// R2_600 - maybe add a bool about not accepting empty strings and return null
R_API RList *r_str_split_list(char *str, const char *c, int n) {
	R_RETURN_VAL_IF_FAIL (str && c, NULL);
	RList *lst = r_list_newf (NULL);
	char *aux = str; // R2_600 - XXX should be an strdup
	int i = 0;
	char *e = aux;
	const size_t clen = strlen (c);
	for (; e;) {
		e = split_next (aux, c, clen);
#if 0
		if (e == aux) {
			aux++;
			continue;
		}
#endif
		if (n > 0) {
			if (++i >= n) {
				r_list_append (lst, aux);
				break;
			}
		}
		if (e) {
			*e = 0;
			e += clen;
		}
		// TODO: make string trim optional
		r_str_trim (aux);
		r_list_append (lst, aux);
		aux = e;
	}
	return lst;
}

R_API RList *r_str_split_duplist(const char *_str, const char *c, bool trim) {
	R_RETURN_VAL_IF_FAIL (_str && c, NULL);
	RList *lst = r_list_newf (free);
	char *str = strdup (_str);
	char *aux = str;
	size_t clen = strlen (c);
	while (aux) {
		char *next = split_next (aux, c, clen);
		if (next) {
			*next = '\0';
			next += clen;
		}
		if (trim) {
			r_str_trim (aux);
		}
		r_list_append (lst, strdup (aux));
		aux = next;
	}
	free (str);
	return lst;
}

R_API size_t *r_str_split_lines(char *str, size_t *count) {
	if (!str) {
		return NULL;
	}
	size_t lines = r_str_char_count (str, '\n');
	// allocate and set indexes
	size_t *indexes = calloc (sizeof (indexes[0]), lines + 1);
	if (!indexes) {
		return NULL;
	}
	size_t line = 0;
	indexes[line++] = 0;
	size_t i;
	for (i = 0; str[i]; i++) {
		if (str[i] == '\n') {
			str[i] = 0;
			indexes[line++] = i + 1;
		}
	}
	if (count) {
		*count = line;
	}
	return indexes;
}

R_API bool r_str_isnumber(const char *str) {
	if (!str || (!isdigit (*str) && *str != '-')) {
		return false;
	}
	for (str++; *str; str++) {
		// consider '.' part of the number?
		if (!isdigit (*str)) {
			return false;
		}
	}
	return true;
}

R_API const char *r_str_last(const char *str, const char *ch) {
	if (!str || !ch) {
		return NULL;
	}
	const size_t slen = strlen (str);
	const size_t clen = strlen (ch);
	if (clen == 0 || clen > slen) {
		return NULL;
	}
	// search backwards from the end of the string
	const char *p = str + slen - clen;
	while (p >= str) {
		if (!memcmp (p, ch, clen)) {
			return p;
		}
		p--;
	}
	return NULL;
}

// copies the WHOLE string but check n against non color code chars only.
static int strncpy_with_color_codes(char *s1, char *s2, int n) {
	int i = 0, j = 0;
	int count = 0;
	while (s2[j] && count < n) {
		// detect (consecutive) color codes
		while (s2[j] == 0x1b) {
			// copy till 'm'
			while (s2[j] && s2[j] != 'm') {
				s1[i++] = s2[j++];
			}
			// copy 'm'
			if (s2[j]) {
				s1[i++] = s2[j++];
			}
		}
		if (s2[j]) {
			s1[i++] = s2[j++];
			count++;
		}
	}
	return i;
}

static int strncmp_ansi(const char *s1, const char *s2, int n) {
	int i = 0, j = 0;
	int count = 0;
	for (i = 0, j = 0; s1[i] && s2[j] && count < n; i++, j++, count++) {
		while (s1[i] == 0x1b) {
			while (s1[i] && s1[i] != 'm') {
				i++;
			}
			if (s1[i]) {
				i++;
			}
		}
		while (s2[j] == 0x1b) {
			while (s2[j] && s2[j] != 'm') {
				j++;
			}
			if (s2[j]) {
				j++;
			}
		}
		if (s1[i] != s2[j]) {
			return -1;
		}
	}

	if (count < n && s1[i] != s2[j]) {
		return -1;
	}

	return 0;
}

static char *strchr_ansi(const char *s, int c) {
	int i = 0;
	for (i = 0; s[i]; i++) {
		while (s[i] && s[i] == 0x1b) {
			while (s[i] && s[i] != 'm') {
				i++;
			}
			if (s[i]) {
				i++;
			}
		}
		if (!s[i] || s[i] == (char)c) {
			return (char *)s + i;
		}
	}
	return NULL;
}

// Global buffer to speed up colorizing performance

R_API char *r_str_highlight(char *str, const char *word, const char *color, const char *color_reset) {
	R_RETURN_VAL_IF_FAIL (str, NULL);
	if (!*str) {
		return NULL;
	}
	size_t i = 0, j = 0, to_copy;
	char *start = str;
	size_t l_str = strlen (str);
	ut32 l_reset = strlen (color_reset);
	ut32 l_color = color? strlen (color): 0;
	if (!color) {
		return strdup (str);
	}
	if (R_STR_ISEMPTY (word)) {
		return r_str_newf ("%s%s%s", color, str, color_reset);
	}
	ut32 l_word = strlen (word);
	// XXX don't use static buffers
	char o[1024] = { 0 };
	while (start && (start < str + l_str)) {
		int copied = 0;
		// find first letter
		start = strchr_ansi (str + i, *word);
		if (start) {
			to_copy = start - (str + i);
			if (to_copy + j + 1 > sizeof (o)) {
				// XXX. no limits
				break;
			}
			strncpy (o + j, str + i, to_copy);
			i += to_copy;
			j += to_copy;
			if (!strncmp_ansi (start, word, l_word)) {
				if (j + strlen (color) >= sizeof (o)) {
					// XXX. no limits
					break;
				}
				strcpy (o + j, color);
				j += l_color;
				if (j + l_word >= sizeof (o)) {
					// XXX. no limits
					break;
				}
				copied = strncpy_with_color_codes (o + j, str + i, l_word);
				i += copied;
				j += copied;
				if (j + strlen (color_reset) >= sizeof (o)) {
					// XXX. no limits
					break;
				}
				strcpy (o + j, color_reset);
				j += l_reset;
			} else {
				o[j++] = str[i++];
			}
		} else {
			if (j + strlen (str + i) >= sizeof (o)) {
				break;
			}
			strcpy (o + j, str + i);
			break;
		}
	}
	return strdup (o);
}

R_API wchar_t *r_str_mb_to_wc_l(const char *buf, int len) {
	R_RETURN_VAL_IF_FAIL (buf && len > 0, NULL);
	wchar_t *res_buf = NULL;
	bool fail = true;

	size_t sz = mbstowcs (NULL, buf, len);
	if (sz == (size_t)-1) {
		goto err_r_str_mb_to_wc;
	}
	res_buf = (wchar_t *)calloc (1, (sz + 1) * sizeof (wchar_t));
	if (!res_buf) {
		goto err_r_str_mb_to_wc;
	}
	sz = mbstowcs (res_buf, buf, sz + 1);
	if (sz == (size_t)-1) {
		goto err_r_str_mb_to_wc;
	}
	fail = false;
err_r_str_mb_to_wc:
	if (fail) {
		R_FREE (res_buf);
	}
	return res_buf;
}

// XXX: this function name is awful
R_API char *r_str_wc_to_mb_l(const wchar_t *buf, int len) {
	R_RETURN_VAL_IF_FAIL (buf, NULL);
	char *res_buf = NULL;
	bool fail = true;

	if (len <= 0) {
		return NULL;
	}
	size_t sz = wcstombs (NULL, buf, 0);
	if (sz == (size_t)-1) {
		goto err_r_str_wc_to_mb;
	}
	res_buf = (char *)calloc (1, (sz + 1) * sizeof (char));
	if (!res_buf) {
		goto err_r_str_wc_to_mb;
	}
	sz = wcstombs (res_buf, buf, sz + 1);
	if (sz == (size_t)-1) {
		goto err_r_str_wc_to_mb;
	}
	fail = false;
err_r_str_wc_to_mb:
	if (fail) {
		R_FREE (res_buf);
	}
	return res_buf;
}

R_API char *r_str_wc_to_mb(const wchar_t *buf) {
	if (!buf) {
		return NULL;
	}
	return r_str_wc_to_mb_l (buf, wcslen (buf));
}

R_API wchar_t *r_str_mb_to_wc(const char *buf) {
	if (!buf) {
		return NULL;
	}
	return r_str_mb_to_wc_l (buf, strlen (buf));
}

R_API char *r_str_from_ut64(ut64 val) {
	int i = 0;
	char *v = (char *)&val;
	char *str = (char *)calloc (1, 9);
	if (!str) {
		return NULL;
	}
	while (!*v && i < 8) {
		v++;
		i++;
	}
	while (i < 8 && *v) {
#if 1
		str[i++] = *v++;
#else
		// WIP: experimental behaviour
		if (IS_PRINTABLE (*v)) {
			str[i++] = *v;
		} else {
			//			break;
		}
		v++;
#endif
	}
	return str;
}

static void conv_append(RStrBuf *sb, bool as_pf, const char *type, const char *pf) {
	if (as_pf) {
		r_strbuf_append (sb, pf);
		return;
	}
	if (r_strbuf_length (sb)) {
		r_strbuf_append (sb, ",");
	}
	r_strbuf_append (sb, type);
}

// mode '*' returns a pf format string (bits resolves long/size_t/pointer width); otherwise a comma-separated list of C type names; NULL if any conversion is unsupported
R_API char *r_str_printfmt(const char *fmt, int bits, int mode) {
	R_RETURN_VAL_IF_FAIL (fmt, NULL);
	const bool as_pf = mode == '*';
	const bool w8 = bits == 64;
	RStrBuf *sb = r_strbuf_new ("");
	if (!sb) {
		return NULL;
	}
	for (const char *p = fmt; *p; p++) {
		if (*p != '%') {
			continue;
		}
		p++;
		if (*p == '%') {
			continue;
		}
		if (*p == '\0') {
			goto fail;
		}
		if (*r_str_trim_head_digits (p) == '$') {
			goto fail; // positional %m$ unsupported
		}
		while (*p && strchr ("-+ #0'", *p)) {
			p++;
		}
		if (*p == '*') {
			conv_append (sb, as_pf, "int", "i");
			p++;
		} else {
			p = r_str_trim_head_digits (p);
		}
		if (*p == '.') {
			p++;
			if (*p == '*') {
				conv_append (sb, as_pf, "int", "i");
				p++;
			} else {
				p = r_str_trim_head_digits (p);
			}
		}
		int isize = 4; // default-promoted int
		bool longdbl = false;
		if (*p == 'h') {
			p++;
			if (*p == 'h') {
				p++;
			}
		} else if (*p == 'l') {
			p++;
			if (*p == 'l') {
				p++;
				isize = 8;
			} else {
				isize = 0; // long
			}
		} else if (*p == 'L') {
			p++;
			longdbl = true;
		} else if (*p == 'j' || *p == 'q') {
			p++;
			isize = 8;
		} else if (*p == 'z' || *p == 't') {
			p++;
			isize = 0;
		}
		const bool wide = (isize == 8) || (isize == 0 && w8);
		const char *type = NULL, *pf = NULL;
		switch (*p) {
		case 'd': case 'i':
			type = isize == 8? "long long": isize == 0? "long": "int";
			pf = wide? "q": "i";
			break;
		case 'u': case 'o': case 'x': case 'X':
			type = isize == 8? "unsigned long long": isize == 0? "unsigned long": "unsigned int";
			pf = wide? "q": "x";
			break;
		case 'c':
			type = "int";
			pf = "i";
			break;
		case 's':
			type = "char *";
			pf = w8? "S": "s";
			break;
		case 'p':
			type = "void *";
			pf = "p";
			break;
		case 'n':
			type = "int *";
			pf = "p";
			break;
		case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A':
			type = longdbl? "long double": "double";
			pf = longdbl? "G": "F";
			break;
		default:
			goto fail;
		}
		conv_append (sb, as_pf, type, pf);
	}
	return r_strbuf_drain (sb);
fail:
	r_strbuf_free (sb);
	return NULL;
}

R_API int r_str_fmtargs(const char *fmt) {
	int n = 0;
	while (*fmt) {
		if (*fmt == '%') {
			if (fmt[1] == '*') {
				n++;
			}
			n++;
		}
		fmt++;
	}
	return n;
}

// Strips in-place all the lines in str that contain key
R_API void r_str_stripline(char *str, const char *key) {
	R_RETURN_IF_FAIL (str && key);
	size_t i, j;
	size_t klen = strlen (key);
	size_t slen = strlen (str);

	for (i = 0; i < slen;) {
		const char *ptr = (char *)r_mem_mem ((ut8 *)str + i, slen - i, (ut8 *)"\n", 1);
		if (!ptr) {
			ptr = (char *)r_mem_mem ((ut8 *)str + i, slen - i, (ut8 *)key, klen);
			if (ptr) {
				str[i] = '\0';
				break;
			}
			break;
		}

		size_t off = (size_t) (ptr - (str + i)) + 1;

		ptr = (char *)r_mem_mem ((ut8 *)str + i, off, (ut8 *)key, klen);
		if (ptr) {
			for (j = i; j < slen - off + 1; j++) {
				str[j] = str[j + off];
			}
			slen -= off;
		} else {
			i += off;
		}
	}
}

R_API char *r_str_list_join(RList *str, const char *sep) {
	R_RETURN_VAL_IF_FAIL (str && sep, NULL);
	RListIter *iter;
	RStrBuf *sb = r_strbuf_new ("");
	r_list_foreach_iter (str, iter) {
		if (iter != str->head) {
			r_strbuf_append (sb, sep);
		}
		r_strbuf_append (sb, iter->data);
	}
	return r_strbuf_drain (sb);
}

R_API char *r_str_array_join(const char **a, size_t n, const char *sep) {
	RStrBuf *sb = r_strbuf_new ("");
	size_t i;
	if (n > 0) {
		r_strbuf_append (sb, a[0]);
	}
	for (i = 1; i < n; i++) {
		r_strbuf_append (sb, sep);
		r_strbuf_append (sb, a[i]);
	}
	return r_strbuf_drain (sb);
}

// str-bool

// Returns "true" or "false" as a string given an input integer. The returned
// value is consistent with C's definition of 0 is false, and all other values
// are true.
R_API const char *r_str_bool(bool b) {
	return b? "true": "false";
}

R_API bool r_str_is_true(const char *s) {
	if (!s) {
		return false;
	}
	return !r_str_casecmp ("yes", s) || !r_str_casecmp ("on", s) || !r_str_casecmp ("true", s) || !r_str_casecmp ("1", s);
}

R_API bool r_str_is_false(const char *s) {
	if (!s) {
		return true;
	}
	return !r_str_casecmp ("no", s) || !r_str_casecmp ("off", s) || !r_str_casecmp ("false", s) || !r_str_casecmp ("0", s) || !*s;
}

R_API bool r_str_is_bool(const char *val) {
	return r_str_is_true (val) || r_str_is_false (val);
}

R_API char *r_str_nextword(char *s, char ch) {
	char *p = strchr (s, ch);
	if (!p) {
		return NULL;
	}
	*p++ = 0;
	return p;
}

R_API char *r_str_scale(const char *s, const int w, const int h) {
	R_RETURN_VAL_IF_FAIL (s && w > 0 && h > 0, NULL);
	char *str = strdup (s);
	RList *lines = r_str_split_list (str, "\n", 0);
	int i, j;
	const int rows = r_list_length (lines);
	RList *out = r_list_newf (free);

	int curline = -1;
	char *linetext = (char *)r_str_pad (NULL, 0, ' ', w);
	for (i = 0; i < h; i++) {
		const int zoomedline = (int) (i *((double)rows / h));
		const char *srcline = r_list_get_n (lines, zoomedline);
		const int cols = srcline ? strlen (srcline) : 0;
		if (cols > 0) {
			for (j = 0; j < w; j++) {
				const int zoomedcol = (int) (j *((double)cols / w));
				linetext[j] = srcline[zoomedcol];
			}
		}
		if (curline != zoomedline) {
			r_list_append (out, strdup (linetext));
			curline = zoomedline;
		}
		memset (linetext, ' ', w);
	}
	free (linetext);
	free (str);
	return r_str_list_join (out, "\n");
}

// returns value between 0 and 100 about how much different the strings are
R_API int r_str_distance(const char *a, const char *b) {
	R_RETURN_VAL_IF_FAIL (a && b, 0);
	ut32 distance = 0;
	double similarity = 0;
	r_diff_buffers_distance_levenshtein (NULL, (const ut8 *)a, strlen (a), (const ut8 *)b, strlen (b), &distance, &similarity);
	return (int) (similarity * 100);
}

R_API const char *r_str_str_xy(const char *s, const char *word, const char *prev, int *x, int *y) {
	R_RETURN_VAL_IF_FAIL (s && word && x && y, NULL);
	R_RETURN_VAL_IF_FAIL (word[0] != '\0' && word[0] != '\n', NULL);
	const char *src = prev? prev + 1: s;
	const char *d = strstr (src, word);
	if (!d) {
		return NULL;
	}
	const char *q;
	for (q = prev? prev: s; q < d; q++) {
		if (*q == '\n') {
			(*y)++;
			*x = 0;

		} else {
			(*x)++;
		}
	}
	return d;
}

// version.c
#include <r_userconf.h>
#include <r_util.h>
#include <r_lib.h>

#ifndef R2_GITTAP
#define R2_GITTAP ""
#endif

#ifndef R2_GITTIP
#define R2_GITTIP ""
#endif

#ifndef R2_BIRTH
#define R2_BIRTH "unknown"
#endif

R_API char *r_str_version(const char *program) {
	const char *release = "";
	const char *asanstr = "";
	const char *newabi = "";
	const char *gplstr = "lgpl";
	char optistr[5] = " -O?";
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
	asanstr = " asan";
#endif
#else
#if defined(__SANITIZE_ADDRESS__) // gcc < 14
	asanstr = " asan";
#endif
#endif
#if R2_VERSION_COMMIT == 0
	release = " release";
#endif
#ifdef _FORTIFY_SOURCE
	// clang
	optistr[3] = '0' + _FORTIFY_SOURCE;
#endif
#ifdef __OPTIMIZE__
	// gcc
	optistr[3] = '0' + __OPTIMIZE__;
#endif
#ifdef __OPTIMIZE_SIZE__
	// -Os and -Oz builds
	optistr[3] = 's';
#endif
#if WITH_GPL
	gplstr = " gpl";
#endif
	char *s = r_str_newf ("%s " R2_VERSION " +%d abi:%d @ " R_SYS_ARCHOSBITS "\n",
		program, R2_VERSION_COMMIT, R2_ABIVERSION);
	int csv = R2_CSVERSION;
	s = r_str_appendf (s, "birth: git.%s " R2_BIRTH "\n",
		*R2_GITTAP? R2_GITTAP: "");
	if (*R2_GITTIP) { // Can't use && because of a bug in clang :lol:
		if (strcmp (R2_GITTIP, "unknown")) {
			s = r_str_append (s, "commit: " R2_GITTIP "\n");
		}
	}
	s = r_str_appendf (s, "options:%s%s%s%s%s cs:%d cl:%d %s",
		gplstr, asanstr, newabi, release, optistr, csv, R_CHECKS_LEVEL, R_BUILDSYSTEM);
	return s;
}

R_API int r_str_size(const char *s, int *rows) {
	RRune ch;
	int cols = 0;
	int h = 0;
	const char *e = s + strlen (s);
	int ll = 0;
	while (*s) {
		if (*s == '\n') {
			h++;
			s++;
			if (ll > cols) {
				cols = ll;
			}
			ll = 0;
			continue;
		}
		int chsz = r_utf8_decode ((const ut8 *)s, e - s, &ch);
		if (chsz < 1) {
			chsz = 1;
		}
		s += chsz;
		ll++;
	}
	if (rows) {
		*rows = h;
	}
	return cols;
}

#undef r_str_startswith
R_API bool r_str_startswith(const char *str, const char *needle) {
	R_RETURN_VAL_IF_FAIL (str && needle, false);
	if (str == needle) {
		return true;
	}
	return !strncmp (str, needle, strlen (needle));
}

// add space after commas
R_API char *r_str_fixspaces(char *str) {
	R_RETURN_VAL_IF_FAIL (str, NULL);
	RStrBuf *sb = r_strbuf_new ("");
	int i;
	for (i = 0; str[i]; i++) {
		const char ch = str[i];
		r_strbuf_append_n (sb, &ch, 1);
		if (ch == ',') {
			r_strbuf_append (sb, " ");
			while (str[i + 1] == ' ') {
				i++;
			}
		}
	}
	char *newstr = r_strbuf_drain (sb);
	r_str_trim_tail (newstr);
	free (str);
	return newstr;
}

R_API char *r_str_tok_r(char *str, const char *delim, char **save_ptr) {
	char *ret = NULL;
	if (str == NULL) {
		str = *save_ptr;
	}
	str += strspn (str, delim);
	if (*str == '\0') {
		return NULL;
	}
	ret = str;
	str += strcspn (str, delim);
	if (*str) {
		*str++ = '\0';
	}
	*save_ptr = str;
	return ret;
}

R_API char *r_str_tok_next(char *s) {
	if (s) {
		return s + strlen (s) + 1;
	}
	return NULL;
}

R_API R_MUSTUSE char *r_str_after(char *s, char c) {
	if (s) {
		char *p = strchr (s, c);
		if (p) {
			*p++ = 0;
			return p;
		}
	}
	return NULL;
}

R_API char *r_str_camel_to_snake(const char *camel) {
	R_RETURN_VAL_IF_FAIL (camel, NULL);
	size_t len = strlen (camel);
	char *snake = malloc (len * 2 + 1);
	if (!snake) {
		return NULL;
	}
	size_t i, j = 0;
	snake[j++] = tolower ((int)camel[0] & 0xff);
	for (i = 1; camel[i]; i++) {
		char c = camel[i];
		if (isupper ((int)c & 0xff)) {
			if (!isupper ((int)camel[i - 1] & 0xff)
				|| (camel[i + 1] && islower ((int)camel[i + 1] & 0xff))) {
				snake[j++] = '_';
			}
		}
		snake[j++] = tolower ((int)c & 0xff);
	}
	snake[j] = '\0';
	return snake;
}

R_API char *r_str_snake_to_camel(const char *snake) {
	R_RETURN_VAL_IF_FAIL (snake, NULL);
	size_t len = strlen (snake);
	char *camel = malloc (len + 1);
	if (!camel) {
		return NULL;
	}
	size_t i, j = 0;
	bool cap_next = true;
	for (i = 0; snake[i]; i++) {
		if (snake[i] == '_') {
			cap_next = true;
		} else {
			char c = snake[i];
			if (cap_next && islower ((int)c & 0xff)) {
				c = toupper ((int)c & 0xff);
			}
			camel[j++] = c;
			cap_next = false;
		}
	}
	camel[j] = '\0';
	return camel;
}
