From: YeshunYe <yeyeshun@uniontech.com> When using the `start` command with a `/d` switch specifying a quoted path that ends with a backslash (e.g., `start /d "c:\path\" app.exe`), subsequent arguments were incorrectly concatenated into the path string due to parsing ambiguity. Signed-off-by: YeshunYe <yeyeshun@uniontech.com> --- programs/cmd/tests/test_builtins.bat.exp | 2 +- programs/cmd/tests/test_builtins.cmd.exp | 2 +- programs/start/start.c | 270 ++++++++++------------- 3 files changed, 122 insertions(+), 152 deletions(-) diff --git a/programs/cmd/tests/test_builtins.bat.exp b/programs/cmd/tests/test_builtins.bat.exp index 20dda0c9ca7..383c237b684 100644 --- a/programs/cmd/tests/test_builtins.bat.exp +++ b/programs/cmd/tests/test_builtins.bat.exp @@ -63,7 +63,7 @@ foo@space@ SUCCESS 1024 @todo_wine@SUCCESS 666 0 -@todo_wine@0 +0 --- success/failure for TYPE command FAILURE 1 SUCCESS 0 diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index f5358709f83..c88dee42c16 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -582,7 +582,7 @@ foo@space@ SUCCESS 1024 @todo_wine@SUCCESS 666 0 -@todo_wine@0 +0 --- success/failure for TYPE command FAILURE 1 SUCCESS 0 diff --git a/programs/start/start.c b/programs/start/start.c index 38aafe870a9..2ff0c177f96 100644 --- a/programs/start/start.c +++ b/programs/start/start.c @@ -116,85 +116,6 @@ static void usage(void) fatal_string(STRING_USAGE); } -/*********************************************************************** - * build_command_line - * - * Build the command line of a process from the argv array. - * - * We must quote and escape characters so that the argv array can be rebuilt - * from the command line: - * - spaces and tabs must be quoted - * 'a b' -> '"a b"' - * - quotes must be escaped - * '"' -> '\"' - * - if '\'s are followed by a '"', they must be doubled and followed by '\"', - * resulting in an odd number of '\' followed by a '"' - * '\"' -> '\\\"' - * '\\"' -> '\\\\\"' - * - '\'s are followed by the closing '"' must be doubled, - * resulting in an even number of '\' followed by a '"' - * ' \' -> '" \\"' - * ' \\' -> '" \\\\"' - * - '\'s that are not followed by a '"' can be left as is - * 'a\b' == 'a\b' - * 'a\\b' == 'a\\b' - */ -static WCHAR *build_command_line( WCHAR **wargv ) -{ - int len; - WCHAR **arg, *ret; - LPWSTR p; - - len = 1; - for (arg = wargv; *arg; arg++) len += 3 + 2 * wcslen( *arg ); - if (!(ret = malloc( len * sizeof(WCHAR) ))) return NULL; - - p = ret; - for (arg = wargv; *arg; arg++) - { - BOOL has_space, has_quote; - int i, bcount; - WCHAR *a; - - /* check for quotes and spaces in this argument */ - has_space = !**arg || wcschr( *arg, ' ' ) || wcschr( *arg, '\t' ); - has_quote = wcschr( *arg, '"' ) != NULL; - - /* now transfer it to the command line */ - if (has_space) *p++ = '"'; - if (has_quote || has_space) - { - bcount = 0; - for (a = *arg; *a; a++) - { - if (*a == '\\') bcount++; - else - { - if (*a == '"') /* double all the '\\' preceding this '"', plus one */ - for (i = 0; i <= bcount; i++) *p++ = '\\'; - bcount = 0; - } - *p++ = *a; - } - } - else - { - wcscpy( p, *arg ); - p += wcslen( p ); - } - if (has_space) - { - /* Double all the '\' preceding the closing quote */ - for (i = 0; i < bcount; i++) *p++ = '\\'; - *p++ = '"'; - } - *p++ = ' '; - } - if (p > ret) p--; /* remove last space */ - *p = 0; - return ret; -} - static WCHAR *get_parent_dir(const WCHAR* path) { WCHAR *last_slash; @@ -214,17 +135,24 @@ static WCHAR *get_parent_dir(const WCHAR* path) return result; } -static BOOL is_option(const WCHAR* arg, const WCHAR* opt) +static BOOL is_option(WCHAR *arg, WCHAR **next_arg, const WCHAR* opt) { - return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, - arg, -1, opt, -1) == CSTR_EQUAL; + int opt_len = wcslen(opt); + BOOL result = (arg[opt_len] == ' ' || arg[opt_len] == '\t' || arg[opt_len] == 0) && + CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, + arg, opt_len, opt, -1) == CSTR_EQUAL; + if (result) + *next_arg = arg + opt_len; + return result; } -static BOOL is_option_with_arg(WCHAR **argv, int *pos, const WCHAR* opt) +static BOOL is_option_with_arg(WCHAR *arg, WCHAR **next_arg, const WCHAR* opt) { - if (!is_option( argv[*pos], opt )) return FALSE; - (*pos)++; - if (!argv[*pos]) + if (!is_option( arg, next_arg, opt )) return FALSE; + while(arg[0] == ' ' || arg[0] == '\t') + arg++; + *next_arg = arg; + if (!arg[0]) { WINE_ERR("missing argument for %s\n", debugstr_w(opt)); usage(); @@ -232,21 +160,62 @@ static BOOL is_option_with_arg(WCHAR **argv, int *pos, const WCHAR* opt) return TRUE; } -static WCHAR *parse_title(const WCHAR *arg) +static WCHAR *parse_next_arg( WCHAR *src, WCHAR **next_arg ) { - /* See: - * WCMD_start() in programs/cmd/builtins.c - * WCMD_parameter_with_delims() in programs/cmd/batch.c - * The shell has already tokenized the command line for us. - * All we need to do is filter out all the quotes. - */ - int next; - const WCHAR *p = arg; - WCHAR *title = malloc( wcslen(arg) * sizeof(WCHAR) ); + WCHAR *arg, *dst; + int in_quotes = 0, bcount = 0; - for (next = 0; *p; p++) if (*p != '"') title[next++] = *p; - title[next] = 0; - return title; + arg = dst = wcsdup(src); + + while (*src == ' ' || *src == '\t') src++; + + while (*src) + { + if ((*src == ' ' || *src == '\t') && !in_quotes) + { + /* skip the remaining spaces */ + while (*src == ' ' || *src == '\t') src++; + if (!*src) break; + /* close the argument and copy it */ + *dst++ = 0; + break; + } + else if (*src == '\\') + { + *dst++ = *src++; + bcount++; + } + else if (*src == '"') + { + if ((bcount & 1) == 0) + { + /* Preceded by an even number of '\', this is half that + * number of '\', plus a '"' which we discard. + */ + dst -= bcount / 2; + src++; + in_quotes = !in_quotes; + if (bcount) bcount--; + } + else + { + /* Preceded by an odd number of '\', this is half that + * number of '\' followed by a '"' + */ + dst -= bcount / 2 + 1; + *dst++ = *src++; + bcount = 0; + } + } + else /* a regular character */ + { + *dst++ = *src++; + bcount = 0; + } + } + *dst = 0; + *next_arg = src; + return arg; } static WCHAR *get_dos_filename( const WCHAR *unix_file ) @@ -421,12 +390,12 @@ static struct BOOL cp_inherit; } opts; -static void parse_command_line( int argc, WCHAR *argv[] ) +static void parse_command_line( void ) { - int i; const WCHAR *file = NULL; BOOL unix_mode = FALSE; BOOL progid_open = FALSE; + WCHAR *arg; opts.sei.cbSize = sizeof(opts.sei); opts.sei.lpVerb = L"open"; @@ -436,115 +405,116 @@ static void parse_command_line( int argc, WCHAR *argv[] ) opts.creation_flags = CREATE_NEW_CONSOLE; opts.cp_inherit = FALSE; + arg = wcsdup(GetCommandLineW()); + /* skip argv[0] */ + parse_next_arg(arg, &arg); + /* Canonical Microsoft commandline flag processing: * flags start with / and are case insensitive. */ - for (i = 1; i < argc; i++) + while (*arg) { /* parse first quoted argument as console title */ - if (!opts.title && argv[i][0] == '"') { - opts.title = parse_title(argv[i]); + if (!opts.title && arg[0] == '"') { + opts.title = parse_next_arg(arg, &arg); continue; } - if (argv[i][0] != '/') + if (arg[0] == ' ' || arg[0] == '\t') { + arg++; + continue; + } + if (arg[0] != '/') break; - if (argv[i][1] == 'd' || argv[i][1] == 'D') { - if (argv[i][2]) - /* The start directory was concatenated to the option */ - opts.sei.lpDirectory = argv[i]+2; - else if (i+1 == argc) { - WINE_ERR("you must specify a directory path for the /d option\n"); - usage(); - } else - opts.sei.lpDirectory = argv[++i]; - - if (opts.sei.lpDirectory[0] == '\"') - { + if (arg[1] == 'd' || arg[1] == 'D') { + arg += 2; + opts.sei.lpDirectory = parse_next_arg(arg, &arg); + + if (opts.sei.lpDirectory[0] == '\"') { int len = wcslen(opts.sei.lpDirectory); - if (len > 1 && opts.sei.lpDirectory[len - 1] == '\"') - { + if (len > 1 && opts.sei.lpDirectory[len - 1] == '\"') { WCHAR* lpDirectory = wcsdup(opts.sei.lpDirectory); lpDirectory[len - 1] = 0; opts.sei.lpDirectory = lpDirectory + 1; } } } - else if (is_option(argv[i], L"/b")) + else if (is_option(arg, &arg, L"/b")) opts.creation_flags &= ~CREATE_NEW_CONSOLE; - else if (argv[i][0] == '/' && (argv[i][1] == 'i' || argv[i][1] == 'I')) + else if (arg[0] == '/' && (arg[1] == 'i' || arg[1] == 'I')) { + arg += 2; TRACE("/i is ignored\n"); /* FIXME */ - else if (is_option(argv[i], L"/min")) + } + else if (is_option(arg, &arg, L"/min")) opts.sei.nShow = SW_SHOWMINIMIZED; - else if (is_option(argv[i], L"/max")) + else if (is_option(arg, &arg, L"/max")) opts.sei.nShow = SW_SHOWMAXIMIZED; - else if (is_option(argv[i], L"/low")) + else if (is_option(arg, &arg, L"/low")) opts.creation_flags |= IDLE_PRIORITY_CLASS; - else if (is_option(argv[i], L"/normal")) + else if (is_option(arg, &arg, L"/normal")) opts.creation_flags |= NORMAL_PRIORITY_CLASS; - else if (is_option(argv[i], L"/high")) + else if (is_option(arg, &arg, L"/high")) opts.creation_flags |= HIGH_PRIORITY_CLASS; - else if (is_option(argv[i], L"/realtime")) + else if (is_option(arg, &arg, L"/realtime")) opts.creation_flags |= REALTIME_PRIORITY_CLASS; - else if (is_option(argv[i], L"/abovenormal")) + else if (is_option(arg, &arg, L"/abovenormal")) opts.creation_flags |= ABOVE_NORMAL_PRIORITY_CLASS; - else if (is_option(argv[i], L"/belownormal")) + else if (is_option(arg, &arg, L"/belownormal")) opts.creation_flags |= BELOW_NORMAL_PRIORITY_CLASS; - else if (is_option(argv[i], L"/separate")) + else if (is_option(arg, &arg, L"/separate")) opts.creation_flags |= CREATE_SEPARATE_WOW_VDM; - else if (is_option(argv[i], L"/shared")) + else if (is_option(arg, &arg, L"/shared")) opts.creation_flags |= CREATE_SHARED_WOW_VDM; - else if (is_option(argv[i], L"/w") || is_option(argv[i], L"/wait")) + else if (is_option(arg, &arg, L"/w") || is_option(arg, &arg, L"/wait")) opts.sei.fMask |= SEE_MASK_NOCLOSEPROCESS; - else if (is_option(argv[i], L"/?")) + else if (is_option(arg, &arg, L"/?")) usage(); - else if (is_option_with_arg(argv, &i, L"/node")) + else if (is_option_with_arg(arg, &arg, L"/node")) TRACE("/node is ignored\n"); /* FIXME */ - else if (is_option_with_arg(argv, &i, L"/affinity")) + else if (is_option_with_arg(arg, &arg, L"/affinity")) TRACE("/affinity is ignored\n"); /* FIXME */ - else if (is_option_with_arg(argv, &i, L"/machine")) + else if (is_option_with_arg(arg, &arg, L"/machine")) { - if (is_option( argv[i], L"x86" )) opts.machine = IMAGE_FILE_MACHINE_I386; - else if (is_option( argv[i], L"amd64" )) opts.machine = IMAGE_FILE_MACHINE_AMD64; - else if (is_option( argv[i], L"arm" )) opts.machine = IMAGE_FILE_MACHINE_ARMNT; - else if (is_option( argv[i], L"arm64" )) opts.machine = IMAGE_FILE_MACHINE_ARM64; + if (is_option(arg, &arg, L"x86")) opts.machine = IMAGE_FILE_MACHINE_I386; + else if (is_option(arg, &arg, L"amd64")) opts.machine = IMAGE_FILE_MACHINE_AMD64; + else if (is_option(arg, &arg, L"arm")) opts.machine = IMAGE_FILE_MACHINE_ARMNT; + else if (is_option(arg, &arg, L"arm64")) opts.machine = IMAGE_FILE_MACHINE_ARM64; else usage(); } /* Wine extensions */ - else if (is_option_with_arg(argv, &i, L"/unix")) { + else if (is_option_with_arg(arg, &arg, L"/unix")) { unix_mode = TRUE; break; } - else if (is_option(argv[i], L"/exec")) { + else if (is_option(arg, &arg, L"/exec")) { opts.creation_flags = 0; opts.sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE | SEE_MASK_FLAG_NO_UI; opts.cp_inherit = TRUE; - i++; break; } - else if (is_option_with_arg(argv, &i, L"/progIDOpen")) { + else if (is_option_with_arg(arg, &arg, L"/progIDOpen")) { progid_open = TRUE; - opts.sei.lpClass = argv[i++]; + opts.sei.lpClass = parse_next_arg(arg, &arg); opts.sei.fMask |= SEE_MASK_CLASSNAME; break; } else { - WINE_ERR("Unknown option '%s'\n", wine_dbgstr_w(argv[i])); + WINE_ERR("Unknown option '%s'\n", wine_dbgstr_w(arg)); usage(); } } - if (i == argc) + if (arg[0] == 0) { if (progid_open) usage(); file = L"cmd.exe"; } - else file = argv[i++]; + else file = parse_next_arg(arg, &arg); - opts.sei.lpParameters = build_command_line( &argv[i] ); + opts.sei.lpParameters = arg; if ((unix_mode || progid_open) && file[0] == '/') { @@ -566,7 +536,7 @@ int __cdecl wmain (int argc, WCHAR *argv[]) { DWORD binary_type; - parse_command_line( argc, argv ); + parse_command_line(); if (GetBinaryTypeW(opts.sei.lpFile, &binary_type)) { WCHAR *commandline; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10008