Skip to content

Commit 880e776

Browse files
authored
Merge pull request #63 from vdye/vdye/stricter-route-parsing
Stricter route parsing (+ CODEOWNERS update)
2 parents ed48647 + d8dac7f commit 880e776

File tree

5 files changed

+158
-19
lines changed

5 files changed

+158
-19
lines changed

CODEOWNERS

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# This repository is maintained by:
2-
* @vdye @derrickstolee @ldennington
2+
* @git-ecosystem/hubbers

cmd/git-bundle-web-server/bundle-server.go

+1-18
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"os"
1010
"os/signal"
1111
"path/filepath"
12-
"strings"
1312
"sync"
1413
"syscall"
1514
"time"
@@ -81,30 +80,14 @@ func NewBundleWebServer(logger log.TraceLogger,
8180
return bundleServer, nil
8281
}
8382

84-
func (b *bundleWebServer) parseRoute(ctx context.Context, path string) (string, string, string, error) {
85-
elements := strings.FieldsFunc(path, func(char rune) bool { return char == '/' })
86-
switch len(elements) {
87-
case 0:
88-
return "", "", "", fmt.Errorf("empty route")
89-
case 1:
90-
return "", "", "", fmt.Errorf("route has owner, but no repo")
91-
case 2:
92-
return elements[0], elements[1], "", nil
93-
case 3:
94-
return elements[0], elements[1], elements[2], nil
95-
default:
96-
return "", "", "", fmt.Errorf("path has depth exceeding three")
97-
}
98-
}
99-
10083
func (b *bundleWebServer) serve(w http.ResponseWriter, r *http.Request) {
10184
ctx := r.Context()
10285

10386
ctx, exitRegion := b.logger.Region(ctx, "http", "serve")
10487
defer exitRegion()
10588

10689
path := r.URL.Path
107-
owner, repo, filename, err := b.parseRoute(ctx, path)
90+
owner, repo, filename, err := core.ParseRoute(path, false)
10891
if err != nil {
10992
w.WriteHeader(http.StatusNotFound)
11093
fmt.Printf("Failed to parse route: %s\n", err)

internal/core/funcs.go

+31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package core
22

33
import (
4+
"fmt"
45
"regexp"
56
"strings"
67
)
@@ -27,3 +28,33 @@ func GetRouteFromUrl(url string) (string, bool) {
2728

2829
return "", false
2930
}
31+
32+
func ParseRoute(route string, repoOnly bool) (string, string, string, error) {
33+
elements := strings.FieldsFunc(route, func(char rune) bool { return char == '/' })
34+
validElementPattern := regexp.MustCompile(`^[\w\.-]+$`)
35+
for _, e := range elements {
36+
if !validElementPattern.MatchString(e) {
37+
return "", "", "",
38+
fmt.Errorf("invalid element '%s'; route may only contain alphanumeric characters, '.', '_', and/or '-'", e)
39+
}
40+
if e == "." || e == ".." {
41+
return "", "", "", fmt.Errorf("invalid route element '%s'", e)
42+
}
43+
}
44+
45+
switch len(elements) {
46+
case 0:
47+
return "", "", "", fmt.Errorf("empty route")
48+
case 1:
49+
return "", "", "", fmt.Errorf("route has owner, but no repo")
50+
case 2:
51+
return elements[0], elements[1], "", nil
52+
case 3:
53+
if repoOnly {
54+
return "", "", "", fmt.Errorf("route is too deep")
55+
}
56+
return elements[0], elements[1], elements[2], nil
57+
default:
58+
return "", "", "", fmt.Errorf("route is too deep")
59+
}
60+
}

internal/core/funcs_test.go

+113
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,116 @@ func TestGetRouteFromUrl(t *testing.T) {
120120
})
121121
}
122122
}
123+
124+
var parseRouteTests = []struct {
125+
route string
126+
repoOnly bool
127+
expectedOwner string
128+
expectedRepo string
129+
expectedFile string
130+
expectedError bool
131+
}{
132+
// Valid routes
133+
{
134+
"test/repo/1.bundle",
135+
false,
136+
"test", "repo", "1.bundle",
137+
false,
138+
},
139+
{
140+
"test/repo",
141+
false,
142+
"test", "repo", "",
143+
false,
144+
},
145+
{
146+
"test_with_undercore/repo-with-dash",
147+
false,
148+
"test_with_undercore", "repo-with-dash", "",
149+
false,
150+
},
151+
{
152+
"//lots/of////path_separators...bundle//",
153+
false,
154+
"lots", "of", "path_separators...bundle",
155+
false,
156+
},
157+
{
158+
"test/repo",
159+
true,
160+
"test", "repo", "",
161+
false,
162+
},
163+
164+
// Invalid routes
165+
{
166+
"",
167+
false,
168+
"", "", "",
169+
true,
170+
},
171+
{
172+
"//",
173+
false,
174+
"", "", "",
175+
true,
176+
},
177+
{
178+
"too-short",
179+
false,
180+
"", "", "",
181+
true,
182+
},
183+
{
184+
"much/much/MUCH/too/long",
185+
false,
186+
"", "", "",
187+
true,
188+
},
189+
{
190+
"test/repo with spaces",
191+
false,
192+
"", "", "",
193+
true,
194+
},
195+
{
196+
"test/./repo",
197+
true,
198+
"", "", "",
199+
true,
200+
},
201+
{
202+
"../test/repo",
203+
true,
204+
"", "", "",
205+
true,
206+
},
207+
{
208+
"test/repo/1.bundle",
209+
true,
210+
"", "", "",
211+
true,
212+
},
213+
}
214+
215+
func TestParseRoute(t *testing.T) {
216+
for _, tt := range parseRouteTests {
217+
title := tt.route
218+
if tt.repoOnly {
219+
title += " (repo only)"
220+
}
221+
222+
t.Run(title, func(t *testing.T) {
223+
owner, repo, file, err := core.ParseRoute(tt.route, tt.repoOnly)
224+
225+
if tt.expectedError {
226+
assert.NotNil(t, err)
227+
} else {
228+
assert.Nil(t, err)
229+
assert.Equal(t, tt.expectedOwner, owner)
230+
assert.Equal(t, tt.expectedRepo, repo)
231+
assert.Equal(t, tt.expectedFile, file)
232+
}
233+
})
234+
}
235+
}

internal/core/repo.go

+12
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ func (r *repoProvider) CreateRepository(ctx context.Context, route string) (*Rep
5050
ctx, exitRegion := r.logger.Region(ctx, "repo", "create_repo")
5151
defer exitRegion()
5252

53+
ownerName, repoName, _, err := ParseRoute(route, true)
54+
if err != nil {
55+
return nil, err
56+
}
57+
route = ownerName + "/" + repoName
58+
5359
user, err := r.user.CurrentUser()
5460
if err != nil {
5561
return nil, err
@@ -93,6 +99,12 @@ func (r *repoProvider) RemoveRoute(ctx context.Context, route string) error {
9399
ctx, exitRegion := r.logger.Region(ctx, "repo", "remove_route")
94100
defer exitRegion()
95101

102+
ownerName, repoName, _, err := ParseRoute(route, true)
103+
if err != nil {
104+
return err
105+
}
106+
route = ownerName + "/" + repoName
107+
96108
repos, err := r.GetRepositories(ctx)
97109
if err != nil {
98110
return fmt.Errorf("failed to parse routes file: %w", err)

0 commit comments

Comments
 (0)