Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support sanitising the URL by removing extra slashes in the URL (#21333) #23300

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 33 additions & 2 deletions routers/common/middleware.go
Expand Up @@ -16,7 +16,7 @@ import (
"code.gitea.io/gitea/modules/web/routing"

"github.com/chi-middleware/proxy"
"github.com/go-chi/chi/v5/middleware"
chi "github.com/go-chi/chi/v5"
)

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

handlers = append(handlers, middleware.StripSlashes)
// Strip slashes.
handlers = append(handlers, stripSlashesMiddleware)

if !setting.Log.DisableRouterLog {
handlers = append(handlers, routing.NewLoggerHandler())
Expand Down Expand Up @@ -81,3 +82,33 @@ func Middlewares() []func(http.Handler) http.Handler {
})
return handlers
}

func stripSlashesMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
var urlPath string
rctx := chi.RouteContext(req.Context())
if rctx != nil && rctx.RoutePath != "" {
urlPath = rctx.RoutePath
} else if req.URL.RawPath != "" {
urlPath = req.URL.RawPath
} else {
urlPath = req.URL.Path
}

sanitizedPath := &strings.Builder{}
prevWasSlash := false
for _, chr := range strings.TrimRight(urlPath, "/") {
if chr != '/' || !prevWasSlash {
sanitizedPath.WriteRune(chr)
}
prevWasSlash = chr == '/'
}

if rctx == nil {
req.URL.Path = sanitizedPath.String()
} else {
rctx.RoutePath = sanitizedPath.String()
}
next.ServeHTTP(resp, req)
})
}
70 changes: 70 additions & 0 deletions routers/common/middleware_test.go
@@ -0,0 +1,70 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestStripSlashesMiddleware(t *testing.T) {
type test struct {
name string
expectedPath string
inputPath string
}

tests := []test{
{
name: "path with multiple slashes",
inputPath: "https://github.com///go-gitea//gitea.git",
expectedPath: "/go-gitea/gitea.git",
},
{
name: "path with no slashes",
inputPath: "https://github.com/go-gitea/gitea.git",
expectedPath: "/go-gitea/gitea.git",
},
{
name: "path with slashes in the middle",
inputPath: "https://git.data.coop//halfd/new-website.git",
expectedPath: "/halfd/new-website.git",
},
{
name: "path with slashes in the middle",
inputPath: "https://git.data.coop//halfd/new-website.git",
expectedPath: "/halfd/new-website.git",
},
{
name: "path with slashes in the end",
inputPath: "/user2//repo1/",
expectedPath: "/user2/repo1",
},
{
name: "path with slashes and query params",
inputPath: "/repo//migrate?service_type=3",
expectedPath: "/repo/migrate",
},
{
name: "path with encoded slash",
inputPath: "/user2/%2F%2Frepo1",
expectedPath: "/user2/%2F%2Frepo1",
},
}

for _, tt := range tests {
testMiddleware := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, tt.expectedPath, r.URL.Path)
})

// pass the test middleware to validate the changes
handlerToTest := stripSlashesMiddleware(testMiddleware)
// create a mock request to use
req := httptest.NewRequest("GET", tt.inputPath, nil)
// call the handler using a mock response recorder
handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
}
}