Fix ExecCommandWith for cmd.exe in Windows (#1072)

Close #1018

Run the command as is in cmd.exe with no parsing and escaping.
Explicity set cmd.SysProcAttr so execCommand does not escape the command.
Technically, the command should be escaped with ^ for special characters,
including ". This allows cmd.exe commands to be chained together.

See https://github.com/neovim/neovim/pull/7343#issuecomment-333350201

This commit also updates quoteEntry to use strings.Replace instead of
strconv.Quote which escapes more than \ and ".
This commit is contained in:
Jan Edmund Lazo 2017-10-02 11:36:19 -04:00 committed by Junegunn Choi
parent 0580fe9046
commit c4185e81e8
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
3 changed files with 36 additions and 8 deletions

View File

@ -1103,9 +1103,18 @@ func keyMatch(key int, event tui.Event) bool {
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
} }
func quoteEntryCmd(entry string) string {
escaped := strings.Replace(entry, `\`, `\\`, -1)
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
return r.ReplaceAllStringFunc(escaped, func(match string) string {
return "^" + match
})
}
func quoteEntry(entry string) string { func quoteEntry(entry string) string {
if util.IsWindows() { if util.IsWindows() {
return strconv.Quote(strings.Replace(entry, "\"", "\\\"", -1)) return quoteEntryCmd(entry)
} }
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'" return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
} }

View File

@ -91,3 +91,22 @@ func TestReplacePlaceholder(t *testing.T) {
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1) result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'") check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
} }
func TestQuoteEntryCmd(t *testing.T) {
tests := map[string]string{
`"`: `^"\^"^"`,
`\`: `^"\\^"`,
`\"`: `^"\\\^"^"`,
`"\\\"`: `^"\^"\\\\\\\^"^"`,
`&|<>()@^%!`: `^"^&^|^<^>^(^)^@^^^%^!^"`,
`%USERPROFILE%`: `^"^%USERPROFILE^%^"`,
`C:\Program Files (x86)\`: `^"C:\\Program Files ^(x86^)\\^"`,
}
for input, expected := range tests {
escaped := quoteEntryCmd(input)
if escaped != expected {
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
}
}
}

View File

@ -6,8 +6,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"syscall" "syscall"
"github.com/mattn/go-shellwords"
) )
// ExecCommand executes the given command with cmd // ExecCommand executes the given command with cmd
@ -18,11 +16,13 @@ func ExecCommand(command string) *exec.Cmd {
// ExecCommandWith executes the given command with cmd. _shell parameter is // ExecCommandWith executes the given command with cmd. _shell parameter is
// ignored on Windows. // ignored on Windows.
func ExecCommandWith(_shell string, command string) *exec.Cmd { func ExecCommandWith(_shell string, command string) *exec.Cmd {
args, _ := shellwords.Parse(command) cmd := exec.Command("cmd")
allArgs := make([]string, len(args)+1) cmd.SysProcAttr = &syscall.SysProcAttr{
allArgs[0] = "/c" HideWindow: false,
copy(allArgs[1:], args) CmdLine: fmt.Sprintf(` /s /c "%s"`, command),
return exec.Command("cmd", allArgs...) CreationFlags: 0,
}
return cmd
} }
// IsWindows returns true on Windows // IsWindows returns true on Windows