Commit d1b667fb authored by Makpoc's avatar Makpoc

Two quotes next to each other result in one escaped quote; Add Split Example,...

Two quotes next to each other result in one escaped quote; Add Split Example, add/refactor tests for every platform.
parent 0d004ccb
...@@ -2,23 +2,23 @@ package middleware ...@@ -2,23 +2,23 @@ package middleware
import ( import (
"errors" "errors"
"fmt"
"runtime" "runtime"
"strings"
"unicode" "unicode"
"github.com/flynn/go-shlex" "github.com/flynn/go-shlex"
) )
var runtimeGoos = runtime.GOOS
// 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) {
var parts []string var parts []string
if runtime.GOOS == "windows" { if runtimeGoos == "windows" {
parts = parseWindowsCommand(command) // parse it Windows-style parts = parseWindowsCommand(command) // parse it Windows-style
} else { } else {
parts, err = shlex.Split(command) // parse it Unix-style parts, err = parseUnixCommand(command) // parse it Unix-style
if err != nil { if err != nil {
err = errors.New("error parsing command: " + err.Error()) err = errors.New("error parsing command: " + err.Error())
return return
...@@ -38,112 +38,76 @@ func SplitCommandAndArgs(command string) (cmd string, args []string, err error) ...@@ -38,112 +38,76 @@ func SplitCommandAndArgs(command string) (cmd string, args []string, err error)
return return
} }
// parseWindowsCommand is a sad but good-enough attempt to // parseUnixCommand parses a unix style command line and returns the
// split a command into the command and its arguments like // command and its arguments or an error
// the Windows command line would; only basic parsing is func parseUnixCommand(cmd string) ([]string, error) {
// supported. This function has to be used on Windows instead return shlex.Split(cmd)
// 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 parseWindowsCommand2(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
} }
// parseWindowsCommand parses windows command lines and
// returns the command and the arguments as an array. It
// should be able to parse commonly used command lines.
// Only basic syntax is supported:
// - spaces in double quotes are not token delimiters
// - double quotes are escaped by either backspace or another double quote
// - except for the above case backspaces are path separators (not special)
//
// Many sources point out that escaping quotes using backslash can be unsafe.
// Use two double quotes when possible. (Source: http://stackoverflow.com/a/31413730/2616179 )
//
// This function has to be used on Windows instead
// of the shlex package because this function treats backslash
// characters properly.
func parseWindowsCommand(cmd string) []string { func parseWindowsCommand(cmd string) []string {
const backslash = '\\'
const quote = '"'
var parts []string var parts []string
var part string var part string
var inQuotes bool var inQuotes bool
var wasBackslash bool var lastRune rune
prefix := "DEBUG:"
fmt.Println(prefix, "Parsing cmd:", cmd)
for i, ch := range cmd { for i, ch := range cmd {
fmt.Println(" ", prefix, "Looking at char:", string(ch), "at index", string(i))
if ch == '\\' { if i != 0 {
wasBackslash = true lastRune = rune(cmd[i-1])
// put it in the part - for now we don't know if it's escaping char or path separator }
if ch == backslash {
// put it in the part - for now we don't know if it's an
// escaping char or path separator
part += string(ch) part += string(ch)
continue continue
} }
if ch == '"' { if ch == quote {
if wasBackslash { if lastRune == backslash {
// remove the backslash from the part and add the escaped quote instead // remove the backslash from the part and add the escaped quote instead
part = part[:len(part)-1] part = part[:len(part)-1]
part += string(ch) part += string(ch)
wasBackslash = false
continue continue
} else { }
// normal escaping quotes
fmt.Println(" ", prefix, "and it's a quote") if lastRune == quote {
// revert the last change of the inQuotes state
// it was an escaping quote
inQuotes = !inQuotes inQuotes = !inQuotes
part += string(ch)
continue continue
} }
// normal escaping quotes
inQuotes = !inQuotes
continue
} }
if unicode.IsSpace(ch) && !inQuotes && len(part) > 0 { if unicode.IsSpace(ch) && !inQuotes && len(part) > 0 {
fmt.Println(" ", prefix, "and it's a space outside quotes")
parts = append(parts, part) parts = append(parts, part)
part = "" part = ""
wasBackslash = false
continue continue
} }
wasBackslash = false
part += string(ch) part += string(ch)
} }
...@@ -152,6 +116,5 @@ func parseWindowsCommand(cmd string) []string { ...@@ -152,6 +116,5 @@ func parseWindowsCommand(cmd string) []string {
part = "" part = ""
} }
fmt.Println(prefix, strings.Join(parts, ","))
return parts return parts
} }
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment