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