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