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
| 587 | * so non-ASCII arguments (e.g. a non-ASCII %USERPROFILE%) survive intact. |
| 588 | * Declared in compat_fs_internal.h so the test suite can drive it. */ |
| 589 | wchar_t *cbm_build_cmdline(const char *const *argv) { |
| 590 | /* First pass: compute required buffer size. */ |
| 591 | size_t total = 1; /* NUL terminator */ |
| 592 | for (int i = 0; argv[i]; i++) { |
| 593 | const char *arg = argv[i]; |
| 594 | bool needs_quote = (arg[0] == '\0'); |
| 595 | for (const char *p = arg; *p; p++) { |
| 596 | if (*p == ' ' || *p == '\t' || *p == '"') { |
| 597 | needs_quote = true; |
| 598 | } |
| 599 | } |
| 600 | if (i > 0) { |
| 601 | total++; /* space separator */ |
| 602 | } |
| 603 | if (needs_quote) { |
| 604 | total += 2; /* opening and closing quote */ |
| 605 | size_t backslashes = 0; |
| 606 | for (const char *p = arg; *p; p++) { |
| 607 | if (*p == '\\') { |
| 608 | backslashes++; |
| 609 | } else if (*p == '"') { |
| 610 | total += backslashes + 1; /* double backslashes + escape backslash */ |
| 611 | backslashes = 0; |
| 612 | } else { |
| 613 | backslashes = 0; |
| 614 | } |
| 615 | total++; |
| 616 | } |
| 617 | /* Trailing backslashes before closing quote must be doubled. */ |
| 618 | total += backslashes; |
| 619 | } else { |
| 620 | total += strlen(arg); |
| 621 | } |
| 622 | } |
| 623 | |
| 624 | /* Build the quoted command line in UTF-8 first, then widen it as a |
| 625 | * whole via cbm_utf8_to_wide. Every character the quoting logic acts |
| 626 | * on (space, tab, '"', '\\') is ASCII and, by UTF-8's design, never |
| 627 | * appears inside a multibyte sequence, so operating on raw bytes here |
| 628 | * is safe and keeps multibyte argument bytes intact for conversion. */ |
| 629 | char *buf = (char *)malloc(total); |
| 630 | if (!buf) { |
| 631 | return NULL; |
| 632 | } |
| 633 | |
| 634 | /* Second pass: write the command line bytes. */ |
| 635 | char *w = buf; |
| 636 | for (int i = 0; argv[i]; i++) { |
| 637 | const char *arg = argv[i]; |
| 638 | bool needs_quote = (arg[0] == '\0'); |
| 639 | for (const char *p = arg; *p; p++) { |
| 640 | if (*p == ' ' || *p == '\t' || *p == '"') { |
| 641 | needs_quote = true; |
| 642 | break; |
| 643 | } |
| 644 | } |
| 645 | if (i > 0) { |
| 646 | *w++ = ' '; |
no test coverage detected