]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/extract-word.c
util-lib: split out fd-related operations into fd-util.[ch]
[thirdparty/systemd.git] / src / basic / extract-word.c
CommitLineData
84ac7bea
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
4f5dd394 22#include "escape.h"
84ac7bea
LP
23#include "utf8.h"
24#include "util.h"
84ac7bea
LP
25#include "extract-word.h"
26
27int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) {
28 _cleanup_free_ char *s = NULL;
29 size_t allocated = 0, sz = 0;
30 int r;
31
32 char quote = 0; /* 0 or ' or " */
33 bool backslash = false; /* whether we've just seen a backslash */
34 bool separator = false; /* whether we've just seen a separator */
35 bool start = true; /* false means we're looking at a value */
36
37 assert(p);
38 assert(ret);
39
40 if (!separators)
41 separators = WHITESPACE;
42
43 /* Bail early if called after last value or with no input */
44 if (!*p)
45 goto finish_force_terminate;
46
47 /* Parses the first word of a string, and returns it in
48 * *ret. Removes all quotes in the process. When parsing fails
49 * (because of an uneven number of quotes or similar), leaves
50 * the pointer *p at the first invalid character. */
51
52 for (;;) {
53 char c = **p;
54
55 if (start) {
56 if (flags & EXTRACT_DONT_COALESCE_SEPARATORS)
57 if (!GREEDY_REALLOC(s, allocated, sz+1))
58 return -ENOMEM;
59
60 if (c == 0)
61 goto finish_force_terminate;
62 else if (strchr(separators, c)) {
63 (*p) ++;
64 if (flags & EXTRACT_DONT_COALESCE_SEPARATORS)
65 goto finish_force_next;
66 continue;
67 }
68
69 /* We found a non-blank character, so we will always
70 * want to return a string (even if it is empty),
71 * allocate it here. */
72 if (!GREEDY_REALLOC(s, allocated, sz+1))
73 return -ENOMEM;
74
75 start = false;
76 }
77
78 if (backslash) {
79 if (!GREEDY_REALLOC(s, allocated, sz+7))
80 return -ENOMEM;
81
82 if (c == 0) {
83 if ((flags & EXTRACT_CUNESCAPE_RELAX) &&
84 (!quote || flags & EXTRACT_RELAX)) {
85 /* If we find an unquoted trailing backslash and we're in
86 * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the
87 * output.
88 *
89 * Unbalanced quotes will only be allowed in EXTRACT_RELAX
90 * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them.
91 */
92 s[sz++] = '\\';
93 goto finish_force_terminate;
94 }
95 if (flags & EXTRACT_RELAX)
96 goto finish_force_terminate;
97 return -EINVAL;
98 }
99
100 if (flags & EXTRACT_CUNESCAPE) {
101 uint32_t u;
102
103 r = cunescape_one(*p, (size_t) -1, &c, &u);
104 if (r < 0) {
105 if (flags & EXTRACT_CUNESCAPE_RELAX) {
106 s[sz++] = '\\';
107 s[sz++] = c;
108 goto end_escape;
109 }
110 return -EINVAL;
111 }
112
113 (*p) += r - 1;
114
115 if (c != 0)
116 s[sz++] = c; /* normal explicit char */
117 else
118 sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */
119 } else
120 s[sz++] = c;
121
122end_escape:
123 backslash = false;
124
125 } else if (quote) { /* inside either single or double quotes */
126 if (c == 0) {
127 if (flags & EXTRACT_RELAX)
128 goto finish_force_terminate;
129 return -EINVAL;
130 } else if (c == quote) /* found the end quote */
131 quote = 0;
132 else if (c == '\\')
133 backslash = true;
134 else {
135 if (!GREEDY_REALLOC(s, allocated, sz+2))
136 return -ENOMEM;
137
138 s[sz++] = c;
139 }
140
141 } else if (separator) {
142 if (c == 0)
143 goto finish_force_terminate;
144 if (!strchr(separators, c))
145 goto finish;
146
147 } else {
148 if (c == 0)
149 goto finish_force_terminate;
150 else if ((c == '\'' || c == '"') && (flags & EXTRACT_QUOTES))
151 quote = c;
152 else if (c == '\\')
153 backslash = true;
154 else if (strchr(separators, c)) {
155 if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
156 (*p) ++;
157 goto finish_force_next;
158 }
159 separator = true;
160 } else {
161 if (!GREEDY_REALLOC(s, allocated, sz+2))
162 return -ENOMEM;
163
164 s[sz++] = c;
165 }
166 }
167
168 (*p) ++;
169 }
170
171finish_force_terminate:
172 *p = NULL;
173finish:
174 if (!s) {
175 *p = NULL;
176 *ret = NULL;
177 return 0;
178 }
179
180finish_force_next:
181 s[sz] = 0;
182 *ret = s;
183 s = NULL;
184
185 return 1;
186}
187
188int extract_first_word_and_warn(
189 const char **p,
190 char **ret,
191 const char *separators,
192 ExtractFlags flags,
193 const char *unit,
194 const char *filename,
195 unsigned line,
196 const char *rvalue) {
197
dea7b6b0
LP
198 /* Try to unquote it, if it fails, warn about it and try again
199 * but this time using EXTRACT_CUNESCAPE_RELAX to keep the
200 * backslashes verbatim in invalid escape sequences. */
201
84ac7bea
LP
202 const char *save;
203 int r;
204
205 save = *p;
206 r = extract_first_word(p, ret, separators, flags);
dea7b6b0
LP
207 if (r >= 0)
208 return r;
209
210 if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) {
84ac7bea
LP
211
212 /* Retry it with EXTRACT_CUNESCAPE_RELAX. */
213 *p = save;
214 r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX);
dea7b6b0
LP
215 if (r >= 0) {
216 /* It worked this time, hence it must have been an invalid escape sequence we could correct. */
217 log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Invalid escape sequences in line, correcting: \"%s\"", rvalue);
218 return r;
219 }
220
221 /* If it's still EINVAL; then it must be unbalanced quoting, report this. */
222 if (r == -EINVAL)
223 return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue);
84ac7bea
LP
224 }
225
dea7b6b0
LP
226 /* Can be any error, report it */
227 return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue);
84ac7bea
LP
228}
229
230int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) {
231 va_list ap;
232 char **l;
233 int n = 0, i, c, r;
234
235 /* Parses a number of words from a string, stripping any
236 * quotes if necessary. */
237
238 assert(p);
239
240 /* Count how many words are expected */
241 va_start(ap, flags);
242 for (;;) {
243 if (!va_arg(ap, char **))
244 break;
245 n++;
246 }
247 va_end(ap);
248
249 if (n <= 0)
250 return 0;
251
252 /* Read all words into a temporary array */
253 l = newa0(char*, n);
254 for (c = 0; c < n; c++) {
255
256 r = extract_first_word(p, &l[c], separators, flags);
257 if (r < 0) {
258 int j;
259
260 for (j = 0; j < c; j++)
261 free(l[j]);
262
263 return r;
264 }
265
266 if (r == 0)
267 break;
268 }
269
270 /* If we managed to parse all words, return them in the passed
271 * in parameters */
272 va_start(ap, flags);
273 for (i = 0; i < n; i++) {
274 char **v;
275
276 v = va_arg(ap, char **);
277 assert(v);
278
279 *v = l[i];
280 }
281 va_end(ap);
282
283 return c;
284}