]>
| Commit | Line | Data |
|---|---|---|
| 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 |
13 | int 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 | 78 | char* 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 | 104 | int 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 | 298 | ssize_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 | 373 | char* 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 |
451 | char* 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 | 460 | char* 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 | 494 | char* 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 | 527 | static 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 | 550 | char* 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 | 563 | char* 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 | 612 | char* 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 | } |