[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