]>
| Commit | Line | Data |
|---|---|---|
| 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
| 2 | ||
| 3 | #include <stdlib.h> | |
| 4 | #include <string.h> | |
| 5 | ||
| 6 | #include "alloc-util.h" | |
| 7 | #include "escape.h" | |
| 8 | #include "hexdecoct.h" | |
| 9 | #include "string-util.h" | |
| 10 | #include "strv.h" | |
| 11 | #include "utf8.h" | |
| 12 | ||
| 13 | int cescape_char(char c, char *buf) { | |
| 14 | char *buf_old = buf; | |
| 15 | ||
| 16 | /* Needs space for 4 characters in the buffer */ | |
| 17 | ||
| 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 | ||
| 78 | char* cescape_length(const char *s, size_t n) { | |
| 79 | const char *f; | |
| 80 | char *r, *t; | |
| 81 | ||
| 82 | /* Does C style string escaping. May be reversed with cunescape(). */ | |
| 83 | ||
| 84 | assert(s || n == 0); | |
| 85 | ||
| 86 | if (n == SIZE_MAX) | |
| 87 | n = strlen(s); | |
| 88 | ||
| 89 | if (n > (SIZE_MAX - 1) / 4) | |
| 90 | return NULL; | |
| 91 | ||
| 92 | r = new(char, n*4 + 1); | |
| 93 | if (!r) | |
| 94 | return NULL; | |
| 95 | ||
| 96 | for (f = s, t = r; f < s + n; f++) | |
| 97 | t += cescape_char(*f, t); | |
| 98 | ||
| 99 | *t = 0; | |
| 100 | ||
| 101 | return r; | |
| 102 | } | |
| 103 | ||
| 104 | int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul) { | |
| 105 | int r = 1; | |
| 106 | ||
| 107 | assert(p); | |
| 108 | assert(ret); | |
| 109 | assert(eight_bit); | |
| 110 | ||
| 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 | */ | |
| 116 | ||
| 117 | if (length != SIZE_MAX && length < 1) | |
| 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 | ||
| 162 | if (length != SIZE_MAX && length < 3) | |
| 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 */ | |
| 174 | if (a == 0 && b == 0 && !accept_nul) | |
| 175 | return -EINVAL; | |
| 176 | ||
| 177 | *ret = (a << 4U) | b; | |
| 178 | *eight_bit = true; | |
| 179 | r = 3; | |
| 180 | break; | |
| 181 | } | |
| 182 | ||
| 183 | case 'u': { | |
| 184 | /* C++11 style 16-bit unicode */ | |
| 185 | ||
| 186 | int a[4]; | |
| 187 | size_t i; | |
| 188 | uint32_t c; | |
| 189 | ||
| 190 | if (length != SIZE_MAX && length < 5) | |
| 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 */ | |
| 202 | if (c == 0 && !accept_nul) | |
| 203 | return -EINVAL; | |
| 204 | ||
| 205 | *ret = c; | |
| 206 | r = 5; | |
| 207 | break; | |
| 208 | } | |
| 209 | ||
| 210 | case 'U': { | |
| 211 | /* C++11 style 32-bit unicode */ | |
| 212 | ||
| 213 | int a[8]; | |
| 214 | size_t i; | |
| 215 | char32_t c; | |
| 216 | ||
| 217 | if (length != SIZE_MAX && length < 9) | |
| 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 | ||
| 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]; | |
| 228 | ||
| 229 | /* Don't allow 0 chars */ | |
| 230 | if (c == 0 && !accept_nul) | |
| 231 | return -EINVAL; | |
| 232 | ||
| 233 | /* Don't allow invalid code points */ | |
| 234 | if (!unichar_is_valid(c)) | |
| 235 | return -EINVAL; | |
| 236 | ||
| 237 | *ret = c; | |
| 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; | |
| 252 | char32_t m; | |
| 253 | ||
| 254 | if (length != SIZE_MAX && length < 3) | |
| 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 */ | |
| 270 | if (a == 0 && b == 0 && c == 0 && !accept_nul) | |
| 271 | return -EINVAL; | |
| 272 | ||
| 273 | /* Don't allow bytes above 255 */ | |
| 274 | m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c; | |
| 275 | if (m > 255) | |
| 276 | return -EINVAL; | |
| 277 | ||
| 278 | *ret = m; | |
| 279 | *eight_bit = true; | |
| 280 | r = 3; | |
| 281 | break; | |
| 282 | } | |
| 283 | ||
| 284 | default: | |
| 285 | return -EINVAL; | |
| 286 | } | |
| 287 | ||
| 288 | return r; | |
| 289 | } | |
| 290 | ||
| 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; | |
| 293 | char *t; | |
| 294 | const char *f; | |
| 295 | size_t pl; | |
| 296 | int r; | |
| 297 | ||
| 298 | assert(s); | |
| 299 | assert(ret); | |
| 300 | ||
| 301 | /* Undoes C style string escaping, and optionally prefixes it. */ | |
| 302 | ||
| 303 | if (length == SIZE_MAX) | |
| 304 | length = strlen(s); | |
| 305 | ||
| 306 | pl = strlen_ptr(prefix); | |
| 307 | ||
| 308 | ans = new(char, pl+length+1); | |
| 309 | if (!ans) | |
| 310 | return -ENOMEM; | |
| 311 | ||
| 312 | if (prefix) | |
| 313 | memcpy(ans, prefix, pl); | |
| 314 | ||
| 315 | for (f = s, t = ans + pl; f < s + length; f++) { | |
| 316 | size_t remaining; | |
| 317 | bool eight_bit = false; | |
| 318 | char32_t u; | |
| 319 | ||
| 320 | remaining = s + length - f; | |
| 321 | assert(remaining > 0); | |
| 322 | ||
| 323 | if (*f != '\\') { | |
| 324 | /* A literal, copy verbatim */ | |
| 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 | ||
| 336 | return -EINVAL; | |
| 337 | } | |
| 338 | ||
| 339 | r = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL); | |
| 340 | if (r < 0) { | |
| 341 | if (flags & UNESCAPE_RELAX) { | |
| 342 | /* Invalid escape code, let's take it literal then */ | |
| 343 | *(t++) = '\\'; | |
| 344 | continue; | |
| 345 | } | |
| 346 | ||
| 347 | return r; | |
| 348 | } | |
| 349 | ||
| 350 | f += r; | |
| 351 | if (eight_bit) | |
| 352 | /* One byte? Set directly as specified */ | |
| 353 | *(t++) = u; | |
| 354 | else | |
| 355 | /* Otherwise encode as multi-byte UTF-8 */ | |
| 356 | t += utf8_encode_unichar(t, u); | |
| 357 | } | |
| 358 | ||
| 359 | *t = 0; | |
| 360 | ||
| 361 | assert(t >= ans); /* Let static analyzers know that the answer is non-negative. */ | |
| 362 | *ret = TAKE_PTR(ans); | |
| 363 | return t - *ret; | |
| 364 | } | |
| 365 | ||
| 366 | char* xescape_full(const char *s, const char *bad, size_t console_width, XEscapeFlags flags) { | |
| 367 | char *ans, *t, *prev, *prev2; | |
| 368 | const char *f; | |
| 369 | ||
| 370 | assert(s); | |
| 371 | ||
| 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. | |
| 375 | * | |
| 376 | * If console_width is reached, or XESCAPE_FORCE_ELLIPSIS is set, output is truncated and "..." is | |
| 377 | * appended. */ | |
| 378 | ||
| 379 | if (console_width == 0) | |
| 380 | return strdup(""); | |
| 381 | ||
| 382 | ans = new(char, MIN(strlen(s), console_width) * 4 + 1); | |
| 383 | if (!ans) | |
| 384 | return NULL; | |
| 385 | ||
| 386 | memset(ans, '_', MIN(strlen(s), console_width) * 4); | |
| 387 | ans[MIN(strlen(s), console_width) * 4] = 0; | |
| 388 | ||
| 389 | bool force_ellipsis = FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS); | |
| 390 | ||
| 391 | for (f = s, t = prev = prev2 = ans; ; f++) { | |
| 392 | char *tmp_t = t; | |
| 393 | ||
| 394 | if (!*f) { | |
| 395 | if (force_ellipsis) | |
| 396 | break; | |
| 397 | ||
| 398 | *t = 0; | |
| 399 | return ans; | |
| 400 | } | |
| 401 | ||
| 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) | |
| 406 | break; | |
| 407 | ||
| 408 | *(t++) = '\\'; | |
| 409 | *(t++) = 'x'; | |
| 410 | *(t++) = hexchar(*f >> 4); | |
| 411 | *(t++) = hexchar(*f); | |
| 412 | } else { | |
| 413 | if ((size_t) (t - ans) + 1 + 3 * force_ellipsis > console_width) | |
| 414 | break; | |
| 415 | ||
| 416 | *(t++) = *f; | |
| 417 | } | |
| 418 | ||
| 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 | } | |
| 423 | ||
| 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; | |
| 440 | } | |
| 441 | ||
| 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); | |
| 445 | else | |
| 446 | return utf8_escape_non_printable_full(str, | |
| 447 | console_width, | |
| 448 | FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS)); | |
| 449 | } | |
| 450 | ||
| 451 | char* octescape_full(const char *s, size_t len, const char *bad) { | |
| 452 | char *buf, *t; | |
| 453 | ||
| 454 | /* Escapes all chars in bad, in addition to \ and " chars, in \nnn octal style escaping. */ | |
| 455 | ||
| 456 | assert(s || len == 0); | |
| 457 | ||
| 458 | if (len == SIZE_MAX) | |
| 459 | len = strlen(s); | |
| 460 | ||
| 461 | if (len > (SIZE_MAX - 1) / 4) | |
| 462 | return NULL; | |
| 463 | ||
| 464 | t = buf = new(char, len * 4 + 1); | |
| 465 | if (!buf) | |
| 466 | return NULL; | |
| 467 | ||
| 468 | for (size_t i = 0; i < len; i++) { | |
| 469 | uint8_t u = (uint8_t) s[i]; | |
| 470 | ||
| 471 | if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || (bad && strchr(bad, u))) { | |
| 472 | *(t++) = '\\'; | |
| 473 | *(t++) = '0' + (u >> 6); | |
| 474 | *(t++) = '0' + ((u >> 3) & 7); | |
| 475 | *(t++) = '0' + (u & 7); | |
| 476 | } else | |
| 477 | *(t++) = u; | |
| 478 | } | |
| 479 | ||
| 480 | *t = 0; | |
| 481 | return buf; | |
| 482 | } | |
| 483 | ||
| 484 | char* decescape(const char *s, size_t len, const char *bad) { | |
| 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 | ||
| 491 | if (len == SIZE_MAX) | |
| 492 | len = strlen(s); | |
| 493 | ||
| 494 | if (len > (SIZE_MAX - 1) / 4) | |
| 495 | return NULL; | |
| 496 | ||
| 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 | ||
| 517 | static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) { | |
| 518 | assert(bad); | |
| 519 | assert(t); | |
| 520 | assert(s); | |
| 521 | ||
| 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) { | |
| 528 | if (*s == '\\' || strchr(bad, *s)) | |
| 529 | *(t++) = '\\'; | |
| 530 | *(t++) = *(s++); | |
| 531 | } else { | |
| 532 | t = mempcpy(t, s, l); | |
| 533 | s += l; | |
| 534 | } | |
| 535 | } | |
| 536 | ||
| 537 | return t; | |
| 538 | } | |
| 539 | ||
| 540 | char* shell_escape(const char *s, const char *bad) { | |
| 541 | char *buf, *t; | |
| 542 | ||
| 543 | buf = new(char, strlen(s)*4+1); | |
| 544 | if (!buf) | |
| 545 | return NULL; | |
| 546 | ||
| 547 | t = strcpy_backslash_escaped(buf, s, bad); | |
| 548 | *t = 0; | |
| 549 | ||
| 550 | return buf; | |
| 551 | } | |
| 552 | ||
| 553 | char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) { | |
| 554 | const char *p; | |
| 555 | char *buf, *t; | |
| 556 | ||
| 557 | assert(s); | |
| 558 | ||
| 559 | /* Encloses a string in quotes if necessary to make it OK as a shell string. */ | |
| 560 | ||
| 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 | ||
| 564 | for (p = s; *p; ) { | |
| 565 | int l = utf8_encoded_valid_unichar(p, SIZE_MAX); | |
| 566 | ||
| 567 | if (char_is_cc(*p) || l < 0 || | |
| 568 | strchr(WHITESPACE SHELL_NEED_QUOTES, *p)) | |
| 569 | break; | |
| 570 | ||
| 571 | p += l; | |
| 572 | } | |
| 573 | ||
| 574 | if (!*p) | |
| 575 | return strdup(s); | |
| 576 | ||
| 577 | buf = new(char, FLAGS_SET(flags, SHELL_ESCAPE_POSIX) + 1 + strlen(s)*4 + 1 + 1); | |
| 578 | if (!buf) | |
| 579 | return NULL; | |
| 580 | ||
| 581 | t = buf; | |
| 582 | if (FLAGS_SET(flags, SHELL_ESCAPE_POSIX)) { | |
| 583 | *(t++) = '$'; | |
| 584 | *(t++) = '\''; | |
| 585 | } else | |
| 586 | *(t++) = '"'; | |
| 587 | ||
| 588 | t = mempcpy(t, s, p - s); | |
| 589 | ||
| 590 | t = strcpy_backslash_escaped(t, p, | |
| 591 | FLAGS_SET(flags, SHELL_ESCAPE_POSIX) ? SHELL_NEED_ESCAPE_POSIX : SHELL_NEED_ESCAPE); | |
| 592 | ||
| 593 | if (FLAGS_SET(flags, SHELL_ESCAPE_POSIX)) | |
| 594 | *(t++) = '\''; | |
| 595 | else | |
| 596 | *(t++) = '"'; | |
| 597 | *t = 0; | |
| 598 | ||
| 599 | return str_realloc(buf); | |
| 600 | } | |
| 601 | ||
| 602 | char* quote_command_line(char * const *argv, ShellEscapeFlags flags) { | |
| 603 | _cleanup_free_ char *result = NULL; | |
| 604 | ||
| 605 | assert(argv); | |
| 606 | ||
| 607 | STRV_FOREACH(a, argv) { | |
| 608 | _cleanup_free_ char *t = NULL; | |
| 609 | ||
| 610 | t = shell_maybe_quote(*a, flags); | |
| 611 | if (!t) | |
| 612 | return NULL; | |
| 613 | ||
| 614 | if (!strextend_with_separator(&result, " ", t)) | |
| 615 | return NULL; | |
| 616 | } | |
| 617 | ||
| 618 | return str_realloc(TAKE_PTR(result)); | |
| 619 | } |