Build a properly-quoted Windows command line from an argv array. * Returns a heap-allocated wide string, or NULL on allocation failure. * Quoting follows the MSVC CRT convention: arguments containing spaces, * tabs, or double-quotes are wrapped in double-quotes, with backslashes * before a closing quote doubled and the quote itself escaped. Argument * bytes are treated as UTF-8 and converted
| 452 | * so non-ASCII arguments (e.g. a non-ASCII %USERPROFILE%) survive intact. |
| 453 | * Declared in compat_fs_internal.h so the test suite can drive it. */ |
| 454 | wchar_t *cbm_build_cmdline(const char *const *argv) { |
| 455 | /* First pass: compute required buffer size. */ |
| 456 | size_t total = 1; /* NUL terminator */ |
| 457 | for (int i = 0; argv[i]; i++) { |
| 458 | const char *arg = argv[i]; |
| 459 | bool needs_quote = (arg[0] == '\0'); |
| 460 | for (const char *p = arg; *p; p++) { |
| 461 | if (*p == ' ' || *p == '\t' || *p == '"') { |
| 462 | needs_quote = true; |
| 463 | } |
| 464 | } |
| 465 | if (i > 0) { |
| 466 | total++; /* space separator */ |
| 467 | } |
| 468 | if (needs_quote) { |
| 469 | total += 2; /* opening and closing quote */ |
| 470 | size_t backslashes = 0; |
| 471 | for (const char *p = arg; *p; p++) { |
| 472 | if (*p == '\\') { |
| 473 | backslashes++; |
| 474 | } else if (*p == '"') { |
| 475 | total += backslashes + 1; /* double backslashes + escape backslash */ |
| 476 | backslashes = 0; |
| 477 | } else { |
| 478 | backslashes = 0; |
| 479 | } |
| 480 | total++; |
| 481 | } |
| 482 | /* Trailing backslashes before closing quote must be doubled. */ |
| 483 | total += backslashes; |
| 484 | } else { |
| 485 | total += strlen(arg); |
| 486 | } |
| 487 | } |
| 488 | |
| 489 | /* Build the quoted command line in UTF-8 first, then widen it as a |
| 490 | * whole via cbm_utf8_to_wide. Every character the quoting logic acts |
| 491 | * on (space, tab, '"', '\\') is ASCII and, by UTF-8's design, never |
| 492 | * appears inside a multibyte sequence, so operating on raw bytes here |
| 493 | * is safe and keeps multibyte argument bytes intact for conversion. */ |
| 494 | char *buf = (char *)malloc(total); |
| 495 | if (!buf) { |
| 496 | return NULL; |
| 497 | } |
| 498 | |
| 499 | /* Second pass: write the command line bytes. */ |
| 500 | char *w = buf; |
| 501 | for (int i = 0; argv[i]; i++) { |
| 502 | const char *arg = argv[i]; |
| 503 | bool needs_quote = (arg[0] == '\0'); |
| 504 | for (const char *p = arg; *p; p++) { |
| 505 | if (*p == ' ' || *p == '\t' || *p == '"') { |
| 506 | needs_quote = true; |
| 507 | break; |
| 508 | } |
| 509 | } |
| 510 | if (i > 0) { |
| 511 | *w++ = ' '; |
no test coverage detected