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
) {
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.
117 if (length
!= SIZE_MAX
&& length
< 1)
154 /* This is an extension of the XDG syntax files */
159 /* hexadecimal encoding */
162 if (length
!= SIZE_MAX
&& length
< 3)
173 /* Don't allow NUL bytes */
174 if (a
== 0 && b
== 0 && !accept_nul
)
177 *ret
= (a
<< 4U) | b
;
184 /* C++11 style 16-bit unicode */
190 if (length
!= SIZE_MAX
&& length
< 5)
193 for (i
= 0; i
< 4; i
++) {
194 a
[i
] = unhexchar(p
[1 + i
]);
199 c
= ((uint32_t) a
[0] << 12U) | ((uint32_t) a
[1] << 8U) | ((uint32_t) a
[2] << 4U) | (uint32_t) a
[3];
201 /* Don't allow 0 chars */
202 if (c
== 0 && !accept_nul
)
211 /* C++11 style 32-bit unicode */
217 if (length
!= SIZE_MAX
&& length
< 9)
220 for (i
= 0; i
< 8; i
++) {
221 a
[i
] = unhexchar(p
[1 + i
]);
226 c
= ((uint32_t) a
[0] << 28U) | ((uint32_t) a
[1] << 24U) | ((uint32_t) a
[2] << 20U) | ((uint32_t) a
[3] << 16U) |
227 ((uint32_t) a
[4] << 12U) | ((uint32_t) a
[5] << 8U) | ((uint32_t) a
[6] << 4U) | (uint32_t) a
[7];
229 /* Don't allow 0 chars */
230 if (c
== 0 && !accept_nul
)
233 /* Don't allow invalid code points */
234 if (!unichar_is_valid(c
))
254 if (length
!= SIZE_MAX
&& length
< 3)
269 /* don't allow NUL bytes */
270 if (a
== 0 && b
== 0 && c
== 0 && !accept_nul
)
273 /* Don't allow bytes above 255 */
274 m
= ((uint32_t) a
<< 6U) | ((uint32_t) b
<< 3U) | (uint32_t) c
;
291 ssize_t
cunescape_length_with_prefix(const char *s
, size_t length
, const char *prefix
, UnescapeFlags flags
, char **ret
) {
292 _cleanup_free_
char *ans
= NULL
;
301 /* Undoes C style string escaping, and optionally prefixes it. */
303 if (length
== SIZE_MAX
)
306 pl
= strlen_ptr(prefix
);
308 ans
= new(char, pl
+length
+1);
313 memcpy(ans
, prefix
, pl
);
315 for (f
= s
, t
= ans
+ pl
; f
< s
+ length
; f
++) {
317 bool eight_bit
= false;
320 remaining
= s
+ length
- f
;
321 assert(remaining
> 0);
324 /* A literal, copy verbatim */
329 if (remaining
== 1) {
330 if (flags
& UNESCAPE_RELAX
) {
331 /* A trailing backslash, copy verbatim */
339 r
= cunescape_one(f
+ 1, remaining
- 1, &u
, &eight_bit
, flags
& UNESCAPE_ACCEPT_NUL
);
341 if (flags
& UNESCAPE_RELAX
) {
342 /* Invalid escape code, let's take it literal then */
352 /* One byte? Set directly as specified */
355 /* Otherwise encode as multi-byte UTF-8 */
356 t
+= utf8_encode_unichar(t
, u
);
361 assert(t
>= ans
); /* Let static analyzers know that the answer is non-negative. */
362 *ret
= TAKE_PTR(ans
);
366 char* xescape_full(const char *s
, const char *bad
, size_t console_width
, XEscapeFlags flags
) {
367 char *ans
, *t
, *prev
, *prev2
;
372 /* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be
373 * reversed with cunescape(). If XESCAPE_8_BIT is specified, characters >= 127 are let through
374 * unchanged. This corresponds to non-ASCII printable characters in pre-unicode encodings.
376 * If console_width is reached, or XESCAPE_FORCE_ELLIPSIS is set, output is truncated and "..." is
379 if (console_width
== 0)
382 ans
= new(char, MIN(strlen(s
), console_width
) * 4 + 1);
386 memset(ans
, '_', MIN(strlen(s
), console_width
) * 4);
387 ans
[MIN(strlen(s
), console_width
) * 4] = 0;
389 bool force_ellipsis
= FLAGS_SET(flags
, XESCAPE_FORCE_ELLIPSIS
);
391 for (f
= s
, t
= prev
= prev2
= ans
; ; f
++) {
402 if ((unsigned char) *f
< ' ' ||
403 (!FLAGS_SET(flags
, XESCAPE_8_BIT
) && (unsigned char) *f
>= 127) ||
404 *f
== '\\' || (bad
&& strchr(bad
, *f
))) {
405 if ((size_t) (t
- ans
) + 4 + 3 * force_ellipsis
> console_width
)
410 *(t
++) = hexchar(*f
>> 4);
411 *(t
++) = hexchar(*f
);
413 if ((size_t) (t
- ans
) + 1 + 3 * force_ellipsis
> console_width
)
419 /* We might need to go back two cycles to fit three dots, so remember two positions */
424 /* We can just write where we want, since chars are one-byte */
425 size_t c
= MIN(console_width
, 3u); /* If the console is too narrow, write fewer dots */
427 if (console_width
- c
>= (size_t) (t
- ans
))
428 off
= (size_t) (t
- ans
);
429 else if (console_width
- c
>= (size_t) (prev
- ans
))
430 off
= (size_t) (prev
- ans
);
431 else if (console_width
- c
>= (size_t) (prev2
- ans
))
432 off
= (size_t) (prev2
- ans
);
434 off
= console_width
- c
;
435 assert(off
<= (size_t) (t
- ans
));
437 memcpy(ans
+ off
, "...", c
);
442 char* escape_non_printable_full(const char *str
, size_t console_width
, XEscapeFlags flags
) {
443 if (FLAGS_SET(flags
, XESCAPE_8_BIT
))
444 return xescape_full(str
, /* bad= */ NULL
, console_width
, flags
);
446 return utf8_escape_non_printable_full(str
,
448 FLAGS_SET(flags
, XESCAPE_FORCE_ELLIPSIS
));
451 char* octescape_full(const char *s
, size_t len
, const char *bad
) {
454 /* Escapes all chars in bad, in addition to \ and " chars, in \nnn octal style escaping. */
456 assert(s
|| len
== 0);
461 if (len
> (SIZE_MAX
- 1) / 4)
464 t
= buf
= new(char, len
* 4 + 1);
468 for (size_t i
= 0; i
< len
; i
++) {
469 uint8_t u
= (uint8_t) s
[i
];
471 if (u
< ' ' || u
>= 127 || IN_SET(u
, '\\', '"') || (bad
&& strchr(bad
, u
))) {
473 *(t
++) = '0' + (u
>> 6);
474 *(t
++) = '0' + ((u
>> 3) & 7);
475 *(t
++) = '0' + (u
& 7);
484 char* decescape(const char *s
, size_t len
, const char *bad
) {
487 /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */
489 assert(s
|| len
== 0);
494 if (len
> (SIZE_MAX
- 1) / 4)
497 t
= buf
= new(char, len
* 4 + 1);
501 for (size_t i
= 0; i
< len
; i
++) {
502 uint8_t u
= (uint8_t) s
[i
];
504 if (u
< ' ' || u
>= 127 || IN_SET(u
, '\\', '"') || strchr(bad
, u
)) {
506 *(t
++) = '0' + (u
/ 100);
507 *(t
++) = '0' + ((u
/ 10) % 10);
508 *(t
++) = '0' + (u
% 10);
517 static char* strcpy_backslash_escaped(char *t
, const char *s
, const char *bad
) {
523 int l
= utf8_encoded_valid_unichar(s
, SIZE_MAX
);
525 if (char_is_cc(*s
) || l
< 0)
526 t
+= cescape_char(*(s
++), t
);
528 if (*s
== '\\' || strchr(bad
, *s
))
532 t
= mempcpy(t
, s
, l
);
540 char* shell_escape(const char *s
, const char *bad
) {
543 buf
= new(char, strlen(s
)*4+1);
547 t
= strcpy_backslash_escaped(buf
, s
, bad
);
553 char* shell_maybe_quote(const char *s
, ShellEscapeFlags flags
) {
559 /* Encloses a string in quotes if necessary to make it OK as a shell string. */
561 if (FLAGS_SET(flags
, SHELL_ESCAPE_EMPTY
) && isempty(s
))
562 return strdup("\"\""); /* We don't use $'' here in the POSIX mode. "" is fine too. */
565 int l
= utf8_encoded_valid_unichar(p
, SIZE_MAX
);
567 if (char_is_cc(*p
) || l
< 0 ||
568 strchr(WHITESPACE SHELL_NEED_QUOTES
, *p
))
577 buf
= new(char, FLAGS_SET(flags
, SHELL_ESCAPE_POSIX
) + 1 + strlen(s
)*4 + 1 + 1);
582 if (FLAGS_SET(flags
, SHELL_ESCAPE_POSIX
)) {
588 t
= mempcpy(t
, s
, p
- s
);
590 t
= strcpy_backslash_escaped(t
, p
,
591 FLAGS_SET(flags
, SHELL_ESCAPE_POSIX
) ? SHELL_NEED_ESCAPE_POSIX
: SHELL_NEED_ESCAPE
);
593 if (FLAGS_SET(flags
, SHELL_ESCAPE_POSIX
))
599 return str_realloc(buf
);
602 char* quote_command_line(char * const *argv
, ShellEscapeFlags flags
) {
603 _cleanup_free_
char *result
= NULL
;
607 STRV_FOREACH(a
, argv
) {
608 _cleanup_free_
char *t
= NULL
;
610 t
= shell_maybe_quote(*a
, flags
);
614 if (!strextend_with_separator(&result
, " ", t
))
618 return str_realloc(TAKE_PTR(result
));