mirror of
https://github.com/radareorg/radare2.git
synced 2024-11-27 15:10:53 +00:00
4184 lines
86 KiB
C
4184 lines
86 KiB
C
/* radare - LGPL - Copyright 2007-2024 - pancake */
|
|
|
|
#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] = "---",
|
|
[9] = "--x",
|
|
[10] = "-w-",
|
|
[11] = "-wx",
|
|
[12] = "r--",
|
|
[13] = "r-x",
|
|
[14] = "rw-",
|
|
[15] = "rwx",
|
|
};
|
|
|
|
// 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_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
|
|
// 580 : 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 || 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 int v) {
|
|
switch (v) {
|
|
case R_SYS_BITS_4: return "4";
|
|
case R_SYS_BITS_8: return "8";
|
|
case R_SYS_BITS_4 | R_SYS_BITS_8: return "4,8";
|
|
case R_SYS_BITS_16: return "16";
|
|
case R_SYS_BITS_27: return "27";
|
|
case R_SYS_BITS_32: return "32";
|
|
case R_SYS_BITS_64: return "64";
|
|
case R_SYS_BITS_16 | R_SYS_BITS_32: return "16,32";
|
|
case R_SYS_BITS_16 | R_SYS_BITS_32 | R_SYS_BITS_64: return "16,32,64";
|
|
case R_SYS_BITS_32 | R_SYS_BITS_64: return "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++) {
|
|
char *ch = strchr (bitz, toupper ((int)(ut8)*buf));
|
|
if (!ch) {
|
|
ch = strchr (bitz, tolower ((int)(ut8)*buf));
|
|
}
|
|
if (ch) {
|
|
int bit = (int)(size_t)(ch - bitz);
|
|
out |= (ut64)(1LL << bit);
|
|
} else {
|
|
return UT64_MAX;
|
|
}
|
|
}
|
|
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, 'm')? 16: 0;
|
|
ret |= strchr (str, 'r')? 4: 0;
|
|
ret |= strchr (str, 'w')? 2: 0;
|
|
ret |= strchr (str, 'x') ? 1 : 0;
|
|
if (*str && ret == 0 && strchr (str, '-')) {
|
|
ret = -1;
|
|
}
|
|
} else if (ret < 0 || ret >= R_ARRAY_SIZE (rwxstr)) {
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Returns the string representation of the permission of the inputted integer.
|
|
R_API const char *r_str_rwx_i(int rwx) {
|
|
if (rwx < 0 || rwx >= R_ARRAY_SIZE (rwxstr)) {
|
|
rwx = 0;
|
|
}
|
|
return rwxstr[rwx % 24]; // 15 for srwx
|
|
}
|
|
|
|
// 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) {
|
|
return r_str_newf ("%s%s%s", r_sys_prefix (NULL), R_SYS_DIR, str);
|
|
}
|
|
|
|
// Compute a modified 64 bit DJB hash of a string
|
|
// The fisrt addition is replaced by a xor for speed
|
|
// See the XOR version in http://www.cse.yorku.ca/~oz/hash.html
|
|
R_API ut64 r_str_hash64(const char *s) {
|
|
ut64 len, h = 5381;
|
|
if (!s) {
|
|
return 0;
|
|
}
|
|
for (len = strlen (s); len > 0; len--) {
|
|
h = (h ^ (h << 5)) ^ *s++;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
// Compute a modified 32bit DJB hash of a string.
|
|
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) {
|
|
char *_a = strchr (p, a);
|
|
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;
|
|
} else {
|
|
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;
|
|
int alen, blen, nlen;
|
|
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;
|
|
}
|
|
alen = (size_t)(p - stra);
|
|
blen = stralen - ((alen + strlen (p)) + 1);
|
|
if (blen < 0) {
|
|
blen = 0;
|
|
}
|
|
nlen = alen + blen + strlen (newstr);
|
|
out = malloc (nlen + 2);
|
|
if (!out) {
|
|
return NULL;
|
|
}
|
|
if (alen > 0) {
|
|
memcpy (out, stra, alen);
|
|
}
|
|
memcpy (out + alen, newstr, strlen (newstr) + 1);
|
|
if (blen > 0) {
|
|
memcpy (out + alen + strlen (newstr) + 1, p + strlen (p) + 1, blen + 1);
|
|
}
|
|
out[nlen + 1] = 0;
|
|
if (newlen) {
|
|
*newlen = nlen + ((blen == 0)? 1 : 0);
|
|
}
|
|
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.
|
|
// TODO: make this const-correct.
|
|
// XXX if the string is only made up of chr, then the pointer will just point to
|
|
// a null byte!
|
|
R_API char *r_str_ichr(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;
|
|
}
|
|
|
|
#if R2_600
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
/* 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;
|
|
while (p >= base) {
|
|
for (i = 0; sep[i]; i++) {
|
|
if (*p == sep[i]) {
|
|
return p;
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
for (; p >= base; p--) {
|
|
if (ch == *p) {
|
|
break;
|
|
}
|
|
}
|
|
return (p >= base) ? p: 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) {
|
|
va_list ap2;
|
|
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);
|
|
}
|
|
va_end (ap2);
|
|
return p;
|
|
}
|
|
|
|
R_API char *r_str_newf(const char *fmt, ...) {
|
|
va_list ap, ap2;
|
|
|
|
va_start (ap, fmt);
|
|
if (!strchr (fmt, '%')) {
|
|
char *p = strdup (fmt);
|
|
va_end (ap);
|
|
return p;
|
|
}
|
|
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);
|
|
}
|
|
va_end (ap2);
|
|
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;
|
|
}
|
|
int i = 0, j = 0;
|
|
for (; list[i] && list[i] != sep; i++, j++) {
|
|
if (item[j] != list[i]) {
|
|
while (list[i] && list[i] != sep) {
|
|
i++;
|
|
}
|
|
if (!list[i]) {
|
|
return false;
|
|
}
|
|
j = -1;
|
|
continue;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
size_t plen = r_str_nlen (ptr, len);
|
|
size_t olen = R_MIN (len, plen);
|
|
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) {
|
|
int slen, plen;
|
|
if (!ptr) {
|
|
return strdup (string);
|
|
}
|
|
plen = strlen (ptr);
|
|
slen = strlen (string);
|
|
ptr = realloc (ptr, slen + plen + 1);
|
|
if (!ptr) {
|
|
return NULL;
|
|
}
|
|
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;
|
|
}
|
|
int plen = strlen (ptr);
|
|
int 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);
|
|
|
|
int off, i, slen;
|
|
char *newstr, *p = str;
|
|
int klen = strlen (key);
|
|
int 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;
|
|
}
|
|
slen = strlen (str);
|
|
char *q = str;
|
|
for (;;) {
|
|
p = strstr (q, key);
|
|
if (!p) {
|
|
break;
|
|
}
|
|
off = (int)(size_t)(p - str);
|
|
if (vlen != klen) {
|
|
int tlen = slen - (off + klen);
|
|
slen += vlen - klen;
|
|
if (vlen > klen) {
|
|
newstr = realloc (str, slen + 1);
|
|
if (!newstr) {
|
|
R_FREE (str);
|
|
break;
|
|
}
|
|
str = newstr;
|
|
}
|
|
p = str + off;
|
|
memmove (p + vlen, p + klen, tlen + 1);
|
|
}
|
|
memcpy (p, val, vlen);
|
|
i = off + vlen;
|
|
q = str + i;
|
|
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);
|
|
size_t slen = strlen (str);
|
|
for (i = 0; i < slen;) {
|
|
p = (char *)r_str_casestr (str + i, key);
|
|
if (!p) {
|
|
break;
|
|
}
|
|
off = (size_t) (p - str);
|
|
if (vlen != klen) {
|
|
int tlen = slen - (off + klen);
|
|
slen += vlen - klen;
|
|
if (vlen > klen) {
|
|
newstr = realloc (str, slen + 1);
|
|
if (!newstr) {
|
|
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);
|
|
if (!tmp_val || !str_case) {
|
|
free (tmp_val);
|
|
free (str_case);
|
|
return NULL;
|
|
}
|
|
tmp_val = r_str_replace_icase (tmp_val, key, str_case, 0, 0);
|
|
free (str_case);
|
|
if (!tmp_val) {
|
|
return NULL;
|
|
}
|
|
memcpy (p, tmp_val, vlen);
|
|
free (tmp_val);
|
|
} else {
|
|
memcpy (p, val, vlen);
|
|
}
|
|
|
|
i = off + vlen;
|
|
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) {
|
|
if (sz < 0) {
|
|
sz = strlen (key);
|
|
}
|
|
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) {
|
|
char *d = c;
|
|
if (d) {
|
|
for (; *d; c++, d++) {
|
|
switch (*d) {
|
|
case '`':
|
|
case '$':
|
|
case '{':
|
|
case '}':
|
|
case '~':
|
|
case '|':
|
|
case ';':
|
|
case '#':
|
|
case '@':
|
|
case '&':
|
|
case '<':
|
|
case '>':
|
|
*c = '_';
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
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);
|
|
|
|
/* Worst case scenario, we convert every byte to a \xhh escape */
|
|
char *new_buf = malloc (1 + 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 = malloc (1 + strlen (buf) * 2);
|
|
if (!new_buf) {
|
|
return NULL;
|
|
}
|
|
const char *p = buf;
|
|
char *q = new_buf;
|
|
while (*p) {
|
|
switch (*p) {
|
|
case ';':
|
|
case '$':
|
|
case '`':
|
|
case '\\':
|
|
case '"':
|
|
*q++ = ' ';
|
|
p++;
|
|
break;
|
|
default:
|
|
*q++ = *p++;
|
|
break;
|
|
}
|
|
}
|
|
*q = '\0';
|
|
return new_buf;
|
|
}
|
|
|
|
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 '`':
|
|
#endif
|
|
case '\\':
|
|
case '"':
|
|
*q++ = '\\';
|
|
/* FALLTHRU */
|
|
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 + (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) {
|
|
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);
|
|
}
|
|
|
|
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 (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;
|
|
|
|
if (!SZT_MUL_OVFCHK (((buf_sz * increment) + 1), SZT_MAX)) {
|
|
return NULL;
|
|
}
|
|
size_t new_sz = (buf_sz * increment) + 1;
|
|
|
|
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 = r_str_escape_utf8_for_json_strip (buf, buf_sz);
|
|
} else {
|
|
encoded_str = r_str_escape_utf8_for_json (buf, buf_sz);
|
|
}
|
|
return encoded_str;
|
|
}
|
|
|
|
R_API char *r_str_escape_utf8_for_json_strip(const char *buf, int buf_size) {
|
|
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 + (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 (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 = 1;
|
|
}
|
|
p += ch_bytes;
|
|
}
|
|
*q = '\0';
|
|
return new_buf;
|
|
}
|
|
|
|
|
|
R_API char *r_str_escape_utf8_for_json(const char *buf, int buf_size) {
|
|
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 + (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++ = '\\';
|
|
*q++ = 'u';
|
|
*q++ = '0';
|
|
*q++ = '0';
|
|
*q++ = "0123456789abcdef"[*p >> 4 & 0xf];
|
|
*q++ = "0123456789abcdef"[*p & 0xf];
|
|
} else {
|
|
*q++ = *p;
|
|
}
|
|
}
|
|
} 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
|
|
// Outside JSON spec, but apparently no better
|
|
// alternative if need to reconstruct the original string
|
|
#if 1
|
|
*q++ = '\\';
|
|
*q++ = 'u';
|
|
*q++ = '0';
|
|
*q++ = '0';
|
|
*q++ = "0123456789abcdef"[*p >> 4 & 0xf];
|
|
*q++ = "0123456789abcdef"[*p & 0xf];
|
|
#else
|
|
*q++ = '\\';
|
|
*q++ = 'x';
|
|
*q++ = "0123456789abcdef"[*p >> 4 & 0xf];
|
|
*q++ = "0123456789abcdef"[*p & 0xf];
|
|
#endif
|
|
ch_bytes = 1;
|
|
}
|
|
p += ch_bytes;
|
|
}
|
|
*q = '\0';
|
|
return new_buf;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
static size_t __str_ansi_length(char const *str) {
|
|
size_t i = 1;
|
|
if (str[0] == 0x1b) {
|
|
if (str[1] == '[') {
|
|
i++;
|
|
while (str[i] && str[i] != 'J' && str[i] != 'm' && str[i] != 'H' && str[i] != 'K') {
|
|
i++;
|
|
}
|
|
} else if (str[1] == '#') {
|
|
while (str[i] && str[i] != 'q') {
|
|
i++;
|
|
}
|
|
}
|
|
if (str[i]) {
|
|
i++;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/* ansi helpers */
|
|
R_API size_t r_str_ansi_nlen(const char *str, size_t slen) {
|
|
size_t i = 0, len = 0;
|
|
if (slen > 0) {
|
|
while (str[i] && i < slen) {
|
|
size_t chlen = __str_ansi_length (str + i);
|
|
if (chlen == 1) {
|
|
len ++;
|
|
}
|
|
i += chlen;
|
|
}
|
|
return len > 0 ? len: 1;
|
|
}
|
|
while (str[i]) {
|
|
size_t chlen = __str_ansi_length (str + i);
|
|
if (chlen == 1) {
|
|
len ++;
|
|
}
|
|
i += chlen;
|
|
}
|
|
return len; // len > 0 ? len: 1;
|
|
}
|
|
|
|
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, decolorizing it
|
|
// TODO : optimize by just using two counter variables instead of strcpy()
|
|
R_API size_t r_str_ansi_strip(char *str) {
|
|
size_t i = 0;
|
|
while (str[i]) {
|
|
size_t chlen = __str_ansi_length (str + i);
|
|
size_t sanitize_len = __str_ansi_sanitize_length (str + i);
|
|
if (chlen > 1) {
|
|
r_str_cpy (str + i, str + i + chlen);
|
|
} else if (sanitize_len > 0) {
|
|
r_str_cpy (str + i, str + i + sanitize_len);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
#if 0
|
|
// unused
|
|
// insert a string into another string, supports ansi control chars
|
|
R_API char *r_str_insert(R_OWN 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 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;
|
|
}
|
|
|
|
//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) {
|
|
#if 0
|
|
size_t pos = r_str_ansi_nlen (str, at);
|
|
return str + pos;
|
|
#endif
|
|
int len, i, li;
|
|
for (li = i = len = 0; str[i] && (n != len); i++) {
|
|
size_t chlen = __str_ansi_length (str + i);
|
|
if (chlen > 1) {
|
|
i += chlen - 1;
|
|
} 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) {
|
|
char *res = strdup (str);
|
|
char * resetbg = r_str_newf ("\x1b[0m%s", bgcolor);
|
|
res = r_str_replace_all (res, "\x1b[0m", "(ºvº)");
|
|
res = r_str_replace_all (res, "(ºvº)", resetbg);
|
|
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 **out, int **cposs, int len) {
|
|
int i, j, *cps;
|
|
|
|
if (len == 0) {
|
|
return 0;
|
|
}
|
|
if (len < 0) {
|
|
len = strlen (str);
|
|
}
|
|
char *tmp = malloc (len + 1);
|
|
if (!tmp) {
|
|
return -1;
|
|
}
|
|
memcpy (tmp, str, len + 1);
|
|
cps = calloc (len + 1, sizeof (int));
|
|
if (!cps) {
|
|
free (tmp);
|
|
return -1;
|
|
}
|
|
|
|
for (i = j = 0; i < len; i++) {
|
|
if (tmp[i] == 0x1b) {
|
|
size_t chlen = __str_ansi_length (str + i);
|
|
if (chlen > 1) {
|
|
i += chlen;
|
|
i--;
|
|
}
|
|
} else {
|
|
str[j] = tmp[i];
|
|
cps[j] = i;
|
|
j++;
|
|
}
|
|
}
|
|
str[j] = tmp[i];
|
|
|
|
if (out) {
|
|
*out = tmp;
|
|
} else {
|
|
free (tmp);
|
|
}
|
|
|
|
if (cposs) {
|
|
*cposs = cps;
|
|
} else {
|
|
free (cps);
|
|
}
|
|
|
|
return j;
|
|
}
|
|
|
|
R_API char *r_str_ansi_crop(const char *str, ut32 x, ut32 y, ut32 x2, ut32 y2) {
|
|
char *r, *r_end, *ret;
|
|
const char *s, *s_start;
|
|
size_t r_len, str_len = 0, nr_of_lines = 0;
|
|
ut32 ch = 0, cw = 0;
|
|
if (x2 <= x || y2 <= y || !str) {
|
|
return strdup ("");
|
|
}
|
|
s = s_start = str;
|
|
while (*s) {
|
|
str_len++;
|
|
if (*s == '\n') {
|
|
nr_of_lines++;
|
|
}
|
|
s++;
|
|
}
|
|
r_len = str_len + nr_of_lines * strlen (Color_RESET) + 1;
|
|
r = ret = malloc (r_len);
|
|
if (!r) {
|
|
return NULL;
|
|
}
|
|
r_end = r + r_len;
|
|
while (*str) {
|
|
/* crop height */
|
|
if (ch >= y2) {
|
|
r--;
|
|
break;
|
|
}
|
|
if (*str == '\n') {
|
|
if (ch >= y && ch < y2) {
|
|
const char *reset = Color_RESET "\n";
|
|
if (strlen (reset) < (r_end - r)) {
|
|
const int reset_length = strlen (reset);
|
|
memcpy (r, reset, reset_length + 1);
|
|
r += reset_length;
|
|
}
|
|
}
|
|
str++;
|
|
ch++;
|
|
cw = 0;
|
|
} else {
|
|
if (ch >= y && ch < y2) {
|
|
if ((*str & 0xc0) == 0x80) {
|
|
if (cw > x) {
|
|
*r++ = *str++;
|
|
} else {
|
|
str++;
|
|
}
|
|
continue;
|
|
}
|
|
if (r_str_char_fullwidth (str, str_len - (str - s_start))) {
|
|
cw++;
|
|
if (cw == x) {
|
|
*r++ = ' ';
|
|
str++;
|
|
continue;
|
|
}
|
|
}
|
|
if (*str == 0x1b && *(str + 1) == '[') {
|
|
const char *ptr = str;
|
|
if ((r_end - r) > 2) {
|
|
/* copy 0x1b and [ */
|
|
*r++ = *str++;
|
|
*r++ = *str++;
|
|
for (ptr = str; *ptr && *ptr != 'J' && *ptr != 'm' && *ptr != 'H'; ptr++) {
|
|
*r++ = *ptr;
|
|
}
|
|
*r++ = *ptr++;
|
|
}
|
|
str = ptr;
|
|
continue;
|
|
} else if (cw >= x && cw < x2) {
|
|
*r++ = *str;
|
|
}
|
|
}
|
|
/* skip until newline */
|
|
if (cw >= x2) {
|
|
while (*str && *str != '\n') {
|
|
str++;
|
|
}
|
|
} else {
|
|
str++;
|
|
}
|
|
cw++;
|
|
}
|
|
}
|
|
*r = 0;
|
|
return ret;
|
|
}
|
|
|
|
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;
|
|
size_t length = strlen (str);
|
|
while (size < length && size < 5) {
|
|
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) {
|
|
int i;
|
|
for (i = 0; i < len && str[i]; i++) {
|
|
if (str[i] == '\n' || str[i] == '\r') {
|
|
break;
|
|
}
|
|
if (!IS_PRINTABLE (str[i])) {
|
|
break;
|
|
}
|
|
}
|
|
str[i] = 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_glob(const char* str, const char *glob) {
|
|
if (!glob) {
|
|
return true;
|
|
}
|
|
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 = malloc (argv_len * sizeof (char *));
|
|
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 = realloc (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 = realloc (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) {
|
|
int idx, _b = 0;
|
|
ut8 *b = (ut8*)&_b;
|
|
const char *isbut, *p;
|
|
const int bsz = sizeof (_b) * 8;
|
|
if (!but) {
|
|
return strchr (s, ch);
|
|
}
|
|
if (strlen (but) >= bsz) {
|
|
R_LOG_ERROR ("but string too long");
|
|
return NULL;
|
|
}
|
|
for (p = s; *p; p++) {
|
|
isbut = strchr (but, *p);
|
|
if (isbut) {
|
|
idx = (int)(size_t)(isbut - but);
|
|
_b = R_BIT_TOGGLE (b, idx);
|
|
continue;
|
|
}
|
|
if (*p == ch && !_b) {
|
|
return p;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
R_API const char *r_str_firstbut_escape(const char *s, char ch, const char *but) {
|
|
int idx, _b = 0;
|
|
ut8 *b = (ut8*)&_b;
|
|
const char *isbut, *p;
|
|
const int bsz = sizeof (_b) * 8;
|
|
if (!but) {
|
|
return strchr (s, ch);
|
|
}
|
|
if (strlen (but) >= bsz) {
|
|
R_LOG_ERROR ("r_str_firstbut: but string too long");
|
|
return NULL;
|
|
}
|
|
for (p = s; *p; p++) {
|
|
if (*p == '\\') {
|
|
p++;
|
|
if (*p == ch || strchr(but, *p)) {
|
|
if (!*p) {
|
|
break;
|
|
}
|
|
continue;
|
|
} else if (!*p) {
|
|
break;
|
|
}
|
|
}
|
|
isbut = strchr (but, *p);
|
|
if (isbut) {
|
|
idx = (int)(size_t)(isbut - but);
|
|
_b = R_BIT_TOGGLE (b, idx);
|
|
if (_b && (_b & (_b - 1))) {
|
|
_b = R_BIT_TOGGLE (b, idx); // cancel a but char if a but is already toggle
|
|
}
|
|
continue;
|
|
}
|
|
if (*p == ch && !_b) {
|
|
return p;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
R_API const char *r_str_lastbut(const char *s, char ch, const char *but) {
|
|
int idx, _b = 0;
|
|
ut8 *b = (ut8*)&_b;
|
|
const char *isbut, *p, *lp = NULL;
|
|
const int bsz = sizeof (_b) * 8;
|
|
if (!but) {
|
|
return r_str_lchr (s, ch);
|
|
}
|
|
if (strlen (but) >= bsz) {
|
|
R_LOG_ERROR ("r_str_lastbut: but string too long");
|
|
return NULL;
|
|
}
|
|
for (p = s; *p; p++) {
|
|
isbut = strchr (but, *p);
|
|
if (isbut) {
|
|
idx = (int)(size_t)(isbut - but);
|
|
_b = R_BIT_TOGGLE (b, idx);
|
|
continue;
|
|
}
|
|
if (*p == ch && !_b) {
|
|
lp = p;
|
|
}
|
|
}
|
|
return lp;
|
|
}
|
|
|
|
// 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) {
|
|
size_t i = 0, j = 0, fullwidths = 0;
|
|
while (s[i]) {
|
|
if ((s[i] & 0xc0) != 0x80) {
|
|
j++;
|
|
if (r_str_char_fullwidth (s + i, 4)) {
|
|
fullwidths++;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
return j + fullwidths;
|
|
}
|
|
|
|
R_API size_t r_str_len_utf8_ansi(const char *str) {
|
|
int i = 0, len = 0, fullwidths = 0;
|
|
int str_len = strlen (str);
|
|
while (str[i]) {
|
|
char ch = str[i];
|
|
size_t chlen = __str_ansi_length (str + i);
|
|
if (chlen > 1) {
|
|
i += chlen - 1;
|
|
} else if ((ch & 0xc0) != 0x80) { // utf8
|
|
len++;
|
|
if (str_len - i >= 4) {
|
|
if (r_str_char_fullwidth (str + i, 4)) {
|
|
fullwidths++;
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
return len + fullwidths;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
} 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 {
|
|
fprintf (stderr, "Invalid range\n");
|
|
}
|
|
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 {
|
|
fprintf (stderr, "Invalid range\n");
|
|
}
|
|
for (r++; *r && *r != ',' && *r != '-'; r++) {
|
|
;
|
|
}
|
|
p = r;
|
|
}
|
|
}
|
|
if (*p) {
|
|
if (addr == r_num_get (NULL, p)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// convert from html escaped sequence "foo%20bar" to "foo bar"
|
|
// TODO: find better name.. unencode? decode
|
|
R_API void r_str_uri_decode(char *s) {
|
|
int n;
|
|
char *d;
|
|
for (d = s; *s; s++, d++) {
|
|
if (*s == '%') {
|
|
sscanf (s + 1, "%02x", &n);
|
|
*d = n;
|
|
s += 2;
|
|
} else {
|
|
*d = *s;
|
|
}
|
|
}
|
|
*d = 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// XXX antipattern, bigendian should be 1 not 0
|
|
R_API int r_str_utf16_to_utf8(ut8 *dst, int len_dst, const ut8 *src, int len_src, int little_endian) {
|
|
ut8 *outstart = dst;
|
|
ut8 *outend = dst + len_dst;
|
|
ut16 *in = (ut16*)src;
|
|
ut16 *inend;
|
|
ut32 c, d, inlen;
|
|
ut8 *tmp;
|
|
int bits;
|
|
|
|
if ((len_src % 2) == 1) {
|
|
len_src--;
|
|
}
|
|
inlen = len_src / 2;
|
|
inend = in + inlen;
|
|
while ((in < inend) && (dst - outstart + 5 < len_dst)) {
|
|
if (little_endian) {
|
|
c = *in++;
|
|
} else {
|
|
tmp = (ut8*) in;
|
|
c = *tmp++;
|
|
if (!c && !*tmp) {
|
|
break;
|
|
}
|
|
c = c | (((ut32)*tmp) << 8);
|
|
in++;
|
|
}
|
|
if ((c & 0xFC00) == 0xD800) { /* surrogates */
|
|
if (in >= inend) { /* (in > inend) shouldn't happens */
|
|
break;
|
|
}
|
|
if (little_endian) {
|
|
d = *in++;
|
|
} else {
|
|
tmp = (ut8*) in;
|
|
d = *tmp++;
|
|
d = d | (((ut32)*tmp) << 8);
|
|
in++;
|
|
}
|
|
if ((d & 0xFC00) == 0xDC00) {
|
|
c &= 0x03FF;
|
|
c <<= 10;
|
|
c |= d & 0x03FF;
|
|
c += 0x10000;
|
|
} else {
|
|
return -2;
|
|
}
|
|
}
|
|
|
|
/* assertion: c is a single UTF-4 value */
|
|
if (dst >= outend) {
|
|
break;
|
|
}
|
|
if (c < 0x80) {
|
|
*dst++ = c; bits= -6;
|
|
} else if (c < 0x800) {
|
|
*dst++ = ((c >> 6) & 0x1F) | 0xC0;
|
|
bits = 0;
|
|
} else if (c < 0x10000) {
|
|
*dst++ = ((c >> 12) & 0x0F) | 0xE0;
|
|
bits = 6;
|
|
} else {
|
|
*dst++ = ((c >> 18) & 0x07) | 0xF0;
|
|
bits = 12;
|
|
}
|
|
|
|
for (; bits >= 0; bits -= 6) {
|
|
if (dst >= outend) {
|
|
break;
|
|
}
|
|
*dst++ = ((c >> bits) & 0x3F) | 0x80;
|
|
}
|
|
}
|
|
len_dst = dst - outstart;
|
|
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);
|
|
}
|
|
if ((len * 7) + 1 < len) {
|
|
return NULL;
|
|
}
|
|
od = d = malloc (1 + (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)
|
|
#define CAST (void*)(size_t)
|
|
R_API ut8 r_str_contains_macro(const char *input_value) {
|
|
char *has_tilde = input_value ? HASCH('~') : NULL,
|
|
*has_bang = input_value ? HASCH('!') : NULL,
|
|
*has_brace = input_value ? CAST(HASCH('[') || HASCH(']')) : NULL,
|
|
*has_paren = input_value ? CAST(HASCH('(') || HASCH(')')) : NULL,
|
|
*has_cbrace = input_value ? CAST(HASCH('{') || HASCH('}')) : 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) {
|
|
char *r, *ret;
|
|
unsigned int ch = 0, cw = 0;
|
|
if (x2 < 1 || y2 < 1 || !str) {
|
|
return strdup ("");
|
|
}
|
|
r = ret = strdup (str);
|
|
while (*str) {
|
|
/* crop height */
|
|
if (ch >= y2) {
|
|
r--;
|
|
break;
|
|
}
|
|
|
|
if (*str == '\n') {
|
|
if (ch >= y && ch < y2) {
|
|
*r++ = *str;
|
|
}
|
|
str++;
|
|
ch++;
|
|
cw = 0;
|
|
} else {
|
|
if (ch >= y && ch < y2 && cw >= x && cw < x2) {
|
|
*r++ = *str;
|
|
}
|
|
/* crop width */
|
|
/* skip until newline */
|
|
if (cw >= x2) {
|
|
while (*str && *str != '\n') {
|
|
str++;
|
|
}
|
|
} else {
|
|
str++;
|
|
}
|
|
cw++;
|
|
}
|
|
}
|
|
*r = 0;
|
|
return ret;
|
|
}
|
|
|
|
// TODO: improve loop to wrap by words
|
|
R_API char *r_str_wrap(const char *str, int w) {
|
|
if (w < 1 || !str) {
|
|
return strdup ("");
|
|
}
|
|
size_t r_size = 8 * strlen (str);
|
|
char *r = malloc (r_size);
|
|
if (!r) {
|
|
return NULL;
|
|
}
|
|
char *ret = r;
|
|
char *end = r + r_size;
|
|
int cw = 0;
|
|
while (*str && r + 1 < end) {
|
|
size_t ansilen = __str_ansi_length (str);
|
|
if (ansilen > 1) {
|
|
memcpy (r, str, ansilen);
|
|
str += ansilen;
|
|
r += ansilen;
|
|
continue;
|
|
}
|
|
if (*str == '\t') {
|
|
// skip
|
|
} else if (*str == '\r') {
|
|
// skip
|
|
} else if (*str == '\n') {
|
|
*r++ = *str++;
|
|
cw = 0;
|
|
} else {
|
|
if (cw > w) {
|
|
*r++ = '\n';
|
|
*r++ = *str++;
|
|
cw = 1;
|
|
} else {
|
|
*r++ = *str++;
|
|
cw++;
|
|
}
|
|
}
|
|
}
|
|
*r = 0;
|
|
return ret;
|
|
}
|
|
|
|
R_API int r_str_do_until_token(str_operation op, char *str, const char tok) {
|
|
int ret;
|
|
if (!str) {
|
|
return -1;
|
|
}
|
|
if (!op) {
|
|
for (ret = 0; (str[ret] != tok) && str[ret]; ret++) {
|
|
//empty body
|
|
}
|
|
} else {
|
|
for (ret = 0; (str[ret] != tok) && str[ret]; ret++) {
|
|
op (str + ret);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
R_API const char *r_str_pad(const char ch, int sz) {
|
|
static R_TH_LOCAL char pad[1024];
|
|
if (sz < 0) {
|
|
sz = 0;
|
|
}
|
|
memset (pad, ch, R_MIN (sz, sizeof (pad)));
|
|
if (sz < sizeof (pad)) {
|
|
pad[sz] = 0;
|
|
}
|
|
pad[sizeof (pad) - 1] = 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) {
|
|
char *c0, *c1;
|
|
if (!cmt || !prefix || !suffix || !*cmt) {
|
|
return NULL;
|
|
}
|
|
c0 = strstr (cmt, prefix);
|
|
if (c0) {
|
|
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;
|
|
}
|
|
int slen = strlen (str);
|
|
int nlen = strlen (needle);
|
|
if (!slen || !nlen || slen < nlen) {
|
|
return false;
|
|
}
|
|
return !strcmp (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;
|
|
}
|
|
|
|
// Splits the string <str> by string <c> and returns the result in a list.
|
|
// R2_600 - char *arg must be const!!
|
|
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 = strstr (aux, c);
|
|
#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 = strstr (aux, c);
|
|
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) {
|
|
int i;
|
|
size_t lines = 0;
|
|
if (!str) {
|
|
return NULL;
|
|
}
|
|
size_t *indexes = NULL;
|
|
// count lines
|
|
for (i = 0; str[i]; i++) {
|
|
if (str[i] == '\n') {
|
|
lines++;
|
|
}
|
|
}
|
|
// allocate and set indexes
|
|
indexes = calloc (sizeof (count[0]), lines + 1);
|
|
if (!indexes) {
|
|
return NULL;
|
|
}
|
|
size_t line = 0;
|
|
indexes[line++] = 0;
|
|
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;
|
|
}
|
|
|
|
/* TODO: optimize to start searching by the end of the string */
|
|
R_API const char *r_str_last(const char *str, const char *ch) {
|
|
char *ptr, *end = NULL;
|
|
if (!str || !ch) {
|
|
return NULL;
|
|
}
|
|
do {
|
|
ptr = strstr (str, ch);
|
|
if (!ptr) {
|
|
break;
|
|
}
|
|
end = ptr;
|
|
str = ptr + 1;
|
|
} while (true);
|
|
return end;
|
|
}
|
|
|
|
// 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_skip_color_codes(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_skip_color_codes(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) {
|
|
if (!str || !*str) {
|
|
return NULL;
|
|
}
|
|
ut32 i = 0, j = 0, to_copy;
|
|
char *start = str;
|
|
ut32 l_str = strlen (str);
|
|
ut32 l_reset = strlen (color_reset);
|
|
ut32 l_color = color? strlen (color): 0;
|
|
if (!color) {
|
|
return strdup (str);
|
|
}
|
|
if (!word || !*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_skip_color_codes (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_skip_color_codes (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) {
|
|
wchar_t *res_buf = NULL;
|
|
size_t sz;
|
|
bool fail = true;
|
|
|
|
if (!buf || len <= 0) {
|
|
return NULL;
|
|
}
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
R_API int r_snprintf(char *string, int len, const char *fmt, ...) {
|
|
va_list ap;
|
|
va_start (ap, fmt);
|
|
int ret = 0;
|
|
if (len > 0) {
|
|
ret = vsnprintf (string, len, fmt, ap);
|
|
string[len - 1] = 0;
|
|
} else {
|
|
*string = 0;
|
|
}
|
|
va_end (ap);
|
|
return ret;
|
|
}
|
|
|
|
// Strips all the lines in str that contain key
|
|
R_API void r_str_stripLine(char *str, const char *key) {
|
|
if (!str || !key) {
|
|
return;
|
|
}
|
|
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 (r_strbuf_length (sb) != 0) {
|
|
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);
|
|
}
|
|
|
|
/* return the number of arguments expected as extra arguments */
|
|
R_API int r_str_fmtargs(const char *fmt) {
|
|
int n = 0;
|
|
while (*fmt) {
|
|
if (*fmt == '%') {
|
|
if (fmt[1] == '*') {
|
|
n++;
|
|
}
|
|
n++;
|
|
}
|
|
fmt++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// 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, int w, int h) {
|
|
// count lines and rows in (s) string
|
|
// compute how many lines we should remove or combine
|
|
// return a string containing
|
|
// for now this function is ascii only (no utf8 or ansi escapes)
|
|
RListIter *iter;
|
|
char *line;
|
|
char *str = strdup (s);
|
|
RList *lines = r_str_split_list (str, "\n", 0);
|
|
int i, j;
|
|
int rows = 0;
|
|
int maxcol = 0;
|
|
|
|
rows = r_list_length (lines);
|
|
r_list_foreach (lines, iter, line) {
|
|
maxcol = R_MAX (strlen (line), maxcol);
|
|
}
|
|
|
|
RList *out = r_list_newf (free);
|
|
|
|
int curline = -1;
|
|
char *linetext = (char*)r_str_pad (' ', w);
|
|
for (i = 0; i < h; i++) {
|
|
int zoomedline = (int)(i * ((double)rows / h));
|
|
const char *srcline = r_list_get_n (lines, zoomedline);
|
|
int cols = strlen (srcline);
|
|
for (j = 0; j < w; j++) {
|
|
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 (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) {
|
|
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>
|
|
|
|
#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
|
|
#if R2_USE_NEW_ABI
|
|
newabi = " newabi";
|
|
#endif
|
|
#ifdef _FORTIFY_SOURCE
|
|
// clang
|
|
optistr[3] = '0' + _FORTIFY_SOURCE;
|
|
#endif
|
|
#ifdef __OPTIMIZE__
|
|
// gcc
|
|
optistr[3] = '0' + __OPTIMIZE__;
|
|
#endif
|
|
#if WITH_GPL
|
|
gplstr = " gpl";
|
|
#endif
|
|
char *s = r_str_newf ("%s "R2_VERSION" %d @ "
|
|
R_SYS_OS"-"
|
|
R_SYS_ARCH"-%d\n",
|
|
program, R2_VERSION_COMMIT,
|
|
(R_SYS_BITS & 8)? 64: 32);
|
|
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));
|
|
}
|
|
|
|
R_API void r_str_fixspaces(char *str) {
|
|
// add space after commas
|
|
char *os = strdup (str);
|
|
int i, j;
|
|
for (i = j = 0; os[i]; i++,j++) {
|
|
char ch = os[i];
|
|
str[j] = ch;
|
|
if (ch == ',') {
|
|
j++;
|
|
str[j] = ' ';
|
|
while (os[i + 1] == ' ') {
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
str[j] = 0;
|
|
free (os);
|
|
r_str_trim_tail (str);
|
|
}
|
|
|
|
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;
|
|
}
|