/src/systemd/src/basic/extract-word.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include "alloc-util.h" |
4 | | #include "escape.h" |
5 | | #include "extract-word.h" |
6 | | #include "log.h" |
7 | | #include "string-util.h" |
8 | | #include "utf8.h" |
9 | | |
10 | 12.9M | int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) { |
11 | 12.9M | _cleanup_free_ char *s = NULL; |
12 | 12.9M | size_t sz = 0; |
13 | 12.9M | char quote = 0; /* 0 or ' or " */ |
14 | 12.9M | bool backslash = false; /* whether we've just seen a backslash */ |
15 | 12.9M | char c; |
16 | 12.9M | int r; |
17 | | |
18 | 12.9M | assert(p); |
19 | 12.9M | assert(ret); |
20 | 12.9M | assert(!FLAGS_SET(flags, EXTRACT_KEEP_QUOTE | EXTRACT_UNQUOTE)); |
21 | | |
22 | | /* Bail early if called after last value or with no input */ |
23 | 12.9M | if (!*p) |
24 | 4.08M | goto finish; |
25 | 8.83M | c = **p; |
26 | | |
27 | 8.83M | if (!separators) |
28 | 4.67M | separators = WHITESPACE; |
29 | | |
30 | | /* Parses the first word of a string, and returns it in |
31 | | * *ret. Removes all quotes in the process. When parsing fails |
32 | | * (because of an uneven number of quotes or similar), leaves |
33 | | * the pointer *p at the first invalid character. */ |
34 | | |
35 | 8.83M | if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) |
36 | 717k | if (!GREEDY_REALLOC(s, sz+1)) |
37 | 0 | return -ENOMEM; |
38 | | |
39 | 8.84M | for (;; (*p)++, c = **p) { |
40 | 8.84M | if (c == 0) |
41 | 399k | goto finish_force_terminate; |
42 | 8.44M | else if (strchr(separators, c)) { |
43 | 343k | if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { |
44 | 335k | if (!(flags & EXTRACT_RETAIN_SEPARATORS)) |
45 | 334k | (*p)++; |
46 | 335k | goto finish_force_next; |
47 | 335k | } |
48 | 8.10M | } else { |
49 | | /* We found a non-blank character, so we will always |
50 | | * want to return a string (even if it is empty), |
51 | | * allocate it here. */ |
52 | 8.10M | if (!GREEDY_REALLOC(s, sz+1)) |
53 | 0 | return -ENOMEM; |
54 | 8.10M | break; |
55 | 8.10M | } |
56 | 8.84M | } |
57 | | |
58 | 8.46M | for (;; (*p)++, c = **p) { |
59 | 8.46M | if (backslash) { |
60 | 182k | if (!GREEDY_REALLOC(s, sz+7)) |
61 | 0 | return -ENOMEM; |
62 | | |
63 | 182k | if (c == 0) { |
64 | 20.7k | if ((flags & EXTRACT_UNESCAPE_RELAX) && |
65 | 111 | (quote == 0 || flags & EXTRACT_RELAX)) { |
66 | | /* If we find an unquoted trailing backslash and we're in |
67 | | * EXTRACT_UNESCAPE_RELAX mode, keep it verbatim in the |
68 | | * output. |
69 | | * |
70 | | * Unbalanced quotes will only be allowed in EXTRACT_RELAX |
71 | | * mode, EXTRACT_UNESCAPE_RELAX mode does not allow them. |
72 | | */ |
73 | 75 | s[sz++] = '\\'; |
74 | 75 | goto finish_force_terminate; |
75 | 75 | } |
76 | 20.7k | if (flags & EXTRACT_RELAX) |
77 | 1.73k | goto finish_force_terminate; |
78 | 18.9k | return -EINVAL; |
79 | 20.7k | } |
80 | | |
81 | 161k | if (flags & (EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX)) { |
82 | 89.5k | bool eight_bit = false; |
83 | 89.5k | char32_t u; |
84 | | |
85 | 89.5k | if ((flags & EXTRACT_CUNESCAPE) && |
86 | 89.5k | (r = cunescape_one(*p, SIZE_MAX, &u, &eight_bit, false)) >= 0) { |
87 | | /* A valid escaped sequence */ |
88 | 58.8k | assert(r >= 1); |
89 | | |
90 | 58.8k | (*p) += r - 1; |
91 | | |
92 | 58.8k | if (eight_bit) |
93 | 35.8k | s[sz++] = u; |
94 | 22.9k | else |
95 | 22.9k | sz += utf8_encode_unichar(s + sz, u); |
96 | 58.8k | } else if ((flags & EXTRACT_UNESCAPE_SEPARATORS) && |
97 | 5.07k | (strchr(separators, **p) || **p == '\\')) |
98 | | /* An escaped separator char or the escape char itself */ |
99 | 3.43k | s[sz++] = c; |
100 | 27.2k | else if (flags & EXTRACT_UNESCAPE_RELAX) { |
101 | 8.73k | s[sz++] = '\\'; |
102 | 8.73k | s[sz++] = c; |
103 | 8.73k | } else |
104 | 18.4k | return -EINVAL; |
105 | 89.5k | } else |
106 | 71.9k | s[sz++] = c; |
107 | | |
108 | 142k | backslash = false; |
109 | | |
110 | 8.27M | } else if (quote != 0) { /* inside either single or double quotes */ |
111 | 1.48M | for (;; (*p)++, c = **p) { |
112 | 1.48M | if (c == 0) { |
113 | 12.7k | if (flags & EXTRACT_RELAX) |
114 | 1.49k | goto finish_force_terminate; |
115 | 11.2k | return -EINVAL; |
116 | 1.46M | } else if (c == quote) { /* found the end quote */ |
117 | 11.2k | quote = 0; |
118 | 11.2k | if (flags & EXTRACT_UNQUOTE) |
119 | 11.2k | break; |
120 | 1.45M | } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { |
121 | 22.4k | backslash = true; |
122 | 22.4k | break; |
123 | 22.4k | } |
124 | | |
125 | 1.43M | if (!GREEDY_REALLOC(s, sz+2)) |
126 | 0 | return -ENOMEM; |
127 | | |
128 | 1.43M | s[sz++] = c; |
129 | | |
130 | 1.43M | if (quote == 0) |
131 | 0 | break; |
132 | 1.43M | } |
133 | | |
134 | 8.23M | } else { |
135 | 211M | for (;; (*p)++, c = **p) { |
136 | 211M | if (c == 0) |
137 | 4.27M | goto finish_force_terminate; |
138 | 207M | else if (IN_SET(c, '\'', '"') && (flags & (EXTRACT_KEEP_QUOTE | EXTRACT_UNQUOTE))) { |
139 | 24.1k | quote = c; |
140 | 24.1k | if (flags & EXTRACT_UNQUOTE) |
141 | 24.1k | break; |
142 | 207M | } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { |
143 | 159k | backslash = true; |
144 | 159k | break; |
145 | 207M | } else if (strchr(separators, c)) { |
146 | 3.77M | if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { |
147 | 102k | if (!(flags & EXTRACT_RETAIN_SEPARATORS)) |
148 | 102k | (*p)++; |
149 | 102k | goto finish_force_next; |
150 | 102k | } |
151 | 3.67M | if (!(flags & EXTRACT_RETAIN_SEPARATORS)) |
152 | | /* Skip additional coalesced separators. */ |
153 | 7.44M | for (;; (*p)++, c = **p) { |
154 | 7.44M | if (c == 0) |
155 | 3.24k | goto finish_force_terminate; |
156 | 7.44M | if (!strchr(separators, c)) |
157 | 3.66M | break; |
158 | 7.44M | } |
159 | 3.66M | goto finish; |
160 | | |
161 | 3.67M | } |
162 | | |
163 | 203M | if (!GREEDY_REALLOC(s, sz+2)) |
164 | 0 | return -ENOMEM; |
165 | | |
166 | 203M | s[sz++] = c; |
167 | | |
168 | 203M | if (quote != 0) |
169 | 0 | break; |
170 | 203M | } |
171 | 8.23M | } |
172 | 8.46M | } |
173 | | |
174 | 4.68M | finish_force_terminate: |
175 | 4.68M | *p = NULL; |
176 | 12.4M | finish: |
177 | 12.4M | if (!s) { |
178 | 4.47M | *p = NULL; |
179 | 4.47M | *ret = NULL; |
180 | 4.47M | return 0; |
181 | 4.47M | } |
182 | | |
183 | 8.40M | finish_force_next: |
184 | 8.40M | s[sz] = 0; |
185 | 8.40M | *ret = TAKE_PTR(s); |
186 | | |
187 | 8.40M | return 1; |
188 | 12.4M | } |
189 | | |
190 | | int extract_first_word_and_warn( |
191 | | const char **p, |
192 | | char **ret, |
193 | | const char *separators, |
194 | | ExtractFlags flags, |
195 | | const char *unit, |
196 | | const char *filename, |
197 | | unsigned line, |
198 | 14.4k | const char *rvalue) { |
199 | | |
200 | | /* Try to unquote it, if it fails, warn about it and try again |
201 | | * but this time using EXTRACT_UNESCAPE_RELAX to keep the |
202 | | * backslashes verbatim in invalid escape sequences. */ |
203 | | |
204 | 14.4k | const char *save; |
205 | 14.4k | int r; |
206 | | |
207 | 14.4k | assert(p); |
208 | 14.4k | assert(ret); |
209 | | |
210 | 14.4k | save = *p; |
211 | 14.4k | r = extract_first_word(p, ret, separators, flags); |
212 | 14.4k | if (r >= 0) |
213 | 9.91k | return r; |
214 | | |
215 | 4.53k | if (r == -EINVAL && !(flags & EXTRACT_UNESCAPE_RELAX)) { |
216 | | |
217 | | /* Retry it with EXTRACT_UNESCAPE_RELAX. */ |
218 | 4.53k | *p = save; |
219 | 4.53k | r = extract_first_word(p, ret, separators, flags|EXTRACT_UNESCAPE_RELAX); |
220 | 4.53k | if (r >= 0) { |
221 | | /* It worked this time, hence it must have been an invalid escape sequence. */ |
222 | 4.18k | log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Ignoring unknown escape sequences: \"%s\"", *ret); |
223 | 4.18k | return r; |
224 | 4.18k | } |
225 | | |
226 | | /* If it's still EINVAL; then it must be unbalanced quoting, report this. */ |
227 | 349 | if (r == -EINVAL) |
228 | 349 | return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue); |
229 | 349 | } |
230 | | |
231 | | /* Can be any error, report it */ |
232 | 0 | return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue); |
233 | 4.53k | } |
234 | | |
235 | | /* We pass ExtractFlags as unsigned int (to avoid undefined behaviour when passing |
236 | | * an object that undergoes default argument promotion as an argument to va_start). |
237 | | * Let's make sure that ExtractFlags fits into an unsigned int. */ |
238 | | assert_cc(sizeof(enum ExtractFlags) <= sizeof(unsigned)); |
239 | | |
240 | 195k | int extract_many_words_internal(const char **p, const char *separators, unsigned flags, ...) { |
241 | 195k | va_list ap; |
242 | 195k | unsigned n = 0; |
243 | 195k | int r; |
244 | | |
245 | | /* Parses a number of words from a string, stripping any quotes if necessary. */ |
246 | | |
247 | 195k | assert(p); |
248 | | |
249 | | /* Count how many words are expected */ |
250 | 195k | va_start(ap, flags); |
251 | 195k | while (va_arg(ap, char**)) |
252 | 525k | n++; |
253 | 195k | va_end(ap); |
254 | | |
255 | 195k | if (n == 0) |
256 | 0 | return 0; |
257 | | |
258 | | /* Read all words into a temporary array */ |
259 | 195k | char **l = newa0(char*, n); |
260 | 195k | unsigned c; |
261 | | |
262 | 489k | for (c = 0; c < n; c++) { |
263 | 432k | r = extract_first_word(p, &l[c], separators, flags); |
264 | 432k | if (r < 0) { |
265 | 6.57k | free_many_charp(l, c); |
266 | 6.57k | return r; |
267 | 6.57k | } |
268 | 425k | if (r == 0) |
269 | 132k | break; |
270 | 425k | } |
271 | | |
272 | | /* If we managed to parse all words, return them in the passed in parameters */ |
273 | 195k | va_start(ap, flags); |
274 | 507k | FOREACH_ARRAY(i, l, n) { |
275 | 507k | char **v = ASSERT_PTR(va_arg(ap, char**)); |
276 | 507k | *v = *i; |
277 | 507k | } |
278 | 189k | va_end(ap); |
279 | | |
280 | 189k | return c; |
281 | 195k | } |