]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/string-util.c
Merge pull request #7042 from vcaputo/iteratedcache
[thirdparty/systemd.git] / src / basic / string-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <stdarg.h>
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <stdio_ext.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "alloc-util.h"
30 #include "gunicode.h"
31 #include "macro.h"
32 #include "string-util.h"
33 #include "terminal-util.h"
34 #include "utf8.h"
35 #include "util.h"
36
37 int strcmp_ptr(const char *a, const char *b) {
38
39 /* Like strcmp(), but tries to make sense of NULL pointers */
40 if (a && b)
41 return strcmp(a, b);
42
43 if (!a && b)
44 return -1;
45
46 if (a && !b)
47 return 1;
48
49 return 0;
50 }
51
52 char* endswith(const char *s, const char *postfix) {
53 size_t sl, pl;
54
55 assert(s);
56 assert(postfix);
57
58 sl = strlen(s);
59 pl = strlen(postfix);
60
61 if (pl == 0)
62 return (char*) s + sl;
63
64 if (sl < pl)
65 return NULL;
66
67 if (memcmp(s + sl - pl, postfix, pl) != 0)
68 return NULL;
69
70 return (char*) s + sl - pl;
71 }
72
73 char* endswith_no_case(const char *s, const char *postfix) {
74 size_t sl, pl;
75
76 assert(s);
77 assert(postfix);
78
79 sl = strlen(s);
80 pl = strlen(postfix);
81
82 if (pl == 0)
83 return (char*) s + sl;
84
85 if (sl < pl)
86 return NULL;
87
88 if (strcasecmp(s + sl - pl, postfix) != 0)
89 return NULL;
90
91 return (char*) s + sl - pl;
92 }
93
94 char* first_word(const char *s, const char *word) {
95 size_t sl, wl;
96 const char *p;
97
98 assert(s);
99 assert(word);
100
101 /* Checks if the string starts with the specified word, either
102 * followed by NUL or by whitespace. Returns a pointer to the
103 * NUL or the first character after the whitespace. */
104
105 sl = strlen(s);
106 wl = strlen(word);
107
108 if (sl < wl)
109 return NULL;
110
111 if (wl == 0)
112 return (char*) s;
113
114 if (memcmp(s, word, wl) != 0)
115 return NULL;
116
117 p = s + wl;
118 if (*p == 0)
119 return (char*) p;
120
121 if (!strchr(WHITESPACE, *p))
122 return NULL;
123
124 p += strspn(p, WHITESPACE);
125 return (char*) p;
126 }
127
128 static size_t strcspn_escaped(const char *s, const char *reject) {
129 bool escaped = false;
130 int n;
131
132 for (n=0; s[n]; n++) {
133 if (escaped)
134 escaped = false;
135 else if (s[n] == '\\')
136 escaped = true;
137 else if (strchr(reject, s[n]))
138 break;
139 }
140
141 /* if s ends in \, return index of previous char */
142 return n - escaped;
143 }
144
145 /* Split a string into words. */
146 const char* split(const char **state, size_t *l, const char *separator, bool quoted) {
147 const char *current;
148
149 current = *state;
150
151 if (!*current) {
152 assert(**state == '\0');
153 return NULL;
154 }
155
156 current += strspn(current, separator);
157 if (!*current) {
158 *state = current;
159 return NULL;
160 }
161
162 if (quoted && strchr("\'\"", *current)) {
163 char quotechars[2] = {*current, '\0'};
164
165 *l = strcspn_escaped(current + 1, quotechars);
166 if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] ||
167 (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
168 /* right quote missing or garbage at the end */
169 *state = current;
170 return NULL;
171 }
172 *state = current++ + *l + 2;
173 } else if (quoted) {
174 *l = strcspn_escaped(current, separator);
175 if (current[*l] && !strchr(separator, current[*l])) {
176 /* unfinished escape */
177 *state = current;
178 return NULL;
179 }
180 *state = current + *l;
181 } else {
182 *l = strcspn(current, separator);
183 *state = current + *l;
184 }
185
186 return current;
187 }
188
189 char *strnappend(const char *s, const char *suffix, size_t b) {
190 size_t a;
191 char *r;
192
193 if (!s && !suffix)
194 return strdup("");
195
196 if (!s)
197 return strndup(suffix, b);
198
199 if (!suffix)
200 return strdup(s);
201
202 assert(s);
203 assert(suffix);
204
205 a = strlen(s);
206 if (b > ((size_t) -1) - a)
207 return NULL;
208
209 r = new(char, a+b+1);
210 if (!r)
211 return NULL;
212
213 memcpy(r, s, a);
214 memcpy(r+a, suffix, b);
215 r[a+b] = 0;
216
217 return r;
218 }
219
220 char *strappend(const char *s, const char *suffix) {
221 return strnappend(s, suffix, strlen_ptr(suffix));
222 }
223
224 char *strjoin_real(const char *x, ...) {
225 va_list ap;
226 size_t l;
227 char *r, *p;
228
229 va_start(ap, x);
230
231 if (x) {
232 l = strlen(x);
233
234 for (;;) {
235 const char *t;
236 size_t n;
237
238 t = va_arg(ap, const char *);
239 if (!t)
240 break;
241
242 n = strlen(t);
243 if (n > ((size_t) -1) - l) {
244 va_end(ap);
245 return NULL;
246 }
247
248 l += n;
249 }
250 } else
251 l = 0;
252
253 va_end(ap);
254
255 r = new(char, l+1);
256 if (!r)
257 return NULL;
258
259 if (x) {
260 p = stpcpy(r, x);
261
262 va_start(ap, x);
263
264 for (;;) {
265 const char *t;
266
267 t = va_arg(ap, const char *);
268 if (!t)
269 break;
270
271 p = stpcpy(p, t);
272 }
273
274 va_end(ap);
275 } else
276 r[0] = 0;
277
278 return r;
279 }
280
281 char *strstrip(char *s) {
282 char *e;
283
284 if (!s)
285 return NULL;
286
287 /* Drops trailing whitespace. Modifies the string in
288 * place. Returns pointer to first non-space character */
289
290 s += strspn(s, WHITESPACE);
291
292 for (e = strchr(s, 0); e > s; e --)
293 if (!strchr(WHITESPACE, e[-1]))
294 break;
295
296 *e = 0;
297
298 return s;
299 }
300
301 char *delete_chars(char *s, const char *bad) {
302 char *f, *t;
303
304 /* Drops all specified bad characters, regardless where in the string */
305
306 if (!s)
307 return NULL;
308
309 if (!bad)
310 bad = WHITESPACE;
311
312 for (f = s, t = s; *f; f++) {
313 if (strchr(bad, *f))
314 continue;
315
316 *(t++) = *f;
317 }
318
319 *t = 0;
320
321 return s;
322 }
323
324 char *delete_trailing_chars(char *s, const char *bad) {
325 char *p, *c = s;
326
327 /* Drops all specified bad characters, at the end of the string */
328
329 if (!s)
330 return NULL;
331
332 if (!bad)
333 bad = WHITESPACE;
334
335 for (p = s; *p; p++)
336 if (!strchr(bad, *p))
337 c = p + 1;
338
339 *c = 0;
340
341 return s;
342 }
343
344 char *truncate_nl(char *s) {
345 assert(s);
346
347 s[strcspn(s, NEWLINE)] = 0;
348 return s;
349 }
350
351 char ascii_tolower(char x) {
352
353 if (x >= 'A' && x <= 'Z')
354 return x - 'A' + 'a';
355
356 return x;
357 }
358
359 char ascii_toupper(char x) {
360
361 if (x >= 'a' && x <= 'z')
362 return x - 'a' + 'A';
363
364 return x;
365 }
366
367 char *ascii_strlower(char *t) {
368 char *p;
369
370 assert(t);
371
372 for (p = t; *p; p++)
373 *p = ascii_tolower(*p);
374
375 return t;
376 }
377
378 char *ascii_strupper(char *t) {
379 char *p;
380
381 assert(t);
382
383 for (p = t; *p; p++)
384 *p = ascii_toupper(*p);
385
386 return t;
387 }
388
389 char *ascii_strlower_n(char *t, size_t n) {
390 size_t i;
391
392 if (n <= 0)
393 return t;
394
395 for (i = 0; i < n; i++)
396 t[i] = ascii_tolower(t[i]);
397
398 return t;
399 }
400
401 int ascii_strcasecmp_n(const char *a, const char *b, size_t n) {
402
403 for (; n > 0; a++, b++, n--) {
404 int x, y;
405
406 x = (int) (uint8_t) ascii_tolower(*a);
407 y = (int) (uint8_t) ascii_tolower(*b);
408
409 if (x != y)
410 return x - y;
411 }
412
413 return 0;
414 }
415
416 int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) {
417 int r;
418
419 r = ascii_strcasecmp_n(a, b, MIN(n, m));
420 if (r != 0)
421 return r;
422
423 if (n < m)
424 return -1;
425 else if (n > m)
426 return 1;
427 else
428 return 0;
429 }
430
431 bool chars_intersect(const char *a, const char *b) {
432 const char *p;
433
434 /* Returns true if any of the chars in a are in b. */
435 for (p = a; *p; p++)
436 if (strchr(b, *p))
437 return true;
438
439 return false;
440 }
441
442 bool string_has_cc(const char *p, const char *ok) {
443 const char *t;
444
445 assert(p);
446
447 /*
448 * Check if a string contains control characters. If 'ok' is
449 * non-NULL it may be a string containing additional CCs to be
450 * considered OK.
451 */
452
453 for (t = p; *t; t++) {
454 if (ok && strchr(ok, *t))
455 continue;
456
457 if (*t > 0 && *t < ' ')
458 return true;
459
460 if (*t == 127)
461 return true;
462 }
463
464 return false;
465 }
466
467 static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
468 size_t x;
469 char *r;
470
471 assert(s);
472 assert(percent <= 100);
473 assert(new_length >= 3);
474
475 if (old_length <= 3 || old_length <= new_length)
476 return strndup(s, old_length);
477
478 r = new0(char, new_length+3);
479 if (!r)
480 return NULL;
481
482 x = (new_length * percent) / 100;
483
484 if (x > new_length - 3)
485 x = new_length - 3;
486
487 memcpy(r, s, x);
488 r[x] = 0xe2; /* tri-dot ellipsis: … */
489 r[x+1] = 0x80;
490 r[x+2] = 0xa6;
491 memcpy(r + x + 3,
492 s + old_length - (new_length - x - 1),
493 new_length - x - 1);
494
495 return r;
496 }
497
498 char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) {
499 size_t x;
500 char *e;
501 const char *i, *j;
502 unsigned k, len, len2;
503 int r;
504
505 assert(s);
506 assert(percent <= 100);
507
508 if (new_length == (size_t) -1)
509 return strndup(s, old_length);
510
511 assert(new_length >= 3);
512
513 /* if no multibyte characters use ascii_ellipsize_mem for speed */
514 if (ascii_is_valid(s))
515 return ascii_ellipsize_mem(s, old_length, new_length, percent);
516
517 if (old_length <= 3 || old_length <= new_length)
518 return strndup(s, old_length);
519
520 x = (new_length * percent) / 100;
521
522 if (x > new_length - 3)
523 x = new_length - 3;
524
525 k = 0;
526 for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) {
527 char32_t c;
528
529 r = utf8_encoded_to_unichar(i, &c);
530 if (r < 0)
531 return NULL;
532 k += unichar_iswide(c) ? 2 : 1;
533 }
534
535 if (k > x) /* last character was wide and went over quota */
536 x++;
537
538 for (j = s + old_length; k < new_length && j > i; ) {
539 char32_t c;
540
541 j = utf8_prev_char(j);
542 r = utf8_encoded_to_unichar(j, &c);
543 if (r < 0)
544 return NULL;
545 k += unichar_iswide(c) ? 2 : 1;
546 }
547 assert(i <= j);
548
549 /* we don't actually need to ellipsize */
550 if (i == j)
551 return memdup(s, old_length + 1);
552
553 /* make space for ellipsis */
554 j = utf8_next_char(j);
555
556 len = i - s;
557 len2 = s + old_length - j;
558 e = new(char, len + 3 + len2 + 1);
559 if (!e)
560 return NULL;
561
562 /*
563 printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n",
564 old_length, new_length, x, len, len2, k);
565 */
566
567 memcpy(e, s, len);
568 e[len] = 0xe2; /* tri-dot ellipsis: … */
569 e[len + 1] = 0x80;
570 e[len + 2] = 0xa6;
571
572 memcpy(e + len + 3, j, len2 + 1);
573
574 return e;
575 }
576
577 char *ellipsize(const char *s, size_t length, unsigned percent) {
578
579 if (length == (size_t) -1)
580 return strdup(s);
581
582 return ellipsize_mem(s, strlen(s), length, percent);
583 }
584
585 bool nulstr_contains(const char *nulstr, const char *needle) {
586 const char *i;
587
588 if (!nulstr)
589 return false;
590
591 NULSTR_FOREACH(i, nulstr)
592 if (streq(i, needle))
593 return true;
594
595 return false;
596 }
597
598 char* strshorten(char *s, size_t l) {
599 assert(s);
600
601 if (strnlen(s, l+1) > l)
602 s[l] = 0;
603
604 return s;
605 }
606
607 char *strreplace(const char *text, const char *old_string, const char *new_string) {
608 size_t l, old_len, new_len, allocated = 0;
609 char *t, *ret = NULL;
610 const char *f;
611
612 assert(old_string);
613 assert(new_string);
614
615 if (!text)
616 return NULL;
617
618 old_len = strlen(old_string);
619 new_len = strlen(new_string);
620
621 l = strlen(text);
622 if (!GREEDY_REALLOC(ret, allocated, l+1))
623 return NULL;
624
625 f = text;
626 t = ret;
627 while (*f) {
628 size_t d, nl;
629
630 if (!startswith(f, old_string)) {
631 *(t++) = *(f++);
632 continue;
633 }
634
635 d = t - ret;
636 nl = l - old_len + new_len;
637
638 if (!GREEDY_REALLOC(ret, allocated, nl + 1))
639 return mfree(ret);
640
641 l = nl;
642 t = ret + d;
643
644 t = stpcpy(t, new_string);
645 f += old_len;
646 }
647
648 *t = 0;
649 return ret;
650 }
651
652 static void advance_offsets(ssize_t diff, size_t offsets[2], size_t shift[2], size_t size) {
653 if (!offsets)
654 return;
655
656 if ((size_t) diff < offsets[0])
657 shift[0] += size;
658 if ((size_t) diff < offsets[1])
659 shift[1] += size;
660 }
661
662 char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
663 const char *i, *begin = NULL;
664 enum {
665 STATE_OTHER,
666 STATE_ESCAPE,
667 STATE_BRACKET
668 } state = STATE_OTHER;
669 char *obuf = NULL;
670 size_t osz = 0, isz, shift[2] = {};
671 FILE *f;
672
673 assert(ibuf);
674 assert(*ibuf);
675
676 /* Strips ANSI color and replaces TABs by 8 spaces */
677
678 isz = _isz ? *_isz : strlen(*ibuf);
679
680 f = open_memstream(&obuf, &osz);
681 if (!f)
682 return NULL;
683
684 /* Note we turn off internal locking on f for performance reasons. It's safe to do so since we created f here
685 * and it doesn't leave our scope. */
686
687 (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
688
689 for (i = *ibuf; i < *ibuf + isz + 1; i++) {
690
691 switch (state) {
692
693 case STATE_OTHER:
694 if (i >= *ibuf + isz) /* EOT */
695 break;
696 else if (*i == '\x1B')
697 state = STATE_ESCAPE;
698 else if (*i == '\t') {
699 fputs(" ", f);
700 advance_offsets(i - *ibuf, highlight, shift, 7);
701 } else
702 fputc(*i, f);
703
704 break;
705
706 case STATE_ESCAPE:
707 if (i >= *ibuf + isz) { /* EOT */
708 fputc('\x1B', f);
709 advance_offsets(i - *ibuf, highlight, shift, 1);
710 break;
711 } else if (*i == '[') {
712 state = STATE_BRACKET;
713 begin = i + 1;
714 } else {
715 fputc('\x1B', f);
716 fputc(*i, f);
717 advance_offsets(i - *ibuf, highlight, shift, 1);
718 state = STATE_OTHER;
719 }
720
721 break;
722
723 case STATE_BRACKET:
724
725 if (i >= *ibuf + isz || /* EOT */
726 (!(*i >= '0' && *i <= '9') && !IN_SET(*i, ';', 'm'))) {
727 fputc('\x1B', f);
728 fputc('[', f);
729 advance_offsets(i - *ibuf, highlight, shift, 2);
730 state = STATE_OTHER;
731 i = begin-1;
732 } else if (*i == 'm')
733 state = STATE_OTHER;
734 break;
735 }
736 }
737
738 if (ferror(f)) {
739 fclose(f);
740 return mfree(obuf);
741 }
742
743 fclose(f);
744
745 free(*ibuf);
746 *ibuf = obuf;
747
748 if (_isz)
749 *_isz = osz;
750
751 if (highlight) {
752 highlight[0] += shift[0];
753 highlight[1] += shift[1];
754 }
755
756 return obuf;
757 }
758
759 char *strextend_with_separator(char **x, const char *separator, ...) {
760 bool need_separator;
761 size_t f, l, l_separator;
762 char *r, *p;
763 va_list ap;
764
765 assert(x);
766
767 l = f = strlen_ptr(*x);
768
769 need_separator = !isempty(*x);
770 l_separator = strlen_ptr(separator);
771
772 va_start(ap, separator);
773 for (;;) {
774 const char *t;
775 size_t n;
776
777 t = va_arg(ap, const char *);
778 if (!t)
779 break;
780
781 n = strlen(t);
782
783 if (need_separator)
784 n += l_separator;
785
786 if (n > ((size_t) -1) - l) {
787 va_end(ap);
788 return NULL;
789 }
790
791 l += n;
792 need_separator = true;
793 }
794 va_end(ap);
795
796 need_separator = !isempty(*x);
797
798 r = realloc(*x, l+1);
799 if (!r)
800 return NULL;
801
802 p = r + f;
803
804 va_start(ap, separator);
805 for (;;) {
806 const char *t;
807
808 t = va_arg(ap, const char *);
809 if (!t)
810 break;
811
812 if (need_separator && separator)
813 p = stpcpy(p, separator);
814
815 p = stpcpy(p, t);
816
817 need_separator = true;
818 }
819 va_end(ap);
820
821 assert(p == r + l);
822
823 *p = 0;
824 *x = r;
825
826 return r + l;
827 }
828
829 char *strrep(const char *s, unsigned n) {
830 size_t l;
831 char *r, *p;
832 unsigned i;
833
834 assert(s);
835
836 l = strlen(s);
837 p = r = malloc(l * n + 1);
838 if (!r)
839 return NULL;
840
841 for (i = 0; i < n; i++)
842 p = stpcpy(p, s);
843
844 *p = 0;
845 return r;
846 }
847
848 int split_pair(const char *s, const char *sep, char **l, char **r) {
849 char *x, *a, *b;
850
851 assert(s);
852 assert(sep);
853 assert(l);
854 assert(r);
855
856 if (isempty(sep))
857 return -EINVAL;
858
859 x = strstr(s, sep);
860 if (!x)
861 return -EINVAL;
862
863 a = strndup(s, x - s);
864 if (!a)
865 return -ENOMEM;
866
867 b = strdup(x + strlen(sep));
868 if (!b) {
869 free(a);
870 return -ENOMEM;
871 }
872
873 *l = a;
874 *r = b;
875
876 return 0;
877 }
878
879 int free_and_strdup(char **p, const char *s) {
880 char *t;
881
882 assert(p);
883
884 /* Replaces a string pointer with an strdup()ed new string,
885 * possibly freeing the old one. */
886
887 if (streq_ptr(*p, s))
888 return 0;
889
890 if (s) {
891 t = strdup(s);
892 if (!t)
893 return -ENOMEM;
894 } else
895 t = NULL;
896
897 free(*p);
898 *p = t;
899
900 return 1;
901 }
902
903 #if !HAVE_EXPLICIT_BZERO
904 /*
905 * Pointer to memset is volatile so that compiler must de-reference
906 * the pointer and can't assume that it points to any function in
907 * particular (such as memset, which it then might further "optimize")
908 * This approach is inspired by openssl's crypto/mem_clr.c.
909 */
910 typedef void *(*memset_t)(void *,int,size_t);
911
912 static volatile memset_t memset_func = memset;
913
914 void explicit_bzero(void *p, size_t l) {
915 memset_func(p, '\0', l);
916 }
917 #endif
918
919 char* string_erase(char *x) {
920 if (!x)
921 return NULL;
922
923 /* A delicious drop of snake-oil! To be called on memory where
924 * we stored passphrases or so, after we used them. */
925 explicit_bzero(x, strlen(x));
926 return x;
927 }
928
929 char *string_free_erase(char *s) {
930 return mfree(string_erase(s));
931 }
932
933 bool string_is_safe(const char *p) {
934 const char *t;
935
936 if (!p)
937 return false;
938
939 for (t = p; *t; t++) {
940 if (*t > 0 && *t < ' ') /* no control characters */
941 return false;
942
943 if (strchr(QUOTES "\\\x7f", *t))
944 return false;
945 }
946
947 return true;
948 }