90 lines
2.2 KiB
Go
90 lines
2.2 KiB
Go
package process
|
|
|
|
import "strings"
|
|
|
|
// scanArgChars inspects each byte of s and returns whether any character
|
|
// requires backslash-escaping (double-quote or backslash) and whether
|
|
// the argument contains whitespace (space or tab).
|
|
func scanArgChars(s string) (needsBackslash, hasSpace bool) {
|
|
for i := 0; i < len(s); i++ {
|
|
switch s[i] {
|
|
case '"', '\\':
|
|
needsBackslash = true
|
|
case ' ', '\t':
|
|
hasSpace = true
|
|
}
|
|
}
|
|
return needsBackslash, hasSpace
|
|
}
|
|
|
|
// appendEscapedBytes appends the bytes of s to b applying MSDN
|
|
// CommandLineToArgvW backslash-doubling rules for double-quote characters.
|
|
// Returns the updated byte slice and the trailing slash count.
|
|
func appendEscapedBytes(b []byte, s string) ([]byte, int) {
|
|
slashes := 0
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
switch c {
|
|
default:
|
|
slashes = 0
|
|
case '\\':
|
|
slashes++
|
|
case '"':
|
|
for ; slashes > 0; slashes-- {
|
|
b = append(b, '\\')
|
|
}
|
|
b = append(b, '\\')
|
|
}
|
|
b = append(b, c)
|
|
}
|
|
return b, slashes
|
|
}
|
|
|
|
// escapeArg rewrites a command-line argument following the MSDN
|
|
// CommandLineToArgvW parsing rules (same algorithm as Go's
|
|
// syscall.EscapeArg on Windows):
|
|
//
|
|
// - every backslash (\) is doubled, but only if immediately
|
|
// followed by a double quote (");
|
|
// - every double quote (") is escaped with a backslash (\);
|
|
// - finally, the argument is wrapped in double quotes only if
|
|
// it contains spaces or tabs.
|
|
// - an empty string becomes "" (two double-quote characters).
|
|
func escapeArg(s string) string {
|
|
if len(s) == 0 {
|
|
return `""`
|
|
}
|
|
|
|
needsBackslash, hasSpace := scanArgChars(s)
|
|
|
|
if !needsBackslash && !hasSpace {
|
|
return s
|
|
}
|
|
if !needsBackslash {
|
|
return `"` + s + `"`
|
|
}
|
|
|
|
var b []byte
|
|
if hasSpace {
|
|
b = append(b, '"')
|
|
}
|
|
b, slashes := appendEscapedBytes(b, s)
|
|
if hasSpace {
|
|
for ; slashes > 0; slashes-- {
|
|
b = append(b, '\\')
|
|
}
|
|
b = append(b, '"')
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// buildCmdLine joins arguments into a single command-line string
|
|
// with proper quoting for Windows CreateProcess.
|
|
func buildCmdLine(args []string) string {
|
|
escaped := make([]string, len(args))
|
|
for i, arg := range args {
|
|
escaped[i] = escapeArg(arg)
|
|
}
|
|
return strings.Join(escaped, " ")
|
|
}
|