Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 259876f

Browse files
committed
fix: Escape shell arguments
The commands passed to "coder sh" are passed as a single argument to "sh -c", so we need to shell-escape the command we pass. This change escapes spaces, backslash, and quotes.
1 parent b8067c0 commit 259876f

File tree

2 files changed

+83
-5
lines changed

2 files changed

+83
-5
lines changed

internal/cmd/shell.go

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,54 @@ coder sh front-end-dev cat ~/config.json`,
8080
}
8181
}
8282

83+
// shellEscape escapes an argument so that we can pass it 'sh -c'
84+
// and have it do the right thing.
85+
//
86+
// Use this to ensure that the result of a command running in
87+
// the development environment behaves the same as the command
88+
// running via "coder sh".
89+
//
90+
// For example:
91+
//
92+
// $ coder sh env
93+
// $ go run ~/test.go 1 2 "3 4" '"abc def" \\abc' 5 6 "7 8 9"
94+
//
95+
// should produce the same output as:
96+
//
97+
// $ coder sh go run ~/test.go 1 2 "3 4" '"abc def" \\abc' 5 6 "7 8 9"
98+
func shellEscape(arg string) string {
99+
r := strings.NewReplacer(`\`, `\\`, `"`, `\"`, `'`, `\'`, ` `, `\ `)
100+
return r.Replace(arg)
101+
}
102+
83103
func shell(cmd *cobra.Command, cmdArgs []string) error {
84104
ctx := cmd.Context()
85-
command := "sh"
86-
args := []string{"-c"}
105+
106+
var command string
107+
var args []string
87108
if len(cmdArgs) > 1 {
88-
args = append(args, strings.Join(cmdArgs[1:], " "))
109+
var escapedArgs strings.Builder
110+
111+
for i, arg := range cmdArgs[1:] {
112+
escapedArgs.WriteString(shellEscape(arg))
113+
114+
// Add spaces between arguments, except the last argument
115+
if i < len(cmdArgs)-2 {
116+
escapedArgs.WriteByte(' ')
117+
}
118+
}
119+
120+
command = "/bin/sh"
121+
args = []string{"-c"}
122+
args = append(args, escapedArgs.String())
89123
} else {
90124
// Bring user into shell if no command is specified.
91125
shell := "$(getent passwd $(id -u) | cut -d: -f 7)"
92-
name := "-$(basename " + shell + ")"
93-
args = append(args, fmt.Sprintf("exec -a %q %q", name, shell))
126+
127+
// force bash for the '-l' flag to the exec built-in
128+
command = "/bin/bash"
129+
args = []string{"-c"}
130+
args = append(args, fmt.Sprintf("exec -l %q", shell))
94131
}
95132

96133
envName := cmdArgs[0]

internal/cmd/shell_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package cmd
2+
3+
import "testing"
4+
5+
func TestShellEscape(t *testing.T) {
6+
t.Parallel()
7+
8+
tests := []struct {
9+
Name string
10+
Input string
11+
Escaped string
12+
}{
13+
{
14+
Name: "single space",
15+
Input: "hello world",
16+
Escaped: `hello\ world`,
17+
},
18+
{
19+
Name: "multiple spaces",
20+
Input: "test message hello world",
21+
Escaped: `test\ message\ hello\ \ world`,
22+
},
23+
{
24+
Name: "mixed quotes",
25+
Input: `"''"`,
26+
Escaped: `\"\'\'\"`,
27+
},
28+
{
29+
Name: "mixed escaped quotes",
30+
Input: `"'\"\"'"`,
31+
Escaped: `\"\'\\\"\\\"\'\"`,
32+
},
33+
}
34+
35+
for _, test := range tests {
36+
if e, a := test.Escaped, shellEscape(test.Input); e != a {
37+
t.Fatalf("test %q failed; expected: %q, got %q (input: %q)",
38+
test.Name, test.Escaped, a, test.Input)
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)