Go · 3138 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package protocol_test
4
5 import (
6 "errors"
7 "testing"
8
9 "github.com/tenseleyFlow/shithub/internal/git/protocol"
10 )
11
12 func TestParseSSHCommand_Accepts(t *testing.T) {
13 t.Parallel()
14 cases := []struct {
15 in string
16 wantService protocol.Service
17 wantOwner string
18 wantRepo string
19 }{
20 {"git-upload-pack 'alice/foo'", protocol.UploadPack, "alice", "foo"},
21 {"git-upload-pack 'alice/foo.git'", protocol.UploadPack, "alice", "foo"},
22 {"git-receive-pack 'bob/bar'", protocol.ReceivePack, "bob", "bar"},
23 {"git-receive-pack alice/foo.git", protocol.ReceivePack, "alice", "foo"},
24 {"git-upload-pack 'alice/rust-by-example'", protocol.UploadPack, "alice", "rust-by-example"},
25 {"git-upload-pack 'alice/name.with.dots'", protocol.UploadPack, "alice", "name.with.dots"},
26 }
27 for _, c := range cases {
28 got, err := protocol.ParseSSHCommand(c.in)
29 if err != nil {
30 t.Errorf("%q: %v", c.in, err)
31 continue
32 }
33 if got.Service != c.wantService || got.Owner != c.wantOwner || got.Repo != c.wantRepo {
34 t.Errorf("%q: got %+v", c.in, got)
35 }
36 }
37 }
38
39 func TestParseSSHCommand_RejectsUnknown(t *testing.T) {
40 t.Parallel()
41 for _, in := range []string{
42 "",
43 " ",
44 "ls",
45 "bash",
46 "git-archive 'alice/foo'",
47 "git-upload-pack", // no path
48 " git-upload-pack 'a/b'", // leading space
49 "git-upload-pack 'a/b' ", // trailing space
50 "git-upload-pack 'a/b' && rm -rf /", // command injection
51 `git-upload-pack 'a/b'; cat /etc/passwd`,
52 "GIT-UPLOAD-PACK 'a/b'", // case
53 "git-upload-pack a/b'", // mismatched quotes
54 } {
55 _, err := protocol.ParseSSHCommand(in)
56 if !errors.Is(err, protocol.ErrUnknownSSHCommand) {
57 t.Errorf("%q: err = %v, want ErrUnknownSSHCommand", in, err)
58 }
59 }
60 }
61
62 func TestParseSSHCommand_RejectsBadPath(t *testing.T) {
63 t.Parallel()
64 for _, in := range []string{
65 "git-upload-pack '../../etc/passwd'",
66 "git-upload-pack 'alice/../bob/foo'",
67 "git-upload-pack 'alice/foo/extra'",
68 "git-upload-pack '/alice/foo'",
69 "git-upload-pack 'alice/'",
70 "git-upload-pack '/foo'",
71 "git-upload-pack 'alice/.git-meta'",
72 "git-upload-pack 'alice/-leading'",
73 "git-upload-pack 'alice/trailing-'",
74 "git-upload-pack 'al!ce/foo'",
75 "git-upload-pack 'alice/foo bar'",
76 } {
77 _, err := protocol.ParseSSHCommand(in)
78 if !errors.Is(err, protocol.ErrInvalidSSHPath) {
79 t.Errorf("%q: err = %v, want ErrInvalidSSHPath", in, err)
80 }
81 }
82 }
83
84 func TestParseSSHCommand_StripsSingleDotGit(t *testing.T) {
85 t.Parallel()
86 got, err := protocol.ParseSSHCommand("git-upload-pack 'alice/foo.git'")
87 if err != nil {
88 t.Fatalf("err: %v", err)
89 }
90 if got.Repo != "foo" {
91 t.Errorf("Repo = %q, want %q", got.Repo, "foo")
92 }
93 // And nested .git.git would split to ".git" repo (which would then
94 // be rejected because leading dot — but the parser doesn't do extra
95 // stripping past the single suffix).
96 got2, err := protocol.ParseSSHCommand("git-upload-pack 'alice/foo.git.git'")
97 if err != nil {
98 t.Fatalf("err: %v", err)
99 }
100 if got2.Repo != "foo.git" {
101 t.Errorf("nested .git.git: Repo = %q, want %q", got2.Repo, "foo.git")
102 }
103 }
104