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