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 || *a == '\0' || *b == '\0')
280 return result;
281 }
282
283 result = tolower(c: *a) - tolower(c: *b);
284 if(result)
285 return result;
286 }
287 return *a - *b;
288}
289
290void str_clean_whitespaces(char *str)
291{
292 char *read = str;
293 char *write = str;
294
295 /* skip initial whitespace */
296 while(*read == ' ')
297 read++;
298
299 /* end of read string is detected in the loop */
300 while(true)
301 {
302 /* skip whitespace */
303 int found_whitespace = 0;
304 for(; *read == ' '; read++)
305 found_whitespace = 1;
306 /* if not at the end of the string, put a found whitespace here */
307 if(*read)
308 {
309 if(found_whitespace)
310 *write++ = ' ';
311 *write++ = *read++;
312 }
313 else
314 {
315 *write = 0;
316 break;
317 }
318 }
319}
320
321char *str_skip_to_whitespace(char *str)
322{
323 while(*str && !str_isspace(c: *str))
324 str++;
325 return str;
326}
327
328const char *str_skip_to_whitespace_const(const char *str)
329{
330 while(*str && !str_isspace(c: *str))
331 str++;
332 return str;
333}
334
335char *str_skip_whitespaces(char *str)
336{
337 while(*str && str_isspace(c: *str))
338 str++;
339 return str;
340}
341
342const char *str_skip_whitespaces_const(const char *str)
343{
344 while(*str && str_isspace(c: *str))
345 str++;
346 return str;
347}
348
349/* case */
350int str_comp_nocase(const char *a, const char *b)
351{
352#if defined(CONF_FAMILY_WINDOWS)
353 return _stricmp(a, b);
354#else
355 return strcasecmp(s1: a, s2: b);
356#endif
357}
358
359int str_comp_nocase_num(const char *a, const char *b, int num)
360{
361#if defined(CONF_FAMILY_WINDOWS)
362 return _strnicmp(a, b, num);
363#else
364 return strncasecmp(s1: a, s2: b, n: num);
365#endif
366}
367
368int str_comp(const char *a, const char *b)
369{
370 return strcmp(s1: a, s2: b);
371}
372
373int str_comp_num(const char *a, const char *b, int num)
374{
375 return strncmp(s1: a, s2: b, n: num);
376}
377
378const char *str_startswith_nocase(const char *str, const char *prefix)
379{
380 int prefixl = str_length(str: prefix);
381 if(str_comp_nocase_num(a: str, b: prefix, num: prefixl) == 0)
382 {
383 return str + prefixl;
384 }
385 else
386 {
387 return nullptr;
388 }
389}
390
391const char *str_startswith(const char *str, const char *prefix)
392{
393 int prefixl = str_length(str: prefix);
394 if(str_comp_num(a: str, b: prefix, num: prefixl) == 0)
395 {
396 return str + prefixl;
397 }
398 else
399 {
400 return nullptr;
401 }
402}
403
404const char *str_endswith_nocase(const char *str, const char *suffix)
405{
406 int strl = str_length(str);
407 int suffixl = str_length(str: suffix);
408 const char *strsuffix;
409 if(strl < suffixl)
410 {
411 return nullptr;
412 }
413 strsuffix = str + strl - suffixl;
414 if(str_comp_nocase(a: strsuffix, b: suffix) == 0)
415 {
416 return strsuffix;
417 }
418 else
419 {
420 return nullptr;
421 }
422}
423
424const char *str_endswith(const char *str, const char *suffix)
425{
426 int strl = str_length(str);
427 int suffixl = str_length(str: suffix);
428 const char *strsuffix;
429 if(strl < suffixl)
430 {
431 return nullptr;
432 }
433 strsuffix = str + strl - suffixl;
434 if(str_comp(a: strsuffix, b: suffix) == 0)
435 {
436 return strsuffix;
437 }
438 else
439 {
440 return nullptr;
441 }
442}
443
444const char *str_find_nocase(const char *haystack, const char *needle)
445{
446 while(*haystack) /* native implementation */
447 {
448 const char *a = haystack;
449 const char *b = needle;
450 while(*a && *b && tolower(c: (unsigned char)*a) == tolower(c: (unsigned char)*b))
451 {
452 a++;
453 b++;
454 }
455 if(!(*b))
456 return haystack;
457 haystack++;
458 }
459
460 return nullptr;
461}
462
463const char *str_find(const char *haystack, const char *needle)
464{
465 while(*haystack) /* native implementation */
466 {
467 const char *a = haystack;
468 const char *b = needle;
469 while(*a && *b && *a == *b)
470 {
471 a++;
472 b++;
473 }
474 if(!(*b))
475 return haystack;
476 haystack++;
477 }
478
479 return nullptr;
480}
481
482static const char *str_token_get(const char *str, const char *delim, size_t *length)
483{
484 size_t len = strspn(s: str, accept: delim);
485 if(len > 1)
486 str++;
487 else
488 str += len;
489 if(!*str)
490 return nullptr;
491
492 *length = strcspn(s: str, reject: delim);
493 return str;
494}
495
496const char *str_next_token(const char *str, const char *delim, char *buffer, size_t buffer_size)
497{
498 dbg_assert(buffer_size > 0, "buffer size 0");
499
500 size_t len = 0;
501 const char *tok = str_token_get(str, delim, length: &len);
502 if(tok == nullptr)
503 {
504 buffer[0] = '\0';
505 return nullptr;
506 }
507
508 len = buffer_size > len ? len : buffer_size - 1;
509 mem_copy(dest: buffer, source: tok, size: len);
510 buffer[len] = '\0';
511
512 return tok + len;
513}
514
515int str_in_list(const char *list, const char *delim, const char *needle)
516{
517 const char *tok = list;
518 size_t len = 0, notfound = 1, needlelen = str_length(str: needle);
519
520 while(notfound && (tok = str_token_get(str: tok, delim, length: &len)))
521 {
522 notfound = needlelen != len || str_comp_num(a: tok, b: needle, num: len);
523 tok = tok + len;
524 }
525
526 return !notfound;
527}
528
529bool str_delimiters_around_offset(const char *haystack, const char *delim, int offset, int *start, int *end)
530{
531 bool found = true;
532 const char *search = haystack;
533 const int delim_len = str_length(str: delim);
534 *start = 0;
535 while(str_find(haystack: search, needle: delim))
536 {
537 const char *test = str_find(haystack: search, needle: delim) + delim_len;
538 int distance = test - haystack;
539 if(distance > offset)
540 break;
541
542 *start = distance;
543 search = test;
544 }
545 if(search == haystack)
546 found = false;
547
548 if(str_find(haystack: search, needle: delim))
549 {
550 *end = str_find(haystack: search, needle: delim) - haystack;
551 }
552 else
553 {
554 *end = str_length(str: haystack);
555 found = false;
556 }
557
558 return found;
559}
560
561const char *str_rchr(const char *haystack, char needle)
562{
563 return strrchr(s: haystack, c: needle);
564}
565
566int str_countchr(const char *haystack, char needle)
567{
568 int count = 0;
569 while(*haystack)
570 {
571 if(*haystack == needle)
572 count++;
573 haystack++;
574 }
575 return count;
576}
577
578void str_hex(char *dst, int dst_size, const void *data, int data_size)
579{
580 static const char hex[] = "0123456789ABCDEF";
581 int data_index;
582 int dst_index;
583 for(data_index = 0, dst_index = 0; data_index < data_size && dst_index < dst_size - 3; data_index++)
584 {
585 dst[data_index * 3] = hex[((const unsigned char *)data)[data_index] >> 4];
586 dst[data_index * 3 + 1] = hex[((const unsigned char *)data)[data_index] & 0xf];
587 dst[data_index * 3 + 2] = ' ';
588 dst_index += 3;
589 }
590 dst[dst_index] = '\0';
591}
592
593void str_hex_cstyle(char *dst, int dst_size, const void *data, int data_size, int bytes_per_line)
594{
595 static const char hex[] = "0123456789ABCDEF";
596 int data_index;
597 int dst_index;
598 int remaining_bytes_per_line = bytes_per_line;
599 for(data_index = 0, dst_index = 0; data_index < data_size && dst_index < dst_size - 6; data_index++)
600 {
601 --remaining_bytes_per_line;
602 dst[data_index * 6] = '0';
603 dst[data_index * 6 + 1] = 'x';
604 dst[data_index * 6 + 2] = hex[((const unsigned char *)data)[data_index] >> 4];
605 dst[data_index * 6 + 3] = hex[((const unsigned char *)data)[data_index] & 0xf];
606 dst[data_index * 6 + 4] = ',';
607 if(remaining_bytes_per_line == 0)
608 {
609 dst[data_index * 6 + 5] = '\n';
610 remaining_bytes_per_line = bytes_per_line;
611 }
612 else
613 {
614 dst[data_index * 6 + 5] = ' ';
615 }
616 dst_index += 6;
617 }
618 dst[dst_index] = '\0';
619 // Remove trailing comma and space/newline
620 if(dst_index >= 1)
621 dst[dst_index - 1] = '\0';
622 if(dst_index >= 2)
623 dst[dst_index - 2] = '\0';
624}
625
626static int hexval(char x)
627{
628 switch(x)
629 {
630 case '0': return 0;
631 case '1': return 1;
632 case '2': return 2;
633 case '3': return 3;
634 case '4': return 4;
635 case '5': return 5;
636 case '6': return 6;
637 case '7': return 7;
638 case '8': return 8;
639 case '9': return 9;
640 case 'a':
641 case 'A': return 10;
642 case 'b':
643 case 'B': return 11;
644 case 'c':
645 case 'C': return 12;
646 case 'd':
647 case 'D': return 13;
648 case 'e':
649 case 'E': return 14;
650 case 'f':
651 case 'F': return 15;
652 default: return -1;
653 }
654}
655
656static int byteval(const char *hex, unsigned char *dst)
657{
658 int v1 = hexval(x: hex[0]);
659 int v2 = hexval(x: hex[1]);
660
661 if(v1 < 0 || v2 < 0)
662 return 1;
663
664 *dst = v1 * 16 + v2;
665 return 0;
666}
667
668int str_hex_decode(void *dst, int dst_size, const char *src)
669{
670 unsigned char *cdst = (unsigned char *)dst;
671 int slen = str_length(str: src);
672 int len = slen / 2;
673 int i;
674 if(slen != dst_size * 2)
675 return 2;
676
677 for(i = 0; i < len && dst_size; i++, dst_size--)
678 {
679 if(byteval(hex: src + i * 2, dst: cdst++))
680 return 1;
681 }
682 return 0;
683}
684
685void str_base64(char *dst, int dst_size, const void *data_raw, int data_size)
686{
687 static const char DIGITS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
688
689 const unsigned char *data = (const unsigned char *)data_raw;
690 unsigned value = 0;
691 int num_bits = 0;
692 int i = 0;
693 int o = 0;
694
695 dst_size -= 1;
696 dst[dst_size] = 0;
697 while(true)
698 {
699 if(num_bits < 6 && i < data_size)
700 {
701 value = (value << 8) | data[i];
702 num_bits += 8;
703 i += 1;
704 }
705 if(o == dst_size)
706 {
707 return;
708 }
709 if(num_bits > 0)
710 {
711 unsigned padded;
712 if(num_bits >= 6)
713 {
714 padded = (value >> (num_bits - 6)) & 0x3f;
715 }
716 else
717 {
718 padded = (value << (6 - num_bits)) & 0x3f;
719 }
720 dst[o] = DIGITS[padded];
721 num_bits -= 6;
722 o += 1;
723 }
724 else if(o % 4 != 0)
725 {
726 dst[o] = '=';
727 o += 1;
728 }
729 else
730 {
731 dst[o] = 0;
732 return;
733 }
734 }
735}
736
737static int base64_digit_value(char digit)
738{
739 if('A' <= digit && digit <= 'Z')
740 {
741 return digit - 'A';
742 }
743 else if('a' <= digit && digit <= 'z')
744 {
745 return digit - 'a' + 26;
746 }
747 else if('0' <= digit && digit <= '9')
748 {
749 return digit - '0' + 52;
750 }
751 else if(digit == '+')
752 {
753 return 62;
754 }
755 else if(digit == '/')
756 {
757 return 63;
758 }
759 return -1;
760}
761
762int str_base64_decode(void *dst_raw, int dst_size, const char *data)
763{
764 unsigned char *dst = (unsigned char *)dst_raw;
765 int data_len = str_length(str: data);
766
767 int i;
768 int o = 0;
769
770 if(data_len % 4 != 0)
771 {
772 return -3;
773 }
774 if(data_len / 4 * 3 > dst_size)
775 {
776 // Output buffer too small.
777 return -2;
778 }
779 for(i = 0; i < data_len; i += 4)
780 {
781 int num_output_bytes = 3;
782 char copy[4];
783 int d[4];
784 int value;
785 int b;
786 mem_copy(dest: copy, source: data + i, size: sizeof(copy));
787 if(i == data_len - 4)
788 {
789 if(copy[3] == '=')
790 {
791 copy[3] = 'A';
792 num_output_bytes = 2;
793 if(copy[2] == '=')
794 {
795 copy[2] = 'A';
796 num_output_bytes = 1;
797 }
798 }
799 }
800 d[0] = base64_digit_value(digit: copy[0]);
801 d[1] = base64_digit_value(digit: copy[1]);
802 d[2] = base64_digit_value(digit: copy[2]);
803 d[3] = base64_digit_value(digit: copy[3]);
804 if(d[0] == -1 || d[1] == -1 || d[2] == -1 || d[3] == -1)
805 {
806 // Invalid digit.
807 return -1;
808 }
809 value = (d[0] << 18) | (d[1] << 12) | (d[2] << 6) | d[3];
810 for(b = 0; b < 3; b++)
811 {
812 unsigned char byte_value = (value >> (16 - 8 * b)) & 0xff;
813 if(b < num_output_bytes)
814 {
815 dst[o] = byte_value;
816 o += 1;
817 }
818 else
819 {
820 if(byte_value != 0)
821 {
822 // Padding not zeroed.
823 return -2;
824 }
825 }
826 }
827 }
828 return o;
829}
830
831void str_escape(char **dst, const char *src, const char *end)
832{
833 while(*src && *dst + 1 < end)
834 {
835 if(*src == '"' || *src == '\\') // escape \ and "
836 {
837 if(*dst + 2 < end)
838 *(*dst)++ = '\\';
839 else
840 break;
841 }
842 *(*dst)++ = *src++;
843 }
844 **dst = 0;
845}
846
847int str_toint(const char *str)
848{
849 return str_toint_base(str, base: 10);
850}
851
852bool str_toint(const char *str, int *out)
853{
854 // returns true if conversion was successful
855 char *end;
856 int value = strtol(nptr: str, endptr: &end, base: 10);
857 if(*end != '\0')
858 return false;
859 if(out != nullptr)
860 *out = value;
861 return true;
862}
863
864int str_toint_base(const char *str, int base)
865{
866 return strtol(nptr: str, endptr: nullptr, base: base);
867}
868
869unsigned long str_toulong_base(const char *str, int base)
870{
871 return strtoul(nptr: str, endptr: nullptr, base: base);
872}
873
874int64_t str_toint64_base(const char *str, int base)
875{
876 return strtoll(nptr: str, endptr: nullptr, base: base);
877}
878
879float str_tofloat(const char *str)
880{
881 return strtod(nptr: str, endptr: nullptr);
882}
883
884bool str_tofloat(const char *str, float *out)
885{
886 // returns true if conversion was successful
887 char *end;
888 float value = strtod(nptr: str, endptr: &end);
889 if(*end != '\0')
890 return false;
891 if(out != nullptr)
892 *out = value;
893 return true;
894}
895
896unsigned str_quickhash(const char *str)
897{
898 unsigned hash = 5381;
899 for(; *str; str++)
900 hash = ((hash << 5) + hash) + (*str); /* hash * 33 + c */
901 return hash;
902}
903
904int str_utf8_encode(char *ptr, int chr)
905{
906 /* encode */
907 if(chr <= 0x7F)
908 {
909 ptr[0] = (char)chr;
910 return 1;
911 }
912 else if(chr <= 0x7FF)
913 {
914 ptr[0] = 0xC0 | ((chr >> 6) & 0x1F);
915 ptr[1] = 0x80 | (chr & 0x3F);
916 return 2;
917 }
918 else if(chr <= 0xFFFF)
919 {
920 ptr[0] = 0xE0 | ((chr >> 12) & 0x0F);
921 ptr[1] = 0x80 | ((chr >> 6) & 0x3F);
922 ptr[2] = 0x80 | (chr & 0x3F);
923 return 3;
924 }
925 else if(chr <= 0x10FFFF)
926 {
927 ptr[0] = 0xF0 | ((chr >> 18) & 0x07);
928 ptr[1] = 0x80 | ((chr >> 12) & 0x3F);
929 ptr[2] = 0x80 | ((chr >> 6) & 0x3F);
930 ptr[3] = 0x80 | (chr & 0x3F);
931 return 4;
932 }
933
934 return 0;
935}
936
937static unsigned char str_byte_next(const char **ptr)
938{
939 unsigned char byte_value = **ptr;
940 (*ptr)++;
941 return byte_value;
942}
943
944static void str_byte_rewind(const char **ptr)
945{
946 (*ptr)--;
947}
948
949int str_utf8_decode(const char **ptr)
950{
951 // As per https://encoding.spec.whatwg.org/#utf-8-decoder.
952 unsigned char utf8_lower_boundary = 0x80;
953 unsigned char utf8_upper_boundary = 0xBF;
954 int utf8_code_point = 0;
955 int utf8_bytes_seen = 0;
956 int utf8_bytes_needed = 0;
957 while(true)
958 {
959 unsigned char byte_value = str_byte_next(ptr);
960 if(utf8_bytes_needed == 0)
961 {
962 if(byte_value <= 0x7F)
963 {
964 return byte_value;
965 }
966 else if(0xC2 <= byte_value && byte_value <= 0xDF)
967 {
968 utf8_bytes_needed = 1;
969 utf8_code_point = byte_value - 0xC0;
970 }
971 else if(0xE0 <= byte_value && byte_value <= 0xEF)
972 {
973 if(byte_value == 0xE0)
974 utf8_lower_boundary = 0xA0;
975 if(byte_value == 0xED)
976 utf8_upper_boundary = 0x9F;
977 utf8_bytes_needed = 2;
978 utf8_code_point = byte_value - 0xE0;
979 }
980 else if(0xF0 <= byte_value && byte_value <= 0xF4)
981 {
982 if(byte_value == 0xF0)
983 utf8_lower_boundary = 0x90;
984 if(byte_value == 0xF4)
985 utf8_upper_boundary = 0x8F;
986 utf8_bytes_needed = 3;
987 utf8_code_point = byte_value - 0xF0;
988 }
989 else
990 {
991 return -1; // Error.
992 }
993 utf8_code_point = utf8_code_point << (6 * utf8_bytes_needed);
994 continue;
995 }
996 if(!(utf8_lower_boundary <= byte_value && byte_value <= utf8_upper_boundary))
997 {
998 // Resetting variables not necessary, will be done when
999 // the function is called again.
1000 str_byte_rewind(ptr);
1001 return -1;
1002 }
1003 utf8_lower_boundary = 0x80;
1004 utf8_upper_boundary = 0xBF;
1005 utf8_bytes_seen += 1;
1006 utf8_code_point = utf8_code_point + ((byte_value - 0x80) << (6 * (utf8_bytes_needed - utf8_bytes_seen)));
1007 if(utf8_bytes_seen != utf8_bytes_needed)
1008 {
1009 continue;
1010 }
1011 // Resetting variables not necessary, see above.
1012 return utf8_code_point;
1013 }
1014}
1015
1016void str_utf8_truncate(char *dst, int dst_size, const char *src, int truncation_len)
1017{
1018 int size = -1;
1019 const char *cursor = src;
1020 int pos = 0;
1021 while(pos <= truncation_len && cursor - src < dst_size && size != cursor - src)
1022 {
1023 size = cursor - src;
1024 if(str_utf8_decode(ptr: &cursor) == 0)
1025 {
1026 break;
1027 }
1028 pos++;
1029 }
1030 str_copy(dst, src, dst_size: size + 1);
1031}
1032
1033int str_utf8_fix_truncation(char *str)
1034{
1035 int len = str_length(str);
1036 if(len > 0)
1037 {
1038 int last_char_index = str_utf8_rewind(str, cursor: len);
1039 const char *last_char = str + last_char_index;
1040 // Fix truncated UTF-8.
1041 if(str_utf8_decode(ptr: &last_char) == -1)
1042 {
1043 str[last_char_index] = 0;
1044 return last_char_index;
1045 }
1046 }
1047 return len;
1048}
1049
1050void str_utf8_trim_right(char *param)
1051{
1052 const char *str = param;
1053 char *end = nullptr;
1054 while(*str)
1055 {
1056 char *str_old = (char *)str;
1057 int code = str_utf8_decode(ptr: &str);
1058
1059 // check if unicode is not empty
1060 if(!str_utf8_isspace(code))
1061 {
1062 end = nullptr;
1063 }
1064 else if(!end)
1065 {
1066 end = str_old;
1067 }
1068 }
1069 if(end)
1070 {
1071 *end = 0;
1072 }
1073}
1074
1075void str_utf8_tolower(const char *input, char *output, size_t size)
1076{
1077 size_t out_pos = 0;
1078 while(*input)
1079 {
1080 const int code = str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &input));
1081 char encoded_code[4];
1082 const int code_size = str_utf8_encode(ptr: encoded_code, chr: code);
1083 if(out_pos + code_size + 1 > size) // +1 for null termination
1084 {
1085 break;
1086 }
1087 mem_copy(dest: &output[out_pos], source: encoded_code, size: code_size);
1088 out_pos += code_size;
1089 }
1090 output[out_pos] = '\0';
1091}
1092
1093int str_utf8_isspace(int code)
1094{
1095 return code <= 0x0020 || code == 0x0085 || code == 0x00A0 || code == 0x034F ||
1096 code == 0x115F || code == 0x1160 || code == 0x1680 || code == 0x180E ||
1097 (code >= 0x2000 && code <= 0x200F) || (code >= 0x2028 && code <= 0x202F) ||
1098 (code >= 0x205F && code <= 0x2064) || (code >= 0x206A && code <= 0x206F) ||
1099 code == 0x2800 || code == 0x3000 || code == 0x3164 ||
1100 (code >= 0xFE00 && code <= 0xFE0F) || code == 0xFEFF || code == 0xFFA0 ||
1101 (code >= 0xFFF9 && code <= 0xFFFC);
1102}
1103
1104int str_utf8_isstart(char c)
1105{
1106 if((c & 0xC0) == 0x80) /* 10xxxxxx */
1107 return 0;
1108 return 1;
1109}
1110
1111int str_utf8_rewind(const char *str, int cursor)
1112{
1113 while(cursor)
1114 {
1115 cursor--;
1116 if(str_utf8_isstart(c: *(str + cursor)))
1117 break;
1118 }
1119 return cursor;
1120}
1121
1122const char *str_utf8_find_nocase(const char *haystack, const char *needle, const char **end)
1123{
1124 while(*haystack) /* native implementation */
1125 {
1126 const char *a = haystack;
1127 const char *b = needle;
1128 const char *a_next = a;
1129 const char *b_next = b;
1130 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)))
1131 {
1132 a = a_next;
1133 b = b_next;
1134 }
1135 if(!(*b))
1136 {
1137 if(end != nullptr)
1138 *end = a_next;
1139 return haystack;
1140 }
1141 str_utf8_decode(ptr: &haystack);
1142 }
1143
1144 if(end != nullptr)
1145 *end = nullptr;
1146 return nullptr;
1147}
1148
1149int str_utf8_comp_nocase(const char *a, const char *b)
1150{
1151 int code_a;
1152 int code_b;
1153
1154 while(*a && *b)
1155 {
1156 code_a = str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &a));
1157 code_b = str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &b));
1158
1159 if(code_a != code_b)
1160 return code_a - code_b;
1161 }
1162 return (unsigned char)*a - (unsigned char)*b;
1163}
1164
1165int str_utf8_comp_nocase_num(const char *a, const char *b, int num)
1166{
1167 int code_a;
1168 int code_b;
1169 const char *old_a = a;
1170
1171 if(num <= 0)
1172 return 0;
1173
1174 while(*a && *b)
1175 {
1176 code_a = str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &a));
1177 code_b = str_utf8_tolower_codepoint(code: str_utf8_decode(ptr: &b));
1178
1179 if(code_a != code_b)
1180 return code_a - code_b;
1181
1182 if(a - old_a >= num)
1183 return 0;
1184 }
1185
1186 return (unsigned char)*a - (unsigned char)*b;
1187}
1188
1189const char *str_utf8_skip_whitespaces(const char *str)
1190{
1191 const char *str_old;
1192 int code;
1193
1194 while(*str)
1195 {
1196 str_old = str;
1197 code = str_utf8_decode(ptr: &str);
1198
1199 // check if unicode is not empty
1200 if(!str_utf8_isspace(code))
1201 {
1202 return str_old;
1203 }
1204 }
1205
1206 return str;
1207}
1208
1209int str_utf8_forward(const char *str, int cursor)
1210{
1211 const char *ptr = str + cursor;
1212 if(str_utf8_decode(ptr: &ptr) == 0)
1213 {
1214 return cursor;
1215 }
1216 return ptr - str;
1217}
1218
1219int str_utf8_check(const char *str)
1220{
1221 int codepoint;
1222 while((codepoint = str_utf8_decode(ptr: &str)))
1223 {
1224 if(codepoint == -1)
1225 {
1226 return 0;
1227 }
1228 }
1229 return 1;
1230}
1231
1232void str_utf8_copy_num(char *dst, const char *src, int dst_size, int num)
1233{
1234 int new_cursor;
1235 int cursor = 0;
1236
1237 while(src[cursor] && num > 0)
1238 {
1239 new_cursor = str_utf8_forward(str: src, cursor);
1240 if(new_cursor >= dst_size) // reserve 1 byte for the null termination
1241 break;
1242 else
1243 cursor = new_cursor;
1244 --num;
1245 }
1246
1247 str_copy(dst, src, dst_size: cursor < dst_size ? cursor + 1 : dst_size);
1248}
1249
1250void str_utf8_stats(const char *str, size_t max_size, size_t max_count, size_t *size, size_t *count)
1251{
1252 const char *cursor = str;
1253 *size = 0;
1254 *count = 0;
1255 while(*size < max_size && *count < max_count)
1256 {
1257 if(str_utf8_decode(ptr: &cursor) == 0)
1258 {
1259 break;
1260 }
1261 if((size_t)(cursor - str) >= max_size)
1262 {
1263 break;
1264 }
1265 *size = cursor - str;
1266 ++(*count);
1267 }
1268}
1269
1270size_t str_utf8_offset_bytes_to_chars(const char *str, size_t byte_offset)
1271{
1272 size_t char_offset = 0;
1273 size_t current_offset = 0;
1274 while(current_offset < byte_offset)
1275 {
1276 const size_t prev_byte_offset = current_offset;
1277 current_offset = str_utf8_forward(str, cursor: current_offset);
1278 if(current_offset == prev_byte_offset)
1279 break;
1280 char_offset++;
1281 }
1282 return char_offset;
1283}
1284
1285size_t str_utf8_offset_chars_to_bytes(const char *str, size_t char_offset)
1286{
1287 size_t byte_offset = 0;
1288 for(size_t i = 0; i < char_offset; i++)
1289 {
1290 const size_t prev_byte_offset = byte_offset;
1291 byte_offset = str_utf8_forward(str, cursor: byte_offset);
1292 if(byte_offset == prev_byte_offset)
1293 break;
1294 }
1295 return byte_offset;
1296}
1297
1298int str_utf8_dist(const char *a, const char *b)
1299{
1300 int buf_len = 2 * (str_length(str: a) + 1 + str_length(str: b) + 1);
1301 int *buf = (int *)calloc(nmemb: buf_len, size: sizeof(*buf));
1302 int result = str_utf8_dist_buffer(a, b, buf, buf_len);
1303 free(ptr: buf);
1304 return result;
1305}
1306
1307static int str_to_utf32_unchecked(const char *str, int **out)
1308{
1309 int out_len = 0;
1310 while((**out = str_utf8_decode(ptr: &str)))
1311 {
1312 (*out)++;
1313 out_len++;
1314 }
1315 return out_len;
1316}
1317
1318int str_utf8_dist_buffer(const char *a_utf8, const char *b_utf8, int *buf, int buf_len)
1319{
1320 int a_utf8_len = str_length(str: a_utf8);
1321 int b_utf8_len = str_length(str: b_utf8);
1322 int *a, *b; // UTF-32
1323 int a_len, b_len; // UTF-32 length
1324 dbg_assert(buf_len >= 2 * (a_utf8_len + 1 + b_utf8_len + 1), "buffer too small");
1325 if(a_utf8_len > b_utf8_len)
1326 {
1327 const char *tmp2 = a_utf8;
1328 a_utf8 = b_utf8;
1329 b_utf8 = tmp2;
1330 }
1331 a = buf;
1332 a_len = str_to_utf32_unchecked(str: a_utf8, out: &buf);
1333 b = buf;
1334 b_len = str_to_utf32_unchecked(str: b_utf8, out: &buf);
1335 return str_utf32_dist_buffer(a, a_len, b, b_len, buf, buf_len: buf_len - b_len - a_len);
1336}
1337
1338int str_utf32_dist_buffer(const int *a, int a_len, const int *b, int b_len, int *buf, int buf_len)
1339{
1340 int i, j;
1341 dbg_assert(buf_len >= (a_len + 1) + (b_len + 1), "buffer too small");
1342 if(a_len > b_len)
1343 {
1344 int tmp1 = a_len;
1345 const int *tmp2 = a;
1346
1347 a_len = b_len;
1348 a = b;
1349
1350 b_len = tmp1;
1351 b = tmp2;
1352 }
1353#define B(i, j) buf[((j) & 1) * (a_len + 1) + (i)]
1354 for(i = 0; i <= a_len; i++)
1355 {
1356 B(i, 0) = i;
1357 }
1358 for(j = 1; j <= b_len; j++)
1359 {
1360 B(0, j) = j;
1361 for(i = 1; i <= a_len; i++)
1362 {
1363 int subst = (a[i - 1] != b[j - 1]);
1364 B(i, j) = std::min(l: {
1365 B(i - 1, j) + 1,
1366 B(i, j - 1) + 1,
1367 B(i - 1, j - 1) + subst,
1368 });
1369 }
1370 }
1371 return B(a_len, b_len);
1372#undef B
1373}
1374