Commit 40b01c1b authored by Francois Gouget's avatar Francois Gouget Committed by Alexandre Julliard

shell32: Fix CommandLineToArgvW()'s handling of the executable path and consecutive quotes.

parent 35004f84
...@@ -64,18 +64,22 @@ WINE_DEFAULT_DEBUG_CHANNEL(shell); ...@@ -64,18 +64,22 @@ WINE_DEFAULT_DEBUG_CHANNEL(shell);
* '"a b"' -> 'a b' * '"a b"' -> 'a b'
* - escaped quotes must be converted back to '"' * - escaped quotes must be converted back to '"'
* '\"' -> '"' * '\"' -> '"'
* - an odd number of '\'s followed by '"' correspond to half that number * - consecutive backslashes preceding a quote see their number halved with
* of '\' followed by a '"' (extension of the above) * the remainder escaping the quote:
* '\\\"' -> '\"' * 2n backslashes + quote -> n backslashes + quote as an argument delimiter
* '\\\\\"' -> '\\"' * 2n+1 backslashes + quote -> n backslashes + literal quote
* - an even number of '\'s followed by a '"' correspond to half that number * - backslashes that are not followed by a quote are copied literally:
* of '\', plus a regular quote serving as an argument delimiter (which
* means it does not appear in the result)
* 'a\\"b c"' -> 'a\b c'
* 'a\\\\"b c"' -> 'a\\b c'
* - '\' that are not followed by a '"' are copied literally
* 'a\b' -> 'a\b' * 'a\b' -> 'a\b'
* 'a\\b' -> 'a\\b' * 'a\\b' -> 'a\\b'
* - in quoted strings, consecutive quotes see their number divided by three
* with the remainder modulo 3 deciding whether to close the string or not.
* Note that the opening quote must be counted in the consecutive quotes,
* that's the (1+) below:
* (1+) 3n quotes -> n quotes
* (1+) 3n+1 quotes -> n quotes plus closes the quoted string
* (1+) 3n+2 quotes -> n+1 quotes plus closes the quoted string
* - in unquoted strings, the first quote opens the quoted string and the
* remaining consecutive quotes follow the above rule.
*/ */
LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs) LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
{ {
...@@ -84,7 +88,7 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs) ...@@ -84,7 +88,7 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
LPCWSTR s; LPCWSTR s;
LPWSTR d; LPWSTR d;
LPWSTR cmdline; LPWSTR cmdline;
int in_quotes,bcount; int qcount,bcount;
if(!numargs) if(!numargs)
{ {
...@@ -120,12 +124,33 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs) ...@@ -120,12 +124,33 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
/* --- First count the arguments */ /* --- First count the arguments */
argc=1; argc=1;
bcount=0;
in_quotes=0;
s=lpCmdline; s=lpCmdline;
/* The first argument, the executable path, follows special rules */
if (*s=='"')
{
/* The executable path ends at the next quote, no matter what */
s++;
while (*s)
if (*s++=='"')
break;
}
else
{
/* The executable path ends at the next space, no matter what */
while (*s && *s!=' ' && *s!='\t')
s++;
}
/* skip to the first argument, if any */
while (*s==' ' || *s=='\t')
s++;
if (*s)
argc++;
/* Analyze the remaining arguments */
qcount=bcount=0;
while (*s) while (*s)
{ {
if (((*s==' ' || *s=='\t') && !in_quotes)) if ((*s==' ' || *s=='\t') && qcount==0)
{ {
/* skip to the next argument and count it if any */ /* skip to the next argument and count it if any */
while (*s==' ' || *s=='\t') while (*s==' ' || *s=='\t')
...@@ -133,26 +158,37 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs) ...@@ -133,26 +158,37 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
if (*s) if (*s)
argc++; argc++;
bcount=0; bcount=0;
continue;
} }
else if (*s=='\\') else if (*s=='\\')
{ {
/* '\', count them */ /* '\', count them */
bcount++; bcount++;
s++;
} }
else if ((*s=='"') && ((bcount & 1)==0)) else if (*s=='"')
{ {
/* unescaped '"' */ /* '"' */
in_quotes=!in_quotes; if ((bcount & 1)==0)
qcount++; /* unescaped '"' */
s++;
bcount=0; bcount=0;
/* consecutive quotes, see comment in copying code below */
while (*s=='"')
{
qcount++;
s++;
}
qcount=qcount % 3;
if (qcount==2)
qcount=0;
} }
else else
{ {
/* a regular character */ /* a regular character */
bcount=0; bcount=0;
}
s++; s++;
} }
}
/* Allocate in a single lump, the string array, and the strings that go /* Allocate in a single lump, the string array, and the strings that go
* with it. This way the caller can make a single LocalFree() call to free * with it. This way the caller can make a single LocalFree() call to free
...@@ -165,13 +201,45 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs) ...@@ -165,13 +201,45 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
strcpyW(cmdline, lpCmdline); strcpyW(cmdline, lpCmdline);
/* --- Then split and copy the arguments */ /* --- Then split and copy the arguments */
argv[0]=d=cmdline;
argc=1; argc=1;
bcount=0; /* The first argument, the executable path, follows special rules */
in_quotes=0; if (*d=='"')
s=argv[0]=d=cmdline; {
/* The executable path ends at the next quote, no matter what */
s=d+1;
while (*s) while (*s)
{ {
if ((*s==' ' || *s=='\t') && !in_quotes) if (*s=='"')
{
s++;
break;
}
*d++=*s++;
}
}
else
{
/* The executable path ends at the next space, no matter what */
while (*d && *d!=' ' && *d!='\t')
d++;
s=d;
if (*s)
s++;
}
/* close the argument */
*d++=0;
/* skip to the first argument and initialize it if any */
while (*s==' ' || *s=='\t')
s++;
if (*s)
argv[argc++]=d;
/* Split and copy the remaining arguments */
qcount=bcount=0;
while (*s)
{
if ((*s==' ' || *s=='\t') && qcount==0)
{ {
/* close the argument */ /* close the argument */
*d++=0; *d++=0;
...@@ -197,8 +265,7 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs) ...@@ -197,8 +265,7 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
* number of '\', plus a quote which we erase. * number of '\', plus a quote which we erase.
*/ */
d-=bcount/2; d-=bcount/2;
in_quotes=!in_quotes; qcount++;
s++;
} }
else else
{ {
...@@ -207,9 +274,24 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs) ...@@ -207,9 +274,24 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
*/ */
d=d-bcount/2-1; d=d-bcount/2-1;
*d++='"'; *d++='"';
s++;
} }
s++;
bcount=0; bcount=0;
/* Now count the number of consecutive quotes. Note that qcount
* already takes into account the opening quote if any, as well as
* the quote that lead us here.
*/
while (*s=='"')
{
if (++qcount==3)
{
*d++='"';
qcount=0;
}
s++;
}
if (qcount==2)
qcount=0;
} }
else else
{ {
......
...@@ -1033,25 +1033,25 @@ static const cmdline_tests_t cmdline_tests[] = ...@@ -1033,25 +1033,25 @@ static const cmdline_tests_t cmdline_tests[] =
{"exe", "twoquotes", "next", NULL}, 0}, {"exe", "twoquotes", "next", NULL}, 0},
{"exe three\"\"\"quotes next", {"exe three\"\"\"quotes next",
{"exe", "three\"quotes", "next", NULL}, 0x21}, {"exe", "three\"quotes", "next", NULL}, 0},
{"exe four\"\"\"\" quotes\" next 4%3=1", {"exe four\"\"\"\" quotes\" next 4%3=1",
{"exe", "four\" quotes", "next", "4%3=1", NULL}, 0x61}, {"exe", "four\" quotes", "next", "4%3=1", NULL}, 0},
{"exe five\"\"\"\"\"quotes next", {"exe five\"\"\"\"\"quotes next",
{"exe", "five\"quotes", "next", NULL}, 0x21}, {"exe", "five\"quotes", "next", NULL}, 0},
{"exe six\"\"\"\"\"\"quotes next", {"exe six\"\"\"\"\"\"quotes next",
{"exe", "six\"\"quotes", "next", NULL}, 0x20}, {"exe", "six\"\"quotes", "next", NULL}, 0},
{"exe seven\"\"\"\"\"\"\" quotes\" next 7%3=1", {"exe seven\"\"\"\"\"\"\" quotes\" next 7%3=1",
{"exe", "seven\"\" quotes", "next", "7%3=1", NULL}, 0x20}, {"exe", "seven\"\" quotes", "next", "7%3=1", NULL}, 0},
{"exe twelve\"\"\"\"\"\"\"\"\"\"\"\"quotes next", {"exe twelve\"\"\"\"\"\"\"\"\"\"\"\"quotes next",
{"exe", "twelve\"\"\"\"quotes", "next", NULL}, 0x20}, {"exe", "twelve\"\"\"\"quotes", "next", NULL}, 0},
{"exe thirteen\"\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1", {"exe thirteen\"\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1",
{"exe", "thirteen\"\"\"\" quotes", "next", "13%3=1", NULL}, 0x20}, {"exe", "thirteen\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
/* Inside a quoted string the opening quote is added to the set of /* Inside a quoted string the opening quote is added to the set of
* consecutive quotes to get the effective quotes count. This gives: * consecutive quotes to get the effective quotes count. This gives:
...@@ -1060,32 +1060,32 @@ static const cmdline_tests_t cmdline_tests[] = ...@@ -1060,32 +1060,32 @@ static const cmdline_tests_t cmdline_tests[] =
* 1+3n+2 quotes -> n+1 quotes plus closes the quoted string * 1+3n+2 quotes -> n+1 quotes plus closes the quoted string
*/ */
{"exe \"two\"\"quotes next", {"exe \"two\"\"quotes next",
{"exe", "two\"quotes", "next", NULL}, 0x21}, {"exe", "two\"quotes", "next", NULL}, 0},
{"exe \"two\"\" next", {"exe \"two\"\" next",
{"exe", "two\"", "next", NULL}, 0x21}, {"exe", "two\"", "next", NULL}, 0},
{"exe \"three\"\"\" quotes\" next 4%3=1", {"exe \"three\"\"\" quotes\" next 4%3=1",
{"exe", "three\" quotes", "next", "4%3=1", NULL}, 0x61}, {"exe", "three\" quotes", "next", "4%3=1", NULL}, 0},
{"exe \"four\"\"\"\"quotes next", {"exe \"four\"\"\"\"quotes next",
{"exe", "four\"quotes", "next", NULL}, 0x21}, {"exe", "four\"quotes", "next", NULL}, 0},
{"exe \"five\"\"\"\"\"quotes next", {"exe \"five\"\"\"\"\"quotes next",
{"exe", "five\"\"quotes", "next", NULL}, 0x20}, {"exe", "five\"\"quotes", "next", NULL}, 0},
{"exe \"six\"\"\"\"\"\" quotes\" next 7%3=1", {"exe \"six\"\"\"\"\"\" quotes\" next 7%3=1",
{"exe", "six\"\" quotes", "next", "7%3=1", NULL}, 0x20}, {"exe", "six\"\" quotes", "next", "7%3=1", NULL}, 0},
{"exe \"eleven\"\"\"\"\"\"\"\"\"\"\"quotes next", {"exe \"eleven\"\"\"\"\"\"\"\"\"\"\"quotes next",
{"exe", "eleven\"\"\"\"quotes", "next", NULL}, 0x20}, {"exe", "eleven\"\"\"\"quotes", "next", NULL}, 0},
{"exe \"twelve\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1", {"exe \"twelve\"\"\"\"\"\"\"\"\"\"\"\" quotes\" next 13%3=1",
{"exe", "twelve\"\"\"\" quotes", "next", "13%3=1", NULL}, 0x20}, {"exe", "twelve\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
/* Escaped consecutive quotes are fun */ /* Escaped consecutive quotes are fun */
{"exe \"the crazy \\\\\"\"\"\\\\\" quotes", {"exe \"the crazy \\\\\"\"\"\\\\\" quotes",
{"exe", "the crazy \\\"\\", "quotes", NULL}, 0x21}, {"exe", "the crazy \\\"\\", "quotes", NULL}, 0},
/* The executable path has its own rules!!! /* The executable path has its own rules!!!
* - Backslashes have no special meaning. * - Backslashes have no special meaning.
...@@ -1099,16 +1099,16 @@ static const cmdline_tests_t cmdline_tests[] = ...@@ -1099,16 +1099,16 @@ static const cmdline_tests_t cmdline_tests[] =
* argument, the latter is parsed using the regular rules. * argument, the latter is parsed using the regular rules.
*/ */
{"exe\"file\"path arg1", {"exe\"file\"path arg1",
{"exe\"file\"path", "arg1", NULL}, 0x10}, {"exe\"file\"path", "arg1", NULL}, 0},
{"exe\"file\"path\targ1", {"exe\"file\"path\targ1",
{"exe\"file\"path", "arg1", NULL}, 0x10}, {"exe\"file\"path", "arg1", NULL}, 0},
{"exe\"path\\ arg1", {"exe\"path\\ arg1",
{"exe\"path\\", "arg1", NULL}, 0x31}, {"exe\"path\\", "arg1", NULL}, 0},
{"\\\"exe \"arg one\"", {"\\\"exe \"arg one\"",
{"\\\"exe", "arg one", NULL}, 0x10}, {"\\\"exe", "arg one", NULL}, 0},
{"\"spaced exe\" \"next arg\"", {"\"spaced exe\" \"next arg\"",
{"spaced exe", "next arg", NULL}, 0}, {"spaced exe", "next arg", NULL}, 0},
...@@ -1117,19 +1117,19 @@ static const cmdline_tests_t cmdline_tests[] = ...@@ -1117,19 +1117,19 @@ static const cmdline_tests_t cmdline_tests[] =
{"spaced exe", "next arg", NULL}, 0}, {"spaced exe", "next arg", NULL}, 0},
{"\"exe\"arg\" one\" argtwo", {"\"exe\"arg\" one\" argtwo",
{"exe", "arg one", "argtwo", NULL}, 0x31}, {"exe", "arg one", "argtwo", NULL}, 0},
{"\"spaced exe\\\"arg1 arg2", {"\"spaced exe\\\"arg1 arg2",
{"spaced exe\\", "arg1", "arg2", NULL}, 0x11}, {"spaced exe\\", "arg1", "arg2", NULL}, 0},
{"\"two\"\" arg1 ", {"\"two\"\" arg1 ",
{"two", " arg1 ", NULL}, 0x11}, {"two", " arg1 ", NULL}, 0},
{"\"three\"\"\" arg2", {"\"three\"\"\" arg2",
{"three", "", "arg2", NULL}, 0x61}, {"three", "", "arg2", NULL}, 0},
{"\"four\"\"\"\"arg1", {"\"four\"\"\"\"arg1",
{"four", "\"arg1", NULL}, 0x11}, {"four", "\"arg1", NULL}, 0},
/* If the first character is a space then the executable path is empty */ /* If the first character is a space then the executable path is empty */
{" \"arg\"one argtwo", {" \"arg\"one argtwo",
...@@ -1270,7 +1270,7 @@ static const argify_tests_t argify_tests[] = ...@@ -1270,7 +1270,7 @@ static const argify_tests_t argify_tests[] =
/* Only (double-)quotes have a special meaning. */ /* Only (double-)quotes have a special meaning. */
{"Params23456", "'p2 p3` p4\\ $even", 0x40, {"Params23456", "'p2 p3` p4\\ $even", 0x40,
{" \"'p2\" \"p3`\" \"p4\\\" \"$even\" \"\"", {" \"'p2\" \"p3`\" \"p4\\\" \"$even\" \"\"",
{"", "'p2", "p3`", "p4\" $even \"", NULL}, 0x80}}, {"", "'p2", "p3`", "p4\" $even \"", NULL}, 0}},
{"Params23456", "p=2 p-3 p4\tp4\rp4\np4", 0x1c2, {"Params23456", "p=2 p-3 p4\tp4\rp4\np4", 0x1c2,
{" \"p=2\" \"p-3\" \"p4\tp4\rp4\np4\" \"\" \"\"", {" \"p=2\" \"p-3\" \"p4\tp4\rp4\np4\" \"\" \"\"",
...@@ -1292,11 +1292,11 @@ static const argify_tests_t argify_tests[] = ...@@ -1292,11 +1292,11 @@ static const argify_tests_t argify_tests[] =
{"Params23456789", "three\"\"\"quotes \"p four\" three\"\"\"quotes p6", 0xff3, {"Params23456789", "three\"\"\"quotes \"p four\" three\"\"\"quotes p6", 0xff3,
{" \"three\"\" \"quotes\" \"p four\" \"three\"\" \"quotes\" \"p6\" \"\" \"\"", {" \"three\"\" \"quotes\" \"p four\" \"three\"\" \"quotes\" \"p6\" \"\" \"\"",
{"", "three\"", "quotes", "p four", "three\"", "quotes", "p6", "", "", NULL}, 0x7e1}}, {"", "three\"", "quotes", "p four", "three\"", "quotes", "p6", "", "", NULL}, 0}},
{"Params23456789", "four\"\"\"\"quotes \"p three\" four\"\"\"\"quotes p5", 0xf3, {"Params23456789", "four\"\"\"\"quotes \"p three\" four\"\"\"\"quotes p5", 0xf3,
{" \"four\"\"quotes\" \"p three\" \"four\"\"quotes\" \"p5\" \"\" \"\" \"\" \"\"", {" \"four\"\"quotes\" \"p three\" \"four\"\"quotes\" \"p5\" \"\" \"\" \"\" \"\"",
{"", "four\"quotes p", "three fourquotes p5 \"", "", "", "", NULL}, 0xde1}}, {"", "four\"quotes p", "three fourquotes p5 \"", "", "", "", NULL}, 0}},
/* Quoted strings cannot be continued by tacking on a non space character /* Quoted strings cannot be continued by tacking on a non space character
* either. * either.
...@@ -1320,11 +1320,11 @@ static const argify_tests_t argify_tests[] = ...@@ -1320,11 +1320,11 @@ static const argify_tests_t argify_tests[] =
{"Params23456789", "\"three q\"\"\"uotes \"p four\" \"three q\"\"\"uotes p7", 0xff3, {"Params23456789", "\"three q\"\"\"uotes \"p four\" \"three q\"\"\"uotes p7", 0xff3,
{" \"three q\"\" \"uotes\" \"p four\" \"three q\"\" \"uotes\" \"p7\" \"\" \"\"", {" \"three q\"\" \"uotes\" \"p four\" \"three q\"\" \"uotes\" \"p7\" \"\" \"\"",
{"", "three q\"", "uotes", "p four", "three q\"", "uotes", "p7", "", "", NULL}, 0x7e1}}, {"", "three q\"", "uotes", "p four", "three q\"", "uotes", "p7", "", "", NULL}, 0}},
{"Params23456789", "\"four \"\"\"\" quotes\" \"p three\" \"four \"\"\"\" quotes\" p5", 0xff3, {"Params23456789", "\"four \"\"\"\" quotes\" \"p three\" \"four \"\"\"\" quotes\" p5", 0xff3,
{" \"four \"\" quotes\" \"p three\" \"four \"\" quotes\" \"p5\" \"\" \"\" \"\" \"\"", {" \"four \"\" quotes\" \"p three\" \"four \"\" quotes\" \"p5\" \"\" \"\" \"\" \"\"",
{"", "four \"", "quotes p", "three four", "", "quotes p5 \"", "", "", "", NULL}, 0x3e0}}, {"", "four \"", "quotes p", "three four", "", "quotes p5 \"", "", "", "", NULL}, 0}},
/* The quoted string rules also apply to consecutive quotes at the start /* The quoted string rules also apply to consecutive quotes at the start
* of a parameter but don't count the opening quote! * of a parameter but don't count the opening quote!
...@@ -1335,11 +1335,11 @@ static const argify_tests_t argify_tests[] = ...@@ -1335,11 +1335,11 @@ static const argify_tests_t argify_tests[] =
{"Params23456789", "\"\"\"three quotes\" \"p three\" \"\"\"three quotes\" p5", 0x6f3, {"Params23456789", "\"\"\"three quotes\" \"p three\" \"\"\"three quotes\" p5", 0x6f3,
{" \"\"three quotes\" \"p three\" \"\"three quotes\" \"p5\" \"\" \"\" \"\" \"\"", {" \"\"three quotes\" \"p three\" \"\"three quotes\" \"p5\" \"\" \"\" \"\" \"\"",
{"", "three", "quotes p", "three \"three", "quotes p5 \"", "", "", "", NULL}, 0x181}}, {"", "three", "quotes p", "three \"three", "quotes p5 \"", "", "", "", NULL}, 0}},
{"Params23456789", "\"\"\"\"fourquotes \"p four\" \"\"\"\"fourquotes p7", 0xbf3, {"Params23456789", "\"\"\"\"fourquotes \"p four\" \"\"\"\"fourquotes p7", 0xbf3,
{" \"\"\" \"fourquotes\" \"p four\" \"\"\" \"fourquotes\" \"p7\" \"\" \"\"", {" \"\"\" \"fourquotes\" \"p four\" \"\"\" \"fourquotes\" \"p7\" \"\" \"\"",
{"", "\"", "fourquotes", "p four", "\"", "fourquotes", "p7", "", "", NULL}, 0x7e1}}, {"", "\"", "fourquotes", "p four", "\"", "fourquotes", "p7", "", "", NULL}, 0}},
/* An unclosed quoted string gets lost! */ /* An unclosed quoted string gets lost! */
{"Params23456", "p2 \"p3\" \"p4 is lost", 0x1c3, {"Params23456", "p2 \"p3\" \"p4 is lost", 0x1c3,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment