1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "alloc-util.h"
9 #include "string-util.h"
13 int cescape_char(char c
, char *buf
) {
16 /* Needs space for 4 characters in the buffer */
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)) {
67 *(buf
++) = octchar((unsigned char) c
>> 6);
68 *(buf
++) = octchar((unsigned char) c
>> 3);
69 *(buf
++) = octchar((unsigned char) c
);
78 char* cescape_length(const char *s
, size_t n
) {
82 /* Does C style string escaping. May be reversed with cunescape(). */
89 if (n
> (SIZE_MAX
- 1) / 4)
92 r
= new(char, n
*4 + 1);
96 for (f
= s
, t
= r
; f
< s
+ n
; f
++)
97 t
+= cescape_char(*f
, t
);
104 int cunescape_one(const char *p
, size_t length
, char32_t
*ret
, bool *eight_bit
, bool accept_nul
) {
110 /* Unescapes C style. Returns the unescaped character in ret.
111 * Sets *eight_bit to true if the escaped sequence either fits in
112 * one byte in UTF-8 or is a non-unicode literal byte and should
113 * instead be copied directly.
116 if (length
!= SIZE_MAX
&& length
< 1)
153 /* This is an extension of the XDG syntax files */
158 /* hexadecimal encoding */
161 if (length
!= SIZE_MAX
&& length
< 3)
172 /* Don't allow NUL bytes */
173 if (a
== 0 && b
== 0 && !accept_nul
)
176 *ret
= (a
<< 4U) | b
;
183 /* C++11 style 16-bit unicode */
189 if (length
!= SIZE_MAX
&& length
< 5)
192 for (i
= 0; i
< 4; i
++) {
193 a
[i
] = unhexchar(p
[1 + i
]);
198 c
= ((uint32_t) a
[0] << 12U) | ((uint32_t) a
[1] << 8U) | ((uint32_t) a
[2] << 4U) | (uint32_t) a
[3];
200 /* Don't allow 0 chars */
201 if (c
== 0 && !accept_nul
)
210 /* C++11 style 32-bit unicode */
216 if (length
!= SIZE_MAX
&& length
< 9)
219 for (i
= 0; i
< 8; i
++) {
220 a
[i
] = unhexchar(p
[1 + i
]);
225 c
= ((uint32_t) a
[0] << 28U) | ((uint32_t) a
[1] << 24U) | ((uint32_t) a
[2] << 20U) | ((uint32_t) a
[3] << 16U) |
226 ((uint32_t) a
[4] << 12U) | ((uint32_t) a
[5] << 8U) | ((uint32_t) a
[6] << 4U) | (uint32_t) a
[7];
228 /* Don't allow 0 chars */
229 if (c
== 0 && !accept_nul
)
232 /* Don't allow invalid code points */
233 if (!unichar_is_valid(c
))
253 if (length
!= SIZE_MAX
&& length
< 3)
268 /* don't allow NUL bytes */
269 if (a
== 0 && b
== 0 && c
== 0 && !accept_nul
)
272 /* Don't allow bytes above 255 */
273 m
= ((uint32_t) a
<< 6U) | ((uint32_t) b
<< 3U) | (uint32_t) c
;
290 ssize_t
cunescape_length_with_prefix(const char *s
, size_t length
, const char *prefix
, UnescapeFlags flags
, char **ret
) {
291 _cleanup_free_
char *ans
= NULL
;
300 /* Undoes C style string escaping, and optionally prefixes it. */
302 if (length
== SIZE_MAX
)
305 pl
= strlen_ptr(prefix
);
307 ans
= new(char, pl
+length
+1);
312 memcpy(ans
, prefix
, pl
);
314 for (f
= s
, t
= ans
+ pl
; f
< s
+ length
; f
++) {
316 bool eight_bit
= false;
319 remaining
= s
+ length
- f
;
320 assert(remaining
> 0);
323 /* A literal, copy verbatim */
328 if (remaining
== 1) {
329 if (flags
& UNESCAPE_RELAX
) {
330 /* A trailing backslash, copy verbatim */
338 r
= cunescape_one(f
+ 1, remaining
- 1, &u
, &eight_bit
, flags
& UNESCAPE_ACCEPT_NUL
);
340 if (flags
& UNESCAPE_RELAX
) {
341 /* Invalid escape code, let's take it literal then */
351 /* One byte? Set directly as specified */
354 /* Otherwise encode as multi-byte UTF-8 */
355 t
+= utf8_encode_unichar(t
, u
);
360 assert(t
>= ans
); /* Let static analyzers know that the answer is non-negative. */
361 *ret
= TAKE_PTR(ans
);
365 char* xescape_full(const char *s
, const char *bad
, size_t console_width
, XEscapeFlags flags
) {
366 char *ans
, *t
, *prev
, *prev2
;
371 /* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be
372 * reversed with cunescape(). If XESCAPE_8_BIT is specified, characters >= 127 are let through
373 * unchanged. This corresponds to non-ASCII printable characters in pre-unicode encodings.
375 * If console_width is reached, or XESCAPE_FORCE_ELLIPSIS is set, output is truncated and "..." is
378 if (console_width
== 0)
381 ans
= new(char, MIN(strlen(s
), console_width
) * 4 + 1);
385 memset(ans
, '_', MIN(strlen(s
), console_width
) * 4);
386 ans
[MIN(strlen(s
), console_width
) * 4] = 0;
388 bool force_ellipsis
= FLAGS_SET(flags
, XESCAPE_FORCE_ELLIPSIS
);
390 for (f
= s
, t
= prev
= prev2
= ans
; ; f
++) {
401 if ((unsigned char) *f
< ' ' ||
402 (!FLAGS_SET(flags
, XESCAPE_8_BIT
) && (unsigned char) *f
>= 127) ||
403 *f
== '\\' || (bad
&& strchr(bad
, *f
))) {
404 if ((size_t) (t
- ans
) + 4 + 3 * force_ellipsis
> console_width
)
409 *(t
++) = hexchar(*f
>> 4);
410 *(t
++) = hexchar(*f
);
412 if ((size_t) (t
- ans
) + 1 + 3 * force_ellipsis
> console_width
)
418 /* We might need to go back two cycles to fit three dots, so remember two positions */
423 /* We can just write where we want, since chars are one-byte */
424 size_t c
= MIN(console_width
, 3u); /* If the console is too narrow, write fewer dots */
426 if (console_width
- c
>= (size_t) (t
- ans
))
427 off
= (size_t) (t
- ans
);
428 else if (console_width
- c
>= (size_t) (prev
- ans
))
429 off
= (size_t) (prev
- ans
);
430 else if (console_width
- c
>= (size_t) (prev2
- ans
))
431 off
= (size_t) (prev2
- ans
);
433 off
= console_width
- c
;
434 assert(off
<= (size_t) (t
- ans
));
436 memcpy(ans
+ off
, "...", c
);
441 char* escape_non_printable_full(const char *str
, size_t console_width
, XEscapeFlags flags
) {
442 if (FLAGS_SET(flags
, XESCAPE_8_BIT
))
443 return xescape_full(str
, /* bad= */ NULL
, console_width
, flags
);
445 return utf8_escape_non_printable_full(str
,
447 FLAGS_SET(flags
, XESCAPE_FORCE_ELLIPSIS
));
450 char* octescape(const char *s
, size_t len
) {
453 /* Escapes \ and " chars, in \nnn style escaping. */
455 assert(s
|| len
== 0);
460 if (len
> (SIZE_MAX
- 1) / 4)
463 t
= buf
= new(char, len
* 4 + 1);
467 for (size_t i
= 0; i
< len
; i
++) {
468 uint8_t u
= (uint8_t) s
[i
];
470 if (u
< ' ' || u
>= 127 || IN_SET(u
, '\\', '"')) {
472 *(t
++) = '0' + (u
>> 6);
473 *(t
++) = '0' + ((u
>> 3) & 7);
474 *(t
++) = '0' + (u
& 7);
483 char* decescape(const char *s
, size_t len
, const char *bad
) {
486 /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */
488 assert(s
|| len
== 0);
493 if (len
> (SIZE_MAX
- 1) / 4)
496 t
= buf
= new(char, len
* 4 + 1);
500 for (size_t i
= 0; i
< len
; i
++) {
501 uint8_t u
= (uint8_t) s
[i
];
503 if (u
< ' ' || u
>= 127 || IN_SET(u
, '\\', '"') || strchr(bad
, u
)) {
505 *(t
++) = '0' + (u
/ 100);
506 *(t
++) = '0' + ((u
/ 10) % 10);
507 *(t
++) = '0' + (u
% 10);
516 static char* strcpy_backslash_escaped(char *t
, const char *s
, const char *bad
) {
522 int l
= utf8_encoded_valid_unichar(s
, SIZE_MAX
);
524 if (char_is_cc(*s
) || l
< 0)
525 t
+= cescape_char(*(s
++), t
);
527 if (*s
== '\\' || strchr(bad
, *s
))
531 t
= mempcpy(t
, s
, l
);
539 char* shell_escape(const char *s
, const char *bad
) {
542 buf
= new(char, strlen(s
)*4+1);
546 t
= strcpy_backslash_escaped(buf
, s
, bad
);
552 char* shell_maybe_quote(const char *s
, ShellEscapeFlags flags
) {
558 /* Encloses a string in quotes if necessary to make it OK as a shell string. */
560 if (FLAGS_SET(flags
, SHELL_ESCAPE_EMPTY
) && isempty(s
))
561 return strdup("\"\""); /* We don't use $'' here in the POSIX mode. "" is fine too. */
564 int l
= utf8_encoded_valid_unichar(p
, SIZE_MAX
);
566 if (char_is_cc(*p
) || l
< 0 ||
567 strchr(WHITESPACE SHELL_NEED_QUOTES
, *p
))
576 buf
= new(char, FLAGS_SET(flags
, SHELL_ESCAPE_POSIX
) + 1 + strlen(s
)*4 + 1 + 1);
581 if (FLAGS_SET(flags
, SHELL_ESCAPE_POSIX
)) {
587 t
= mempcpy(t
, s
, p
- s
);
589 t
= strcpy_backslash_escaped(t
, p
,
590 FLAGS_SET(flags
, SHELL_ESCAPE_POSIX
) ? SHELL_NEED_ESCAPE_POSIX
: SHELL_NEED_ESCAPE
);
592 if (FLAGS_SET(flags
, SHELL_ESCAPE_POSIX
))
598 return str_realloc(buf
);
601 char* quote_command_line(char **argv
, ShellEscapeFlags flags
) {
602 _cleanup_free_
char *result
= NULL
;
606 STRV_FOREACH(a
, argv
) {
607 _cleanup_free_
char *t
= NULL
;
609 t
= shell_maybe_quote(*a
, flags
);
613 if (!strextend_with_separator(&result
, " ", t
))
617 return str_realloc(TAKE_PTR(result
));