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

Martin Wilck mwilck at arcor.de
Mon Sep 19 18:51:12 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/wcmdmain.c |  249 +++++++++++++++++------------------------------
 1 files changed, 89 insertions(+), 160 deletions(-)

diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c
index 92b76fc..c0fcc08 100644
--- a/programs/cmd/wcmdmain.c
+++ b/programs/cmd/wcmdmain.c
@@ -2245,6 +2245,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
@@ -2253,7 +2315,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];
@@ -2263,6 +2324,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));
 
@@ -2272,19 +2334,22 @@ int wmain (int argc, WCHAR *argvW[])
   wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
   strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
 
-  args  = argc;
-  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--;
-          continue;
-      }
+  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));
 
-      c=(*argvW)[1];
+  opt_c=opt_k=opt_q=opt_s=0;
+  for (; *chr != '\0'; chr++) {
+      WCHAR c, *t;
+      if (*chr != '/')
+	  continue;
+      c = *(++chr);
+      if (c == '\0')
+	  break;
       if (tolowerW(c)=='c') {
           opt_c=1;
       } else if (tolowerW(c)=='q') {
@@ -2297,21 +2362,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;
   }
@@ -2322,147 +2381,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++;
-          }
-      }
-
-      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++;
-          }
+      for (cmd = chr+1; *cmd == ' ' || *cmd == '\t'; cmd++);
 
-          /* 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) {
@@ -2478,7 +2407,7 @@ int wmain (int argc, WCHAR *argvW[])
       WCMD_free_commands(toExecute);
       toExecute = NULL;
 
-      HeapFree(GetProcessHeap(), 0, cmd);
+      HeapFree(GetProcessHeap(), 0, (void*)cmdline);
       return errorlevel;
   }
 
@@ -2573,7 +2502,7 @@ int wmain (int argc, WCHAR *argvW[])
       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
       WCMD_free_commands(toExecute);
       toExecute = NULL;
-      HeapFree(GetProcessHeap(), 0, cmd);
+      HeapFree(GetProcessHeap(), 0, (void*)cmdline);
   }
 
 /*
-- 
1.7.3.4



More information about the wine-patches mailing list