- Notifications
You must be signed in to change notification settings - Fork18
fix: Escape shell arguments#227
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -81,14 +81,46 @@ coder sh front-end-dev cat ~/config.json`, | ||
} | ||
} | ||
// shellEscape escapes an argument so that we can pass it 'sh -c' | ||
// and have it do the right thing. | ||
// | ||
// Use this to ensure that the result of a command running in | ||
// the development environment behaves the same as the command | ||
// running via "coder sh". | ||
// | ||
// For example: | ||
// | ||
// $ coder sh env | ||
// $ go run ~/test.go 1 2 "3 4" '"abc def" \\abc' 5 6 "7 8 9" | ||
// | ||
// should produce the same output as: | ||
// | ||
// $ coder sh go run ~/test.go 1 2 "3 4" '"abc def" \\abc' 5 6 "7 8 9" | ||
func shellEscape(arg string) string { | ||
r := strings.NewReplacer(`\`, `\\`, `"`, `\"`, `'`, `\'`, ` `, `\ `) | ||
return r.Replace(arg) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I think eventually we should be relying on shlex (or similar) to be parsing and escaping shell commands from strings into arg arrays, and avoid rolling our own for things like this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. @tychoish Agree, I did a quick search bit didn't find any good ones. Happy to switch over now, or we can do it later, WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. https://github.com/google/shlex is functional. I think this might also solve the %q question in the other PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Hmm it looks like shlex is for parsing shell expressions, how do I use it to escape things? | ||
} | ||
func shell(cmd *cobra.Command, cmdArgs []string) error { | ||
ctx := cmd.Context() | ||
var command string | ||
var args []string | ||
if len(cmdArgs) > 1 { | ||
var escapedArgs strings.Builder | ||
for i, arg := range cmdArgs[1:] { | ||
escapedArgs.WriteString(shellEscape(arg)) | ||
// Add spaces between arguments, except the last argument | ||
if i < len(cmdArgs)-2 { | ||
escapedArgs.WriteByte(' ') | ||
} | ||
} | ||
command = "/bin/sh" | ||
args = []string{"-c"} | ||
args = append(args,escapedArgs.String()) | ||
} else { | ||
// Bring user into shell if no command is specified. | ||
shell := "$(getent passwd $(id -u) | cut -d: -f 7)" | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package cmd | ||
import "testing" | ||
func TestShellEscape(t *testing.T) { | ||
t.Parallel() | ||
tests := []struct { | ||
Name string | ||
Input string | ||
Escaped string | ||
}{ | ||
{ | ||
Name: "single space", | ||
Input: "hello world", | ||
Escaped: `hello\ world`, | ||
}, | ||
{ | ||
Name: "multiple spaces", | ||
Input: "test message hello world", | ||
Escaped: `test\ message\ hello\ \ world`, | ||
}, | ||
{ | ||
Name: "mixed quotes", | ||
Input: `"''"`, | ||
Escaped: `\"\'\'\"`, | ||
}, | ||
{ | ||
Name: "mixed escaped quotes", | ||
Input: `"'\"\"'"`, | ||
Escaped: `\"\'\\\"\\\"\'\"`, | ||
}, | ||
} | ||
for _, test := range tests { | ||
if e, a := test.Escaped, shellEscape(test.Input); e != a { | ||
t.Fatalf("test %q failed; expected: %q, got %q (input: %q)", | ||
test.Name, test.Escaped, a, test.Input) | ||
} | ||
} | ||
} |