Skip to content

Commit bf55fc9

Browse files
committed
Merge branch version/0-45-0-RC1 to adopt changes from PR #3389
2 parents f4282e8 + 71944be commit bf55fc9

File tree

10 files changed

+280
-13
lines changed

10 files changed

+280
-13
lines changed

cmd/state-exec/cmd.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ package main
33
import (
44
"fmt"
55
"os"
6-
"os/exec"
76
)
87

98
func runCmd(meta *executorMeta) (int, error) {
109
userArgs := os.Args[1:]
11-
cmd := exec.Command(meta.MatchingBin, userArgs...)
10+
cmd := Command(meta.MatchingBin, userArgs...)
1211
cmd.Stdin = os.Stdin
1312
cmd.Stdout = os.Stdout
1413
cmd.Stderr = os.Stderr

cmd/state-exec/cmd_lin_mac.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//go:build !windows
2+
// +build !windows
3+
4+
package main
5+
6+
import "os/exec"
7+
8+
func Command(name string, arg ...string) *exec.Cmd {
9+
return exec.Command(name, arg...)
10+
}

cmd/state-exec/cmd_windows.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package main
2+
3+
import (
4+
"os/exec"
5+
"path/filepath"
6+
"strings"
7+
"syscall"
8+
9+
"github.com/ActiveState/cli/cmd/state-exec/internal/logr"
10+
)
11+
12+
func Command(name string, arg ...string) *exec.Cmd {
13+
cmd := exec.Command(name, arg...)
14+
15+
exeName := filepath.Base(strings.ToLower(name))
16+
if exeName == "cmd" || strings.HasSuffix(exeName, ".bat") || strings.HasSuffix(exeName, ".cmd") {
17+
// Go currently does not escape arguments properly on Windows, it account for spaces and tab characters, but not
18+
// other characters that need escaping such as `<` and `>`.
19+
// This can be dropped once we update to a Go version that fixes this bug: https://github.com/golang/go/issues/68313
20+
cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: makeCmdLine(cmd.Args)}
21+
logr.Debug("processed command line: %s", cmd.SysProcAttr.CmdLine)
22+
}
23+
24+
return cmd
25+
}
26+
27+
// makeCmdLine builds a command line out of args by escaping "special"
28+
// characters and joining the arguments with spaces.
29+
// Based on syscall\exec_windows.go
30+
func makeCmdLine(args []string) string {
31+
var b []byte
32+
for _, v := range args {
33+
if len(b) > 0 {
34+
b = append(b, ' ')
35+
}
36+
b = appendEscapeArg(b, v)
37+
}
38+
return string(b)
39+
}
40+
41+
// appendEscapeArg escapes the string s, as per escapeArg,
42+
// appends the result to b, and returns the updated slice.
43+
// Based on syscall\exec_windows.go
44+
func appendEscapeArg(b []byte, s string) []byte {
45+
if len(s) == 0 {
46+
return append(b, `""`...)
47+
}
48+
49+
needsBackslash := false
50+
needsQuotes := false
51+
for i := 0; i < len(s); i++ {
52+
switch s[i] {
53+
case '"', '\\':
54+
needsBackslash = true
55+
// Based on https://github.com/sebres/PoC/blob/master/SB-0D-001-win-exec/SOLUTION.md#definition
56+
case ' ', '\t', '<', '>', '&', '|', '^', '!', '(', ')', '%':
57+
needsQuotes = true
58+
}
59+
}
60+
61+
if !needsBackslash && !needsQuotes {
62+
// No special handling required; normal case.
63+
return append(b, s...)
64+
}
65+
if !needsBackslash {
66+
// hasSpace is true, so we need to quote the string.
67+
b = append(b, '"')
68+
b = append(b, s...)
69+
return append(b, '"')
70+
}
71+
72+
if needsQuotes {
73+
b = append(b, '"')
74+
}
75+
slashes := 0
76+
for i := 0; i < len(s); i++ {
77+
c := s[i]
78+
switch c {
79+
default:
80+
slashes = 0
81+
case '\\':
82+
slashes++
83+
case '"':
84+
for ; slashes > 0; slashes-- {
85+
b = append(b, '\\')
86+
}
87+
b = append(b, '\\')
88+
}
89+
b = append(b, c)
90+
}
91+
if needsQuotes {
92+
for ; slashes > 0; slashes-- {
93+
b = append(b, '\\')
94+
}
95+
b = append(b, '"')
96+
}
97+
98+
return b
99+
}

internal/osutils/exeutils.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func ExecSimple(bin string, args []string, env []string) (string, string, error)
9797

9898
func ExecSimpleFromDir(dir, bin string, args []string, env []string) (string, string, error) {
9999
logging.Debug("ExecSimpleFromDir: dir: %s, bin: %s, args: %#v, env: %#v", dir, bin, args, env)
100-
c := exec.Command(bin, args...)
100+
c := Command(bin, args...)
101101
if dir != "" {
102102
c.Dir = dir
103103
}
@@ -117,7 +117,7 @@ func ExecSimpleFromDir(dir, bin string, args []string, env []string) (string, st
117117

118118
// Execute will run the given command and with optional settings for the exec.Cmd struct
119119
func Execute(command string, arg []string, optSetter func(cmd *exec.Cmd) error) (int, *exec.Cmd, error) {
120-
cmd := exec.Command(command, arg...)
120+
cmd := Command(command, arg...)
121121
logging.Debug("Executing command: %s, with args: %s", cmd, arg)
122122
if optSetter != nil {
123123
if err := optSetter(cmd); err != nil {
@@ -145,7 +145,7 @@ func ExecuteAndPipeStd(command string, arg []string, env []string) (int, *exec.C
145145
// ExecuteAndForget will run the given command in the background, returning immediately.
146146
func ExecuteAndForget(command string, args []string, opts ...func(cmd *exec.Cmd) error) (*os.Process, error) {
147147
logging.Debug("Executing: %s %v", command, args)
148-
cmd := exec.Command(command, args...)
148+
cmd := Command(command, args...)
149149

150150
for _, optSetter := range opts {
151151
if err := optSetter(cmd); err != nil {
@@ -172,7 +172,7 @@ func ExecuteAndForget(command string, args []string, opts ...func(cmd *exec.Cmd)
172172
func ExecuteInBackground(command string, args []string, opts ...func(cmd *exec.Cmd) error) (*exec.Cmd, *bytes.Buffer, *bytes.Buffer, error) {
173173
logging.Debug("Executing: %s %v", command, args)
174174

175-
cmd := exec.Command(command, args...)
175+
cmd := Command(command, args...)
176176
var stdoutBuf, stderrBuf bytes.Buffer
177177

178178
for _, optSetter := range opts {

internal/osutils/exeutils_unix.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44
package osutils
55

6+
import "os/exec"
7+
68
const ExeExtension = ""
79

810
var exts = []string{}
11+
12+
func Command(name string, arg ...string) *exec.Cmd {
13+
return exec.Command(name, arg...)
14+
}

internal/osutils/exeutils_windows.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package osutils
22

33
import (
44
"os"
5+
"os/exec"
6+
"path/filepath"
57
"strings"
8+
"syscall"
69

710
"github.com/thoas/go-funk"
811
)
@@ -15,3 +18,91 @@ func init() {
1518
PATHEXT := os.Getenv("PATHEXT")
1619
exts = funk.Uniq(funk.Map(strings.Split(PATHEXT, string(os.PathListSeparator)), strings.ToLower).([]string)).([]string)
1720
}
21+
22+
func Command(name string, arg ...string) *exec.Cmd {
23+
cmd := exec.Command(name, arg...)
24+
25+
exeName := filepath.Base(strings.ToLower(name))
26+
if exeName == "cmd" || strings.HasSuffix(exeName, ".bat") || strings.HasSuffix(exeName, ".cmd") {
27+
// Go currently does not escape arguments properly on Windows, it account for spaces and tab characters, but not
28+
// other characters that need escaping such as `<` and `>`.
29+
// This can be dropped once we update to a Go version that fixes this bug: https://github.com/golang/go/issues/68313
30+
cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: makeCmdLine(cmd.Args)}
31+
}
32+
33+
return cmd
34+
}
35+
36+
// makeCmdLine builds a command line out of args by escaping "special"
37+
// characters and joining the arguments with spaces.
38+
// Based on syscall\exec_windows.go
39+
func makeCmdLine(args []string) string {
40+
var b []byte
41+
for _, v := range args {
42+
if len(b) > 0 {
43+
b = append(b, ' ')
44+
}
45+
b = appendEscapeArg(b, v)
46+
}
47+
return string(b)
48+
}
49+
50+
// appendEscapeArg escapes the string s, as per escapeArg,
51+
// appends the result to b, and returns the updated slice.
52+
// Based on syscall\exec_windows.go
53+
func appendEscapeArg(b []byte, s string) []byte {
54+
if len(s) == 0 {
55+
return append(b, `""`...)
56+
}
57+
58+
needsBackslash := false
59+
needsQuotes := false
60+
for i := 0; i < len(s); i++ {
61+
switch s[i] {
62+
case '"', '\\':
63+
needsBackslash = true
64+
// Based on https://github.com/sebres/PoC/blob/master/SB-0D-001-win-exec/SOLUTION.md#definition
65+
case ' ', '\t', '<', '>', '&', '|', '^', '!', '(', ')', '%':
66+
needsQuotes = true
67+
}
68+
}
69+
70+
if !needsBackslash && !needsQuotes {
71+
// No special handling required; normal case.
72+
return append(b, s...)
73+
}
74+
if !needsBackslash {
75+
// hasSpace is true, so we need to quote the string.
76+
b = append(b, '"')
77+
b = append(b, s...)
78+
return append(b, '"')
79+
}
80+
81+
if needsQuotes {
82+
b = append(b, '"')
83+
}
84+
slashes := 0
85+
for i := 0; i < len(s); i++ {
86+
c := s[i]
87+
switch c {
88+
default:
89+
slashes = 0
90+
case '\\':
91+
slashes++
92+
case '"':
93+
for ; slashes > 0; slashes-- {
94+
b = append(b, '\\')
95+
}
96+
b = append(b, '\\')
97+
}
98+
b = append(b, c)
99+
}
100+
if needsQuotes {
101+
for ; slashes > 0; slashes-- {
102+
b = append(b, '\\')
103+
}
104+
b = append(b, '"')
105+
}
106+
107+
return b
108+
}

internal/testhelpers/e2e/session.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@ import (
44
"fmt"
55
"io/fs"
66
"os"
7-
"os/exec"
87
"path/filepath"
98
"regexp"
109
"runtime"
1110
"strings"
1211
"testing"
1312
"time"
1413

15-
"github.com/ActiveState/cli/internal/subshell"
16-
"github.com/ActiveState/cli/pkg/platform/model"
17-
"github.com/ActiveState/cli/pkg/platform/model/buildplanner"
18-
"github.com/ActiveState/cli/pkg/projectfile"
1914
"github.com/ActiveState/termtest"
2015
"github.com/go-openapi/strfmt"
2116
"github.com/google/uuid"
2217
"github.com/phayes/permbits"
2318
"github.com/stretchr/testify/require"
2419

20+
"github.com/ActiveState/cli/internal/subshell"
21+
"github.com/ActiveState/cli/pkg/platform/model"
22+
"github.com/ActiveState/cli/pkg/platform/model/buildplanner"
23+
"github.com/ActiveState/cli/pkg/projectfile"
24+
2525
"github.com/ActiveState/cli/internal/condition"
2626
"github.com/ActiveState/cli/internal/config"
2727
"github.com/ActiveState/cli/internal/constants"
@@ -298,7 +298,7 @@ func (s *Session) SpawnCmdWithOpts(exe string, optSetters ...SpawnOptSetter) *Sp
298298
args = spawnOpts.Args
299299
}
300300

301-
cmd := exec.Command(shell, args...)
301+
cmd := osutils.Command(shell, args...)
302302

303303
cmd.Env = spawnOpts.Env
304304
if spawnOpts.Dir != "" {

pkg/platform/runtime/executors/executors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ func (es *Executors) ExecutorSrc() (string, error) {
4646
return installation.ExecutorExec()
4747
}
4848

49+
func (es *Executors) SetExecutorSrc(path string) {
50+
es.altExecSrcPath = path
51+
}
52+
4953
func (es *Executors) Apply(sockPath string, targeter Targeter, env map[string]string, exes envdef.ExecutablePaths) error {
5054
logging.Debug("Creating executors at %s, exes: %v", es.executorPath, exes)
5155

0 commit comments

Comments
 (0)