[PATCH 2/5] cmd: use GetCommandline() rather than argv

Martin Wilck mwilck at arcor.de
Wed Sep 28 17:46:35 CDT 2011


Windows CMD.EXE doesn't use argc/argv for the command line arguments.
Automatic argv processing in Wine strips most quotes from the command
line, leading to wrong results (try cmd /c @echo "hi").

This patch makes CMD use GetCommandLine() instead. Some code that
served to work around argv parsing artefacts can be dropped.
---
 programs/cmd/tests/test_cmdline.cmd     |    3 +-
 programs/cmd/tests/test_cmdline.cmd.exp |   20 ++--
 programs/cmd/wcmdmain.c                 |  247 +++++++++++--------------------
 3 files changed, 99 insertions(+), 171 deletions(-)

diff --git a/programs/cmd/tests/test_cmdline.cmd b/programs/cmd/tests/test_cmdline.cmd
index d1fbba1..a9e0260 100755
--- a/programs/cmd/tests/test_cmdline.cmd
+++ b/programs/cmd/tests/test_cmdline.cmd
@@ -85,8 +85,7 @@ cmd /c"say one"
 rem ignore quote before qualifier
 cmd "/c"say one
 rem ignore anything before /c
-rem FIXME the next command in wine starts a sub-CMD
-echo THIS FAILS: cmd ignoreme/c say one
+cmd ignoreme/c say one
 echo --------- Testing special characters --------------
 echo @echo amp > "say&.bat"
 call say&
diff --git a/programs/cmd/tests/test_cmdline.cmd.exp b/programs/cmd/tests/test_cmdline.cmd.exp
index 9bc41a4..de8d575 100755
--- a/programs/cmd/tests/test_cmdline.cmd.exp
+++ b/programs/cmd/tests/test_cmdline.cmd.exp
@@ -15,25 +15,25 @@
 0 at space@
 1 at space@
 @todo_wine at 0@space@
- at todo_wine@0 at space@
-1 at space@
 0 at space@
-1 at space@
-2 at space@
+ at todo_wine@1 at space@
+0 at space@
+ at todo_wine@1 at space@
+ at todo_wine@2 at space@
 0 at space@
 @todo_wine at 3@space@
 @todo_wine at 4@space@
 ---------- Testing CMD /C quoting -----------------
- at todo_wine@"hi"
+"hi"
+ at todo_wine@1 at space@
+"\"\\"\\\"\\\\"@space@"\"\\"\\\"\\\\"
 1 at space@
- at todo_wine@"\"\\"\\\"\\\\"@space@"\"\\"\\\"\\\\"
+0 at space@
 1 at space@
 0 at space@
- at todo_wine@1 at space@
- at todo_wine@0 at space@
 @todo_wine at 0@space@
 0 at space@@or_broken at 3@space@
- at todo_wine@3 at space@
+3 at space@
 2 at space@
 2 at space@
 2 at space@
@@ -43,7 +43,7 @@
 0 at space@
 1 at space@
 0 at space@
-THIS at space@FAILS:@space at cmd@space at ignoreme/c at space@say at space@one
+0 at space@
 --------- Testing special characters --------------
 0 at space@
 @todo_wine at 0@space@
diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c
index 3f2cfcf..78416ac 100644
--- a/programs/cmd/wcmdmain.c
+++ b/programs/cmd/wcmdmain.c
@@ -2269,6 +2269,68 @@ void WCMD_free_commands(CMD_LIST *cmds) {
     }
 }
 
+/*
+ * Helper for wmain - Skips program name from command line.
+ */
+static const WCHAR*  skip_program_name(const WCHAR *cmdline)
+{
+    const WCHAR *p;
+    BOOL in_quotes;
+    for (p = cmdline, in_quotes = FALSE; *p != '\0'; p++) {
+        if (!in_quotes && (*p == ' ' || *p == '\t')) {
+            do { p++; } while (*p == ' ' || *p == '\t');
+            break;
+        }
+        if (*p == '"')
+            in_quotes = !in_quotes;
+    }
+    return p;
+}
+
+/*
+ * If the first /c or /k command character is a quote, cmd follows
+ * a complex logic to decide whether quotes will be kept:
+ *   1. no /s switch
+ *   2. exactly 2 quotes
+ *   3. at least 1 whitespace between the quotes
+ *   4. no special char (see below) between the quotes
+ *   5. string between quotes is a valid exe or bat file
+ * This function checks 2.-4., 5. is checked in WCMD_run_program().
+ */
+static BOOL may_keep_quotes(const WCHAR *chr)
+{
+    const WCHAR *pt, *pq;
+    BOOL spc;
+
+    for (pt = chr+1, spc = FALSE, pq = NULL; *pt != '\0'; pt++) {
+          if (*pt == '"') {
+              if (pq != NULL)
+                  /* don't keep if > 2 quotes */
+                  return FALSE;
+              pq = pt;
+              continue;
+          }
+          if (pq != NULL)
+              /* already behind closing quote */
+              continue;
+          switch (*pt) {
+          case ' ': case '\t':
+              spc = TRUE;
+              break;
+          /* don't keep if text between quotes contains special character */
+          case '&': case '<': case '>': case '(':
+          case ')': case '@': case '^': case '|':
+              return FALSE;
+          default:
+              break;
+          }
+    }
+
+    /* don't keep if no space between the quotes, or if < 2 quotes */
+    if (!spc || pq == NULL)
+        return FALSE;
+    return TRUE;
+}
 
 /*****************************************************************************
  * Main entry point. This is a console application so we have a main() not a
@@ -2277,7 +2339,6 @@ void WCMD_free_commands(CMD_LIST *cmds) {
 
 int wmain (int argc, WCHAR *argvW[])
 {
-  int     args;
   WCHAR  *cmd   = NULL;
   WCHAR string[1024];
   WCHAR envvar[4];
@@ -2289,6 +2350,7 @@ int wmain (int argc, WCHAR *argvW[])
   static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
   char ansiVersion[100];
   CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
+  WCHAR *cmdline, *chr;
 
   srand(time(NULL));
 
@@ -2298,19 +2360,22 @@ int wmain (int argc, WCHAR *argvW[])
   wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
   strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
 
-  args  = argc;
+  cmdline = WCMD_strdupW(GetCommandLineW());
+  if (cmdline == NULL) {
+      SetLastError(ERROR_OUTOFMEMORY);
+      exit(1);
+  }
+  chr = (WCHAR*)skip_program_name(cmdline);
+  WINE_TRACE("Command line: '%s'\n", wine_dbgstr_w(chr));
+
   opt_c=opt_k=opt_q=opt_s=0;
-  while (args > 0)
-  {
-      WCHAR c;
-      WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
-      if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
-          argvW++;
-          args--;
+  for (; *chr != '\0'; chr++) {
+      WCHAR c, *t;
+      if (*chr != '/')
           continue;
-      }
-
-      c=(*argvW)[1];
+      c = *(++chr);
+      if (c == '\0')
+          break;
       if (tolowerW(c)=='c') {
           opt_c=1;
       } else if (tolowerW(c)=='q') {
@@ -2323,21 +2388,15 @@ int wmain (int argc, WCHAR *argvW[])
           unicodePipes=FALSE;
       } else if (tolowerW(c)=='u') {
           unicodePipes=TRUE;
-      } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
-          opt_t=strtoulW(&(*argvW)[3], NULL, 16);
+      } else if (tolowerW(c)=='t' && *(chr+1) == ':') {
+          opt_t=strtoulW(chr+2, &t, 16);
+          if (t == chr+2)
+              opt_t = 0;
+          chr = t-1; /* will be incremented in for loop */
       } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
           /* Ignored for compatibility with Windows */
       }
 
-      if ((*argvW)[2]==0) {
-          argvW++;
-          args--;
-      }
-      else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
-      {
-          *argvW+=2;
-      }
-
       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
           break;
   }
@@ -2348,147 +2407,17 @@ int wmain (int argc, WCHAR *argvW[])
   }
 
   if (opt_c || opt_k) {
-      int     len,qcount;
-      WCHAR** arg;
-      int     argsLeft;
-      WCHAR*  p;
-
-      /* 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--)
-      {
-          int has_space,bcount;
-          WCHAR* a;
-
-          has_space=0;
-          bcount=0;
-          a=*arg;
-          if( !*a ) has_space=1;
-          while (*a!='\0') {
-              if (*a=='\\') {
-                  bcount++;
-              } else {
-                  if (*a==' ' || *a=='\t') {
-                      has_space=1;
-                  } 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 (qcount!=2)
-          opt_s=1;
 
-      /* check argvW[0] for a space and invalid characters */
-      if (!opt_s) {
-          opt_s=1;
-          p=*argvW;
-          while (*p!='\0') {
-              if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
-                  || *p=='@' || *p=='^' || *p=='|') {
-                  opt_s=1;
-                  break;
-              }
-              if (*p==' ')
-                  opt_s=0;
-              p++;
-          }
-      }
+      for (cmd = chr+1; *cmd == ' ' || *cmd == '\t'; cmd++);
 
-      cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
-      if (!cmd)
-          exit(1);
-
-      p = cmd;
-      argsLeft = args;
-      for (arg = argvW; argsLeft>0; arg++,argsLeft--)
-      {
-          int has_space,has_quote;
-          WCHAR* a;
-
-          /* Check for quotes and spaces in this argument */
-          has_space=has_quote=0;
-          a=*arg;
-          if( !*a ) has_space=1;
-          while (*a!='\0') {
-              if (*a==' ' || *a=='\t') {
-                  has_space=1;
-                  if (has_quote)
-                      break;
-              } else if (*a=='"') {
-                  has_quote=1;
-                  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));
+      opt_s |= !may_keep_quotes(cmd);
 
       /* strip first and last quote characters if opt_s; check for invalid
        * executable is done later */
       if (opt_s && *cmd=='\"')
           WCMD_opt_s_strip_quotes(cmd);
+
+      WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
   }
 
   if (opt_c) {
@@ -2504,7 +2433,7 @@ int wmain (int argc, WCHAR *argvW[])
       WCMD_free_commands(toExecute);
       toExecute = NULL;
 
-      HeapFree(GetProcessHeap(), 0, cmd);
+      HeapFree(GetProcessHeap(), 0, (void*)cmdline);
       return errorlevel;
   }
 
@@ -2599,13 +2528,13 @@ int wmain (int argc, WCHAR *argvW[])
       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
       WCMD_free_commands(toExecute);
       toExecute = NULL;
-      HeapFree(GetProcessHeap(), 0, cmd);
   }
 
 /*
  *	Loop forever getting commands and executing them.
  */
 
+  HeapFree(GetProcessHeap(), 0, (void*)cmdline);
   SetEnvironmentVariableW(promptW, defaultpromptW);
   WCMD_version ();
   is_console = !!GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &dummy);
-- 
1.7.3.4



More information about the wine-patches mailing list