From 77c04107f3a9e8678b3ffb24c4922b7c17f3afb1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 24 Feb 2023 11:46:50 +0530 Subject: [PATCH] Add test for tarfile exclusion --- tools/cmd/ssh/config.go | 1 + tools/cmd/ssh/data.go | 15 ++++-- tools/cmd/ssh/main.go | 6 +-- tools/cmd/ssh/main_test.go | 96 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 107 insertions(+), 11 deletions(-) diff --git a/tools/cmd/ssh/config.go b/tools/cmd/ssh/config.go index d1624ecbc..5cd7900bb 100644 --- a/tools/cmd/ssh/config.go +++ b/tools/cmd/ssh/config.go @@ -365,6 +365,7 @@ func load_config(hostname_to_match string, username_to_match string, overrides [ if len(paths) == 0 { paths = []string{filepath.Join(utils.ConfigDir(), "ssh.conf")} } + paths = utils.Filter(paths, func(x string) bool { return x != "" }) err := p.ParseFiles(paths...) if err != nil && !errors.Is(err, fs.ErrNotExist) { return nil, err diff --git a/tools/cmd/ssh/data.go b/tools/cmd/ssh/data.go index 8b79793ea..4b224f9b1 100644 --- a/tools/cmd/ssh/data.go +++ b/tools/cmd/ssh/data.go @@ -9,7 +9,8 @@ import ( "fmt" "io" "kitty/tools/utils" - "path/filepath" + "regexp" + "strings" ) var _ = fmt.Print @@ -44,13 +45,17 @@ var Data = (&utils.Once[Container]{Run: func() Container { return ans }}).Get -func (self Container) files_matching(include_pattern string, exclude_patterns ...string) []string { +func (self Container) files_matching(prefix string, exclude_patterns ...string) []string { ans := make([]string, 0, len(self)) + patterns := make([]*regexp.Regexp, len(exclude_patterns)) + for i, exp := range exclude_patterns { + patterns[i] = regexp.MustCompile(exp) + } for name := range self { - if matched, err := filepath.Match(include_pattern, name); matched && err == nil { + if strings.HasPrefix(name, prefix) { excluded := false - for _, pat := range exclude_patterns { - if matched, err := filepath.Match(pat, name); matched && err == nil { + for _, pat := range patterns { + if matched := pat.FindString(name); matched != "" { excluded = true break } diff --git a/tools/cmd/ssh/main.go b/tools/cmd/ssh/main.go index 81cc00710..4a79e0f59 100644 --- a/tools/cmd/ssh/main.go +++ b/tools/cmd/ssh/main.go @@ -305,9 +305,9 @@ func make_tarfile(cd *connection_data, get_local_env func(string) (string, bool) add_data(fe{"data.sh", utils.UnsafeStringToBytes(env_script)}) if ksi != "" { for _, fname := range Data().files_matching( - "shell-integration/*", - "shell-integration/ssh/*", // bootstrap files are sent as command line args - "shell_integration/zsh/kitty.zsh", // backward compat file not needed by ssh kitten + "shell-integration/", + "shell-integration/ssh/.+", // bootstrap files are sent as command line args + "shell-integration/zsh/kitty.zsh", // backward compat file not needed by ssh kitten ) { arcname := path.Join("home/", rd, "/", path.Dir(fname)) err = add_entries(arcname, Data()[fname]) diff --git a/tools/cmd/ssh/main_test.go b/tools/cmd/ssh/main_test.go index c073f0531..ab859145c 100644 --- a/tools/cmd/ssh/main_test.go +++ b/tools/cmd/ssh/main_test.go @@ -6,10 +6,17 @@ import ( "encoding/binary" "encoding/json" "fmt" + "io/fs" "kitty/tools/utils/shm" + "os" + "os/exec" + "path" + "path/filepath" + "strings" "testing" "github.com/google/go-cmp/cmp" + "golang.org/x/sys/unix" ) var _ = fmt.Print @@ -38,11 +45,17 @@ func TestCloneEnv(t *testing.T) { } } -func basic_connection_data() *connection_data { - return &connection_data{ - script_type: "sh", request_id: "123-123", remote_args: []string{}, host_opts: NewConfig(), +func basic_connection_data(overrides ...string) *connection_data { + ans := &connection_data{ + script_type: "sh", request_id: "123-123", remote_args: []string{}, username: "testuser", hostname_for_match: "host.test", } + opts, err := load_config(ans.hostname_for_match, ans.username, overrides, "") + if err != nil { + panic(err) + } + ans.host_opts = opts + return ans } func TestSSHBootstrapScriptLimit(t *testing.T) { @@ -59,3 +72,80 @@ func TestSSHBootstrapScriptLimit(t *testing.T) { t.Fatalf("Bootstrap script too large: %d bytes", total) } } + +func TestSSHTarfile(t *testing.T) { + tdir := t.TempDir() + cd := basic_connection_data() + data, err := make_tarfile(cd, func(key string) (val string, found bool) { return }) + if err != nil { + t.Fatal(err) + } + cmd := exec.Command("tar", "xpzf", "-", "-C", tdir) + cmd.Stderr = os.Stderr + inp, err := cmd.StdinPipe() + if err != nil { + t.Fatal(err) + } + err = cmd.Start() + if err != nil { + t.Fatal(err) + } + _, err = inp.Write(data) + if err != nil { + t.Fatal(err) + } + inp.Close() + err = cmd.Wait() + if err != nil { + t.Fatal(err) + } + + seen := map[string]bool{} + err = filepath.WalkDir(tdir, func(name string, d fs.DirEntry, werr error) error { + if werr != nil { + return werr + } + rname, werr := filepath.Rel(tdir, name) + if werr != nil { + return werr + } + rname = strings.ReplaceAll(rname, "\\", "/") + if rname == "." { + return nil + } + fi, werr := d.Info() + if werr != nil { + return werr + } + if fi.Mode().Perm()&0o600 == 0 { + return fmt.Errorf("%s is not rw for its owner. Actual permissions: %s", rname, fi.Mode().String()) + } + seen[rname] = true + return nil + }) + if err != nil { + t.Fatal(err) + } + if !seen["data.sh"] { + t.Fatalf("data.sh missing") + } + for _, x := range []string{".terminfo/kitty.terminfo", ".terminfo/x/xterm-kitty"} { + if !seen["home/"+x] { + t.Fatalf("%s missing", x) + } + } + for _, x := range []string{"shell-integration/bash/kitty.bash", "shell-integration/fish/vendor_completions.d/kitty.fish"} { + if !seen[path.Join("home", cd.host_opts.Remote_dir, x)] { + t.Fatalf("%s missing", x) + } + } + for _, x := range []string{"kitty", "kitten"} { + p := filepath.Join(tdir, "home", cd.host_opts.Remote_dir, "kitty", "bin", x) + if err = unix.Access(p, unix.X_OK); err != nil { + t.Fatalf("Cannot execute %s with error: %s", x, err) + } + } + if seen[path.Join("home", cd.host_opts.Remote_dir, "shell-integration", "ssh", "kitten")] { + t.Fatalf("Contents of shell-integration/ssh not excluded") + } +}