1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2010 Lennart Poettering
17 #include "alloc-util.h"
19 #include "extract-word.h"
22 #include "string-util.h"
25 int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) {
26 _cleanup_free_ char *s = NULL;
27 size_t allocated = 0, sz = 0;
31 char quote = 0; /* 0 or ' or " */
32 bool backslash = false; /* whether we've just seen a backslash */
37 /* Bail early if called after last value or with no input */
43 separators = WHITESPACE;
45 /* Parses the first word of a string, and returns it in
46 * *ret. Removes all quotes in the process. When parsing fails
47 * (because of an uneven number of quotes or similar), leaves
48 * the pointer *p at the first invalid character. */
50 if (flags & EXTRACT_DONT_COALESCE_SEPARATORS)
51 if (!GREEDY_REALLOC(s, allocated, sz+1))
54 for (;; (*p)++, c = **p) {
56 goto finish_force_terminate;
57 else if (strchr(separators, c)) {
58 if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
60 goto finish_force_next;
63 /* We found a non-blank character, so we will always
64 * want to return a string (even if it is empty),
65 * allocate it here. */
66 if (!GREEDY_REALLOC(s, allocated, sz+1))
72 for (;; (*p)++, c = **p) {
74 if (!GREEDY_REALLOC(s, allocated, sz+7))
78 if ((flags & EXTRACT_CUNESCAPE_RELAX) &&
79 (!quote || flags & EXTRACT_RELAX)) {
80 /* If we find an unquoted trailing backslash and we're in
81 * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the
84 * Unbalanced quotes will only be allowed in EXTRACT_RELAX
85 * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them.
88 goto finish_force_terminate;
90 if (flags & EXTRACT_RELAX)
91 goto finish_force_terminate;
95 if (flags & EXTRACT_CUNESCAPE) {
96 bool eight_bit = false;
99 r = cunescape_one(*p, (size_t) -1, &u, &eight_bit);
101 if (flags & EXTRACT_CUNESCAPE_RELAX) {
112 sz += utf8_encode_unichar(s + sz, u);
119 } else if (quote) { /* inside either single or double quotes */
120 for (;; (*p)++, c = **p) {
122 if (flags & EXTRACT_RELAX)
123 goto finish_force_terminate;
125 } else if (c == quote) { /* found the end quote */
128 } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
132 if (!GREEDY_REALLOC(s, allocated, sz+2))
140 for (;; (*p)++, c = **p) {
142 goto finish_force_terminate;
143 else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_QUOTES)) {
146 } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
149 } else if (strchr(separators, c)) {
150 if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
152 goto finish_force_next;
154 /* Skip additional coalesced separators. */
155 for (;; (*p)++, c = **p) {
157 goto finish_force_terminate;
158 if (!strchr(separators, c))
164 if (!GREEDY_REALLOC(s, allocated, sz+2))
173 finish_force_terminate:
189 #if 0 /// UNNEEDED by elogind
190 int extract_first_word_and_warn(
193 const char *separators,
196 const char *filename,
198 const char *rvalue) {
200 /* Try to unquote it, if it fails, warn about it and try again
201 * but this time using EXTRACT_CUNESCAPE_RELAX to keep the
202 * backslashes verbatim in invalid escape sequences. */
208 r = extract_first_word(p, ret, separators, flags);
212 if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) {
214 /* Retry it with EXTRACT_CUNESCAPE_RELAX. */
216 r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX);
218 /* It worked this time, hence it must have been an invalid escape sequence. */
219 log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Ignoring unknown escape sequences: \"%s\"", *ret);
223 /* If it's still EINVAL; then it must be unbalanced quoting, report this. */
225 return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue);
228 /* Can be any error, report it */
229 return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue);
232 /* We pass ExtractFlags as unsigned int (to avoid undefined behaviour when passing
233 * an object that undergoes default argument promotion as an argument to va_start).
234 * Let's make sure that ExtractFlags fits into an unsigned int. */
235 assert_cc(sizeof(enum ExtractFlags) <= sizeof(unsigned));
237 int extract_many_words(const char **p, const char *separators, unsigned flags, ...) {
242 /* Parses a number of words from a string, stripping any
243 * quotes if necessary. */
247 /* Count how many words are expected */
250 if (!va_arg(ap, char **))
259 /* Read all words into a temporary array */
261 for (c = 0; c < n; c++) {
263 r = extract_first_word(p, &l[c], separators, flags);
267 for (j = 0; j < c; j++)
277 /* If we managed to parse all words, return them in the passed
280 for (i = 0; i < n; i++) {
283 v = va_arg(ap, char **);