[3/4] shell32: Fix CommandLineToArgvW()'s handling of the executable path and consecutive quotes.

Francois Gouget fgouget at codeweavers.com
Mon Oct 8 17:13:48 CDT 2012


---
 dlls/shell32/shell32_main.c  |  134 ++++++++++++++++++++++++++++++++++--------
 dlls/shell32/tests/shlexec.c |   64 ++++++++++----------
 2 files changed, 140 insertions(+), 58 deletions(-)

diff --git a/dlls/shell32/shell32_main.c b/dlls/shell32/shell32_main.c
index 827cbfc..7648dbb 100644
--- a/dlls/shell32/shell32_main.c
+++ b/dlls/shell32/shell32_main.c
@@ -64,18 +64,22 @@ WINE_DEFAULT_DEBUG_CHANNEL(shell);
  *   '"a b"'   -> 'a b'
  * - escaped quotes must be converted back to '"'
  *   '\"'      -> '"'
- * - an odd number of '\'s followed by '"' correspond to half that number
- *   of '\' followed by a '"' (extension of the above)
- *   '\\\"'    -> '\"'
- *   '\\\\\"'  -> '\\"'
- * - an even number of '\'s followed by a '"' correspond to half that number
- *   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
+ * - consecutive backslashes preceding a quote see their number halved with
+ *   the remainder escaping the quote:
+ *   2n   backslashes + quote -> n backslashes + quote as an argument delimiter
+ *   2n+1 backslashes + quote -> n backslashes + literal quote
+ * - backslashes that are not followed by a quote are copied literally:
  *   '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)
 {
@@ -84,7 +88,7 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
     LPCWSTR s;
     LPWSTR d;
     LPWSTR cmdline;
-    int in_quotes,bcount;
+    int qcount,bcount;
 
     if(!numargs)
     {
@@ -120,12 +124,33 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
 
     /* --- First count the arguments */
     argc=1;
-    bcount=0;
-    in_quotes=0;
     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)
     {
-        if (((*s==' ' || *s=='\t') && !in_quotes))
+        if ((*s==' ' || *s=='\t') && qcount==0)
         {
             /* skip to the next argument and count it if any */
             while (*s==' ' || *s=='\t')
@@ -133,25 +158,36 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
             if (*s)
                 argc++;
             bcount=0;
-            continue;
         }
         else if (*s=='\\')
         {
             /* '\', count them */
             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;
+            /* consecutive quotes, see comment in copying code below */
+            while (*s=='"')
+            {
+                qcount++;
+                s++;
+            }
+            qcount=qcount % 3;
+            if (qcount==2)
+                qcount=0;
         }
         else
         {
             /* a regular character */
             bcount=0;
+            s++;
         }
-        s++;
     }
 
     /* Allocate in a single lump, the string array, and the strings that go
@@ -165,13 +201,45 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
     strcpyW(cmdline, lpCmdline);
 
     /* --- Then split and copy the arguments */
+    argv[0]=d=cmdline;
     argc=1;
-    bcount=0;
-    in_quotes=0;
-    s=argv[0]=d=cmdline;
+    /* The first argument, the executable path, follows special rules */
+    if (*d=='"')
+    {
+        /* The executable path ends at the next quote, no matter what */
+        s=d+1;
+        while (*s)
+        {
+            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') && !in_quotes)
+        if ((*s==' ' || *s=='\t') && qcount==0)
         {
             /* close the argument */
             *d++=0;
@@ -197,8 +265,7 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
                  * number of '\', plus a quote which we erase.
                  */
                 d-=bcount/2;
-                in_quotes=!in_quotes;
-                s++;
+                qcount++;
             }
             else
             {
@@ -207,9 +274,24 @@ LPWSTR* WINAPI CommandLineToArgvW(LPCWSTR lpCmdline, int* numargs)
                  */
                 d=d-bcount/2-1;
                 *d++='"';
-                s++;
             }
+            s++;
             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
         {
diff --git a/dlls/shell32/tests/shlexec.c b/dlls/shell32/tests/shlexec.c
index adcd957..ac68ad0 100644
--- a/dlls/shell32/tests/shlexec.c
+++ b/dlls/shell32/tests/shlexec.c
@@ -1033,25 +1033,25 @@ static const cmdline_tests_t cmdline_tests[] =
      {"exe", "twoquotes", "next", NULL}, 0},
 
     {"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", NULL}, 0x61},
+     {"exe", "four\" quotes", "next", "4%3=1", NULL}, 0},
 
     {"exe five\"\"\"\"\"quotes next",
-     {"exe", "five\"quotes", "next", NULL}, 0x21},
+     {"exe", "five\"quotes", "next", NULL}, 0},
 
     {"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", NULL}, 0x20},
+     {"exe", "seven\"\" quotes", "next", "7%3=1", NULL}, 0},
 
     {"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", NULL}, 0x20},
+     {"exe", "thirteen\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
 
     /* Inside a quoted string the opening quote is added to the set of
      * consecutive quotes to get the effective quotes count. This gives:
@@ -1060,32 +1060,32 @@ static const cmdline_tests_t cmdline_tests[] =
      * 1+3n+2 quotes -> n+1 quotes plus closes the quoted string
      */
     {"exe \"two\"\"quotes next",
-     {"exe", "two\"quotes", "next", NULL}, 0x21},
+     {"exe", "two\"quotes", "next", NULL}, 0},
 
     {"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", NULL}, 0x61},
+     {"exe", "three\" quotes", "next", "4%3=1", NULL}, 0},
 
     {"exe \"four\"\"\"\"quotes next",
-     {"exe", "four\"quotes", "next", NULL}, 0x21},
+     {"exe", "four\"quotes", "next", NULL}, 0},
 
     {"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", NULL}, 0x20},
+     {"exe", "six\"\" quotes", "next", "7%3=1", NULL}, 0},
 
     {"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", NULL}, 0x20},
+     {"exe", "twelve\"\"\"\" quotes", "next", "13%3=1", NULL}, 0},
 
     /* Escaped consecutive quotes are fun */
     {"exe \"the crazy \\\\\"\"\"\\\\\" quotes",
-     {"exe", "the crazy \\\"\\", "quotes", NULL}, 0x21},
+     {"exe", "the crazy \\\"\\", "quotes", NULL}, 0},
 
     /* The executable path has its own rules!!!
      * - Backslashes have no special meaning.
@@ -1099,16 +1099,16 @@ static const cmdline_tests_t cmdline_tests[] =
      *   argument, the latter is parsed using the regular rules.
      */
     {"exe\"file\"path arg1",
-     {"exe\"file\"path", "arg1", NULL}, 0x10},
+     {"exe\"file\"path", "arg1", NULL}, 0},
 
     {"exe\"file\"path\targ1",
-     {"exe\"file\"path", "arg1", NULL}, 0x10},
+     {"exe\"file\"path", "arg1", NULL}, 0},
 
     {"exe\"path\\ arg1",
-     {"exe\"path\\", "arg1", NULL}, 0x31},
+     {"exe\"path\\", "arg1", NULL}, 0},
 
     {"\\\"exe \"arg one\"",
-     {"\\\"exe", "arg one", NULL}, 0x10},
+     {"\\\"exe", "arg one", NULL}, 0},
 
     {"\"spaced exe\" \"next arg\"",
      {"spaced exe", "next arg", NULL}, 0},
@@ -1117,19 +1117,19 @@ static const cmdline_tests_t cmdline_tests[] =
      {"spaced exe", "next arg", NULL}, 0},
 
     {"\"exe\"arg\" one\" argtwo",
-     {"exe", "arg one", "argtwo", NULL}, 0x31},
+     {"exe", "arg one", "argtwo", NULL}, 0},
 
     {"\"spaced exe\\\"arg1 arg2",
-     {"spaced exe\\", "arg1", "arg2", NULL}, 0x11},
+     {"spaced exe\\", "arg1", "arg2", NULL}, 0},
 
     {"\"two\"\" arg1 ",
-     {"two", " arg1 ", NULL}, 0x11},
+     {"two", " arg1 ", NULL}, 0},
 
     {"\"three\"\"\" arg2",
-     {"three", "", "arg2", NULL}, 0x61},
+     {"three", "", "arg2", NULL}, 0},
 
     {"\"four\"\"\"\"arg1",
-     {"four", "\"arg1", NULL}, 0x11},
+     {"four", "\"arg1", NULL}, 0},
 
     /* If the first character is a space then the executable path is empty */
     {" \"arg\"one argtwo",
@@ -1270,7 +1270,7 @@ static const argify_tests_t argify_tests[] =
     /* Only (double-)quotes have a special meaning. */
     {"Params23456", "'p2 p3` p4\\ $even", 0x40,
      {" \"'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,
      {" \"p=2\" \"p-3\" \"p4\tp4\rp4\np4\" \"\" \"\"",
@@ -1292,11 +1292,11 @@ static const argify_tests_t argify_tests[] =
 
     {"Params23456789", "three\"\"\"quotes \"p four\" three\"\"\"quotes p6", 0xff3,
      {" \"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,
      {" \"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
      * either.
@@ -1320,11 +1320,11 @@ static const argify_tests_t argify_tests[] =
 
     {"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", "", "", NULL}, 0x7e1}},
+      {"", "three q\"", "uotes", "p four", "three q\"", "uotes", "p7", "", "", NULL}, 0}},
 
     {"Params23456789", "\"four \"\"\"\" quotes\" \"p three\" \"four \"\"\"\" quotes\" p5", 0xff3,
      {" \"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
      * of a parameter but don't count the opening quote!
@@ -1335,11 +1335,11 @@ static const argify_tests_t argify_tests[] =
 
     {"Params23456789", "\"\"\"three quotes\" \"p three\" \"\"\"three quotes\" p5", 0x6f3,
      {" \"\"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,
      {" \"\"\" \"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! */
     {"Params23456", "p2 \"p3\" \"p4 is lost", 0x1c3,
-- 
1.7.10.4




More information about the wine-patches mailing list