From 364b0c4798b7522293b6f7c47feb6ca493760192 Mon Sep 17 00:00:00 2001 From: mitchell Date: Fri, 22 Mar 2024 15:13:11 -0400 Subject: [PATCH] Quote `state exec` arguments on Windows. --- internal/osutils/shellescape.go | 4 ++-- internal/runners/exec/exec.go | 2 +- internal/subshell/sscommon/sscommon.go | 7 ++++++- test/integration/exec_int_test.go | 20 ++++++++++++++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/internal/osutils/shellescape.go b/internal/osutils/shellescape.go index f4a3fe059b..91eece81f7 100644 --- a/internal/osutils/shellescape.go +++ b/internal/osutils/shellescape.go @@ -12,7 +12,7 @@ type ShellEscape struct { escapeWith string } -//NewBashEscaper creates a new instance of ShellEscape that's configured for escaping bash style arguments +// NewBashEscaper creates a new instance of ShellEscape that's configured for escaping bash style arguments func NewBashEscaper() *ShellEscape { return &ShellEscape{ regexp.MustCompile(`^[\w]+$`), @@ -33,7 +33,7 @@ func NewBatchEscaper() *ShellEscape { // NewCmdEscaper creates a new instance of ShellEscape that's configured for escaping cmd arguments func NewCmdEscaper() *ShellEscape { return &ShellEscape{ - regexp.MustCompile(`^[^ &()\[\]{}^=;!'+,~]+$`), // cmd.exe /? lists these characters as requiring quotes + regexp.MustCompile(`^[^ &()\[\]{}^=;!'+,~<>]+$`), // cmd.exe /? lists these characters as requiring quotes regexp.MustCompile(`"`), `""`, } diff --git a/internal/runners/exec/exec.go b/internal/runners/exec/exec.go index 5868024978..20785f1b71 100644 --- a/internal/runners/exec/exec.go +++ b/internal/runners/exec/exec.go @@ -197,7 +197,7 @@ func (s *Exec) Run(params *Params, args ...string) (rerr error) { scriptArgs := fmt.Sprintf(`%q "$@"`, exeTarget) if strings.Contains(s.subshell.Binary(), "cmd") { lang = language.Batch - scriptArgs = fmt.Sprintf("@ECHO OFF\n%q %%*", exeTarget) + scriptArgs = fmt.Sprintf(`@ECHO OFF\n%q %%*`, exeTarget) } sf, err := scriptfile.New(lang, "state-exec", scriptArgs) diff --git a/internal/subshell/sscommon/sscommon.go b/internal/subshell/sscommon/sscommon.go index f1b5a2acfd..37dadafd0e 100644 --- a/internal/subshell/sscommon/sscommon.go +++ b/internal/subshell/sscommon/sscommon.go @@ -137,7 +137,12 @@ func runWithCmd(env []string, name string, args ...string) error { return locale.NewInputError("err_sscommon_unsupported_language", "", ext) } - return runDirect(env, name, args...) + esc := osutils.NewCmdEscaper() + quoted := make([]string, 0, len(args)) + for _, arg := range args { + quoted = append(quoted, esc.Quote(arg)) + } + return runDirect(env, name, quoted...) } func binaryPathCmd(env []string, name string) (string, error) { diff --git a/test/integration/exec_int_test.go b/test/integration/exec_int_test.go index 9f5861661e..d351d156a1 100644 --- a/test/integration/exec_int_test.go +++ b/test/integration/exec_int_test.go @@ -197,6 +197,26 @@ func (suite *ExecIntegrationTestSuite) TestExecWithPath() { } +func (suite *ExecIntegrationTestSuite) TestExecQuotedArgs() { + suite.OnlyRunForTags(tagsuite.Exec) + ts := e2e.New(suite.T(), false) + defer ts.Close() + + cp := ts.Spawn("checkout", "ActiveState-CLI/Perl-5.32", ".") + cp.Expect("Skipping runtime setup") + cp.Expect("Checked out project") + cp.ExpectExitCode(0) + + fileutils.WriteFile(filepath.Join(ts.Dirs.Work, "testargs.txt"), []byte(`printf "Argument: '%s'.\n", $ARGV[0];`)) + + cp = ts.SpawnWithOpts( + e2e.OptArgs("exec", "perl", "testargs.txt", "a