xcopy: Fix command line parsing logic.

Experiments show xcopy under Windows does not use argc/argv logic for
treating double quotes and backslashes. The xcopy logic is simplified
because literal quotes are illegal in Windows file names, thus escaped
double quotes (\") need not be treated.  'XCOPY "c:\dir a" "c:\dir
b\"' works under Windows.
This commit is contained in:
Martin Wilck 2010-10-27 01:49:06 +02:00 committed by Alexandre Julliard
parent b62c0d23f7
commit 766f6b0b4b
1 changed files with 103 additions and 39 deletions

View File

@ -49,9 +49,8 @@
WINE_DEFAULT_DEBUG_CHANNEL(xcopy); WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
/* Prototypes */ /* Prototypes */
static int XCOPY_ParseCommandLine(int argc, WCHAR **argvW, static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
WCHAR *suppliedsource, WCHAR *supplieddestination, DWORD *flags);
WCHAR *supplieddestination, DWORD *flags);
static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
WCHAR *spec, DWORD flags); WCHAR *spec, DWORD flags);
static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem,
@ -130,12 +129,12 @@ int wmain (int argc, WCHAR *argvW[])
/* /*
* Parse the command line * Parse the command line
*/ */
if ((rc = XCOPY_ParseCommandLine(argc, argvW, suppliedsource, if ((rc = XCOPY_ParseCommandLine(suppliedsource, supplieddestination,
supplieddestination, &flags)) != RC_OK) { &flags)) != RC_OK) {
if (rc == RC_HELP) if (rc == RC_HELP)
return RC_OK; return RC_OK;
else else
return rc; return rc;
} }
/* Trace out the supplied information */ /* Trace out the supplied information */
@ -196,43 +195,102 @@ int wmain (int argc, WCHAR *argvW[])
/* ========================================================================= /* =========================================================================
XCOPY_ParseCommandLine - Parses the command line XCOPY_ParseCommandLine - Parses the command line
========================================================================= */ ========================================================================= */
static int XCOPY_ParseCommandLine(int argc, WCHAR **argvW, static BOOL is_whitespace(WCHAR c)
WCHAR *suppliedsource, {
WCHAR *supplieddestination, DWORD *pflags) return c == ' ' || c == '\t';
}
static WCHAR *skip_whitespace(WCHAR *p)
{
for (; *p && is_whitespace(*p); p++);
return p;
}
/* Windows XCOPY uses a simplified command line parsing algorithm
that lacks the escaped-quote logic of build_argv(), because
literal double quotes are illegal in any of its arguments.
Example: 'XCOPY "c:\DIR A" "c:DIR B\"' is OK. */
static int find_end_of_word(const WCHAR *word, WCHAR **end)
{
BOOL in_quotes = 0;
const WCHAR *ptr = word;
for (;;) {
for (; *ptr != '\0' && *ptr != '"' &&
(in_quotes || !is_whitespace(*ptr)); ptr++);
if (*ptr == '"') {
in_quotes = !in_quotes;
ptr++;
}
/* Odd number of double quotes is illegal for XCOPY */
if (in_quotes && *ptr == '\0')
return RC_INITERROR;
if (*ptr == '\0' || (!in_quotes && is_whitespace(*ptr)))
break;
}
*end = (WCHAR*)ptr;
return RC_OK;
}
/* Remove all double quotes from a word */
static void strip_quotes(WCHAR *word, WCHAR **end)
{
WCHAR *rp, *wp;
for (rp = word, wp = word; *rp != '\0'; rp++) {
if (*rp == '"')
continue;
if (wp < rp)
*wp = *rp;
wp++;
}
*wp = '\0';
*end = wp;
}
static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
WCHAR *supplieddestination, DWORD *pflags)
{ {
const WCHAR EXCLUDE[] = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0}; const WCHAR EXCLUDE[] = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
DWORD flags = *pflags; DWORD flags = *pflags;
WCHAR *cmdline, *word, *end, *next;
int rc = RC_INITERROR;
/* Confirm at least one parameter */ cmdline = _wcsdup(GetCommandLineW());
if (argc < 2) { if (cmdline == NULL)
XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS)); return rc;
return RC_INITERROR;
}
/* Skip first arg, which is the program name */ /* Skip first arg, which is the program name */
argvW++; if ((rc = find_end_of_word(cmdline, &word)) != RC_OK)
goto out;
word = skip_whitespace(word);
while (argc > 1) while (*word)
{ {
argc--; WCHAR first;
WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW)); if ((rc = find_end_of_word(word, &end)) != RC_OK)
goto out;
next = skip_whitespace(end);
first = word[0];
*end = '\0';
strip_quotes(word, &end);
WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(word));
/* First non-switch parameter is source, second is destination */ /* First non-switch parameter is source, second is destination */
if (*argvW[0] != '/') { if (first != '/') {
if (suppliedsource[0] == 0x00) { if (suppliedsource[0] == 0x00) {
lstrcpyW(suppliedsource, *argvW); lstrcpyW(suppliedsource, word);
} else if (supplieddestination[0] == 0x00) { } else if (supplieddestination[0] == 0x00) {
lstrcpyW(supplieddestination, *argvW); lstrcpyW(supplieddestination, word);
} else { } else {
XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS)); XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
return RC_INITERROR; goto out;
} }
} else { } else {
/* Process all the switch options /* Process all the switch options
Note: Windows docs say /P prompts when dest is created Note: Windows docs say /P prompts when dest is created
but tests show it is done for each src file but tests show it is done for each src file
regardless of the destination */ regardless of the destination */
switch (toupper(argvW[0][1])) { switch (toupper(word[1])) {
case 'I': flags |= OPT_ASSUMEDIR; break; case 'I': flags |= OPT_ASSUMEDIR; break;
case 'S': flags |= OPT_RECURSIVE; break; case 'S': flags |= OPT_RECURSIVE; break;
case 'Q': flags |= OPT_QUIET; break; case 'Q': flags |= OPT_QUIET; break;
@ -254,19 +312,19 @@ static int XCOPY_ParseCommandLine(int argc, WCHAR **argvW,
/* E can be /E or /EXCLUDE */ /* E can be /E or /EXCLUDE */
case 'E': if (CompareStringW(LOCALE_USER_DEFAULT, case 'E': if (CompareStringW(LOCALE_USER_DEFAULT,
NORM_IGNORECASE | SORT_STRINGSORT, NORM_IGNORECASE | SORT_STRINGSORT,
&argvW[0][1], 8, &word[1], 8,
EXCLUDE, -1) == 2) { EXCLUDE, -1) == 2) {
if (XCOPY_ProcessExcludeList(&argvW[0][9])) { if (XCOPY_ProcessExcludeList(&word[9])) {
XCOPY_FailMessage(ERROR_INVALID_PARAMETER); XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
return RC_INITERROR; goto out;
} else flags |= OPT_EXCLUDELIST; } else flags |= OPT_EXCLUDELIST;
} else flags |= OPT_EMPTYDIR | OPT_RECURSIVE; } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
break; break;
/* D can be /D or /D: */ /* D can be /D or /D: */
case 'D': if ((argvW[0][2])==':' && isdigit(argvW[0][3])) { case 'D': if (word[2]==':' && isdigit(word[3])) {
SYSTEMTIME st; SYSTEMTIME st;
WCHAR *pos = &argvW[0][3]; WCHAR *pos = &word[3];
BOOL isError = FALSE; BOOL isError = FALSE;
memset(&st, 0x00, sizeof(st)); memset(&st, 0x00, sizeof(st));
@ -285,6 +343,7 @@ static int XCOPY_ParseCommandLine(int argc, WCHAR **argvW,
/* Parse the arg : Day */ /* Parse the arg : Day */
if (!isError) { if (!isError) {
st.wYear = _wtol(pos); st.wYear = _wtol(pos);
while (*pos && isdigit(*pos)) pos++;
if (st.wYear < 100) st.wYear+=2000; if (st.wYear < 100) st.wYear+=2000;
} }
@ -305,24 +364,25 @@ static int XCOPY_ParseCommandLine(int argc, WCHAR **argvW,
wine_dbgstr_w(datestring), wine_dbgstr_w(timestring)); wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
} else { } else {
XCOPY_FailMessage(ERROR_INVALID_PARAMETER); XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
return RC_INITERROR; goto out;
} }
} else { } else {
flags |= OPT_DATENEWER; flags |= OPT_DATENEWER;
} }
break; break;
case '-': if (toupper(argvW[0][2])=='Y') case '-': if (toupper(word[2])=='Y')
flags &= ~OPT_NOPROMPT; break; flags &= ~OPT_NOPROMPT; break;
case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP)); case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
return RC_HELP; rc = RC_HELP;
goto out;
default: default:
WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW)); WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(word));
XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), *argvW); XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), word);
return RC_INITERROR; goto out;
} }
} }
argvW++; word = next;
} }
/* Default the destination if not supplied */ /* Default the destination if not supplied */
@ -330,7 +390,11 @@ static int XCOPY_ParseCommandLine(int argc, WCHAR **argvW,
lstrcpyW(supplieddestination, wchr_dot); lstrcpyW(supplieddestination, wchr_dot);
*pflags = flags; *pflags = flags;
return RC_OK; rc = RC_OK;
out:
free(cmdline);
return rc;
} }