Skip to content

Commit 547c173

Browse files
sandyydkzeripathwolfogrelunny
authored
Support sanitising the URL by removing extra slashes in the URL (#21333)
Changes in this PR : Strips incoming request URL of additional slashes (/). For example an input like `https://git.data.coop//halfd/new-website.git` is translated to `https://git.data.coop/halfd/new-website.git` Fixes #20462 Fix #23242 --------- Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Jason Song <i@wolfogre.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
1 parent 188c8c1 commit 547c173

File tree

2 files changed

+103
-2
lines changed

2 files changed

+103
-2
lines changed

routers/common/middleware.go

+33-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"code.gitea.io/gitea/modules/web/routing"
1717

1818
"github.com/chi-middleware/proxy"
19-
"github.com/go-chi/chi/v5/middleware"
19+
chi "github.com/go-chi/chi/v5"
2020
)
2121

2222
// Middlewares returns common middlewares
@@ -48,7 +48,8 @@ func Middlewares() []func(http.Handler) http.Handler {
4848
handlers = append(handlers, proxy.ForwardedHeaders(opt))
4949
}
5050

51-
handlers = append(handlers, middleware.StripSlashes)
51+
// Strip slashes.
52+
handlers = append(handlers, stripSlashesMiddleware)
5253

5354
if !setting.Log.DisableRouterLog {
5455
handlers = append(handlers, routing.NewLoggerHandler())
@@ -81,3 +82,33 @@ func Middlewares() []func(http.Handler) http.Handler {
8182
})
8283
return handlers
8384
}
85+
86+
func stripSlashesMiddleware(next http.Handler) http.Handler {
87+
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
88+
var urlPath string
89+
rctx := chi.RouteContext(req.Context())
90+
if rctx != nil && rctx.RoutePath != "" {
91+
urlPath = rctx.RoutePath
92+
} else if req.URL.RawPath != "" {
93+
urlPath = req.URL.RawPath
94+
} else {
95+
urlPath = req.URL.Path
96+
}
97+
98+
sanitizedPath := &strings.Builder{}
99+
prevWasSlash := false
100+
for _, chr := range strings.TrimRight(urlPath, "/") {
101+
if chr != '/' || !prevWasSlash {
102+
sanitizedPath.WriteRune(chr)
103+
}
104+
prevWasSlash = chr == '/'
105+
}
106+
107+
if rctx == nil {
108+
req.URL.Path = sanitizedPath.String()
109+
} else {
110+
rctx.RoutePath = sanitizedPath.String()
111+
}
112+
next.ServeHTTP(resp, req)
113+
})
114+
}

routers/common/middleware_test.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
package common
4+
5+
import (
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestStripSlashesMiddleware(t *testing.T) {
14+
type test struct {
15+
name string
16+
expectedPath string
17+
inputPath string
18+
}
19+
20+
tests := []test{
21+
{
22+
name: "path with multiple slashes",
23+
inputPath: "https://github.com///go-gitea//gitea.git",
24+
expectedPath: "/go-gitea/gitea.git",
25+
},
26+
{
27+
name: "path with no slashes",
28+
inputPath: "https://github.com/go-gitea/gitea.git",
29+
expectedPath: "/go-gitea/gitea.git",
30+
},
31+
{
32+
name: "path with slashes in the middle",
33+
inputPath: "https://git.data.coop//halfd/new-website.git",
34+
expectedPath: "/halfd/new-website.git",
35+
},
36+
{
37+
name: "path with slashes in the middle",
38+
inputPath: "https://git.data.coop//halfd/new-website.git",
39+
expectedPath: "/halfd/new-website.git",
40+
},
41+
{
42+
name: "path with slashes in the end",
43+
inputPath: "/user2//repo1/",
44+
expectedPath: "/user2/repo1",
45+
},
46+
{
47+
name: "path with slashes and query params",
48+
inputPath: "/repo//migrate?service_type=3",
49+
expectedPath: "/repo/migrate",
50+
},
51+
{
52+
name: "path with encoded slash",
53+
inputPath: "/user2/%2F%2Frepo1",
54+
expectedPath: "/user2/%2F%2Frepo1",
55+
},
56+
}
57+
58+
for _, tt := range tests {
59+
testMiddleware := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
60+
assert.Equal(t, tt.expectedPath, r.URL.Path)
61+
})
62+
63+
// pass the test middleware to validate the changes
64+
handlerToTest := stripSlashesMiddleware(testMiddleware)
65+
// create a mock request to use
66+
req := httptest.NewRequest("GET", tt.inputPath, nil)
67+
// call the handler using a mock response recorder
68+
handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
69+
}
70+
}

0 commit comments

Comments
 (0)