]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/escape.c
resolvectl: merge JSON error fields
[thirdparty/systemd.git] / src / basic / escape.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
4f5dd394 2
11c3a366
TA
3#include <stdlib.h>
4#include <string.h>
5
b5efdb8a 6#include "alloc-util.h"
e4e73a63
LP
7#include "escape.h"
8#include "hexdecoct.h"
0c15577a 9#include "string-util.h"
eeb91d29 10#include "strv.h"
4f5dd394 11#include "utf8.h"
4f5dd394 12
b778252b
ZJS
13int cescape_char(char c, char *buf) {
14 char *buf_old = buf;
4f5dd394 15
76a35973
LP
16 /* Needs space for 4 characters in the buffer */
17
4f5dd394
LP
18 switch (c) {
19
20 case '\a':
21 *(buf++) = '\\';
22 *(buf++) = 'a';
23 break;
24 case '\b':
25 *(buf++) = '\\';
26 *(buf++) = 'b';
27 break;
28 case '\f':
29 *(buf++) = '\\';
30 *(buf++) = 'f';
31 break;
32 case '\n':
33 *(buf++) = '\\';
34 *(buf++) = 'n';
35 break;
36 case '\r':
37 *(buf++) = '\\';
38 *(buf++) = 'r';
39 break;
40 case '\t':
41 *(buf++) = '\\';
42 *(buf++) = 't';
43 break;
44 case '\v':
45 *(buf++) = '\\';
46 *(buf++) = 'v';
47 break;
48 case '\\':
49 *(buf++) = '\\';
50 *(buf++) = '\\';
51 break;
52 case '"':
53 *(buf++) = '\\';
54 *(buf++) = '"';
55 break;
56 case '\'':
57 *(buf++) = '\\';
58 *(buf++) = '\'';
59 break;
60
61 default:
62 /* For special chars we prefer octal over
63 * hexadecimal encoding, simply because glib's
64 * g_strescape() does the same */
65 if ((c < ' ') || (c >= 127)) {
66 *(buf++) = '\\';
67 *(buf++) = octchar((unsigned char) c >> 6);
68 *(buf++) = octchar((unsigned char) c >> 3);
69 *(buf++) = octchar((unsigned char) c);
70 } else
71 *(buf++) = c;
72 break;
73 }
74
75 return buf - buf_old;
76}
77
31be0e9e 78char* cescape_length(const char *s, size_t n) {
4f5dd394 79 const char *f;
a5ef3638 80 char *r, *t;
4f5dd394 81
7de7c7b6
MY
82 /* Does C style string escaping. May be reversed with cunescape(). */
83
a5ef3638 84 assert(s || n == 0);
4f5dd394 85
7de7c7b6
MY
86 if (n == SIZE_MAX)
87 n = strlen(s);
88
89 if (n > (SIZE_MAX - 1) / 4)
90 return NULL;
4f5dd394 91
a5ef3638 92 r = new(char, n*4 + 1);
4f5dd394
LP
93 if (!r)
94 return NULL;
95
a5ef3638 96 for (f = s, t = r; f < s + n; f++)
4f5dd394
LP
97 t += cescape_char(*f, t);
98
99 *t = 0;
100
101 return r;
102}
103
0e72e469 104int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul) {
4f5dd394
LP
105 int r = 1;
106
107 assert(p);
4f5dd394 108 assert(ret);
18e70eec 109 assert(eight_bit);
4f5dd394 110
3565e095
ZJS
111 /* Unescapes C style. Returns the unescaped character in ret.
112 * Sets *eight_bit to true if the escaped sequence either fits in
113 * one byte in UTF-8 or is a non-unicode literal byte and should
114 * instead be copied directly.
115 */
4f5dd394 116
f5fbe71d 117 if (length != SIZE_MAX && length < 1)
4f5dd394
LP
118 return -EINVAL;
119
120 switch (p[0]) {
121
122 case 'a':
123 *ret = '\a';
124 break;
125 case 'b':
126 *ret = '\b';
127 break;
128 case 'f':
129 *ret = '\f';
130 break;
131 case 'n':
132 *ret = '\n';
133 break;
134 case 'r':
135 *ret = '\r';
136 break;
137 case 't':
138 *ret = '\t';
139 break;
140 case 'v':
141 *ret = '\v';
142 break;
143 case '\\':
144 *ret = '\\';
145 break;
146 case '"':
147 *ret = '"';
148 break;
149 case '\'':
150 *ret = '\'';
151 break;
152
153 case 's':
154 /* This is an extension of the XDG syntax files */
155 *ret = ' ';
156 break;
157
158 case 'x': {
159 /* hexadecimal encoding */
160 int a, b;
161
f5fbe71d 162 if (length != SIZE_MAX && length < 3)
4f5dd394
LP
163 return -EINVAL;
164
165 a = unhexchar(p[1]);
166 if (a < 0)
167 return -EINVAL;
168
169 b = unhexchar(p[2]);
170 if (b < 0)
171 return -EINVAL;
172
173 /* Don't allow NUL bytes */
0e72e469 174 if (a == 0 && b == 0 && !accept_nul)
4f5dd394
LP
175 return -EINVAL;
176
3565e095
ZJS
177 *ret = (a << 4U) | b;
178 *eight_bit = true;
4f5dd394
LP
179 r = 3;
180 break;
181 }
182
183 case 'u': {
da890466 184 /* C++11 style 16-bit unicode */
4f5dd394
LP
185
186 int a[4];
da6053d0 187 size_t i;
4f5dd394
LP
188 uint32_t c;
189
f5fbe71d 190 if (length != SIZE_MAX && length < 5)
4f5dd394
LP
191 return -EINVAL;
192
193 for (i = 0; i < 4; i++) {
194 a[i] = unhexchar(p[1 + i]);
195 if (a[i] < 0)
196 return a[i];
197 }
198
199 c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3];
200
201 /* Don't allow 0 chars */
0e72e469 202 if (c == 0 && !accept_nul)
4f5dd394
LP
203 return -EINVAL;
204
eb032670
AS
205 /* Don't allow UTF-16 surrogates, they cannot be encoded as valid UTF-8. Note we
206 * deliberately do *not* use unichar_is_valid() here (unlike the \U case below):
207 * it also rejects noncharacters such as U+FFFE, which callers legitimately round-trip
208 * through \u (e.g. systemd.mount-extra= parsing, see test-fstab-generator). */
209 if (utf16_is_surrogate(c))
210 return -EINVAL;
211
3565e095 212 *ret = c;
4f5dd394
LP
213 r = 5;
214 break;
215 }
216
217 case 'U': {
da890466 218 /* C++11 style 32-bit unicode */
4f5dd394
LP
219
220 int a[8];
da6053d0 221 size_t i;
c932fb71 222 char32_t c;
4f5dd394 223
f5fbe71d 224 if (length != SIZE_MAX && length < 9)
4f5dd394
LP
225 return -EINVAL;
226
227 for (i = 0; i < 8; i++) {
228 a[i] = unhexchar(p[1 + i]);
229 if (a[i] < 0)
230 return a[i];
231 }
232
dcd12626
LP
233 c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) |
234 ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] << 8U) | ((uint32_t) a[6] << 4U) | (uint32_t) a[7];
4f5dd394
LP
235
236 /* Don't allow 0 chars */
0e72e469 237 if (c == 0 && !accept_nul)
4f5dd394
LP
238 return -EINVAL;
239
240 /* Don't allow invalid code points */
241 if (!unichar_is_valid(c))
242 return -EINVAL;
243
3565e095 244 *ret = c;
4f5dd394
LP
245 r = 9;
246 break;
247 }
248
249 case '0':
250 case '1':
251 case '2':
252 case '3':
253 case '4':
254 case '5':
255 case '6':
256 case '7': {
257 /* octal encoding */
258 int a, b, c;
c932fb71 259 char32_t m;
4f5dd394 260
f5fbe71d 261 if (length != SIZE_MAX && length < 3)
4f5dd394
LP
262 return -EINVAL;
263
264 a = unoctchar(p[0]);
265 if (a < 0)
266 return -EINVAL;
267
268 b = unoctchar(p[1]);
269 if (b < 0)
270 return -EINVAL;
271
272 c = unoctchar(p[2]);
273 if (c < 0)
274 return -EINVAL;
275
276 /* don't allow NUL bytes */
0e72e469 277 if (a == 0 && b == 0 && c == 0 && !accept_nul)
4f5dd394
LP
278 return -EINVAL;
279
280 /* Don't allow bytes above 255 */
dcd12626 281 m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c;
4f5dd394
LP
282 if (m > 255)
283 return -EINVAL;
284
285 *ret = m;
3565e095 286 *eight_bit = true;
4f5dd394
LP
287 r = 3;
288 break;
289 }
290
291 default:
292 return -EINVAL;
293 }
294
295 return r;
296}
297
e437538f 298ssize_t cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) {
ddedf7ca
ZJS
299 _cleanup_free_ char *ans = NULL;
300 char *t;
4f5dd394
LP
301 const char *f;
302 size_t pl;
ddedf7ca 303 int r;
4f5dd394
LP
304
305 assert(s);
306 assert(ret);
307
308 /* Undoes C style string escaping, and optionally prefixes it. */
309
0c15577a
DDM
310 if (length == SIZE_MAX)
311 length = strlen(s);
312
7bf7ce28 313 pl = strlen_ptr(prefix);
4f5dd394 314
ddedf7ca
ZJS
315 ans = new(char, pl+length+1);
316 if (!ans)
4f5dd394
LP
317 return -ENOMEM;
318
319 if (prefix)
ddedf7ca 320 memcpy(ans, prefix, pl);
4f5dd394 321
ddedf7ca 322 for (f = s, t = ans + pl; f < s + length; f++) {
4f5dd394 323 size_t remaining;
3565e095 324 bool eight_bit = false;
c932fb71 325 char32_t u;
4f5dd394
LP
326
327 remaining = s + length - f;
328 assert(remaining > 0);
329
330 if (*f != '\\') {
629ff674 331 /* A literal, copy verbatim */
4f5dd394
LP
332 *(t++) = *f;
333 continue;
334 }
335
336 if (remaining == 1) {
337 if (flags & UNESCAPE_RELAX) {
338 /* A trailing backslash, copy verbatim */
339 *(t++) = *f;
340 continue;
341 }
342
4f5dd394
LP
343 return -EINVAL;
344 }
345
ddedf7ca
ZJS
346 r = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL);
347 if (r < 0) {
4f5dd394
LP
348 if (flags & UNESCAPE_RELAX) {
349 /* Invalid escape code, let's take it literal then */
350 *(t++) = '\\';
351 continue;
352 }
353
ddedf7ca 354 return r;
4f5dd394
LP
355 }
356
ddedf7ca 357 f += r;
3565e095
ZJS
358 if (eight_bit)
359 /* One byte? Set directly as specified */
360 *(t++) = u;
4f5dd394 361 else
3565e095 362 /* Otherwise encode as multi-byte UTF-8 */
4f5dd394 363 t += utf8_encode_unichar(t, u);
4f5dd394
LP
364 }
365
366 *t = 0;
367
1421705d 368 assert(t >= ans); /* Let static analyzers know that the answer is non-negative. */
ddedf7ca
ZJS
369 *ret = TAKE_PTR(ans);
370 return t - *ret;
4f5dd394
LP
371}
372
b19f2116 373char* xescape_full(const char *s, const char *bad, size_t console_width, XEscapeFlags flags) {
70d55819 374 char *ans, *t, *prev, *prev2;
4f5dd394
LP
375 const char *f;
376
ea844c49
LP
377 assert(s);
378
70d55819 379 /* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be
b19f2116
ZJS
380 * reversed with cunescape(). If XESCAPE_8_BIT is specified, characters >= 127 are let through
381 * unchanged. This corresponds to non-ASCII printable characters in pre-unicode encodings.
70d55819 382 *
fc96e5c0
ZJS
383 * If console_width is reached, or XESCAPE_FORCE_ELLIPSIS is set, output is truncated and "..." is
384 * appended. */
4f5dd394 385
70d55819
ZJS
386 if (console_width == 0)
387 return strdup("");
388
5a03efc9
JW
389 size_t len_forced_ellipsis = FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS) ? STRLEN("...") : 0;
390 size_t len_s = strlen(s);
4f5dd394 391
5a03efc9
JW
392 size_t len_body = MIN(console_width, SIZE_MAX - 1); /* We need room for the NUL byte */
393 if (len_body > len_forced_ellipsis && len_s <= (len_body - len_forced_ellipsis) / 4)
394 len_body = len_s * 4 + len_forced_ellipsis;
70d55819 395
5a03efc9
JW
396 ans = new(char, len_body + 1);
397 if (!ans)
398 return NULL;
fc96e5c0 399
70d55819
ZJS
400 for (f = s, t = prev = prev2 = ans; ; f++) {
401 char *tmp_t = t;
402
403 if (!*f) {
5a03efc9 404 if (len_forced_ellipsis != 0)
fc96e5c0
ZJS
405 break;
406
70d55819
ZJS
407 *t = 0;
408 return ans;
409 }
410
b19f2116
ZJS
411 if ((unsigned char) *f < ' ' ||
412 (!FLAGS_SET(flags, XESCAPE_8_BIT) && (unsigned char) *f >= 127) ||
ea844c49 413 *f == '\\' || (bad && strchr(bad, *f))) {
5a03efc9 414 if ((size_t) (t - ans) + 4 + len_forced_ellipsis > len_body)
70d55819 415 break;
4f5dd394 416
4f5dd394
LP
417 *(t++) = '\\';
418 *(t++) = 'x';
419 *(t++) = hexchar(*f >> 4);
420 *(t++) = hexchar(*f);
70d55819 421 } else {
5a03efc9 422 if ((size_t) (t - ans) + 1 + len_forced_ellipsis > len_body)
70d55819
ZJS
423 break;
424
4f5dd394 425 *(t++) = *f;
70d55819 426 }
4f5dd394 427
70d55819
ZJS
428 /* We might need to go back two cycles to fit three dots, so remember two positions */
429 prev2 = prev;
430 prev = tmp_t;
431 }
4f5dd394 432
70d55819 433 /* We can just write where we want, since chars are one-byte */
5a03efc9 434 size_t c = MIN(len_body, STRLEN("...")); /* If the console is too narrow, write fewer dots */
70d55819 435 size_t off;
5a03efc9 436 if (len_body - c >= (size_t) (t - ans))
70d55819 437 off = (size_t) (t - ans);
5a03efc9 438 else if (len_body - c >= (size_t) (prev - ans))
70d55819 439 off = (size_t) (prev - ans);
5a03efc9 440 else if (len_body - c >= (size_t) (prev2 - ans))
70d55819
ZJS
441 off = (size_t) (prev2 - ans);
442 else
5a03efc9 443 off = len_body - c;
70d55819
ZJS
444 assert(off <= (size_t) (t - ans));
445
446 memcpy(ans + off, "...", c);
447 ans[off + c] = '\0';
448 return ans;
4f5dd394
LP
449}
450
b19f2116
ZJS
451char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags) {
452 if (FLAGS_SET(flags, XESCAPE_8_BIT))
ea844c49 453 return xescape_full(str, /* bad= */ NULL, console_width, flags);
e3b4efd2 454 else
fc96e5c0
ZJS
455 return utf8_escape_non_printable_full(str,
456 console_width,
457 FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS));
e3b4efd2
ZJS
458}
459
e98a26dd 460char* octescape_full(const char *s, size_t len, const char *bad) {
76519cec 461 char *buf, *t;
95052df3 462
5a03efc9
JW
463 /* Escapes all chars in bad, in addition to \ and " chars, in \nnn octal style escaping. May be
464 * reversed with cunescape(). */
95052df3 465
76519cec
YW
466 assert(s || len == 0);
467
c6342e35
LP
468 if (len == SIZE_MAX)
469 len = strlen(s);
470
60cf4059 471 if (len > (SIZE_MAX - 1) / 4)
c6342e35
LP
472 return NULL;
473
76519cec
YW
474 t = buf = new(char, len * 4 + 1);
475 if (!buf)
95052df3
ZJS
476 return NULL;
477
76519cec
YW
478 for (size_t i = 0; i < len; i++) {
479 uint8_t u = (uint8_t) s[i];
95052df3 480
e98a26dd 481 if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || (bad && strchr(bad, u))) {
95052df3 482 *(t++) = '\\';
76519cec
YW
483 *(t++) = '0' + (u >> 6);
484 *(t++) = '0' + ((u >> 3) & 7);
485 *(t++) = '0' + (u & 7);
95052df3 486 } else
76519cec 487 *(t++) = u;
95052df3
ZJS
488 }
489
490 *t = 0;
76519cec 491 return buf;
95052df3
ZJS
492}
493
6bdbfb7e 494char* decescape(const char *s, size_t len, const char *bad) {
b699f5f2
RP
495 char *buf, *t;
496
497 /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */
498
499 assert(s || len == 0);
500
7de7c7b6
MY
501 if (len == SIZE_MAX)
502 len = strlen(s);
503
504 if (len > (SIZE_MAX - 1) / 4)
505 return NULL;
506
b699f5f2
RP
507 t = buf = new(char, len * 4 + 1);
508 if (!buf)
509 return NULL;
510
511 for (size_t i = 0; i < len; i++) {
512 uint8_t u = (uint8_t) s[i];
513
514 if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || strchr(bad, u)) {
515 *(t++) = '\\';
516 *(t++) = '0' + (u / 100);
517 *(t++) = '0' + ((u / 10) % 10);
518 *(t++) = '0' + (u % 10);
519 } else
520 *(t++) = u;
521 }
522
523 *t = 0;
524 return buf;
525}
526
566d06ae 527static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
4f5dd394 528 assert(bad);
0b82a6fa 529 assert(t);
530 assert(s);
4f5dd394 531
00f57157 532 while (*s) {
533 int l = utf8_encoded_valid_unichar(s, SIZE_MAX);
534
535 if (char_is_cc(*s) || l < 0)
536 t += cescape_char(*(s++), t);
537 else if (l == 1) {
0089ab08
ZJS
538 if (*s == '\\' || strchr(bad, *s))
539 *(t++) = '\\';
00f57157 540 *(t++) = *(s++);
541 } else {
542 t = mempcpy(t, s, l);
543 s += l;
804ee07c 544 }
00f57157 545 }
804ee07c 546
4f5dd394
LP
547 return t;
548}
549
31be0e9e 550char* shell_escape(const char *s, const char *bad) {
0089ab08 551 char *buf, *t;
4f5dd394 552
0089ab08
ZJS
553 buf = new(char, strlen(s)*4+1);
554 if (!buf)
4f5dd394
LP
555 return NULL;
556
0089ab08 557 t = strcpy_backslash_escaped(buf, s, bad);
4f5dd394
LP
558 *t = 0;
559
0089ab08 560 return buf;
4f5dd394
LP
561}
562
9e53c10a 563char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) {
4f5dd394 564 const char *p;
0089ab08 565 char *buf, *t;
4f5dd394
LP
566
567 assert(s);
568
0089ab08 569 /* Encloses a string in quotes if necessary to make it OK as a shell string. */
4f5dd394 570
1129cd8a
ZJS
571 if (FLAGS_SET(flags, SHELL_ESCAPE_EMPTY) && isempty(s))
572 return strdup("\"\""); /* We don't use $'' here in the POSIX mode. "" is fine too. */
573
00f57157 574 for (p = s; *p; ) {
575 int l = utf8_encoded_valid_unichar(p, SIZE_MAX);
576
577 if (char_is_cc(*p) || l < 0 ||
0089ab08 578 strchr(WHITESPACE SHELL_NEED_QUOTES, *p))
4f5dd394
LP
579 break;
580
00f57157 581 p += l;
582 }
583
4f5dd394
LP
584 if (!*p)
585 return strdup(s);
586
0089ab08
ZJS
587 buf = new(char, FLAGS_SET(flags, SHELL_ESCAPE_POSIX) + 1 + strlen(s)*4 + 1 + 1);
588 if (!buf)
4f5dd394
LP
589 return NULL;
590
0089ab08 591 t = buf;
9e53c10a 592 if (FLAGS_SET(flags, SHELL_ESCAPE_POSIX)) {
804ee07c
ZJS
593 *(t++) = '$';
594 *(t++) = '\'';
9e53c10a
ZJS
595 } else
596 *(t++) = '"';
804ee07c 597
4f5dd394
LP
598 t = mempcpy(t, s, p - s);
599
9e53c10a 600 t = strcpy_backslash_escaped(t, p,
566d06ae 601 FLAGS_SET(flags, SHELL_ESCAPE_POSIX) ? SHELL_NEED_ESCAPE_POSIX : SHELL_NEED_ESCAPE);
4f5dd394 602
9e53c10a 603 if (FLAGS_SET(flags, SHELL_ESCAPE_POSIX))
804ee07c 604 *(t++) = '\'';
9e53c10a
ZJS
605 else
606 *(t++) = '"';
4f5dd394
LP
607 *t = 0;
608
0089ab08 609 return str_realloc(buf);
4f5dd394 610}
eeb91d29 611
6ed684db 612char* quote_command_line(char * const *argv, ShellEscapeFlags flags) {
eeb91d29
ZJS
613 _cleanup_free_ char *result = NULL;
614
615 assert(argv);
616
eeb91d29
ZJS
617 STRV_FOREACH(a, argv) {
618 _cleanup_free_ char *t = NULL;
619
4ef15008 620 t = shell_maybe_quote(*a, flags);
eeb91d29
ZJS
621 if (!t)
622 return NULL;
623
624 if (!strextend_with_separator(&result, " ", t))
625 return NULL;
626 }
627
7d0cede0 628 return str_realloc(TAKE_PTR(result));
eeb91d29 629}