]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/string-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2010 Lennart Poettering
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.
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.
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/>.
28 #include "alloc-util.h"
31 #include "string-util.h"
35 int strcmp_ptr(const char *a
, const char *b
) {
37 /* Like strcmp(), but tries to make sense of NULL pointers */
50 char* endswith(const char *s
, const char *postfix
) {
60 return (char*) s
+ sl
;
65 if (memcmp(s
+ sl
- pl
, postfix
, pl
) != 0)
68 return (char*) s
+ sl
- pl
;
71 char* endswith_no_case(const char *s
, const char *postfix
) {
81 return (char*) s
+ sl
;
86 if (strcasecmp(s
+ sl
- pl
, postfix
) != 0)
89 return (char*) s
+ sl
- pl
;
92 char* first_word(const char *s
, const char *word
) {
99 /* Checks if the string starts with the specified word, either
100 * followed by NUL or by whitespace. Returns a pointer to the
101 * NUL or the first character after the whitespace. */
112 if (memcmp(s
, word
, wl
) != 0)
119 if (!strchr(WHITESPACE
, *p
))
122 p
+= strspn(p
, WHITESPACE
);
126 static size_t strcspn_escaped(const char *s
, const char *reject
) {
127 bool escaped
= false;
130 for (n
=0; s
[n
]; n
++) {
133 else if (s
[n
] == '\\')
135 else if (strchr(reject
, s
[n
]))
139 /* if s ends in \, return index of previous char */
143 /* Split a string into words. */
144 const char* split(const char **state
, size_t *l
, const char *separator
, bool quoted
) {
150 assert(**state
== '\0');
154 current
+= strspn(current
, separator
);
160 if (quoted
&& strchr("\'\"", *current
)) {
161 char quotechars
[2] = {*current
, '\0'};
163 *l
= strcspn_escaped(current
+ 1, quotechars
);
164 if (current
[*l
+ 1] == '\0' || current
[*l
+ 1] != quotechars
[0] ||
165 (current
[*l
+ 2] && !strchr(separator
, current
[*l
+ 2]))) {
166 /* right quote missing or garbage at the end */
170 *state
= current
++ + *l
+ 2;
172 *l
= strcspn_escaped(current
, separator
);
173 if (current
[*l
] && !strchr(separator
, current
[*l
])) {
174 /* unfinished escape */
178 *state
= current
+ *l
;
180 *l
= strcspn(current
, separator
);
181 *state
= current
+ *l
;
187 char *strnappend(const char *s
, const char *suffix
, size_t b
) {
195 return strndup(suffix
, b
);
204 if (b
> ((size_t) -1) - a
)
207 r
= new(char, a
+b
+1);
212 memcpy(r
+a
, suffix
, b
);
218 char *strappend(const char *s
, const char *suffix
) {
219 return strnappend(s
, suffix
, strlen_ptr(suffix
));
222 char *strjoin_real(const char *x
, ...) {
236 t
= va_arg(ap
, const char *);
241 if (n
> ((size_t) -1) - l
) {
265 t
= va_arg(ap
, const char *);
279 char *strstrip(char *s
) {
285 /* Drops trailing whitespace. Modifies the string in
286 * place. Returns pointer to first non-space character */
288 s
+= strspn(s
, WHITESPACE
);
290 for (e
= strchr(s
, 0); e
> s
; e
--)
291 if (!strchr(WHITESPACE
, e
[-1]))
299 char *delete_chars(char *s
, const char *bad
) {
302 /* Drops all specified bad characters, regardless where in the string */
310 for (f
= s
, t
= s
; *f
; f
++) {
322 char *delete_trailing_chars(char *s
, const char *bad
) {
325 /* Drops all specified bad characters, at the end of the string */
334 if (!strchr(bad
, *p
))
342 char *truncate_nl(char *s
) {
345 s
[strcspn(s
, NEWLINE
)] = 0;
349 char ascii_tolower(char x
) {
351 if (x
>= 'A' && x
<= 'Z')
352 return x
- 'A' + 'a';
357 char ascii_toupper(char x
) {
359 if (x
>= 'a' && x
<= 'z')
360 return x
- 'a' + 'A';
365 char *ascii_strlower(char *t
) {
371 *p
= ascii_tolower(*p
);
376 char *ascii_strupper(char *t
) {
382 *p
= ascii_toupper(*p
);
387 char *ascii_strlower_n(char *t
, size_t n
) {
393 for (i
= 0; i
< n
; i
++)
394 t
[i
] = ascii_tolower(t
[i
]);
399 int ascii_strcasecmp_n(const char *a
, const char *b
, size_t n
) {
401 for (; n
> 0; a
++, b
++, n
--) {
404 x
= (int) (uint8_t) ascii_tolower(*a
);
405 y
= (int) (uint8_t) ascii_tolower(*b
);
414 int ascii_strcasecmp_nn(const char *a
, size_t n
, const char *b
, size_t m
) {
417 r
= ascii_strcasecmp_n(a
, b
, MIN(n
, m
));
429 bool chars_intersect(const char *a
, const char *b
) {
432 /* Returns true if any of the chars in a are in b. */
440 bool string_has_cc(const char *p
, const char *ok
) {
446 * Check if a string contains control characters. If 'ok' is
447 * non-NULL it may be a string containing additional CCs to be
451 for (t
= p
; *t
; t
++) {
452 if (ok
&& strchr(ok
, *t
))
455 if (*t
> 0 && *t
< ' ')
465 static char *ascii_ellipsize_mem(const char *s
, size_t old_length
, size_t new_length
, unsigned percent
) {
470 assert(percent
<= 100);
471 assert(new_length
>= 3);
473 if (old_length
<= 3 || old_length
<= new_length
)
474 return strndup(s
, old_length
);
476 r
= new0(char, new_length
+3);
480 x
= (new_length
* percent
) / 100;
482 if (x
> new_length
- 3)
486 r
[x
] = 0xe2; /* tri-dot ellipsis: … */
490 s
+ old_length
- (new_length
- x
- 1),
496 char *ellipsize_mem(const char *s
, size_t old_length
, size_t new_length
, unsigned percent
) {
500 unsigned k
, len
, len2
;
504 assert(percent
<= 100);
506 if (new_length
== (size_t) -1)
507 return strndup(s
, old_length
);
509 assert(new_length
>= 3);
511 /* if no multibyte characters use ascii_ellipsize_mem for speed */
512 if (ascii_is_valid(s
))
513 return ascii_ellipsize_mem(s
, old_length
, new_length
, percent
);
515 if (old_length
<= 3 || old_length
<= new_length
)
516 return strndup(s
, old_length
);
518 x
= (new_length
* percent
) / 100;
520 if (x
> new_length
- 3)
524 for (i
= s
; k
< x
&& i
< s
+ old_length
; i
= utf8_next_char(i
)) {
527 r
= utf8_encoded_to_unichar(i
, &c
);
530 k
+= unichar_iswide(c
) ? 2 : 1;
533 if (k
> x
) /* last character was wide and went over quota */
536 for (j
= s
+ old_length
; k
< new_length
&& j
> i
; ) {
539 j
= utf8_prev_char(j
);
540 r
= utf8_encoded_to_unichar(j
, &c
);
543 k
+= unichar_iswide(c
) ? 2 : 1;
547 /* we don't actually need to ellipsize */
549 return memdup(s
, old_length
+ 1);
551 /* make space for ellipsis */
552 j
= utf8_next_char(j
);
555 len2
= s
+ old_length
- j
;
556 e
= new(char, len
+ 3 + len2
+ 1);
561 printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n",
562 old_length, new_length, x, len, len2, k);
566 e
[len
] = 0xe2; /* tri-dot ellipsis: … */
570 memcpy(e
+ len
+ 3, j
, len2
+ 1);
575 char *ellipsize(const char *s
, size_t length
, unsigned percent
) {
577 if (length
== (size_t) -1)
580 return ellipsize_mem(s
, strlen(s
), length
, percent
);
583 bool nulstr_contains(const char *nulstr
, const char *needle
) {
589 NULSTR_FOREACH(i
, nulstr
)
590 if (streq(i
, needle
))
596 char* strshorten(char *s
, size_t l
) {
599 if (strnlen(s
, l
+1) > l
)
605 char *strreplace(const char *text
, const char *old_string
, const char *new_string
) {
608 size_t l
, old_len
, new_len
;
614 old_len
= strlen(old_string
);
615 new_len
= strlen(new_string
);
628 if (!startswith(f
, old_string
)) {
634 nl
= l
- old_len
+ new_len
;
635 a
= realloc(r
, nl
+ 1);
643 t
= stpcpy(t
, new_string
);
654 char *strip_tab_ansi(char **ibuf
, size_t *_isz
) {
655 const char *i
, *begin
= NULL
;
660 } state
= STATE_OTHER
;
668 /* Strips ANSI color and replaces TABs by 8 spaces */
670 isz
= _isz
? *_isz
: strlen(*ibuf
);
672 f
= open_memstream(&obuf
, &osz
);
676 /* Note we use the _unlocked() stdio variants on f for performance
677 * reasons. It's safe to do so since we created f here and it
678 * doesn't leave our scope.
681 for (i
= *ibuf
; i
< *ibuf
+ isz
+ 1; i
++) {
686 if (i
>= *ibuf
+ isz
) /* EOT */
688 else if (*i
== '\x1B')
689 state
= STATE_ESCAPE
;
691 fputs_unlocked(" ", f
);
693 fputc_unlocked(*i
, f
);
697 if (i
>= *ibuf
+ isz
) { /* EOT */
698 fputc_unlocked('\x1B', f
);
700 } else if (*i
== '[') {
701 state
= STATE_BRACKET
;
704 fputc_unlocked('\x1B', f
);
705 fputc_unlocked(*i
, f
);
713 if (i
>= *ibuf
+ isz
|| /* EOT */
714 (!(*i
>= '0' && *i
<= '9') && !IN_SET(*i
, ';', 'm'))) {
715 fputc_unlocked('\x1B', f
);
716 fputc_unlocked('[', f
);
719 } else if (*i
== 'm')
741 char *strextend(char **x
, ...) {
748 l
= f
= strlen_ptr(*x
);
755 t
= va_arg(ap
, const char *);
760 if (n
> ((size_t) -1) - l
) {
769 r
= realloc(*x
, l
+1);
779 t
= va_arg(ap
, const char *);
793 char *strrep(const char *s
, unsigned n
) {
801 p
= r
= malloc(l
* n
+ 1);
805 for (i
= 0; i
< n
; i
++)
812 int split_pair(const char *s
, const char *sep
, char **l
, char **r
) {
827 a
= strndup(s
, x
- s
);
831 b
= strdup(x
+ strlen(sep
));
843 int free_and_strdup(char **p
, const char *s
) {
848 /* Replaces a string pointer with an strdup()ed new string,
849 * possibly freeing the old one. */
851 if (streq_ptr(*p
, s
))
867 #if !HAVE_EXPLICIT_BZERO
869 * Pointer to memset is volatile so that compiler must de-reference
870 * the pointer and can't assume that it points to any function in
871 * particular (such as memset, which it then might further "optimize")
872 * This approach is inspired by openssl's crypto/mem_clr.c.
874 typedef void *(*memset_t
)(void *,int,size_t);
876 static volatile memset_t memset_func
= memset
;
878 void explicit_bzero(void *p
, size_t l
) {
879 memset_func(p
, '\0', l
);
883 char* string_erase(char *x
) {
887 /* A delicious drop of snake-oil! To be called on memory where
888 * we stored passphrases or so, after we used them. */
889 explicit_bzero(x
, strlen(x
));
893 char *string_free_erase(char *s
) {
894 return mfree(string_erase(s
));
897 bool string_is_safe(const char *p
) {
903 for (t
= p
; *t
; t
++) {
904 if (*t
> 0 && *t
< ' ') /* no control characters */
907 if (strchr(QUOTES
"\\\x7f", *t
))