]>
| 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 | ||
| 3565e095 | 205 | *ret = c; |
| 4f5dd394 LP |
206 | r = 5; |
| 207 | break; | |
| 208 | } | |
| 209 | ||
| 210 | case 'U': { | |
| da890466 | 211 | /* C++11 style 32-bit unicode */ |
| 4f5dd394 LP |
212 | |
| 213 | int a[8]; | |
| da6053d0 | 214 | size_t i; |
| c932fb71 | 215 | char32_t c; |
| 4f5dd394 | 216 | |
| f5fbe71d | 217 | if (length != SIZE_MAX && length < 9) |
| 4f5dd394 LP |
218 | return -EINVAL; |
| 219 | ||
| 220 | for (i = 0; i < 8; i++) { | |
| 221 | a[i] = unhexchar(p[1 + i]); | |
| 222 | if (a[i] < 0) | |
| 223 | return a[i]; | |
| 224 | } | |
| 225 | ||
| dcd12626 LP |
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]; | |
| 4f5dd394 LP |
228 | |
| 229 | /* Don't allow 0 chars */ | |
| 0e72e469 | 230 | if (c == 0 && !accept_nul) |
| 4f5dd394 LP |
231 | return -EINVAL; |
| 232 | ||
| 233 | /* Don't allow invalid code points */ | |
| 234 | if (!unichar_is_valid(c)) | |
| 235 | return -EINVAL; | |
| 236 | ||
| 3565e095 | 237 | *ret = c; |
| 4f5dd394 LP |
238 | r = 9; |
| 239 | break; | |
| 240 | } | |
| 241 | ||
| 242 | case '0': | |
| 243 | case '1': | |
| 244 | case '2': | |
| 245 | case '3': | |
| 246 | case '4': | |
| 247 | case '5': | |
| 248 | case '6': | |
| 249 | case '7': { | |
| 250 | /* octal encoding */ | |
| 251 | int a, b, c; | |
| c932fb71 | 252 | char32_t m; |
| 4f5dd394 | 253 | |
| f5fbe71d | 254 | if (length != SIZE_MAX && length < 3) |
| 4f5dd394 LP |
255 | return -EINVAL; |
| 256 | ||
| 257 | a = unoctchar(p[0]); | |
| 258 | if (a < 0) | |
| 259 | return -EINVAL; | |
| 260 | ||
| 261 | b = unoctchar(p[1]); | |
| 262 | if (b < 0) | |
| 263 | return -EINVAL; | |
| 264 | ||
| 265 | c = unoctchar(p[2]); | |
| 266 | if (c < 0) | |
| 267 | return -EINVAL; | |
| 268 | ||
| 269 | /* don't allow NUL bytes */ | |
| 0e72e469 | 270 | if (a == 0 && b == 0 && c == 0 && !accept_nul) |
| 4f5dd394 LP |
271 | return -EINVAL; |
| 272 | ||
| 273 | /* Don't allow bytes above 255 */ | |
| dcd12626 | 274 | m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c; |
| 4f5dd394 LP |
275 | if (m > 255) |
| 276 | return -EINVAL; | |
| 277 | ||
| 278 | *ret = m; | |
| 3565e095 | 279 | *eight_bit = true; |
| 4f5dd394 LP |
280 | r = 3; |
| 281 | break; | |
| 282 | } | |
| 283 | ||
| 284 | default: | |
| 285 | return -EINVAL; | |
| 286 | } | |
| 287 | ||
| 288 | return r; | |
| 289 | } | |
| 290 | ||
| e437538f | 291 | ssize_t cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { |
| ddedf7ca ZJS |
292 | _cleanup_free_ char *ans = NULL; |
| 293 | char *t; | |
| 4f5dd394 LP |
294 | const char *f; |
| 295 | size_t pl; | |
| ddedf7ca | 296 | int r; |
| 4f5dd394 LP |
297 | |
| 298 | assert(s); | |
| 299 | assert(ret); | |
| 300 | ||
| 301 | /* Undoes C style string escaping, and optionally prefixes it. */ | |
| 302 | ||
| 0c15577a DDM |
303 | if (length == SIZE_MAX) |
| 304 | length = strlen(s); | |
| 305 | ||
| 7bf7ce28 | 306 | pl = strlen_ptr(prefix); |
| 4f5dd394 | 307 | |
| ddedf7ca ZJS |
308 | ans = new(char, pl+length+1); |
| 309 | if (!ans) | |
| 4f5dd394 LP |
310 | return -ENOMEM; |
| 311 | ||
| 312 | if (prefix) | |
| ddedf7ca | 313 | memcpy(ans, prefix, pl); |
| 4f5dd394 | 314 | |
| ddedf7ca | 315 | for (f = s, t = ans + pl; f < s + length; f++) { |
| 4f5dd394 | 316 | size_t remaining; |
| 3565e095 | 317 | bool eight_bit = false; |
| c932fb71 | 318 | char32_t u; |
| 4f5dd394 LP |
319 | |
| 320 | remaining = s + length - f; | |
| 321 | assert(remaining > 0); | |
| 322 | ||
| 323 | if (*f != '\\') { | |
| 629ff674 | 324 | /* A literal, copy verbatim */ |
| 4f5dd394 LP |
325 | *(t++) = *f; |
| 326 | continue; | |
| 327 | } | |
| 328 | ||
| 329 | if (remaining == 1) { | |
| 330 | if (flags & UNESCAPE_RELAX) { | |
| 331 | /* A trailing backslash, copy verbatim */ | |
| 332 | *(t++) = *f; | |
| 333 | continue; | |
| 334 | } | |
| 335 | ||
| 4f5dd394 LP |
336 | return -EINVAL; |
| 337 | } | |
| 338 | ||
| ddedf7ca ZJS |
339 | r = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL); |
| 340 | if (r < 0) { | |
| 4f5dd394 LP |
341 | if (flags & UNESCAPE_RELAX) { |
| 342 | /* Invalid escape code, let's take it literal then */ | |
| 343 | *(t++) = '\\'; | |
| 344 | continue; | |
| 345 | } | |
| 346 | ||
| ddedf7ca | 347 | return r; |
| 4f5dd394 LP |
348 | } |
| 349 | ||
| ddedf7ca | 350 | f += r; |
| 3565e095 ZJS |
351 | if (eight_bit) |
| 352 | /* One byte? Set directly as specified */ | |
| 353 | *(t++) = u; | |
| 4f5dd394 | 354 | else |
| 3565e095 | 355 | /* Otherwise encode as multi-byte UTF-8 */ |
| 4f5dd394 | 356 | t += utf8_encode_unichar(t, u); |
| 4f5dd394 LP |
357 | } |
| 358 | ||
| 359 | *t = 0; | |
| 360 | ||
| 1421705d | 361 | assert(t >= ans); /* Let static analyzers know that the answer is non-negative. */ |
| ddedf7ca ZJS |
362 | *ret = TAKE_PTR(ans); |
| 363 | return t - *ret; | |
| 4f5dd394 LP |
364 | } |
| 365 | ||
| b19f2116 | 366 | char* xescape_full(const char *s, const char *bad, size_t console_width, XEscapeFlags flags) { |
| 70d55819 | 367 | char *ans, *t, *prev, *prev2; |
| 4f5dd394 LP |
368 | const char *f; |
| 369 | ||
| ea844c49 LP |
370 | assert(s); |
| 371 | ||
| 70d55819 | 372 | /* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be |
| b19f2116 ZJS |
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. | |
| 70d55819 | 375 | * |
| fc96e5c0 ZJS |
376 | * If console_width is reached, or XESCAPE_FORCE_ELLIPSIS is set, output is truncated and "..." is |
| 377 | * appended. */ | |
| 4f5dd394 | 378 | |
| 70d55819 ZJS |
379 | if (console_width == 0) |
| 380 | return strdup(""); | |
| 381 | ||
| 382 | ans = new(char, MIN(strlen(s), console_width) * 4 + 1); | |
| 383 | if (!ans) | |
| 4f5dd394 LP |
384 | return NULL; |
| 385 | ||
| 70d55819 ZJS |
386 | memset(ans, '_', MIN(strlen(s), console_width) * 4); |
| 387 | ans[MIN(strlen(s), console_width) * 4] = 0; | |
| 388 | ||
| fc96e5c0 ZJS |
389 | bool force_ellipsis = FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS); |
| 390 | ||
| 70d55819 ZJS |
391 | for (f = s, t = prev = prev2 = ans; ; f++) { |
| 392 | char *tmp_t = t; | |
| 393 | ||
| 394 | if (!*f) { | |
| fc96e5c0 ZJS |
395 | if (force_ellipsis) |
| 396 | break; | |
| 397 | ||
| 70d55819 ZJS |
398 | *t = 0; |
| 399 | return ans; | |
| 400 | } | |
| 401 | ||
| b19f2116 ZJS |
402 | if ((unsigned char) *f < ' ' || |
| 403 | (!FLAGS_SET(flags, XESCAPE_8_BIT) && (unsigned char) *f >= 127) || | |
| ea844c49 | 404 | *f == '\\' || (bad && strchr(bad, *f))) { |
| fc96e5c0 | 405 | if ((size_t) (t - ans) + 4 + 3 * force_ellipsis > console_width) |
| 70d55819 | 406 | break; |
| 4f5dd394 | 407 | |
| 4f5dd394 LP |
408 | *(t++) = '\\'; |
| 409 | *(t++) = 'x'; | |
| 410 | *(t++) = hexchar(*f >> 4); | |
| 411 | *(t++) = hexchar(*f); | |
| 70d55819 | 412 | } else { |
| fc96e5c0 | 413 | if ((size_t) (t - ans) + 1 + 3 * force_ellipsis > console_width) |
| 70d55819 ZJS |
414 | break; |
| 415 | ||
| 4f5dd394 | 416 | *(t++) = *f; |
| 70d55819 | 417 | } |
| 4f5dd394 | 418 | |
| 70d55819 ZJS |
419 | /* We might need to go back two cycles to fit three dots, so remember two positions */ |
| 420 | prev2 = prev; | |
| 421 | prev = tmp_t; | |
| 422 | } | |
| 4f5dd394 | 423 | |
| 70d55819 ZJS |
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 */ | |
| 426 | size_t off; | |
| 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); | |
| 433 | else | |
| 434 | off = console_width - c; | |
| 435 | assert(off <= (size_t) (t - ans)); | |
| 436 | ||
| 437 | memcpy(ans + off, "...", c); | |
| 438 | ans[off + c] = '\0'; | |
| 439 | return ans; | |
| 4f5dd394 LP |
440 | } |
| 441 | ||
| b19f2116 ZJS |
442 | char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags) { |
| 443 | if (FLAGS_SET(flags, XESCAPE_8_BIT)) | |
| ea844c49 | 444 | return xescape_full(str, /* bad= */ NULL, console_width, flags); |
| e3b4efd2 | 445 | else |
| fc96e5c0 ZJS |
446 | return utf8_escape_non_printable_full(str, |
| 447 | console_width, | |
| 448 | FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS)); | |
| e3b4efd2 ZJS |
449 | } |
| 450 | ||
| e98a26dd | 451 | char* octescape_full(const char *s, size_t len, const char *bad) { |
| 76519cec | 452 | char *buf, *t; |
| 95052df3 | 453 | |
| e98a26dd | 454 | /* Escapes all chars in bad, in addition to \ and " chars, in \nnn octal style escaping. */ |
| 95052df3 | 455 | |
| 76519cec YW |
456 | assert(s || len == 0); |
| 457 | ||
| c6342e35 LP |
458 | if (len == SIZE_MAX) |
| 459 | len = strlen(s); | |
| 460 | ||
| 60cf4059 | 461 | if (len > (SIZE_MAX - 1) / 4) |
| c6342e35 LP |
462 | return NULL; |
| 463 | ||
| 76519cec YW |
464 | t = buf = new(char, len * 4 + 1); |
| 465 | if (!buf) | |
| 95052df3 ZJS |
466 | return NULL; |
| 467 | ||
| 76519cec YW |
468 | for (size_t i = 0; i < len; i++) { |
| 469 | uint8_t u = (uint8_t) s[i]; | |
| 95052df3 | 470 | |
| e98a26dd | 471 | if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || (bad && strchr(bad, u))) { |
| 95052df3 | 472 | *(t++) = '\\'; |
| 76519cec YW |
473 | *(t++) = '0' + (u >> 6); |
| 474 | *(t++) = '0' + ((u >> 3) & 7); | |
| 475 | *(t++) = '0' + (u & 7); | |
| 95052df3 | 476 | } else |
| 76519cec | 477 | *(t++) = u; |
| 95052df3 ZJS |
478 | } |
| 479 | ||
| 480 | *t = 0; | |
| 76519cec | 481 | return buf; |
| 95052df3 ZJS |
482 | } |
| 483 | ||
| 6bdbfb7e | 484 | char* decescape(const char *s, size_t len, const char *bad) { |
| b699f5f2 RP |
485 | char *buf, *t; |
| 486 | ||
| 487 | /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */ | |
| 488 | ||
| 489 | assert(s || len == 0); | |
| 490 | ||
| 7de7c7b6 MY |
491 | if (len == SIZE_MAX) |
| 492 | len = strlen(s); | |
| 493 | ||
| 494 | if (len > (SIZE_MAX - 1) / 4) | |
| 495 | return NULL; | |
| 496 | ||
| b699f5f2 RP |
497 | t = buf = new(char, len * 4 + 1); |
| 498 | if (!buf) | |
| 499 | return NULL; | |
| 500 | ||
| 501 | for (size_t i = 0; i < len; i++) { | |
| 502 | uint8_t u = (uint8_t) s[i]; | |
| 503 | ||
| 504 | if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || strchr(bad, u)) { | |
| 505 | *(t++) = '\\'; | |
| 506 | *(t++) = '0' + (u / 100); | |
| 507 | *(t++) = '0' + ((u / 10) % 10); | |
| 508 | *(t++) = '0' + (u % 10); | |
| 509 | } else | |
| 510 | *(t++) = u; | |
| 511 | } | |
| 512 | ||
| 513 | *t = 0; | |
| 514 | return buf; | |
| 515 | } | |
| 516 | ||
| 566d06ae | 517 | static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) { |
| 4f5dd394 | 518 | assert(bad); |
| 0b82a6fa | 519 | assert(t); |
| 520 | assert(s); | |
| 4f5dd394 | 521 | |
| 00f57157 | 522 | while (*s) { |
| 523 | int l = utf8_encoded_valid_unichar(s, SIZE_MAX); | |
| 524 | ||
| 525 | if (char_is_cc(*s) || l < 0) | |
| 526 | t += cescape_char(*(s++), t); | |
| 527 | else if (l == 1) { | |
| 0089ab08 ZJS |
528 | if (*s == '\\' || strchr(bad, *s)) |
| 529 | *(t++) = '\\'; | |
| 00f57157 | 530 | *(t++) = *(s++); |
| 531 | } else { | |
| 532 | t = mempcpy(t, s, l); | |
| 533 | s += l; | |
| 804ee07c | 534 | } |
| 00f57157 | 535 | } |
| 804ee07c | 536 | |
| 4f5dd394 LP |
537 | return t; |
| 538 | } | |
| 539 | ||
| 31be0e9e | 540 | char* shell_escape(const char *s, const char *bad) { |
| 0089ab08 | 541 | char *buf, *t; |
| 4f5dd394 | 542 | |
| 0089ab08 ZJS |
543 | buf = new(char, strlen(s)*4+1); |
| 544 | if (!buf) | |
| 4f5dd394 LP |
545 | return NULL; |
| 546 | ||
| 0089ab08 | 547 | t = strcpy_backslash_escaped(buf, s, bad); |
| 4f5dd394 LP |
548 | *t = 0; |
| 549 | ||
| 0089ab08 | 550 | return buf; |
| 4f5dd394 LP |
551 | } |
| 552 | ||
| 9e53c10a | 553 | char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) { |
| 4f5dd394 | 554 | const char *p; |
| 0089ab08 | 555 | char *buf, *t; |
| 4f5dd394 LP |
556 | |
| 557 | assert(s); | |
| 558 | ||
| 0089ab08 | 559 | /* Encloses a string in quotes if necessary to make it OK as a shell string. */ |
| 4f5dd394 | 560 | |
| 1129cd8a ZJS |
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. */ | |
| 563 | ||
| 00f57157 | 564 | for (p = s; *p; ) { |
| 565 | int l = utf8_encoded_valid_unichar(p, SIZE_MAX); | |
| 566 | ||
| 567 | if (char_is_cc(*p) || l < 0 || | |
| 0089ab08 | 568 | strchr(WHITESPACE SHELL_NEED_QUOTES, *p)) |
| 4f5dd394 LP |
569 | break; |
| 570 | ||
| 00f57157 | 571 | p += l; |
| 572 | } | |
| 573 | ||
| 4f5dd394 LP |
574 | if (!*p) |
| 575 | return strdup(s); | |
| 576 | ||
| 0089ab08 ZJS |
577 | buf = new(char, FLAGS_SET(flags, SHELL_ESCAPE_POSIX) + 1 + strlen(s)*4 + 1 + 1); |
| 578 | if (!buf) | |
| 4f5dd394 LP |
579 | return NULL; |
| 580 | ||
| 0089ab08 | 581 | t = buf; |
| 9e53c10a | 582 | if (FLAGS_SET(flags, SHELL_ESCAPE_POSIX)) { |
| 804ee07c ZJS |
583 | *(t++) = '$'; |
| 584 | *(t++) = '\''; | |
| 9e53c10a ZJS |
585 | } else |
| 586 | *(t++) = '"'; | |
| 804ee07c | 587 | |
| 4f5dd394 LP |
588 | t = mempcpy(t, s, p - s); |
| 589 | ||
| 9e53c10a | 590 | t = strcpy_backslash_escaped(t, p, |
| 566d06ae | 591 | FLAGS_SET(flags, SHELL_ESCAPE_POSIX) ? SHELL_NEED_ESCAPE_POSIX : SHELL_NEED_ESCAPE); |
| 4f5dd394 | 592 | |
| 9e53c10a | 593 | if (FLAGS_SET(flags, SHELL_ESCAPE_POSIX)) |
| 804ee07c | 594 | *(t++) = '\''; |
| 9e53c10a ZJS |
595 | else |
| 596 | *(t++) = '"'; | |
| 4f5dd394 LP |
597 | *t = 0; |
| 598 | ||
| 0089ab08 | 599 | return str_realloc(buf); |
| 4f5dd394 | 600 | } |
| eeb91d29 | 601 | |
| 6ed684db | 602 | char* quote_command_line(char * const *argv, ShellEscapeFlags flags) { |
| eeb91d29 ZJS |
603 | _cleanup_free_ char *result = NULL; |
| 604 | ||
| 605 | assert(argv); | |
| 606 | ||
| eeb91d29 ZJS |
607 | STRV_FOREACH(a, argv) { |
| 608 | _cleanup_free_ char *t = NULL; | |
| 609 | ||
| 4ef15008 | 610 | t = shell_maybe_quote(*a, flags); |
| eeb91d29 ZJS |
611 | if (!t) |
| 612 | return NULL; | |
| 613 | ||
| 614 | if (!strextend_with_separator(&result, " ", t)) | |
| 615 | return NULL; | |
| 616 | } | |
| 617 | ||
| 7d0cede0 | 618 | return str_realloc(TAKE_PTR(result)); |
| eeb91d29 | 619 | } |