Skip to content

Commit 364e511

Browse files
committed
add test, support backslashes
1 parent 44d19f1 commit 364e511

File tree

2 files changed

+59
-4
lines changed

2 files changed

+59
-4
lines changed

cli/configssh_internal_test.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func Test_sshConfigSplitOnCoderSection(t *testing.T) {
139139
// This test tries to mimic the behavior of OpenSSH
140140
// when executing e.g. a ProxyCommand.
141141
// nolint:tparallel
142-
func Test_sshConfigExecEscape(t *testing.T) {
142+
func Test_sshConfigProxyCommandEscape(t *testing.T) {
143143
t.Parallel()
144144

145145
tests := []struct {
@@ -186,6 +186,60 @@ func Test_sshConfigExecEscape(t *testing.T) {
186186
}
187187
}
188188

189+
// This test tries to mimic the behavior of OpenSSH
190+
// when executing e.g. a match exec command.
191+
// nolint:tparallel
192+
func Test_sshConfigMatchExecEscape(t *testing.T) {
193+
t.Parallel()
194+
195+
tests := []struct {
196+
name string
197+
path string
198+
wantErr bool
199+
}{
200+
{"no spaces", "simple", false},
201+
{"spaces", "path with spaces", false},
202+
{"quotes fails", "path with \"quotes\"", true},
203+
{"backslashes", "path with \\backslashes", false},
204+
{"tabs", "path with \ttabs", false},
205+
{"newline fails", "path with \nnewline", true},
206+
}
207+
// nolint:paralleltest // Fixes a flake
208+
for _, tt := range tests {
209+
tt := tt
210+
t.Run(tt.name, func(t *testing.T) {
211+
cmd := "/bin/sh"
212+
arg := "-c"
213+
contents := []byte("#!/bin/sh\necho yay\n")
214+
if runtime.GOOS == "windows" {
215+
cmd = "cmd.exe"
216+
arg = "/c"
217+
contents = []byte("echo yay\n")
218+
}
219+
220+
dir := filepath.Join(t.TempDir(), tt.path)
221+
err := os.MkdirAll(dir, 0o755)
222+
require.NoError(t, err)
223+
bin := filepath.Join(dir, "coder.bat") // Windows will treat it as batch, Linux doesn't care
224+
225+
err = os.WriteFile(bin, contents, 0o755) //nolint:gosec
226+
require.NoError(t, err)
227+
228+
escaped, err := sshConfigMatchExecEscape(bin)
229+
if tt.wantErr {
230+
require.Error(t, err)
231+
return
232+
}
233+
require.NoError(t, err)
234+
235+
b, err := exec.Command(cmd, arg, escaped).CombinedOutput() //nolint:gosec
236+
require.NoError(t, err)
237+
got := strings.TrimSpace(string(b))
238+
require.Equal(t, "yay", got)
239+
})
240+
}
241+
}
242+
189243
func Test_sshConfigExecEscapeSeparatorForce(t *testing.T) {
190244
t.Parallel()
191245

cli/configssh_other.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ func sshConfigMatchExecEscape(path string) (string, error) {
2626
return "", xerrors.Errorf("path must not contain quotes: %q", path)
2727
}
2828

29-
// OpenSSH passes the match exec string directly to the user's shell. sh, bash and zsh accept spaces and tabs
30-
// simply escaped by a `\`. It's hard to predict exactly what more exotic shells might do, but this should work for
31-
// macOS and most Linux distros in their default configuration.
29+
// OpenSSH passes the match exec string directly to the user's shell. sh, bash and zsh accept spaces, tabs and
30+
// backslashes simply escaped by a `\`. It's hard to predict exactly what more exotic shells might do, but this
31+
// should work for macOS and most Linux distros in their default configuration.
32+
path = strings.ReplaceAll(path, `\`, `\\`) // must be first, since later replacements add backslashes.
3233
path = strings.ReplaceAll(path, " ", "\\ ")
3334
path = strings.ReplaceAll(path, "\t", "\\\t")
3435
return path, nil

0 commit comments

Comments
 (0)