1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2010 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
30 #include "alloc-util.h"
32 #include "extract-word.h"
35 #include "string-util.h"
38 int extract_first_word(const char **p
, char **ret
, const char *separators
, ExtractFlags flags
) {
39 _cleanup_free_
char *s
= NULL
;
40 size_t allocated
= 0, sz
= 0;
44 char quote
= 0; /* 0 or ' or " */
45 bool backslash
= false; /* whether we've just seen a backslash */
50 /* Bail early if called after last value or with no input */
56 separators
= WHITESPACE
;
58 /* Parses the first word of a string, and returns it in
59 * *ret. Removes all quotes in the process. When parsing fails
60 * (because of an uneven number of quotes or similar), leaves
61 * the pointer *p at the first invalid character. */
63 if (flags
& EXTRACT_DONT_COALESCE_SEPARATORS
)
64 if (!GREEDY_REALLOC(s
, allocated
, sz
+1))
67 for (;; (*p
)++, c
= **p
) {
69 goto finish_force_terminate
;
70 else if (strchr(separators
, c
)) {
71 if (flags
& EXTRACT_DONT_COALESCE_SEPARATORS
) {
73 goto finish_force_next
;
76 /* We found a non-blank character, so we will always
77 * want to return a string (even if it is empty),
78 * allocate it here. */
79 if (!GREEDY_REALLOC(s
, allocated
, sz
+1))
85 for (;; (*p
)++, c
= **p
) {
87 if (!GREEDY_REALLOC(s
, allocated
, sz
+7))
91 if ((flags
& EXTRACT_CUNESCAPE_RELAX
) &&
92 (!quote
|| flags
& EXTRACT_RELAX
)) {
93 /* If we find an unquoted trailing backslash and we're in
94 * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the
97 * Unbalanced quotes will only be allowed in EXTRACT_RELAX
98 * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them.
101 goto finish_force_terminate
;
103 if (flags
& EXTRACT_RELAX
)
104 goto finish_force_terminate
;
108 if (flags
& EXTRACT_CUNESCAPE
) {
109 bool eight_bit
= false;
112 r
= cunescape_one(*p
, (size_t) -1, &u
, &eight_bit
);
114 if (flags
& EXTRACT_CUNESCAPE_RELAX
) {
125 sz
+= utf8_encode_unichar(s
+ sz
, u
);
132 } else if (quote
) { /* inside either single or double quotes */
133 for (;; (*p
)++, c
= **p
) {
135 if (flags
& EXTRACT_RELAX
)
136 goto finish_force_terminate
;
138 } else if (c
== quote
) { /* found the end quote */
141 } else if (c
== '\\' && !(flags
& EXTRACT_RETAIN_ESCAPE
)) {
145 if (!GREEDY_REALLOC(s
, allocated
, sz
+2))
153 for (;; (*p
)++, c
= **p
) {
155 goto finish_force_terminate
;
156 else if (IN_SET(c
, '\'', '"') && (flags
& EXTRACT_QUOTES
)) {
159 } else if (c
== '\\' && !(flags
& EXTRACT_RETAIN_ESCAPE
)) {
162 } else if (strchr(separators
, c
)) {
163 if (flags
& EXTRACT_DONT_COALESCE_SEPARATORS
) {
165 goto finish_force_next
;
167 /* Skip additional coalesced separators. */
168 for (;; (*p
)++, c
= **p
) {
170 goto finish_force_terminate
;
171 if (!strchr(separators
, c
))
177 if (!GREEDY_REALLOC(s
, allocated
, sz
+2))
186 finish_force_terminate
:
203 int extract_first_word_and_warn(
206 const char *separators
,
209 const char *filename
,
211 const char *rvalue
) {
213 /* Try to unquote it, if it fails, warn about it and try again
214 * but this time using EXTRACT_CUNESCAPE_RELAX to keep the
215 * backslashes verbatim in invalid escape sequences. */
221 r
= extract_first_word(p
, ret
, separators
, flags
);
225 if (r
== -EINVAL
&& !(flags
& EXTRACT_CUNESCAPE_RELAX
)) {
227 /* Retry it with EXTRACT_CUNESCAPE_RELAX. */
229 r
= extract_first_word(p
, ret
, separators
, flags
|EXTRACT_CUNESCAPE_RELAX
);
231 /* It worked this time, hence it must have been an invalid escape sequence. */
232 log_syntax(unit
, LOG_WARNING
, filename
, line
, EINVAL
, "Ignoring unknown escape sequences: \"%s\"", *ret
);
236 /* If it's still EINVAL; then it must be unbalanced quoting, report this. */
238 return log_syntax(unit
, LOG_ERR
, filename
, line
, r
, "Unbalanced quoting, ignoring: \"%s\"", rvalue
);
241 /* Can be any error, report it */
242 return log_syntax(unit
, LOG_ERR
, filename
, line
, r
, "Unable to decode word \"%s\", ignoring: %m", rvalue
);
245 /* We pass ExtractFlags as unsigned int (to avoid undefined behaviour when passing
246 * an object that undergoes default argument promotion as an argument to va_start).
247 * Let's make sure that ExtractFlags fits into an unsigned int. */
248 assert_cc(sizeof(enum ExtractFlags
) <= sizeof(unsigned));
250 int extract_many_words(const char **p
, const char *separators
, unsigned flags
, ...) {
255 /* Parses a number of words from a string, stripping any
256 * quotes if necessary. */
260 /* Count how many words are expected */
263 if (!va_arg(ap
, char **))
272 /* Read all words into a temporary array */
274 for (c
= 0; c
< n
; c
++) {
276 r
= extract_first_word(p
, &l
[c
], separators
, flags
);
280 for (j
= 0; j
< c
; j
++)
290 /* If we managed to parse all words, return them in the passed
293 for (i
= 0; i
< n
; i
++) {
296 v
= va_arg(ap
, char **);