Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <errno.h>
4 : : #include <stdarg.h>
5 : : #include <stdbool.h>
6 : : #include <stddef.h>
7 : : #include <stdint.h>
8 : : #include <stdlib.h>
9 : : #include <string.h>
10 : : #include <syslog.h>
11 : :
12 : : #include "alloc-util.h"
13 : : #include "escape.h"
14 : : #include "extract-word.h"
15 : : #include "log.h"
16 : : #include "macro.h"
17 : : #include "string-util.h"
18 : : #include "utf8.h"
19 : :
20 : 58856 : int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) {
21 : 58856 : _cleanup_free_ char *s = NULL;
22 : 58856 : size_t allocated = 0, sz = 0;
23 : : char c;
24 : : int r;
25 : :
26 : 58856 : char quote = 0; /* 0 or ' or " */
27 : 58856 : bool backslash = false; /* whether we've just seen a backslash */
28 : :
29 [ - + ]: 58856 : assert(p);
30 [ - + ]: 58856 : assert(ret);
31 : :
32 : : /* Bail early if called after last value or with no input */
33 [ + + ]: 58856 : if (!*p)
34 : 10334 : goto finish;
35 : 48522 : c = **p;
36 : :
37 [ + + ]: 48522 : if (!separators)
38 : 23574 : separators = WHITESPACE;
39 : :
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. */
44 : :
45 [ + + ]: 48522 : if (flags & EXTRACT_DONT_COALESCE_SEPARATORS)
46 [ - + ]: 8784 : if (!GREEDY_REALLOC(s, allocated, sz+1))
47 : 0 : return -ENOMEM;
48 : :
49 : 396 : for (;; (*p)++, c = **p) {
50 [ + + ]: 48918 : if (c == 0)
51 : 96 : goto finish_force_terminate;
52 [ + + ]: 48822 : else if (strchr(separators, c)) {
53 [ + + ]: 420 : if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
54 : 24 : (*p)++;
55 : 24 : goto finish_force_next;
56 : : }
57 : : } else {
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 [ - + ]: 48402 : if (!GREEDY_REALLOC(s, allocated, sz+1))
62 : 0 : return -ENOMEM;
63 : 48402 : break;
64 : : }
65 : : }
66 : :
67 : 1720 : for (;; (*p)++, c = **p) {
68 [ + + ]: 50122 : if (backslash) {
69 [ - + ]: 364 : if (!GREEDY_REALLOC(s, allocated, sz+7))
70 : 0 : return -ENOMEM;
71 : :
72 [ + + ]: 364 : if (c == 0) {
73 [ + + + + ]: 108 : if ((flags & EXTRACT_CUNESCAPE_RELAX) &&
74 [ + + ]: 24 : (!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
77 : : * output.
78 : : *
79 : : * Unbalanced quotes will only be allowed in EXTRACT_RELAX
80 : : * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them.
81 : : */
82 : 32 : s[sz++] = '\\';
83 : 32 : goto finish_force_terminate;
84 : : }
85 [ + + ]: 76 : if (flags & EXTRACT_RELAX)
86 : 20 : goto finish_force_terminate;
87 : 56 : return -EINVAL;
88 : : }
89 : :
90 [ + + ]: 256 : if (flags & EXTRACT_CUNESCAPE) {
91 : 192 : bool eight_bit = false;
92 : : char32_t u;
93 : :
94 : 192 : r = cunescape_one(*p, (size_t) -1, &u, &eight_bit);
95 [ + + ]: 192 : if (r < 0) {
96 [ + + ]: 88 : if (flags & EXTRACT_CUNESCAPE_RELAX) {
97 : 56 : s[sz++] = '\\';
98 : 56 : s[sz++] = c;
99 : : } else
100 : 32 : return -EINVAL;
101 : : } else {
102 : 104 : (*p) += r - 1;
103 : :
104 [ + + ]: 104 : if (eight_bit)
105 : 20 : s[sz++] = u;
106 : : else
107 : 84 : sz += utf8_encode_unichar(s + sz, u);
108 : : }
109 : : } else
110 : 64 : s[sz++] = c;
111 : :
112 : 224 : backslash = false;
113 : :
114 [ + + ]: 49758 : } else if (quote) { /* inside either single or double quotes */
115 : 4148 : for (;; (*p)++, c = **p) {
116 [ + + ]: 4832 : if (c == 0) {
117 [ + + ]: 104 : if (flags & EXTRACT_RELAX)
118 : 12 : goto finish_force_terminate;
119 : 92 : return -EINVAL;
120 [ + + ]: 4728 : } else if (c == quote) { /* found the end quote */
121 : 488 : quote = 0;
122 : 488 : break;
123 [ + + + + ]: 4240 : } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
124 : 92 : backslash = true;
125 : 92 : break;
126 : : } else {
127 [ - + ]: 4148 : if (!GREEDY_REALLOC(s, allocated, sz+2))
128 : 0 : return -ENOMEM;
129 : :
130 : 4148 : s[sz++] = c;
131 : : }
132 : : }
133 : :
134 : : } else {
135 : 769740 : for (;; (*p)++, c = **p) {
136 [ + + ]: 818814 : if (c == 0)
137 : 10946 : goto finish_force_terminate;
138 [ + + + + : 807868 : else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_UNQUOTE)) {
+ + ]
139 : 644 : quote = c;
140 : 644 : break;
141 [ + + + + ]: 807224 : } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
142 : 272 : backslash = true;
143 : 272 : break;
144 [ + + ]: 806952 : } else if (strchr(separators, c)) {
145 [ + + ]: 37212 : if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
146 : 7000 : (*p)++;
147 : 7000 : goto finish_force_next;
148 : : }
149 : : /* Skip additional coalesced separators. */
150 : 41088 : for (;; (*p)++, c = **p) {
151 [ + + ]: 71300 : if (c == 0)
152 : 176 : goto finish_force_terminate;
153 [ + + ]: 71124 : if (!strchr(separators, c))
154 : 30036 : break;
155 : : }
156 : 30036 : goto finish;
157 : :
158 : : } else {
159 [ - + ]: 769740 : if (!GREEDY_REALLOC(s, allocated, sz+2))
160 : 0 : return -ENOMEM;
161 : :
162 : 769740 : s[sz++] = c;
163 : : }
164 : : }
165 : : }
166 : : }
167 : :
168 : 11282 : finish_force_terminate:
169 : 11282 : *p = NULL;
170 : 51652 : finish:
171 [ + + ]: 51652 : if (!s) {
172 : 10414 : *p = NULL;
173 : 10414 : *ret = NULL;
174 : 10414 : return 0;
175 : : }
176 : :
177 : 41238 : finish_force_next:
178 : 48262 : s[sz] = 0;
179 : 48262 : *ret = TAKE_PTR(s);
180 : :
181 : 48262 : return 1;
182 : : }
183 : :
184 : 564 : int extract_first_word_and_warn(
185 : : const char **p,
186 : : char **ret,
187 : : const char *separators,
188 : : ExtractFlags flags,
189 : : const char *unit,
190 : : const char *filename,
191 : : unsigned line,
192 : : const char *rvalue) {
193 : :
194 : : /* Try to unquote it, if it fails, warn about it and try again
195 : : * but this time using EXTRACT_CUNESCAPE_RELAX to keep the
196 : : * backslashes verbatim in invalid escape sequences. */
197 : :
198 : : const char *save;
199 : : int r;
200 : :
201 : 564 : save = *p;
202 : 564 : r = extract_first_word(p, ret, separators, flags);
203 [ + + ]: 564 : if (r >= 0)
204 : 500 : return r;
205 : :
206 [ + - + - ]: 64 : if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) {
207 : :
208 : : /* Retry it with EXTRACT_CUNESCAPE_RELAX. */
209 : 64 : *p = save;
210 : 64 : r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX);
211 [ + + ]: 64 : if (r >= 0) {
212 : : /* It worked this time, hence it must have been an invalid escape sequence. */
213 [ + - ]: 36 : log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Ignoring unknown escape sequences: \"%s\"", *ret);
214 : 36 : return r;
215 : : }
216 : :
217 : : /* If it's still EINVAL; then it must be unbalanced quoting, report this. */
218 [ + - ]: 28 : if (r == -EINVAL)
219 [ + - ]: 28 : return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue);
220 : : }
221 : :
222 : : /* Can be any error, report it */
223 [ # # ]: 0 : return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue);
224 : : }
225 : :
226 : : /* We pass ExtractFlags as unsigned int (to avoid undefined behaviour when passing
227 : : * an object that undergoes default argument promotion as an argument to va_start).
228 : : * Let's make sure that ExtractFlags fits into an unsigned int. */
229 : : assert_cc(sizeof(enum ExtractFlags) <= sizeof(unsigned));
230 : :
231 : 360 : int extract_many_words(const char **p, const char *separators, unsigned flags, ...) {
232 : : va_list ap;
233 : : char **l;
234 : 360 : int n = 0, i, c, r;
235 : :
236 : : /* Parses a number of words from a string, stripping any
237 : : * quotes if necessary. */
238 : :
239 [ - + ]: 360 : assert(p);
240 : :
241 : : /* Count how many words are expected */
242 : 360 : va_start(ap, flags);
243 : : for (;;) {
244 [ + + ]: 2396 : if (!va_arg(ap, char **))
245 : 360 : break;
246 : 2036 : n++;
247 : : }
248 : 360 : va_end(ap);
249 : :
250 [ + + ]: 360 : if (n <= 0)
251 : 4 : return 0;
252 : :
253 : : /* Read all words into a temporary array */
254 [ - + - + : 356 : l = newa0(char*, n);
- + ]
255 [ + + ]: 1928 : for (c = 0; c < n; c++) {
256 : :
257 : 1716 : r = extract_first_word(p, &l[c], separators, flags);
258 [ + + ]: 1716 : if (r < 0) {
259 : : int j;
260 : :
261 [ + + ]: 16 : for (j = 0; j < c; j++)
262 : 8 : free(l[j]);
263 : :
264 : 8 : return r;
265 : : }
266 : :
267 [ + + ]: 1708 : if (r == 0)
268 : 136 : break;
269 : : }
270 : :
271 : : /* If we managed to parse all words, return them in the passed
272 : : * in parameters */
273 : 348 : va_start(ap, flags);
274 [ + + ]: 2336 : for (i = 0; i < n; i++) {
275 : : char **v;
276 : :
277 : 1988 : v = va_arg(ap, char **);
278 [ - + ]: 1988 : assert(v);
279 : :
280 : 1988 : *v = l[i];
281 : : }
282 : 348 : va_end(ap);
283 : :
284 : 348 : return c;
285 : : }
|