[cmd] Change command line parsing away from argv/argc

Ann and Jason Edmeades jason at edmeades.me.uk
Tue Oct 2 18:53:42 CDT 2012


The existing cmd processing attempts to rebuild a command line
from argv/argc, by 'guessing' whether a parameter would originally
have had quotes by whether it contains a space, and argv already
has surrounding quotes removed. This means a simple command like
cmd.exe /c echo "hello" can never retain the quotes. The only
solution to this is to get the original command line and parse
that instead.

[Fixes 21131 plus a lot of todos]
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.winehq.org/pipermail/wine-patches/attachments/20121003/55552b6d/attachment.html>
-------------- next part --------------
From 198daae1d7d92e2ce2fa9b5afd67e861ff97b78d Mon Sep 17 00:00:00 2001
From: Jason Edmeades <jason at edmeades.me.uk>
Date: Wed, 3 Oct 2012 00:50:13 +0100
Subject: [PATCH 1/1] [cmd] Change command line parsing away from argv/argc

The existing cmd processing attempts to rebuild a command line
from argv/argc, by 'guessing' whether a parameter would originally
have had quotes by whether it contains a space, and argv already
has surrounding quotes removed. This means a simple command like
cmd.exe /c echo "hello" can never retain the quotes. The only
solution to this is to get the original command line and parse
that instead.

[Fixes 21131 plus a lot of todos]
---
 programs/cmd/tests/test_builtins.cmd     |    7 +-
 programs/cmd/tests/test_builtins.cmd.exp |   22 ++--
 programs/cmd/wcmdmain.c                  |  187 +++++++++---------------------
 3 files changed, 77 insertions(+), 139 deletions(-)

diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd
index 3e064b9..bdd6cd0 100644
--- a/programs/cmd/tests/test_builtins.cmd
+++ b/programs/cmd/tests/test_builtins.cmd
@@ -1861,7 +1861,12 @@ cmd.exe /c " echo passed2 "
 echo --- Test 14
 cmd.exe /c "dir /ad ..\fooba* /b"
 echo --- Test 15
-
+cmd.exe /cecho No whitespace
+echo --- Test 16
+cmd.exe /c
+echo --- Test 17
+cmd.exe /c at space@
+echo --- Test 18
 rem Ensure no interactive prompting when cmd.exe /c or /k
 echo file2 > file2
 cmd.exe /c copy file1 file2 >nul
diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp
index 6483f1e..1d004b7 100644
--- a/programs/cmd/tests/test_builtins.cmd.exp
+++ b/programs/cmd/tests/test_builtins.cmd.exp
@@ -910,23 +910,23 @@ value1
 ------------ cmd.exe command lines ------------
 --- Test 1
 Line1
- at todo_wine@"Line2"
+"Line2"
 --- Test 2
- at todo_wine@Test quotes "&" work
+Test quotes "&" work
 --- Test 3
- at todo_wine@"&"
+"&"
 --- Test 4
- at todo_wine@"<"
+"<"
 --- Test 5
- at todo_wine@">"
+">"
 --- Test 6
- at todo_wine@"\"
+"\"
 --- Test 7
- at todo_wine@"|"
+"|"
 --- Test 8
- at todo_wine@"`"
+"`"
 --- Test 9
- at todo_wine@"""
+"""
 --- Test 10
 --- Test 11
 --- Test 12
@@ -936,6 +936,10 @@ passed2 at space@
 --- Test 14
 foobar
 --- Test 15
+No whitespace
+--- Test 16
+--- Test 17
+--- Test 18
 No prompts or I would not get here1
 No prompts or I would not get here2
 %hello1%
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c
index 675256b..a6c4faa 100644
--- a/programs/cmd/wcmdmain.c
+++ b/programs/cmd/wcmdmain.c
@@ -2297,10 +2297,12 @@ void WCMD_free_commands(CMD_LIST *cmds) {
  * winmain().
  */
 
-int wmain (int argc, WCHAR *argvW[])
+int wmain (int unusedargc, WCHAR *unusedargvW[])
 {
   int     args;
-  WCHAR  *cmd;
+  WCHAR  *cmdLine = NULL;
+  WCHAR  *cmd     = NULL;
+  WCHAR  *argPos  = NULL;
   WCHAR string[1024];
   WCHAR envvar[4];
   BOOL opt_q;
@@ -2318,19 +2320,26 @@ int wmain (int argc, WCHAR *argvW[])
   LocalFree(cmd);
   cmd = NULL;
 
-  args  = argc;
+  /* Can't use argc/argv as it will have stripped quotes from parameters
+   * meaning cmd.exe /C echo "quoted string" is impossible
+   */
+  cmdLine = GetCommandLineW();
+  WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
+  args = 1;                /* start at first arg, skipping cmd.exe itself */
+
   opt_c = opt_k = opt_q = opt_s = FALSE;
-  while (args > 0)
+  WCMD_parameter(cmdLine, args, &argPos, NULL, TRUE);
+  while (argPos && argPos[0] != 0x00)
   {
       WCHAR c;
-      WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
-      if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
-          argvW++;
-          args--;
+      WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(argPos));
+      if (argPos[0]!='/' || argPos[1]=='\0') {
+          args++;
+          WCMD_parameter(cmdLine, args, &argPos, NULL, TRUE);
           continue;
       }
 
-      c=(*argvW)[1];
+      c=argPos[1];
       if (tolowerW(c)=='c') {
           opt_c = TRUE;
       } else if (tolowerW(c)=='q') {
@@ -2343,19 +2352,20 @@ int wmain (int argc, WCHAR *argvW[])
           unicodeOutput = FALSE;
       } else if (tolowerW(c)=='u') {
           unicodeOutput = TRUE;
-      } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
-          opt_t=strtoulW(&(*argvW)[3], NULL, 16);
+      } else if (tolowerW(c)=='t' && argPos[2]==':') {
+          opt_t=strtoulW(&argPos[3], NULL, 16);
       } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
           /* Ignored for compatibility with Windows */
       }
 
-      if ((*argvW)[2]==0) {
-          argvW++;
-          args--;
+      if (argPos[2]==0 || argPos[2]==' ' || argPos[2]=='\t') {
+          args++;
+          WCMD_parameter(cmdLine, args, &argPos, NULL, TRUE);
       }
       else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
       {
-          *argvW+=2;
+          /* Do not step to next paramater, instead carry on parsing this one */
+          argPos+=2;
       }
 
       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
@@ -2371,64 +2381,50 @@ int wmain (int argc, WCHAR *argvW[])
   interactive = FALSE;
 
   if (opt_c || opt_k) {
-      int     len,qcount;
-      WCHAR** arg;
-      int     argsLeft;
-      WCHAR*  p;
+      int     len;
+      WCHAR   *q1,*q2,*p;
+
+      /* Handle very edge case error scenarion, "cmd.exe /c" ie when there are no
+       * parameters after the /C or /K by pretending there was a single space     */
+      if (argPos == NULL) argPos = (WCHAR *)spaceW;
+
+      /* Build the command to execute - It is what is left in argPos */
+      len = strlenW(argPos);
+
+      /* Take a copy */
+      cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
+      if (!cmd)
+          exit(1);
+      strcpyW(cmd, argPos);
 
       /* opt_s left unflagged if the command starts with and contains exactly
        * one quoted string (exactly two quote characters). The quoted string
        * must be an executable name that has whitespace and must not have the
        * following characters: &<>()@^| */
 
-      /* Build the command to execute */
-      len = 0;
-      qcount = 0;
-      argsLeft = args;
-      for (arg = argvW; argsLeft>0; arg++,argsLeft--)
-      {
-          BOOL has_space = FALSE;
-          int bcount;
-          WCHAR* a;
-
-          bcount=0;
-          a=*arg;
-          if( !*a ) has_space = TRUE;
-          while (*a!='\0') {
-              if (*a=='\\') {
-                  bcount++;
-              } else {
-                  if (*a==' ' || *a=='\t') {
-                      has_space = TRUE;
-                  } else if (*a=='"') {
-                      /* doubling of '\' preceding a '"',
-                       * plus escaping of said '"'
-                       */
-                      len+=2*bcount+1;
-                      qcount++;
-                  }
-                  bcount=0;
-              }
-              a++;
-          }
-          len+=(a-*arg) + 1; /* for the separating space */
-          if (has_space)
-          {
-              len+=2; /* for the quotes */
-              qcount+=2;
-          }
+      if (!opt_s) {
+        /* 1. Confirm there is at least one quote */
+        q1 = strchrW(argPos, '"');
+        if (!q1) opt_s=1;
       }
 
-      /* If there is not exactly 2 quote characters, then /S (old behaviour) is enabled */
-      if (qcount!=2)
-          opt_s = TRUE;
+      if (!opt_s) {
+          /* 2. Confirm there is a second quote */
+          q2 = strchrW(q1+1, '"');
+          if (!q2) opt_s=1;
+      }
 
-      /* check argvW[0] for a space and invalid characters. There must not be any invalid
-       * characters, but there must be one or more whitespace                             */
+      if (!opt_s) {
+          /* 3. Ensure there are no more quotes */
+          if (strchrW(q2+1, '"')) opt_s=1;
+      }
+
+      /* check first parameter for a space and invalid characters. There must not be any
+       * invalid characters, but there must be one or more whitespace                    */
       if (!opt_s) {
           opt_s = TRUE;
-          p=*argvW;
-          while (*p!='\0') {
+          p=q1;
+          while (p!=q2) {
               if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
                   || *p=='@' || *p=='^' || *p=='|') {
                   opt_s = TRUE;
@@ -2440,73 +2436,6 @@ int wmain (int argc, WCHAR *argvW[])
           }
       }
 
-      cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
-      if (!cmd)
-          exit(1);
-
-      p = cmd;
-      argsLeft = args;
-      for (arg = argvW; argsLeft>0; arg++,argsLeft--)
-      {
-          BOOL has_space = FALSE, has_quote = FALSE;
-          WCHAR* a;
-
-          /* Check for quotes and spaces in this argument */
-          a=*arg;
-          if( !*a ) has_space = TRUE;
-          while (*a!='\0') {
-              if (*a==' ' || *a=='\t') {
-                  has_space = TRUE;
-                  if (has_quote)
-                      break;
-              } else if (*a=='"') {
-                  has_quote = TRUE;
-                  if (has_space)
-                      break;
-              }
-              a++;
-          }
-
-          /* Now transfer it to the command line */
-          if (has_space)
-              *p++='"';
-          if (has_quote) {
-              int bcount;
-              WCHAR* a;
-
-              bcount=0;
-              a=*arg;
-              while (*a!='\0') {
-                  if (*a=='\\') {
-                      *p++=*a;
-                      bcount++;
-                  } else {
-                      if (*a=='"') {
-                          int i;
-
-                          /* Double all the '\\' preceding this '"', plus one */
-                          for (i=0;i<=bcount;i++)
-                              *p++='\\';
-                          *p++='"';
-                      } else {
-                          *p++=*a;
-                      }
-                      bcount=0;
-                  }
-                  a++;
-              }
-          } else {
-              strcpyW(p,*arg);
-              p+=strlenW(*arg);
-          }
-          if (has_space)
-              *p++='"';
-          *p++=' ';
-      }
-      if (p > cmd)
-          p--;  /* remove last space */
-      *p = '\0';
-
       WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
 
       /* Finally, we only stay in new mode IF the first parameter is quoted and
-- 
1.7.9.5


More information about the wine-patches mailing list