Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions gin.go
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,11 @@ func redirectTrailingSlash(c *Context) {
prefix = removeRepeatedChar(prefix, '/')

p = prefix + "/" + req.URL.Path
// Keep the redirect target rooted at this server. Sanitizing can leave a
// prefix that is empty or has no leading slash, and then p starts with
// "//", which a client reads as a host, or with a path segment, which it
// resolves against the request path and so repeats the prefix.
p = "/" + strings.TrimLeft(p, "/")
}
req.URL.Path = p + "/"
if length := len(p); length > 1 && p[length-1] == '/' {
Expand Down
12 changes: 9 additions & 3 deletions routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
assert.Equal(t, http.StatusMovedPermanently, w.Code)

w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../"})
assert.Equal(t, "//path", w.Header().Get("Location"))
assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)

w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../../"})
Expand All @@ -276,11 +276,11 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
assert.Equal(t, http.StatusMovedPermanently, w.Code)

w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"})
assert.Equal(t, "https/gin-goniccom/https/gin-goniccom/path", w.Header().Get("Location"))
assert.Equal(t, "/https/gin-goniccom/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)

w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#api"})
assert.Equal(t, "api/api/path", w.Header().Get("Location"))
assert.Equal(t, "/api/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)

w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/#?a=1"})
Expand All @@ -291,6 +291,12 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
assert.Equal(t, "/nor-mal/2e2e/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)

// A prefix that sanitizes to nothing must not leave a Location starting
// with "//", which a client reads as the start of a host.
w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: ".."})
assert.Equal(t, "/path2/", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)

router.RedirectTrailingSlash = false

w = PerformRequest(router, http.MethodGet, "/path/")
Expand Down
Loading