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

Commit 460c53f

Browse files
authored
fix: Escape shell arguments (#227)
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 51fd2d3 commit 460c53f

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

internal/cmd/shell.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,46 @@ coder sh front-end-dev cat ~/config.json`,
8181
}
8282
}
8383

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

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy