Skip to content

Commit

Permalink
Add SplitCommandLine implementation in Go (elastic#24732)
Browse files Browse the repository at this point in the history
  • Loading branch information
marc-gr authored Mar 30, 2021
1 parent b1083f8 commit a48eb6a
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 54 deletions.
86 changes: 60 additions & 26 deletions libbeat/processors/script/javascript/module/windows/windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,49 +15,83 @@
// specific language governing permissions and limitations
// under the License.

// +build windows

package windows

import (
"syscall"
"unsafe"

"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/require"
)

// SplitCommandLine splits a string into a list of space separated arguments.
// See Window's CommandLineToArgvW for more details.
func SplitCommandLine(cmd string) []string {
args, err := commandLineToArgvW(cmd)
if err != nil {
panic(err)
}

return args
return commandLineToArgv(cmd)
}

func commandLineToArgvW(in string) ([]string, error) {
ptr, err := syscall.UTF16PtrFromString(in)
if err != nil {
return nil, err
// appendBSBytes appends n '\\' bytes to b and returns the resulting slice.
func appendBSBytes(b []byte, n int) []byte {
for ; n > 0; n-- {
b = append(b, '\\')
}
return b
}

var numArgs int32
argsWide, err := syscall.CommandLineToArgv(ptr, &numArgs)
if err != nil {
return nil, err
// readNextArg splits command line string cmd into next
// argument and command line remainder.
func readNextArg(cmd string) (arg []byte, rest string) {
var b []byte
var inquote bool
var nslash int
for ; len(cmd) > 0; cmd = cmd[1:] {
c := cmd[0]
switch c {
case ' ', '\t':
if !inquote {
return appendBSBytes(b, nslash), cmd[1:]
}
case '"':
b = appendBSBytes(b, nslash/2)
if nslash%2 == 0 {
// use "Prior to 2008" rule from
// http://daviddeley.com/autohotkey/parameters/parameters.htm
// section 5.2 to deal with double double quotes
if inquote && len(cmd) > 1 && cmd[1] == '"' {
b = append(b, c)
cmd = cmd[1:]
}
inquote = !inquote
} else {
b = append(b, c)
}
nslash = 0
continue
case '\\':
nslash++
continue
}
b = appendBSBytes(b, nslash)
nslash = 0
b = append(b, c)
}
return appendBSBytes(b, nslash), ""
}

// Free memory allocated for CommandLineToArgvW arguments.
defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(argsWide)))

args := make([]string, numArgs)
for idx := range args {
args[idx] = syscall.UTF16ToString(argsWide[idx][:])
// commandLineToArgv splits a command line into individual argument
// strings, following the Windows conventions documented
// at http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV
// Original implementation found at: https://github.com/golang/go/commit/39c8d2b7faed06b0e91a1ad7906231f53aab45d1
func commandLineToArgv(cmd string) []string {
var args []string
for len(cmd) > 0 {
if cmd[0] == ' ' || cmd[0] == '\t' {
cmd = cmd[1:]
continue
}
var arg []byte
arg, cmd = readNextArg(cmd)
args = append(args, string(arg))
}
return args, nil
return args
}

// Require registers the windows module that has utilities specific to
Expand Down
139 changes: 111 additions & 28 deletions libbeat/processors/script/javascript/module/windows/windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
// specific language governing permissions and limitations
// under the License.

// +build windows

package windows

import (
Expand All @@ -25,33 +23,118 @@ import (
"github.com/stretchr/testify/assert"
)

const quotedCommandLine = `"C:\Program Files (x86)\Steam\bin\cef\cef.win7x64\steamwebhelper.exe" "-lang=en_US" "-cachedir=C:\Users\jimmy\AppData\Local\Steam\htmlcache" "-steampid=796" "-buildid=1546909276" "-steamid=0" "-steamuniverse=Dev" "-clientui=C:\Program Files (x86)\Steam\clientui" --disable-spell-checking --disable-out-of-process-pac --enable-blink-features=ResizeObserver,Worklet,AudioWorklet --disable-features=TouchpadAndWheelScrollLatching,AsyncWheelEvents --enable-media-stream --disable-smooth-scrolling --num-raster-threads=4 --enable-direct-write "--log-file=C:\Program Files (x86)\Steam\logs\cef_log.txt"`

func TestSplitCommandLine(t *testing.T) {
args := SplitCommandLine(quotedCommandLine)

for _, a := range args {
t.Log(a)
func TestCommandLineToArgv(t *testing.T) {
cases := []struct {
cmd string
args []string
}{
{
cmd: ``,
args: nil,
},
{
cmd: ` `,
args: nil,
},
{
cmd: "\t",
args: nil,
},
{
cmd: `test`,
args: []string{`test`},
},
{
cmd: `test a b c`,
args: []string{`test`, `a`, `b`, `c`},
},
{
cmd: `test "`,
args: []string{`test`, ``},
},
{
cmd: `test ""`,
args: []string{`test`, ``},
},
{
cmd: `test """`,
args: []string{`test`, `"`},
},
{
cmd: `test "" a`,
args: []string{`test`, ``, `a`},
},
{
cmd: `test "123"`,
args: []string{`test`, `123`},
},
{
cmd: `test \"123\"`,
args: []string{`test`, `"123"`},
},
{
cmd: `test \"123 456\"`,
args: []string{`test`, `"123`, `456"`},
},
{
cmd: `test \\"`,
args: []string{`test`, `\`},
},
{
cmd: `test \\\"`,
args: []string{`test`, `\"`},
},
{
cmd: `test \\\\\"`,
args: []string{`test`, `\\"`},
},
{
cmd: `test \\\"x`,
args: []string{`test`, `\"x`},
},
{
cmd: `test """"\""\\\"`,
args: []string{`test`, `""\"`},
},
{
cmd: `"cmd line" abc`,
args: []string{`cmd line`, `abc`},
},
{
cmd: `test \\\\\""x"""y z`,
args: []string{`test`, `\\"x"y z`},
},
{
cmd: "test\tb\t\"x\ty\"",
args: []string{`test`, `b`, "x\ty"},
},
{
cmd: `"C:\Program Files (x86)\Steam\bin\cef\cef.win7x64\steamwebhelper.exe" "-lang=en_US" "-cachedir=C:\Users\jimmy\AppData\Local\Steam\htmlcache" "-steampid=796" "-buildid=1546909276" "-steamid=0" "-steamuniverse=Dev" "-clientui=C:\Program Files (x86)\Steam\clientui" --disable-spell-checking --disable-out-of-process-pac --enable-blink-features=ResizeObserver,Worklet,AudioWorklet --disable-features=TouchpadAndWheelScrollLatching,AsyncWheelEvents --enable-media-stream --disable-smooth-scrolling --num-raster-threads=4 --enable-direct-write "--log-file=C:\Program Files (x86)\Steam\logs\cef_log.txt"`,
args: []string{
`C:\Program Files (x86)\Steam\bin\cef\cef.win7x64\steamwebhelper.exe`,
`-lang=en_US`,
`-cachedir=C:\Users\jimmy\AppData\Local\Steam\htmlcache`,
`-steampid=796`,
`-buildid=1546909276`,
`-steamid=0`,
`-steamuniverse=Dev`,
`-clientui=C:\Program Files (x86)\Steam\clientui`,
`--disable-spell-checking`,
`--disable-out-of-process-pac`,
`--enable-blink-features=ResizeObserver,Worklet,AudioWorklet`,
`--disable-features=TouchpadAndWheelScrollLatching,AsyncWheelEvents`,
`--enable-media-stream`,
`--disable-smooth-scrolling`,
`--num-raster-threads=4`,
`--enable-direct-write`,
`--log-file=C:\Program Files (x86)\Steam\logs\cef_log.txt`,
},
},
}

expected := []string{
`C:\Program Files (x86)\Steam\bin\cef\cef.win7x64\steamwebhelper.exe`,
`-lang=en_US`,
`-cachedir=C:\Users\jimmy\AppData\Local\Steam\htmlcache`,
`-steampid=796`,
`-buildid=1546909276`,
`-steamid=0`,
`-steamuniverse=Dev`,
`-clientui=C:\Program Files (x86)\Steam\clientui`,
`--disable-spell-checking`,
`--disable-out-of-process-pac`,
`--enable-blink-features=ResizeObserver,Worklet,AudioWorklet`,
`--disable-features=TouchpadAndWheelScrollLatching,AsyncWheelEvents`,
`--enable-media-stream`,
`--disable-smooth-scrolling`,
`--num-raster-threads=4`,
`--enable-direct-write`,
`--log-file=C:\Program Files (x86)\Steam\logs\cef_log.txt`,
for _, tc := range cases {
t.Run(tc.cmd, func(t *testing.T) {
assert.Equal(t, tc.args, SplitCommandLine(tc.cmd))
})
}
assert.Equal(t, expected, args)
}

0 comments on commit a48eb6a

Please sign in to comment.