Coverage Report

Created: 2026-09-14 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}