1 /* SPDX-License-Identifier: LGPL-2.1+ */
12 #include "alloc-util.h"
14 #include "extract-word.h"
17 #include "string-util.h"
20 int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) {
21 _cleanup_free_ char *s = NULL;
22 size_t allocated = 0, sz = 0;
26 char quote = 0; /* 0 or ' or " */
27 bool backslash = false; /* whether we've just seen a backslash */
32 /* Bail early if called after last value or with no input */
38 separators = WHITESPACE;
40 /* Parses the first word of a string, and returns it in
41 * *ret. Removes all quotes in the process. When parsing fails
42 * (because of an uneven number of quotes or similar), leaves
43 * the pointer *p at the first invalid character. */
45 if (flags & EXTRACT_DONT_COALESCE_SEPARATORS)
46 if (!GREEDY_REALLOC(s, allocated, sz+1))
49 for (;; (*p)++, c = **p) {
51 goto finish_force_terminate;
52 else if (strchr(separators, c)) {
53 if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
55 goto finish_force_next;
58 /* We found a non-blank character, so we will always
59 * want to return a string (even if it is empty),
60 * allocate it here. */
61 if (!GREEDY_REALLOC(s, allocated, sz+1))
67 for (;; (*p)++, c = **p) {
69 if (!GREEDY_REALLOC(s, allocated, sz+7))
73 if ((flags & EXTRACT_CUNESCAPE_RELAX) &&
74 (!quote || flags & EXTRACT_RELAX)) {
75 /* If we find an unquoted trailing backslash and we're in
76 * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the
79 * Unbalanced quotes will only be allowed in EXTRACT_RELAX
80 * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them.
83 goto finish_force_terminate;
85 if (flags & EXTRACT_RELAX)
86 goto finish_force_terminate;
90 if (flags & EXTRACT_CUNESCAPE) {
91 bool eight_bit = false;
94 r = cunescape_one(*p, (size_t) -1, &u, &eight_bit);
96 if (flags & EXTRACT_CUNESCAPE_RELAX) {
107 sz += utf8_encode_unichar(s + sz, u);
114 } else if (quote) { /* inside either single or double quotes */
115 for (;; (*p)++, c = **p) {
117 if (flags & EXTRACT_RELAX)
118 goto finish_force_terminate;
120 } else if (c == quote) { /* found the end quote */
123 } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
127 if (!GREEDY_REALLOC(s, allocated, sz+2))
135 for (;; (*p)++, c = **p) {
137 goto finish_force_terminate;
138 else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_QUOTES)) {
141 } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
144 } else if (strchr(separators, c)) {
145 if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
147 goto finish_force_next;
149 /* Skip additional coalesced separators. */
150 for (;; (*p)++, c = **p) {
152 goto finish_force_terminate;
153 if (!strchr(separators, c))
159 if (!GREEDY_REALLOC(s, allocated, sz+2))
168 finish_force_terminate:
184 #if 0 /// UNNEEDED by elogind
185 int extract_first_word_and_warn(
188 const char *separators,
191 const char *filename,
193 const char *rvalue) {
195 /* Try to unquote it, if it fails, warn about it and try again
196 * but this time using EXTRACT_CUNESCAPE_RELAX to keep the
197 * backslashes verbatim in invalid escape sequences. */
203 r = extract_first_word(p, ret, separators, flags);
207 if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) {
209 /* Retry it with EXTRACT_CUNESCAPE_RELAX. */
211 r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX);
213 /* It worked this time, hence it must have been an invalid escape sequence. */
214 log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Ignoring unknown escape sequences: \"%s\"", *ret);
218 /* If it's still EINVAL; then it must be unbalanced quoting, report this. */
220 return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue);
223 /* Can be any error, report it */
224 return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue);
227 /* We pass ExtractFlags as unsigned int (to avoid undefined behaviour when passing
228 * an object that undergoes default argument promotion as an argument to va_start).
229 * Let's make sure that ExtractFlags fits into an unsigned int. */
230 assert_cc(sizeof(enum ExtractFlags) <= sizeof(unsigned));
232 int extract_many_words(const char **p, const char *separators, unsigned flags, ...) {
237 /* Parses a number of words from a string, stripping any
238 * quotes if necessary. */
242 /* Count how many words are expected */
245 if (!va_arg(ap, char **))
254 /* Read all words into a temporary array */
256 for (c = 0; c < n; c++) {
258 r = extract_first_word(p, &l[c], separators, flags);
262 for (j = 0; j < c; j++)
272 /* If we managed to parse all words, return them in the passed
275 for (i = 0; i < n; i++) {
278 v = va_arg(ap, char **);