-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
#include <stdarg.h>
#include "alloc-util.h"
#include "escape.h"
+#include "extract-word.h"
#include "fileio.h"
#include "gunicode.h"
#include "locale-util.h"
#include "macro.h"
#include "memory-util.h"
#include "string-util.h"
+#include "strv.h"
#include "terminal-util.h"
#include "utf8.h"
#include "util.h"
-int strcmp_ptr(const char *a, const char *b) {
-
- /* Like strcmp(), but tries to make sense of NULL pointers */
- if (a && b)
- return strcmp(a, b);
-
- if (!a && b)
- return -1;
-
- if (a && !b)
- return 1;
-
- return 0;
-}
-
-char* endswith(const char *s, const char *postfix) {
- size_t sl, pl;
-
- assert(s);
- assert(postfix);
-
- sl = strlen(s);
- pl = strlen(postfix);
-
- if (pl == 0)
- return (char*) s + sl;
-
- if (sl < pl)
- return NULL;
-
- if (memcmp(s + sl - pl, postfix, pl) != 0)
- return NULL;
-
- return (char*) s + sl - pl;
-}
-
-char* endswith_no_case(const char *s, const char *postfix) {
- size_t sl, pl;
-
- assert(s);
- assert(postfix);
-
- sl = strlen(s);
- pl = strlen(postfix);
-
- if (pl == 0)
- return (char*) s + sl;
-
- if (sl < pl)
- return NULL;
-
- if (strcasecmp(s + sl - pl, postfix) != 0)
- return NULL;
-
- return (char*) s + sl - pl;
-}
-
char* first_word(const char *s, const char *word) {
size_t sl, wl;
const char *p;
return (char*) p;
}
-static size_t strcspn_escaped(const char *s, const char *reject) {
- bool escaped = false;
- int n;
-
- for (n=0; s[n]; n++) {
- if (escaped)
- escaped = false;
- else if (s[n] == '\\')
- escaped = true;
- else if (strchr(reject, s[n]))
- break;
- }
-
- /* if s ends in \, return index of previous char */
- return n - escaped;
-}
-
-/* Split a string into words. */
-const char* split(const char **state, size_t *l, const char *separator, SplitFlags flags) {
- const char *current;
-
- current = *state;
-
- if (!*current) {
- assert(**state == '\0');
- return NULL;
- }
-
- current += strspn(current, separator);
- if (!*current) {
- *state = current;
- return NULL;
- }
-
- if (flags & SPLIT_QUOTES && strchr("\'\"", *current)) {
- char quotechars[2] = {*current, '\0'};
-
- *l = strcspn_escaped(current + 1, quotechars);
- if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] ||
- (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
- /* right quote missing or garbage at the end */
- if (flags & SPLIT_RELAX) {
- *state = current + *l + 1 + (current[*l + 1] != '\0');
- return current + 1;
- }
- *state = current;
- return NULL;
- }
- *state = current++ + *l + 2;
- } else if (flags & SPLIT_QUOTES) {
- *l = strcspn_escaped(current, separator);
- if (current[*l] && !strchr(separator, current[*l]) && !(flags & SPLIT_RELAX)) {
- /* unfinished escape */
- *state = current;
- return NULL;
- }
- *state = current + *l;
- } else {
- *l = strcspn(current, separator);
- *state = current + *l;
- }
-
- return current;
-}
-
char *strnappend(const char *s, const char *suffix, size_t b) {
size_t a;
char *r;
assert(suffix);
a = strlen(s);
- if (b > ((size_t) -1) - a)
+ if (b > (SIZE_MAX) - a)
return NULL;
r = new(char, a+b+1);
char *strjoin_real(const char *x, ...) {
va_list ap;
- size_t l;
+ size_t l = 1;
char *r, *p;
va_start(ap, x);
+ for (const char *t = x; t; t = va_arg(ap, const char *)) {
+ size_t n;
- if (x) {
- l = strlen(x);
-
- for (;;) {
- const char *t;
- size_t n;
-
- t = va_arg(ap, const char *);
- if (!t)
- break;
-
- n = strlen(t);
- if (n > ((size_t) -1) - l) {
- va_end(ap);
- return NULL;
- }
-
- l += n;
+ n = strlen(t);
+ if (n > SIZE_MAX - l) {
+ va_end(ap);
+ return NULL;
}
- } else
- l = 0;
-
+ l += n;
+ }
va_end(ap);
- r = new(char, l+1);
+ p = r = new(char, l);
if (!r)
return NULL;
- if (x) {
- p = stpcpy(r, x);
-
- va_start(ap, x);
-
- for (;;) {
- const char *t;
-
- t = va_arg(ap, const char *);
- if (!t)
- break;
-
- p = stpcpy(p, t);
- }
+ va_start(ap, x);
+ for (const char *t = x; t; t = va_arg(ap, const char *))
+ p = stpcpy(p, t);
+ va_end(ap);
- va_end(ap);
- } else
- r[0] = 0;
+ *p = 0;
return r;
}
assert(s);
assert(percent <= 100);
- assert(new_length != (size_t) -1);
+ assert(new_length != SIZE_MAX);
if (old_length <= new_length)
return strndup(s, old_length);
assert(s);
assert(percent <= 100);
- if (new_length == (size_t) -1)
+ if (new_length == SIZE_MAX)
return strndup(s, old_length);
if (new_length == 0)
return *ibuf;
}
-char *strextend_with_separator(char **x, const char *separator, ...) {
- bool need_separator;
+char *strextend_with_separator_internal(char **x, const char *separator, ...) {
size_t f, l, l_separator;
- char *r, *p;
+ bool need_separator;
+ char *nr, *p;
va_list ap;
assert(x);
if (need_separator)
n += l_separator;
- if (n > ((size_t) -1) - l) {
+ if (n >= SIZE_MAX - l) {
va_end(ap);
return NULL;
}
need_separator = !isempty(*x);
- r = realloc(*x, l+1);
- if (!r)
+ nr = realloc(*x, GREEDY_ALLOC_ROUND_UP(l+1));
+ if (!nr)
return NULL;
- p = r + f;
+ *x = nr;
+ p = nr + f;
va_start(ap, separator);
for (;;) {
}
va_end(ap);
- assert(p == r + l);
+ assert(p == nr + l);
*p = 0;
- *x = r;
- return r + l;
+ return p;
}
char *strrep(const char *s, unsigned n) {
- size_t l;
char *r, *p;
- unsigned i;
+ size_t l;
assert(s);
if (!r)
return NULL;
- for (i = 0; i < n; i++)
+ for (unsigned i = 0; i < n; i++)
p = stpcpy(p, s);
*p = 0;
if (!p)
return false;
+ /* Checks if the specified string contains no quotes or control characters */
+
for (t = p; *t; t++) {
if (*t > 0 && *t < ' ') /* no control characters */
return false;
return true;
}
+
+char* string_erase(char *x) {
+ if (!x)
+ return NULL;
+
+ /* A delicious drop of snake-oil! To be called on memory where we stored passphrases or so, after we
+ * used them. */
+ explicit_bzero_safe(x, strlen(x));
+ return x;
+}
+
+int string_truncate_lines(const char *s, size_t n_lines, char **ret) {
+ const char *p = s, *e = s;
+ bool truncation_applied = false;
+ char *copy;
+ size_t n = 0;
+
+ assert(s);
+
+ /* Truncate after the specified number of lines. Returns > 0 if a truncation was applied or == 0 if
+ * there were fewer lines in the string anyway. Trailing newlines on input are ignored, and not
+ * generated either. */
+
+ for (;;) {
+ size_t k;
+
+ k = strcspn(p, "\n");
+
+ if (p[k] == 0) {
+ if (k == 0) /* final empty line */
+ break;
+
+ if (n >= n_lines) /* above threshold */
+ break;
+
+ e = p + k; /* last line to include */
+ break;
+ }
+
+ assert(p[k] == '\n');
+
+ if (n >= n_lines)
+ break;
+
+ if (k > 0)
+ e = p + k;
+
+ p += k + 1;
+ n++;
+ }
+
+ /* e points after the last character we want to keep */
+ if (isempty(e))
+ copy = strdup(s);
+ else {
+ if (!in_charset(e, "\n")) /* We only consider things truncated if we remove something that
+ * isn't a new-line or a series of them */
+ truncation_applied = true;
+
+ copy = strndup(s, e - s);
+ }
+ if (!copy)
+ return -ENOMEM;
+
+ *ret = copy;
+ return truncation_applied;
+}
+
+int string_extract_line(const char *s, size_t i, char **ret) {
+ const char *p = s;
+ size_t c = 0;
+
+ /* Extract the i'nth line from the specified string. Returns > 0 if there are more lines after that,
+ * and == 0 if we are looking at the last line or already beyond the last line. As special
+ * optimization, if the first line is requested and the string only consists of one line we return
+ * NULL, indicating the input string should be used as is, and avoid a memory allocation for a very
+ * common case. */
+
+ for (;;) {
+ const char *q;
+
+ q = strchr(p, '\n');
+ if (i == c) {
+ /* The line we are looking for! */
+
+ if (q) {
+ char *m;
+
+ m = strndup(p, q - p);
+ if (!m)
+ return -ENOMEM;
+
+ *ret = m;
+ return !isempty(q + 1); /* more coming? */
+ } else {
+ if (p == s)
+ *ret = NULL; /* Just use the input string */
+ else {
+ char *m;
+
+ m = strdup(p);
+ if (!m)
+ return -ENOMEM;
+
+ *ret = m;
+ }
+
+ return 0; /* The end */
+ }
+ }
+
+ if (!q) {
+ char *m;
+
+ /* No more lines, return empty line */
+
+ m = strdup("");
+ if (!m)
+ return -ENOMEM;
+
+ *ret = m;
+ return 0; /* The end */
+ }
+
+ p = q + 1;
+ c++;
+ }
+}
+
+int string_contains_word_strv(const char *string, const char *separators, char **words, const char **ret_word) {
+ /* In the default mode with no separators specified, we split on whitespace and
+ * don't coalesce separators. */
+ const ExtractFlags flags = separators ? EXTRACT_DONT_COALESCE_SEPARATORS : 0;
+
+ const char *found = NULL;
+
+ for (const char *p = string;;) {
+ _cleanup_free_ char *w = NULL;
+ int r;
+
+ r = extract_first_word(&p, &w, separators, flags);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ found = strv_find(words, w);
+ if (found)
+ break;
+ }
+
+ if (ret_word)
+ *ret_word = found;
+ return !!found;
+}