1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include "str.h"
5
6#include "dbg.h"
7#include "detect.h"
8#include "mem.h"
9
10#include <algorithm>
11#include <cctype>
12#include <charconv> // std::to_chars
13#include <cstdarg>
14#include <cstdio>
15#include <cstdlib>
16#include <cstring>
17
18int str_copy(char *dst, const char *src, int dst_size)
19{
20 dst[0] = '\0';
21 strncat(dest: dst, src: src, n: dst_size - 1);
22 return str_utf8_fix_truncation(str: dst);
23}
24
25void str_append(char *dst, const char *src, int dst_size)
26{
27 int s = str_length(str: dst);
28 int i = 0;
29 while(s < dst_size)
30 {
31 dst[s] = src[i];
32 if(!src[i]) /* check for null termination */
33 break;
34 s++;
35 i++;
36 }
37
38 dst[dst_size - 1] = 0; /* assure null termination */
39 str_utf8_fix_truncation(str: dst);
40}
41
42void str_truncate(char *dst, int dst_size, const char *src, int truncation_len)
43{
44 int size = dst_size;
45 if(truncation_len < size)
46 {
47 size = truncation_len + 1;
48 }
49 str_copy(dst, src, dst_size: size);
50}
51
52int str_length(const char *str)
53{
54 return (int)strlen(s: str);
55}
56
57int str_format_v(char *buffer, int buffer_size, const char *format, va_list args)
58{
59#if defined(CONF_FAMILY_WINDOWS)
60 _vsprintf_p(buffer, buffer_size, format, args);
61 buffer[buffer_size - 1] = 0; /* assure null termination */
62#else
63 vsnprintf(s: buffer, maxlen: buffer_size, format: format, arg: args);
64 /* null termination is assured by definition of vsnprintf */
65#endif
66 return str_utf8_fix_truncation(str: buffer);
67}
68
69#if !defined(CONF_DEBUG)
70int str_format_int(char *buffer, size_t buffer_size, int value)
71{
72 buffer[0] = '\0'; // Fix false positive clang-analyzer-core.UndefinedBinaryOperatorResult when using result
73 auto result = std::to_chars(buffer, buffer + buffer_size - 1, value);
74 result.ptr[0] = '\0';
75 return result.ptr - buffer;
76}
77#endif
78
79#undef str_format
80int str_format(char *buffer, int buffer_size, const char *format, ...)
81{
82 va_list args;
83 va_start(args, format);
84 int length = str_format_v(buffer, buffer_size, format, args);
85 va_end(args);
86 return length;
87}
88#if !defined(CONF_DEBUG)
89#define str_format str_format_opt
90#endif
91
92char str_uppercase(char c)
93{
94 if(c >= 'a' && c <= 'z')
95 return 'A' + (c - 'a');
96 return c;
97}
98
99bool str_isnum(char c)
100{
101 return c >= '0' && c <= '9';
102}
103
104int str_isallnum(const char *str)
105{
106 while(*str)
107 {
108 if(!str_isnum(c: *str))
109 return 0;
110 str++;
111 }
112 return 1;
113}
114
115int str_isallnum_hex(const char *str)
116{
117 while(*str)
118 {
119 if(!str_isnum(c: *str) && !(*str >= 'a' && *str <= 'f') && !(*str >= 'A' && *str <= 'F'))
120 return 0;
121 str++;
122 }
123 return 1;
124}
125
126int str_isspace(char c)
127{
128 return c == ' ' || c == '\n' || c == '\r' || c == '\t';
129}
130
131const char *str_trim_words(const char *str, int words)
132{
133 while(*str && str_isspace(c: *str))
134 str++;
135 while(words && *str)
136 {
137 if(str_isspace(c: *str) && !str_isspace(c: *(str + 1)))
138 words--;
139 str++;
140 }
141 return str;
142}
143
144bool str_has_cc(const char *str)
145{
146 unsigned char *s = (unsigned char *)str;
147 while(*s)
148 {
149 if(*s < 32)
150 {
151 return true;
152 }
153 s++;
154 }
155 return false;
156}
157
158/* makes sure that the string only contains the characters between 32 and 255 */
159void str_sanitize_cc(char *str_in)
160{
161 unsigned char *str = (unsigned char *)str_in;
162 while(*str)
163 {
164 if(*str < 32)
165 *str = ' ';
166 str++;
167 }
168}
169
170/* makes sure that the string only contains the characters between 32 and 255 + \r\n\t */
171void str_sanitize(char *str_in)
172{
173 unsigned char *str = (unsigned char *)str_in;
174 while(*str)
175 {
176 if(*str < 32 && !(*str == '\r') && !(*str == '\n') && !(*str == '\t'))
177 *str = ' ';
178 str++;
179 }
180}
181
182void str_sanitize_filename(char *str_in)
183{
184 unsigned char *str = (unsigned char *)str_in;
185 while(*str)
186 {
187 if(*str <= 0x1F || *str == 0x7F || *str == '\\' || *str == '/' || *str == '|' || *str == ':' ||
188 *str == '*' || *str == '?' || *str == '<' || *str == '>' || *str == '"')
189 {
190 *str = ' ';
191 }
192 str++;
193 }
194}
195
196bool str_valid_filename(const char *str)
197{
198 // References:
199 // - https://en.wikipedia.org/w/index.php?title=Filename&oldid=1281340521#Comparison_of_filename_limitations
200 // - https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file (last update 2024-08-28)
201 if(str[0] == '\0')
202 {
203 return false; // empty name not allowed
204 }
205
206 bool prev_space = false;
207 bool prev_period = false;
208 bool first_space_checked = false;
209 const char *iterator = str;
210 while(*iterator)
211 {
212 const int code = str_utf8_decode(ptr: &iterator);
213 if(code <= 0x1F || code == 0x7F || code == '\\' || code == '/' || code == '|' || code == ':' ||
214 code == '*' || code == '?' || code == '<' || code == '>' || code == '"')
215 {
216 return false; // disallowed characters, mostly for Windows
217 }
218 else if(str_utf8_isspace(code) && code != ' ')
219 {
220 return false; // we only allow regular space characters
221 }
222 if(code == ' ')
223 {
224 if(!first_space_checked)
225 {
226 return false; // leading spaces not allowed
227 }
228 if(prev_space)
229 {
230 return false; // multiple consecutive spaces not allowed
231 }
232 prev_space = true;
233 prev_period = false;
234 }
235 else
236 {
237 prev_space = false;
238 prev_period = code == '.';
239 first_space_checked = true;
240 }
241 }
242 if(prev_space || prev_period)
243 {
244 return false; // trailing spaces and periods not allowed
245 }
246
247 static constexpr const char *RESERVED_NAMES[] = {
248 "CON", "PRN", "AUX", "NUL",
249 "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "COM¹", "COM²", "COM³",
250 "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "LPT¹", "LPT²", "LPT³"};
251 return std::none_of(first: std::begin(arr: RESERVED_NAMES), last: std::end(arr: RESERVED_NAMES), pred: [str](const char *reserved_name) {
252 const char *prefix = str_startswith_nocase(str, prefix: reserved_name);
253 // reserved name not allowed when it makes up the entire filename or when followed by period
254 return prefix != nullptr && (prefix[0] == '\0' || prefix[0] == '.');
255 });
256}
257
258int str_comp_filenames(const char *a, const char *b)
259{
260 int result;
261
262 for(; *a && *b; ++a, ++b)
263 {
264 if(str_isnum(c: *a) && str_isnum(c: *b))
265 {
266 result = 0;
267 do
268 {
269 if(!result)
270 result = *a - *b;
271 ++a;
272 ++b;
273 } while(str_isnum(c: *a) && str_isnum(c: *b));
274
275 if(str_isnum(c: *a))
276 return 1;
277 else if(str_isnum(c: *b))
278 return -1;
279 else if(result)
280 return result;
281 else if(*a == '\0' || *b == '\0')
282 return *a - *b;
283 }
284
285 result = tolower(c: *a) - tolower(c: *b);
286 if(result)
287 return result;
288 }
289 return *a - *b;
290}
291
292void str_clean_whitespaces(char *str)
293{
294 char *read = str;
295 char *write = str;
296
297 /* skip initial whitespace */
298 while(*read == ' ')
299 read++;
300
301 /* end of read string is detected in the loop */
302 while(true)
303 {
304 /* skip whitespace */
305 int found_whitespace = 0;
306 for(; *read == ' '; read++)
307 found_whitespace = 1;
308 /* if not at the end of the string, put a found whitespace here */
309 if(*read)
310 {
311 if(found_whitespace)
312 *write++ = ' ';
313 *write++ = *read++;
314 }
315 else
316 {
317 *write = 0;
318 break;
319 }
320 }
321}
322
323char *str_skip_to_whitespace(char *str)
324{
325 while(*str && !str_isspace(c: *str))
326 str++;
327 return str;
328}
329
330const char *str_skip_to_whitespace_const(const char *str)
331{
332 while(*str && !str_isspace(c: *str))
333 str++;
334 return str;
335}
336
337char *str_skip_whitespaces(char *str)
338{
339 while(*str && str_isspace(c: *str))
340 str++;
341 return str;
342}
343
344const char *str_skip_whitespaces_const(const char *str)
345{
346 while(*str && str_isspace(c: *str))
347 str++;
348 return str;
349}
350
351/* case */
352int str_comp_nocase(const char *a, const char *b)
353{
354#if defined(CONF_FAMILY_WINDOWS)
355 return _stricmp(a, b);
356#else
357 return strcasecmp(s1: a, s2: b);
358#endif
359}
360
361int str_comp_nocase_num(const char *a, const char *b, int num)
362{
363#if defined(CONF_FAMILY_WINDOWS)
364 return _strnicmp(a, b, num);
365#else
366 return strncasecmp(s1: a, s2: b, n: num);
367#endif
368}
369
370int str_comp(const char *a, const char *b)
371{
372 return strcmp(s1: a, s2: b);
373}
374
375int str_comp_num(const char *a, const char *b, int num)
376{
377 return strncmp(s1: a, s2: b, n: num);
378}
379
380const char *str_startswith_nocase(const char *str, const char *prefix)
381{
382 int prefixl = str_length(str: prefix);
383 if(str_comp_nocase_num(a: str, b: prefix, num: prefixl) == 0)
384 {
385 return str + prefixl;
386 }
387 else
388 {
389 return nullptr;
390 }
391}
392
393const char *str_startswith(const char *str, const char *prefix)
394{
395 int prefixl = str_length(str: prefix);
396 if(str_comp_num(a: str, b: prefix, num: prefixl) == 0)
397 {
398 return str + prefixl;
399 }
400 else
401 {
402 return nullptr;
403 }
404}
405
406const char *str_endswith_nocase(const char *str, const char *suffix)
407{
408 int strl = str_length(str);
409 int suffixl = str_length(str: suffix);
410 const char *strsuffix;
411 if(strl < suffixl)
412 {
413 return nullptr;
414 }
415 strsuffix = str + strl - suffixl;
416 if(str_comp_nocase(a: strsuffix, b: suffix) == 0)
417 {
418 return strsuffix;
419 }
420 else
421 {
422 return nullptr;
423 }
424}
425
426const char *str_endswith(const char *str, const char *suffix)
427{
428 int strl = str_length(str);
429 int suffixl = str_length(str: suffix);
430 const char *strsuffix;
431 if(strl < suffixl)
432 {
433 return nullptr;
434 }
435 strsuffix = str + strl - suffixl;
436 if(str_comp(a: strsuffix, b: suffix) == 0)
437 {
438 return strsuffix;
439 }
440 else
441 {
442 return nullptr;
443 }
444}
445
446const char *str_find_nocase(const char *haystack, const char *needle)
447{
448 while(*haystack) /* native implementation */
449 {
450 const char *a = haystack;
451 const char *b = needle;
452 while(*a && *b && tolower(c: (unsigned char)*a) == tolower(c: (unsigned char)*b))
453 {
454 a++;
455 b++;
456 }
457 if(!(*b))
458 return haystack;
459 haystack++;
460 }
461
462 return nullptr;
463}
464
465const char *str_find(const char *haystack, const char *needle)
466{
467 while(*haystack) /* native implementation */
468 {
469 const char *a = haystack;
470 const char *b = needle;
471 while(*a && *b && *a == *b)
472 {
473 a++;
474 b++;
475 }
476 if(!(*b))
477 return haystack;
478 haystack++;
479 }
480
481 return nullptr;
482}
483
484static const char *str_token_get(const char *str, const char *delim, size_t *length)
485{
486 size_t len = strspn(s: str, accept: delim);
487 if(len > 1)
488 str++;
489 else
490 str += len;
491 if(!*str)
492 return nullptr;
493
494 *length = strcspn(s: str, reject: delim);
495 return str;
496}
497
498const char *str_next_token(const char *str, const char *delim, char *buffer, size_t buffer_size)
499{
500 dbg_assert(buffer_size > 0, "buffer size 0");
501
502 size_t len = 0;
503 const char *tok = str_token_get(str, delim, length: &len);
504 if(tok == nullptr)
505 {
506 buffer[0] = '\0';
507 return nullptr;
508 }
509
510 len = buffer_size > len ? len : buffer_size - 1;
511 mem_copy(dest: buffer, source: tok, size: len);
512 buffer[len] = '\0';
513
514 return tok + len;
515}
516
517int str_in_list(const char *list, const char *delim, const char *needle)
518{
519 const char *tok = list;
520 size_t len = 0, notfound = 1, needlelen = str_length(str: needle);
521
522 while(notfound && (tok = str_token_get(str: tok, delim, length: &len)))
523 {
524 notfound = needlelen != len || str_comp_num(a: tok, b: needle, num: len);
525 tok = tok + len;
526 }
527
528 return !notfound;
529}
530
531bool str_delimiters_around_offset(const char *haystack, const char *delim, int offset, int *start, int *end)
532{
533 bool found = true;
534 const char *search = haystack;
535 const int delim_len = str_length(str: delim);
536 *start = 0;
537 while(str_find(haystack: search, needle: delim))
538 {
539 const char *test = str_find(haystack: search, needle: delim) + delim_len;
540 int distance = test - haystack;
541 if(distance > offset)
542 break;
543
544 *start = distance;
545 search = test;
546 }
547 if(search == haystack)
548 found = false;
549
550 if(str_find(haystack: search, needle: delim))
551 {
552 *end = str_find(haystack: search, needle: delim) - haystack;
553 }
554 else
555 {
556 *end = str_length(str: haystack);
557 found = false;
558 }
559
560 return found;
561}
562
563const char *str_rchr(const char *haystack, char needle)
564{
565 return strrchr(s: haystack, c: needle);
566}
567
568int str_countchr(const char *haystack, char needle)
569{
570 int count = 0;
571 while(*haystack)
572 {
573 if(*haystack == needle)
574 count++;
575 haystack++;
576 }
577 return count;
578}
579
580void str_hex(char *dst, int dst_size, const void *data, int data_size)
581{
582 static const char hex[] = "0123456789ABCDEF";
583 int data_index;
584 int dst_index;
585 for(data_index = 0, dst_index = 0; data_index < data_size && dst_index < dst_size - 3; data_index++)
586 {
587 dst[data_index * 3] = hex[((const unsigned char *)data)[data_index] >> 4];
588 dst[data_index * 3 + 1] = hex[((const unsigned char *)data)[data_index] & 0xf];
589 dst[data_index * 3 + 2] = ' ';
590 dst_index += 3;
591 }
592 dst[dst_index] = '\0';
593}
594
595void str_hex_cstyle(char *dst, int dst_size, const void *data, int data_size, int bytes_per_line)
596{
597 static const char hex[] = "0123456789ABCDEF";
598 int data_index;
599 int dst_index;
600 int remaining_bytes_per_line = bytes_per_line;
601 for(data_index = 0, dst_index = 0; data_index < data_size && dst_index < dst_size - 6; data_index++)
602 {
603 --remaining_bytes_per_line;
604 dst[data_index * 6] = '0';
605 dst[data_index * 6 + 1] = 'x';
606 dst[data_index * 6 + 2] = hex[((const unsigned char *)data)[data_index] >> 4];
607 dst[data_index * 6 + 3] = hex[((const unsigned char *)data)[data_index] & 0xf];
608 dst[data_index * 6 + 4] = ',';
609 if(remaining_bytes_per_line == 0)
610 {
611 dst[data_index * 6 + 5] = '\n';
612 remaining_bytes_per_line = bytes_per_line;
613 }
614 else
615 {
616 dst[data_index * 6 + 5] = ' ';
617 }
618 dst_index += 6;
619 }
620 dst[dst_index] = '\0';
621 // Remove trailing comma and space/newline
622 if(dst_index >= 1)
623 dst[dst_index - 1] = '\0';
624 if(dst_index >= 2)
625 dst[dst_index - 2] = '\0';
626}
627
628static int hexval(char x)
629{
630 switch(x)
631 {
632 case '0': return 0;
633 case '1': return 1;
634 case '2': return 2;
635 case '3': return 3;
636 case '4': return 4;
637 case '5': return 5;
638 case '6': return 6;
639 case '7': return 7;
640 case '8': return 8;
641 case '9': return 9;
642 case 'a':
643 case 'A': return 10;
644 case 'b':
645 case 'B': return 11;
646 case 'c':
647 case 'C': return 12;
648 case 'd':
649 case 'D': return 13;
650 case 'e':
651 case 'E': return 14;
652 case 'f':
653 case 'F': return 15;
654 default: return -1;
655 }
656}
657
658static int byteval(const char *hex, unsigned char *dst)
659{
660 int v1 = hexval(x: hex[0]);
661 int v2 = hexval(x: hex[1]);
662
663 if(v1 < 0 || v2 < 0)
664 return 1;
665
666 *dst = v1 * 16 + v2;
667 return 0;
668}
669
670int str_hex_decode(void *dst, int dst_size, const char *src)
671{
672 unsigned char *cdst = (unsigned char *)dst;
673 int slen = str_length(str: src);
674 int len = slen / 2;
675 int i;
676 if(slen != dst_size * 2)
677 return 2;
678
679 for(i = 0; i < len && dst_size; i++, dst_size--)
680 {
681 if(byteval(hex: src + i * 2, dst: cdst++))
682 return 1;
683 }
684 return 0;
685}
686
687void str_base64(char *dst, int dst_size, const void *data_raw, int data_size)
688{
689 static const char DIGITS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
690
691 const unsigned char *data = (const unsigned char *)data_raw;
692 unsigned value = 0;
693 int num_bits = 0;
694 int i = 0;
695 int o = 0;
696
697 dst_size -= 1;
698 dst[dst_size] = 0;
699 while(true)
700 {
701 if(num_bits < 6 && i < data_size)
702 {
703 value = (value << 8) | data[i];
704 num_bits += 8;
705 i += 1;
706 }
707 if(o == dst_size)
708 {
709 return;
710 }
711 if(num_bits > 0)
712 {
713 unsigned padded;
714 if(num_bits >= 6)
715 {
716 padded = (value >> (num_bits - 6)) & 0x3f;
717 }
718 else
719 {
720 padded = (value << (6 - num_bits)) & 0x3f;
721 }
722 dst[o] = DIGITS[padded];
723 num_bits -= 6;
724 o += 1;
725 }
726 else if(o % 4 != 0)
727 {
728 dst[o] = '=';
729 o += 1;
730 }
731 else
732 {
733 dst[o] = 0;
734 return;
735 }
736 }
737}
738
739static int base64_digit_value(char digit)
740{
741 if('A' <= digit && digit <= 'Z')
742 {
743 return digit - 'A';
744 }
745 else if('a' <= digit && digit <= 'z')
746 {
747 return digit - 'a' + 26;
748 }
749 else if('0' <= digit && digit <= '9')
750 {
751 return digit - '0' + 52;
752 }
753 else if(digit == '+')
754 {
755 return 62;
756 }
757 else if(digit == '/')
758 {
759 return 63;
760 }
761 return -1;
762}
763
764int str_base64_decode(void *dst_raw, int dst_size, const char *data)
765{
766 unsigned char *dst = (unsigned char *)dst_raw;
767 int data_len = str_length(str: data);
768
769 int i;
770 int o = 0;
771
772 if(data_len % 4 != 0)
773 {
774 return -3;
775 }
776 if(data_len / 4 * 3 > dst_size)
777 {
778 // Output buffer too small.
779 return -2;
780 }
781 for(i = 0; i < data_len; i += 4)
782 {
783 int num_output_bytes = 3;
784 char copy[4];
785 int d[4];
786 int value;
787 int b;
788 mem_copy(dest: copy, source: data + i, size: sizeof(copy));
789 if(i == data_len - 4)
790 {
791 if(copy[3] == '=')
792 {
793 copy[3] = 'A';
794 num_output_bytes = 2;
795 if(copy[2] == '=')
796 {
797 copy[2] = 'A';
798 num_output_bytes = 1;
799 }
800 }
801 }
802 d[0] = base64_digit_value(digit: copy[0]);
803 d[1] = base64_digit_value(digit: copy[1]);
804 d[2] = base64_digit_value(digit: copy[2]);
805 d[3] = base64_digit_value(digit: copy[3]);
806 if(d[0] == -1 || d[1] == -1 || d[2] == -1 || d[3] == -1)
807 {
808 // Invalid digit.
809 return -1;
810 }
811 value = (d[0] << 18) | (d[1] << 12) | (d[2] << 6) | d[3];
812 for(b = 0; b < 3; b++)
813 {
814 unsigned char byte_value = (value >> (16 - 8 * b)) & 0xff;
815 if(b < num_output_bytes)
816 {
817 dst[o] = byte_value;
818 o += 1;
819 }
820 else
821 {
822 if(byte_value != 0)
823 {
824 // Padding not zeroed.
825 return -2;
826 }
827 }
828 }
829 }
830 return o;
831}
832
833void str_escape(char **dst, const char *src, const char *end)
834{
835 while(*src && *dst + 1 < end)
836 {
837 if(*src == '"' || *src == '\\') // escape \ and "
838 {
839 if(*dst + 2 < end)
840 *(*dst)++ = '\\';
841 else
842 break;
843 }
844 *(*dst)++ = *src++;
845 }
846 **dst = 0;
847}
848
849int str_toint(const char *str)
850{
851 return str_toint_base(str, base: 10);
852}
853
854bool str_toint(const char *str, int *out)
855{
856 // returns true if conversion was successful
857 char *end;
858 int value = strtol(nptr: str, endptr: &end, base: 10);
859 if(*end != '\0')
860 return false;
861 if(out != nullptr)
862 *out = value;
863 return true;
864}
865
866int str_toint_base(const char *str, int base)
867{
868 return strtol(nptr: str, endptr: nullptr, base: base);
869}
870
871unsigned long str_toulong_base(const char *str, int base)
872{
873 return strtoul(nptr: str, endptr: nullptr, base: base);
874}
875
876int64_t str_toint64_base(const char *str, int base)
877{
878 return strtoll(nptr: str, endptr: nullptr, base: base);
879}
880
881float str_tofloat(const char *str)
882{
883 return strtod(nptr: str, endptr: nullptr);
884}
885
886bool str_tofloat(const char *str, float *out)
887{
888 // returns true if conversion was successful
889 char *end;
890 float value = strtod(nptr: str, endptr: &end);
891 if(*end != '\0')
892 return false;
893 if(out != nullptr)
894 *out = value;
895 return true;
896}
897
898unsigned str_quickhash(const char *str)
899{
900 unsigned hash = 5381;
901 for(; *str; str++)
902 hash = ((hash << 5) + hash) + (*str); /* hash * 33 + c */
903 return hash;
904}
905
906int str_utf8_encode(char *ptr, int chr)
907{
908 /* encode */
909 if(chr <= 0x7F)
910 {
911 ptr[0] = (char)chr;
912 return 1;
913 }
914 else if(chr <= 0x7FF)
915 {
916 ptr[0] = 0xC0 | ((chr >> 6) & 0x1F);
917 ptr[1] = 0x80 | (chr & 0x3F);
918 return 2;
919 }
920 else if(chr <= 0xFFFF)
921 {
922 ptr[0] = 0xE0 | ((chr >> 12) & 0x0F);
923 ptr[1] = 0x80 | ((chr >> 6) & 0x3F);
924 ptr[2] = 0x80 | (chr & 0x3F);
925 return 3;
926 }
927 else if(chr <= 0x10FFFF)
928 {
929 ptr[0] = 0xF0 | ((chr >> 18) & 0x07);
930 ptr[1] = 0x80 | ((chr >> 12) & 0x3F);
931 ptr[2] = 0x80 | ((chr >> 6) & 0x3F);
932 ptr[3] = 0x80 | (chr & 0x3F);
933 return 4;
934 }
935
936 return 0;
937}
938
939static unsigned char str_byte_next(const char **ptr)
940{
941 unsigned char byte_value = **ptr;
942 (*ptr)++;
943 return byte_value;
944}
945
946static void str_byte_rewind(const char **ptr)
947{
948 (*ptr)--;
949}
950
951int str_utf8_decode(const char **ptr)
952{
953 // As per https://encoding.spec.whatwg.org/#utf-8-decoder.
954 unsigned char utf8_lower_boundary = 0x80;
955 unsigned char utf8_upper_boundary = 0xBF;
956 int utf8_code_point = 0;
957 int utf8_bytes_seen = 0;
958 int utf8_bytes_needed = 0;
959 while(true)
960 {
961 unsigned char byte_value = str_byte_next(ptr);
962 if(utf8_bytes_needed == 0)
963 {
964 if(byte_value <= 0x7F)
965 {
966 return byte_value;
967 }
968 else if(0xC2 <= byte_value && byte_value <= 0xDF)
969 {
970 utf8_bytes_needed = 1;
971 utf8_code_point = byte_value - 0xC0;
972 }
973 else if(0xE0 <= byte_value && byte_value <= 0xEF)
974 {
975 if(byte_value == 0xE0)
976 utf8_lower_boundary = 0xA0;
977 if(byte_value == 0xED)
978 utf8_upper_boundary = 0x9F;
979 utf8_bytes_needed = 2;
980 utf8_code_point = byte_value - 0xE0;
981 }
982 else if(0xF0 <= byte_value && byte_value <= 0xF4)
983 {
984 if(byte_value == 0xF0)
985 utf8_lower_boundary = 0x90;
986 if(byte_value == 0xF4)
987 utf8_upper_boundary = 0x8F;
988 utf8_bytes_needed = 3;
989 utf8_code_point = byte_value - 0xF0;
990 }
991 else
992 {
993 return -1; // Error.
994 }
995 utf8_code_point = utf8_code_point << (6 * utf8_bytes_needed);
996 continue;
997 }
998 if(!(utf8_lower_boundary <= byte_value && byte_value <= utf8_upper_boundary))
999 {
1000 // Resetting variables not necessary, will be done when
1001 // the function is called again.
1002 str_byte_rewind(ptr);
1003 return -1;
1004 }
1005 utf8_lower_boundary = 0x80;
1006 utf8_upper_boundary = 0xBF;
1007 utf8_bytes_seen += 1;
1008 utf8_code_point = utf8_code_point + ((byte_value - 0x80) << (6 * (utf8_bytes_needed - utf8_bytes_seen)));
1009 if(utf8_bytes_seen != utf8_bytes_needed)
1010 {
1011 continue;
1012 }
1013 // Resetting variables not necessary, see above.
1014 return utf8_code_point;
1015 }
1016}
1017
1018void str_utf8_truncate(char *dst, int dst_size, const char *src, int truncation_len)
1019{
1020 int size = -1;
1021 const char *cursor = src;
1022 int pos = 0;
1023 while(pos <= truncation_len && cursor - src < dst_size && size != cursor - src)
1024 {
1025 size = cursor - src;
1026 if(str_utf8_decode(ptr: &cursor) == 0)
1027 {
1028 break;
1029 }
1030 pos++;
1031 }
1032 str_copy(dst, src, dst_size: size + 1);
1033}
1034
1035int str_utf8_fix_truncation(char *str)
1036{
1037 int len = str_length(str);
1038 if(len > 0)
1039 {
1040 int last_char_index = str_utf8_rewind(str, cursor: len);
1041 const char *last_char = str + last_char_index;
1042 // Fix truncated UTF-8.
1043 if(str_utf8_decode(ptr: &last_char) == -1)
1044 {
1045 str[last_char_index] = 0;
1046 return last_char_index;
1047 }
1048 }
1049 return len;
1050}
1051
1052void str_utf8_trim_right(char *param)
1053{
1054 const char *str = param;
1055 char *end = nullptr;
1056 while(*str)
1057 {
1058 char *str_old = (char *)str;
1059 int code = str_utf8_decode(ptr: &str);
1060
1061 // check if unicode is not empty
1062 if(!str_utf8_isspace(code))
1063 {
1064 end = nullptr;
1065 }
1066 else if(!end)
1067 {
1068 end = str_old;
1069 }
1070 }
1071 if(end)
1072 {
1073 *end = 0;
1074 }
1075}
1076
1077void str_utf8_tolower(const char *input, char *output, size_t size)
1078{
1079 size_t out_pos = 0;
1080 while(*input)
1081 {
1082 const int code = str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &input));
1083 char encoded_code[4];
1084 const int code_size = str_utf8_encode(ptr: encoded_code, chr: code);
1085 if(out_pos + code_size + 1 > size) // +1 for null termination
1086 {
1087 break;
1088 }
1089 mem_copy(dest: &output[out_pos], source: encoded_code, size: code_size);
1090 out_pos += code_size;
1091 }
1092 output[out_pos] = '\0';
1093}
1094
1095int str_utf8_isspace(int code)
1096{
1097 return code <= 0x0020 || code == 0x0085 || code == 0x00A0 || code == 0x034F ||
1098 code == 0x115F || code == 0x1160 || code == 0x1680 || code == 0x180E ||
1099 (code >= 0x2000 && code <= 0x200F) || (code >= 0x2028 && code <= 0x202F) ||
1100 (code >= 0x205F && code <= 0x2064) || (code >= 0x206A && code <= 0x206F) ||
1101 code == 0x2800 || code == 0x3000 || code == 0x3164 ||
1102 (code >= 0xFE00 && code <= 0xFE0F) || code == 0xFEFF || code == 0xFFA0 ||
1103 (code >= 0xFFF9 && code <= 0xFFFC);
1104}
1105
1106int str_utf8_isstart(char c)
1107{
1108 if((c & 0xC0) == 0x80) /* 10xxxxxx */
1109 return 0;
1110 return 1;
1111}
1112
1113int str_utf8_rewind(const char *str, int cursor)
1114{
1115 while(cursor)
1116 {
1117 cursor--;
1118 if(str_utf8_isstart(c: *(str + cursor)))
1119 break;
1120 }
1121 return cursor;
1122}
1123
1124const char *str_utf8_find_nocase(const char *haystack, const char *needle, const char **end)
1125{
1126 while(*haystack) /* native implementation */
1127 {
1128 const char *a = haystack;
1129 const char *b = needle;
1130 const char *a_next = a;
1131 const char *b_next = b;
1132 while(*a && *b && str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &a_next)) == str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &b_next)))
1133 {
1134 a = a_next;
1135 b = b_next;
1136 }
1137 if(!(*b))
1138 {
1139 if(end != nullptr)
1140 *end = a_next;
1141 return haystack;
1142 }
1143 str_utf8_decode(ptr: &haystack);
1144 }
1145
1146 if(end != nullptr)
1147 *end = nullptr;
1148 return nullptr;
1149}
1150
1151int str_utf8_comp_nocase(const char *a, const char *b)
1152{
1153 int code_a;
1154 int code_b;
1155
1156 while(*a && *b)
1157 {
1158 code_a = str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &a));
1159 code_b = str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &b));
1160
1161 if(code_a != code_b)
1162 return code_a - code_b;
1163 }
1164 return (unsigned char)*a - (unsigned char)*b;
1165}
1166
1167int str_utf8_comp_nocase_num(const char *a, const char *b, int num)
1168{
1169 int code_a;
1170 int code_b;
1171 const char *old_a = a;
1172
1173 if(num <= 0)
1174 return 0;
1175
1176 while(*a && *b)
1177 {
1178 code_a = str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &a));
1179 code_b = str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &b));
1180
1181 if(code_a != code_b)
1182 return code_a - code_b;
1183
1184 if(a - old_a >= num)
1185 return 0;
1186 }
1187
1188 return (unsigned char)*a - (unsigned char)*b;
1189}
1190
1191const char *str_utf8_skip_whitespaces(const char *str)
1192{
1193 const char *str_old;
1194 int code;
1195
1196 while(*str)
1197 {
1198 str_old = str;
1199 code = str_utf8_decode(ptr: &str);
1200
1201 // check if unicode is not empty
1202 if(!str_utf8_isspace(code))
1203 {
1204 return str_old;
1205 }
1206 }
1207
1208 return str;
1209}
1210
1211int str_utf8_forward(const char *str, int cursor)
1212{
1213 const char *ptr = str + cursor;
1214 if(str_utf8_decode(ptr: &ptr) == 0)
1215 {
1216 return cursor;
1217 }
1218 return ptr - str;
1219}
1220
1221int str_utf8_check(const char *str)
1222{
1223 int codepoint;
1224 while((codepoint = str_utf8_decode(ptr: &str)))
1225 {
1226 if(codepoint == -1)
1227 {
1228 return 0;
1229 }
1230 }
1231 return 1;
1232}
1233
1234void str_utf8_copy_num(char *dst, const char *src, int dst_size, int num)
1235{
1236 int new_cursor;
1237 int cursor = 0;
1238
1239 while(src[cursor] && num > 0)
1240 {
1241 new_cursor = str_utf8_forward(str: src, cursor);
1242 if(new_cursor >= dst_size) // reserve 1 byte for the null termination
1243 break;
1244 else
1245 cursor = new_cursor;
1246 --num;
1247 }
1248
1249 str_copy(dst, src, dst_size: cursor < dst_size ? cursor + 1 : dst_size);
1250}
1251
1252void str_utf8_stats(const char *str, size_t max_size, size_t max_count, size_t *size, size_t *count)
1253{
1254 const char *cursor = str;
1255 *size = 0;
1256 *count = 0;
1257 while(*size < max_size && *count < max_count)
1258 {
1259 if(str_utf8_decode(ptr: &cursor) == 0)
1260 {
1261 break;
1262 }
1263 if((size_t)(cursor - str) >= max_size)
1264 {
1265 break;
1266 }
1267 *size = cursor - str;
1268 ++(*count);
1269 }
1270}
1271
1272size_t str_utf8_offset_bytes_to_chars(const char *str, size_t byte_offset)
1273{
1274 size_t char_offset = 0;
1275 size_t current_offset = 0;
1276 while(current_offset < byte_offset)
1277 {
1278 const size_t prev_byte_offset = current_offset;
1279 current_offset = str_utf8_forward(str, cursor: current_offset);
1280 if(current_offset == prev_byte_offset)
1281 break;
1282 char_offset++;
1283 }
1284 return char_offset;
1285}
1286
1287size_t str_utf8_offset_chars_to_bytes(const char *str, size_t char_offset)
1288{
1289 size_t byte_offset = 0;
1290 for(size_t i = 0; i < char_offset; i++)
1291 {
1292 const size_t prev_byte_offset = byte_offset;
1293 byte_offset = str_utf8_forward(str, cursor: byte_offset);
1294 if(byte_offset == prev_byte_offset)
1295 break;
1296 }
1297 return byte_offset;
1298}
1299
1300int str_utf8_dist(const char *a, const char *b)
1301{
1302 int buf_len = 2 * (str_length(str: a) + 1 + str_length(str: b) + 1);
1303 int *buf = (int *)calloc(nmemb: buf_len, size: sizeof(*buf));
1304 int result = str_utf8_dist_buffer(a, b, buf, buf_len);
1305 free(ptr: buf);
1306 return result;
1307}
1308
1309static int str_to_utf32_unchecked(const char *str, int **out)
1310{
1311 int out_len = 0;
1312 while((**out = str_utf8_decode(ptr: &str)))
1313 {
1314 (*out)++;
1315 out_len++;
1316 }
1317 return out_len;
1318}
1319
1320int str_utf8_dist_buffer(const char *a_utf8, const char *b_utf8, int *buf, int buf_len)
1321{
1322 int a_utf8_len = str_length(str: a_utf8);
1323 int b_utf8_len = str_length(str: b_utf8);
1324 int *a, *b; // UTF-32
1325 int a_len, b_len; // UTF-32 length
1326 dbg_assert(buf_len >= 2 * (a_utf8_len + 1 + b_utf8_len + 1), "buffer too small");
1327 if(a_utf8_len > b_utf8_len)
1328 {
1329 const char *tmp2 = a_utf8;
1330 a_utf8 = b_utf8;
1331 b_utf8 = tmp2;
1332 }
1333 a = buf;
1334 a_len = str_to_utf32_unchecked(str: a_utf8, out: &buf);
1335 b = buf;
1336 b_len = str_to_utf32_unchecked(str: b_utf8, out: &buf);
1337 return str_utf32_dist_buffer(a, a_len, b, b_len, buf, buf_len: buf_len - b_len - a_len);
1338}
1339
1340int str_utf32_dist_buffer(const int *a, int a_len, const int *b, int b_len, int *buf, int buf_len)
1341{
1342 int i, j;
1343 dbg_assert(buf_len >= (a_len + 1) + (b_len + 1), "buffer too small");
1344 if(a_len > b_len)
1345 {
1346 int tmp1 = a_len;
1347 const int *tmp2 = a;
1348
1349 a_len = b_len;
1350 a = b;
1351
1352 b_len = tmp1;
1353 b = tmp2;
1354 }
1355#define B(i, j) buf[((j) & 1) * (a_len + 1) + (i)]
1356 for(i = 0; i <= a_len; i++)
1357 {
1358 B(i, 0) = i;
1359 }
1360 for(j = 1; j <= b_len; j++)
1361 {
1362 B(0, j) = j;
1363 for(i = 1; i <= a_len; i++)
1364 {
1365 int subst = (a[i - 1] != b[j - 1]);
1366 B(i, j) = std::min(l: {
1367 B(i - 1, j) + 1,
1368 B(i, j - 1) + 1,
1369 B(i - 1, j - 1) + subst,
1370 });
1371 }
1372 }
1373 return B(a_len, b_len);
1374#undef B
1375}
1376