mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
Parse Windows commands differently than Unix commands
Stinkin' backslashes
This commit is contained in:
parent
136119f8ac
commit
29362e45bc
2 changed files with 144 additions and 5 deletions
|
@ -2,6 +2,9 @@ package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/flynn/go-shlex"
|
"github.com/flynn/go-shlex"
|
||||||
)
|
)
|
||||||
|
@ -9,11 +12,19 @@ import (
|
||||||
// SplitCommandAndArgs takes a command string and parses it
|
// SplitCommandAndArgs takes a command string and parses it
|
||||||
// shell-style into the command and its separate arguments.
|
// shell-style into the command and its separate arguments.
|
||||||
func SplitCommandAndArgs(command string) (cmd string, args []string, err error) {
|
func SplitCommandAndArgs(command string) (cmd string, args []string, err error) {
|
||||||
parts, err := shlex.Split(command)
|
var parts []string
|
||||||
if err != nil {
|
|
||||||
err = errors.New("error parsing command: " + err.Error())
|
if runtime.GOOS == "windows" {
|
||||||
return
|
parts = parseWindowsCommand(command) // parse it Windows-style
|
||||||
} else if len(parts) == 0 {
|
} else {
|
||||||
|
parts, err = shlex.Split(command) // parse it Unix-style
|
||||||
|
if err != nil {
|
||||||
|
err = errors.New("error parsing command: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) == 0 {
|
||||||
err = errors.New("no command contained in '" + command + "'")
|
err = errors.New("no command contained in '" + command + "'")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -25,3 +36,64 @@ func SplitCommandAndArgs(command string) (cmd string, args []string, err error)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseWindowsCommand is a sad but good-enough attempt to
|
||||||
|
// split a command into the command and its arguments like
|
||||||
|
// the Windows command line would; only basic parsing is
|
||||||
|
// supported. This function has to be used on Windows instead
|
||||||
|
// of the shlex package because this function treats backslash
|
||||||
|
// characters properly.
|
||||||
|
//
|
||||||
|
// Loosely based off the rules here: http://stackoverflow.com/a/4094897/1048862
|
||||||
|
// True parsing is much, much trickier.
|
||||||
|
func parseWindowsCommand(cmd string) []string {
|
||||||
|
var parts []string
|
||||||
|
var part string
|
||||||
|
var quoted bool
|
||||||
|
var backslashes int
|
||||||
|
|
||||||
|
for _, ch := range cmd {
|
||||||
|
if ch == '\\' {
|
||||||
|
backslashes++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var evenBacksl = (backslashes % 2) == 0
|
||||||
|
if backslashes > 0 && ch != '\\' {
|
||||||
|
numBacksl := (backslashes / 2) + 1
|
||||||
|
if ch == '"' {
|
||||||
|
numBacksl--
|
||||||
|
}
|
||||||
|
part += strings.Repeat(`\`, numBacksl)
|
||||||
|
backslashes = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if quoted {
|
||||||
|
if ch == '"' && evenBacksl {
|
||||||
|
quoted = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
part += string(ch)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if unicode.IsSpace(ch) && len(part) > 0 {
|
||||||
|
parts = append(parts, part)
|
||||||
|
part = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '"' && evenBacksl {
|
||||||
|
quoted = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
part += string(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(part) > 0 {
|
||||||
|
parts = append(parts, part)
|
||||||
|
part = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,73 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestParseWindowsCommand(t *testing.T) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
input string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{ // 0
|
||||||
|
input: `cmd`,
|
||||||
|
expected: []string{`cmd`},
|
||||||
|
},
|
||||||
|
{ // 1
|
||||||
|
input: `cmd arg1 arg2`,
|
||||||
|
expected: []string{`cmd`, `arg1`, `arg2`},
|
||||||
|
},
|
||||||
|
{ // 2
|
||||||
|
input: `cmd "combined arg" arg2`,
|
||||||
|
expected: []string{`cmd`, `combined arg`, `arg2`},
|
||||||
|
},
|
||||||
|
{ // 3
|
||||||
|
input: `mkdir C:\Windows\foo\bar`,
|
||||||
|
expected: []string{`mkdir`, `C:\Windows\foo\bar`},
|
||||||
|
},
|
||||||
|
{ // 4
|
||||||
|
input: `"command here"`,
|
||||||
|
expected: []string{`command here`},
|
||||||
|
},
|
||||||
|
{ // 5
|
||||||
|
input: `cmd \"arg\"`,
|
||||||
|
expected: []string{`cmd`, `"arg"`},
|
||||||
|
},
|
||||||
|
{ // 6
|
||||||
|
input: `cmd "a \"quoted value\""`,
|
||||||
|
expected: []string{`cmd`, `a "quoted value"`},
|
||||||
|
},
|
||||||
|
{ // 7
|
||||||
|
input: `mkdir "C:\directory name\foobar"`,
|
||||||
|
expected: []string{`mkdir`, `C:\directory name\foobar`},
|
||||||
|
},
|
||||||
|
{ // 8
|
||||||
|
input: `mkdir C:\ space`,
|
||||||
|
expected: []string{`mkdir`, `C:\`, `space`},
|
||||||
|
},
|
||||||
|
{ // 9
|
||||||
|
input: `mkdir "C:\ space"`,
|
||||||
|
expected: []string{`mkdir`, `C:\ space`},
|
||||||
|
},
|
||||||
|
{ // 10
|
||||||
|
input: `\\"`,
|
||||||
|
expected: []string{`\`},
|
||||||
|
},
|
||||||
|
{ // 11
|
||||||
|
input: `"\\\""`,
|
||||||
|
expected: []string{`\"`},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
actual := parseWindowsCommand(test.input)
|
||||||
|
if len(actual) != len(test.expected) {
|
||||||
|
t.Errorf("Test %d: Expected %d parts, got %d: %#v", i, len(test.expected), len(actual), actual)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for j := 0; j < len(actual); j++ {
|
||||||
|
if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart {
|
||||||
|
t.Errorf("Test %d: Expected: %v Actual: %v (index %d)", i, expectedPart, actualPart, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSplitCommandAndArgs(t *testing.T) {
|
func TestSplitCommandAndArgs(t *testing.T) {
|
||||||
var parseErrorContent = "error parsing command:"
|
var parseErrorContent = "error parsing command:"
|
||||||
var noCommandErrContent = "no command contained in"
|
var noCommandErrContent = "no command contained in"
|
||||||
|
|
Loading…
Reference in a new issue