Improve Windows quoting robustness

This commit is contained in:
Daniel Colascione 2011-04-26 03:44:03 -07:00
parent 0c6b7b19e5
commit 8f91bf9345
4 changed files with 148 additions and 38 deletions

View file

@ -1,3 +1,7 @@
2011-04-26 Daniel Colascione <dan.colascione@gmail.com>
* subr.el (shell-quote-argument): Escape correctly under Windows.
2011-04-25 Stefan Monnier <monnier@iro.umontreal.ca>
* emulation/cua-base.el (cua-selection-mode): Make it toggle again.
@ -50,6 +54,7 @@
* net/network-stream.el (network-stream-open-starttls): Give host
parameter to `gnutls-negotiate'.
(gnutls-negotiate): Adjust `gnutls-negotiate' declaration.
* subr.el (shell-quote-argument): Escape correctly under Windows.
2011-04-24 Daniel Colascione <dan.colascione@gmail.com>

View file

@ -2505,27 +2505,63 @@ Note: :data and :device are currently not supported on Windows."
(defun shell-quote-argument (argument)
"Quote ARGUMENT for passing as argument to an inferior shell."
(if (or (eq system-type 'ms-dos)
(and (eq system-type 'windows-nt) (w32-shell-dos-semantics)))
;; Quote using double quotes, but escape any existing quotes in
;; the argument with backslashes.
(let ((result "")
(start 0)
end)
(if (or (null (string-match "[^\"]" argument))
(< (match-end 0) (length argument)))
(while (string-match "[\"]" argument start)
(setq end (match-beginning 0)
result (concat result (substring argument start end)
"\\" (substring argument end (1+ end)))
start (1+ end))))
(concat "\"" result (substring argument start) "\""))
(cond
((eq system-type 'ms-dos)
;; Quote using double quotes, but escape any existing quotes in
;; the argument with backslashes.
(let ((result "")
(start 0)
end)
(if (or (null (string-match "[^\"]" argument))
(< (match-end 0) (length argument)))
(while (string-match "[\"]" argument start)
(setq end (match-beginning 0)
result (concat result (substring argument start end)
"\\" (substring argument end (1+ end)))
start (1+ end))))
(concat "\"" result (substring argument start) "\"")))
((and (eq system-type 'windows-nt) (w32-shell-dos-semantics))
;; First, quote argument so that CommandLineToArgvW will
;; understand it. See
;; http://msdn.microsoft.com/en-us/library/17w5ykft%28v=vs.85%29.aspx
;; After we perform that level of quoting, escape shell
;; metacharacters so that cmd won't mangle our argument. If the
;; argument contains no double quote characters, we can just
;; surround it with double quotes. Otherwise, we need to prefix
;; each shell metacharacter with a caret.
(setq argument
;; escape backslashes at end of string
(replace-regexp-in-string
"\\(\\\\*\\)$"
"\\1\\1"
;; escape backslashes and quotes in string body
(replace-regexp-in-string
"\\(\\\\*\\)\""
"\\1\\1\\\\\""
argument)))
(if (string-match "\"" argument)
(concat
"^\""
(replace-regexp-in-string
"\\([%!()\"<>&|^]\\)"
"^\\1"
argument)
"^\"")
(concat "\"" argument "\"")))
(t
(if (equal argument "")
"''"
;; Quote everything except POSIX filename characters.
;; This should be safe enough even for really weird shells.
(replace-regexp-in-string "\n" "'\n'"
(replace-regexp-in-string "[^-0-9a-zA-Z_./\n]" "\\\\\\&" argument)))))
(replace-regexp-in-string
"\n" "'\n'"
(replace-regexp-in-string "[^-0-9a-zA-Z_./\n]" "\\\\\\&" argument))))
))
(defun string-or-null-p (object)
"Return t if OBJECT is a string or nil.

View file

@ -1,3 +1,8 @@
2011-04-26 Daniel Colascione <dan.colascione@gmail.com>
* cmdproxy.c (try_dequote_cmdline): New function.
(main): Use it.
2011-04-24 Teodor Zlatanov <tzz@lifelogs.com>
* configure.bat: New options --without-gnutls and --lib, new build

View file

@ -309,6 +309,74 @@ make_absolute (const char *prog)
return NULL;
}
/* Try to decode the given command line the way cmd would do it. On
success, return 1 with cmdline dequoted. Otherwise, when we've
found constructs only cmd can properly interpret, return 0 and
leave cmdline unchanged. */
int
try_dequote_cmdline (char* cmdline)
{
/* Dequoting can only subtract characters, so the length of the
original command line is a bound on the amount of scratch space
we need. This length, in turn, is bounded by the 32k
CreateProces limit. */
char * old_pos = cmdline;
char * new_cmdline = alloca (strlen(cmdline));
char * new_pos = new_cmdline;
char c;
enum {
NORMAL,
AFTER_CARET,
INSIDE_QUOTE
} state = NORMAL;
while ((c = *old_pos++))
{
switch (state)
{
case NORMAL:
switch(c)
{
case '"':
*new_pos++ = c;
state = INSIDE_QUOTE;
break;
case '^':
state = AFTER_CARET;
break;
case '<': case '>':
case '&': case '|':
case '(': case ')':
case '%': case '!':
/* We saw an unquoted shell metacharacter and we don't
understand it. Bail out. */
return 0;
default:
*new_pos++ = c;
break;
}
break;
case AFTER_CARET:
*new_pos++ = c;
state = NORMAL;
break;
case INSIDE_QUOTE:
*new_pos++ = c;
if (c == '"')
state = NORMAL;
break;
}
}
/* We were able to dequote the entire string. Copy our scratch
buffer on top of the original buffer and return success. */
memcpy (cmdline, new_cmdline, new_pos - new_cmdline);
cmdline[new_pos - new_cmdline] = '\0';
return 1;
}
/*****************************************************************/
#if 0
@ -574,30 +642,26 @@ main (int argc, char ** argv)
execute the command directly ourself. */
if (cmdline)
{
/* If no redirection or piping, and if program can be found, then
run program directly. Otherwise invoke a real shell. */
const char *args;
static char copout_chars[] = "|<>&";
/* The program name is the first token of cmdline. Since
filenames cannot legally contain embedded quotes, the value
of escape_char doesn't matter. */
args = cmdline;
if (!get_next_token (path, &args))
fail ("error: no program name specified.\n");
if (strpbrk (cmdline, copout_chars) == NULL)
{
const char *args;
canon_filename (path);
progname = make_absolute (path);
/* The program name is the first token of cmdline. Since
filenames cannot legally contain embedded quotes, the value
of escape_char doesn't matter. */
args = cmdline;
if (!get_next_token (path, &args))
fail ("error: no program name specified.\n");
canon_filename (path);
progname = make_absolute (path);
/* If we found the program, run it directly (if not found it
might be an internal shell command, so don't fail). */
if (progname != NULL)
need_shell = FALSE;
}
/* If we found the program and the rest of the command line does
not contain unquoted shell metacharacters, run the program
directly (if not found it might be an internal shell command,
so don't fail). */
if (progname != NULL && try_dequote_cmdline (cmdline))
need_shell = FALSE;
else
progname = NULL;
}
pass_to_shell: