Refactor route path normalization (#31381)
Refactor route path normalization and decouple it from the chi router. Fix the TODO, fix the legacy strange path behavior.
This commit is contained in:
parent
5a7376c060
commit
d32648b204
@ -5,8 +5,10 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
|
||||||
"gitea.com/go-chi/binding"
|
"gitea.com/go-chi/binding"
|
||||||
@ -160,7 +162,7 @@ func (r *Route) Patch(pattern string, h ...any) {
|
|||||||
|
|
||||||
// ServeHTTP implements http.Handler
|
// ServeHTTP implements http.Handler
|
||||||
func (r *Route) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (r *Route) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
r.R.ServeHTTP(w, req)
|
r.normalizeRequestPath(w, req, r.R)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotFound defines a handler to respond whenever a route could not be found.
|
// NotFound defines a handler to respond whenever a route could not be found.
|
||||||
@ -168,6 +170,61 @@ func (r *Route) NotFound(h http.HandlerFunc) {
|
|||||||
r.R.NotFound(h)
|
r.R.NotFound(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Route) normalizeRequestPath(resp http.ResponseWriter, req *http.Request, next http.Handler) {
|
||||||
|
normalized := false
|
||||||
|
normalizedPath := req.URL.EscapedPath()
|
||||||
|
if normalizedPath == "" {
|
||||||
|
normalizedPath, normalized = "/", true
|
||||||
|
} else if normalizedPath != "/" {
|
||||||
|
normalized = strings.HasSuffix(normalizedPath, "/")
|
||||||
|
normalizedPath = strings.TrimRight(normalizedPath, "/")
|
||||||
|
}
|
||||||
|
removeRepeatedSlashes := strings.Contains(normalizedPath, "//")
|
||||||
|
normalized = normalized || removeRepeatedSlashes
|
||||||
|
|
||||||
|
// the following code block is a slow-path for replacing all repeated slashes "//" to one single "/"
|
||||||
|
// if the path doesn't have repeated slashes, then no need to execute it
|
||||||
|
if removeRepeatedSlashes {
|
||||||
|
buf := &strings.Builder{}
|
||||||
|
for i := 0; i < len(normalizedPath); i++ {
|
||||||
|
if i == 0 || normalizedPath[i-1] != '/' || normalizedPath[i] != '/' {
|
||||||
|
buf.WriteByte(normalizedPath[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
normalizedPath = buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the config tells Gitea to use a sub-url path directly without reverse proxy,
|
||||||
|
// then we need to remove the sub-url path from the request URL path.
|
||||||
|
// But "/v2" is special for OCI container registry, it should always be in the root of the site.
|
||||||
|
if setting.UseSubURLPath {
|
||||||
|
remainingPath, ok := strings.CutPrefix(normalizedPath, setting.AppSubURL+"/")
|
||||||
|
if ok {
|
||||||
|
normalizedPath = "/" + remainingPath
|
||||||
|
} else if normalizedPath == setting.AppSubURL {
|
||||||
|
normalizedPath = "/"
|
||||||
|
} else if !strings.HasPrefix(normalizedPath+"/", "/v2/") {
|
||||||
|
// do not respond to other requests, to simulate a real sub-path environment
|
||||||
|
http.Error(resp, "404 page not found, sub-path is: "+setting.AppSubURL, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
normalized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the path is normalized, then fill it back to the request
|
||||||
|
if normalized {
|
||||||
|
decodedPath, err := url.PathUnescape(normalizedPath)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(resp, "400 Bad Request: unable to unescape path "+normalizedPath, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.URL.RawPath = normalizedPath
|
||||||
|
req.URL.Path = decodedPath
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(resp, req)
|
||||||
|
}
|
||||||
|
|
||||||
// Combo delegates requests to Combo
|
// Combo delegates requests to Combo
|
||||||
func (r *Route) Combo(pattern string, h ...any) *Combo {
|
func (r *Route) Combo(pattern string, h ...any) *Combo {
|
||||||
return &Combo{r, pattern, h}
|
return &Combo{r, pattern, h}
|
||||||
|
@ -10,6 +10,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
chi "github.com/go-chi/chi/v5"
|
chi "github.com/go-chi/chi/v5"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -176,3 +179,44 @@ func TestRoute3(t *testing.T) {
|
|||||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||||
assert.EqualValues(t, 4, hit)
|
assert.EqualValues(t, 4, hit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteNormalizePath(t *testing.T) {
|
||||||
|
type paths struct {
|
||||||
|
EscapedPath, RawPath, Path string
|
||||||
|
}
|
||||||
|
testPath := func(reqPath string, expectedPaths paths) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
recorder.Body = bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
actualPaths := paths{EscapedPath: "(none)", RawPath: "(none)", Path: "(none)"}
|
||||||
|
r := NewRoute()
|
||||||
|
r.Get("/*", func(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
actualPaths.EscapedPath = req.URL.EscapedPath()
|
||||||
|
actualPaths.RawPath = req.URL.RawPath
|
||||||
|
actualPaths.Path = req.URL.Path
|
||||||
|
})
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", reqPath, nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
r.ServeHTTP(recorder, req)
|
||||||
|
assert.Equal(t, expectedPaths, actualPaths, "req path = %q", reqPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawPath could be empty if the EscapedPath is the same as escape(Path) and it is already normalized
|
||||||
|
testPath("/", paths{EscapedPath: "/", RawPath: "", Path: "/"})
|
||||||
|
testPath("//", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
|
||||||
|
testPath("/%2f", paths{EscapedPath: "/%2f", RawPath: "/%2f", Path: "//"})
|
||||||
|
testPath("///a//b/", paths{EscapedPath: "/a/b", RawPath: "/a/b", Path: "/a/b"})
|
||||||
|
|
||||||
|
defer test.MockVariableValue(&setting.UseSubURLPath, true)()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "/sub-path")()
|
||||||
|
testPath("/", paths{EscapedPath: "(none)", RawPath: "(none)", Path: "(none)"}) // 404
|
||||||
|
testPath("/sub-path", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
|
||||||
|
testPath("/sub-path/", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
|
||||||
|
testPath("/sub-path//a/b///", paths{EscapedPath: "/a/b", RawPath: "/a/b", Path: "/a/b"})
|
||||||
|
testPath("/sub-path/%2f/", paths{EscapedPath: "/%2f", RawPath: "/%2f", Path: "//"})
|
||||||
|
// "/v2" is special for OCI container registry, it should always be in the root of the site
|
||||||
|
testPath("/v2", paths{EscapedPath: "/v2", RawPath: "/v2", Path: "/v2"})
|
||||||
|
testPath("/v2/", paths{EscapedPath: "/v2", RawPath: "/v2", Path: "/v2"})
|
||||||
|
testPath("/v2/%2f", paths{EscapedPath: "/v2/%2f", RawPath: "/v2/%2f", Path: "/v2//"})
|
||||||
|
}
|
||||||
|
@ -19,13 +19,23 @@ import (
|
|||||||
|
|
||||||
"gitea.com/go-chi/session"
|
"gitea.com/go-chi/session"
|
||||||
"github.com/chi-middleware/proxy"
|
"github.com/chi-middleware/proxy"
|
||||||
chi "github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
|
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
|
||||||
func ProtocolMiddlewares() (handlers []any) {
|
func ProtocolMiddlewares() (handlers []any) {
|
||||||
// first, normalize the URL path
|
// make sure chi uses EscapedPath(RawPath) as RoutePath, then "%2f" could be handled correctly
|
||||||
handlers = append(handlers, normalizeRequestPathMiddleware)
|
handlers = append(handlers, func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
ctx := chi.RouteContext(req.Context())
|
||||||
|
if req.URL.RawPath == "" {
|
||||||
|
ctx.RoutePath = req.URL.EscapedPath()
|
||||||
|
} else {
|
||||||
|
ctx.RoutePath = req.URL.RawPath
|
||||||
|
}
|
||||||
|
next.ServeHTTP(resp, req)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// prepare the ContextData and panic recovery
|
// prepare the ContextData and panic recovery
|
||||||
handlers = append(handlers, func(next http.Handler) http.Handler {
|
handlers = append(handlers, func(next http.Handler) http.Handler {
|
||||||
@ -75,58 +85,6 @@ func ProtocolMiddlewares() (handlers []any) {
|
|||||||
return handlers
|
return handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeRequestPathMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
|
||||||
// escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
|
|
||||||
req.URL.RawPath = req.URL.EscapedPath()
|
|
||||||
|
|
||||||
urlPath := req.URL.RawPath
|
|
||||||
rctx := chi.RouteContext(req.Context())
|
|
||||||
if rctx != nil && rctx.RoutePath != "" {
|
|
||||||
urlPath = rctx.RoutePath
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizedPath := strings.TrimRight(urlPath, "/")
|
|
||||||
// the following code block is a slow-path for replacing all repeated slashes "//" to one single "/"
|
|
||||||
// if the path doesn't have repeated slashes, then no need to execute it
|
|
||||||
if strings.Contains(normalizedPath, "//") {
|
|
||||||
buf := &strings.Builder{}
|
|
||||||
prevWasSlash := false
|
|
||||||
for _, chr := range normalizedPath {
|
|
||||||
if chr != '/' || !prevWasSlash {
|
|
||||||
buf.WriteRune(chr)
|
|
||||||
}
|
|
||||||
prevWasSlash = chr == '/'
|
|
||||||
}
|
|
||||||
normalizedPath = buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if setting.UseSubURLPath {
|
|
||||||
remainingPath, ok := strings.CutPrefix(normalizedPath, setting.AppSubURL+"/")
|
|
||||||
if ok {
|
|
||||||
normalizedPath = "/" + remainingPath
|
|
||||||
} else if normalizedPath == setting.AppSubURL {
|
|
||||||
normalizedPath = "/"
|
|
||||||
} else if !strings.HasPrefix(normalizedPath+"/", "/v2/") {
|
|
||||||
// do not respond to other requests, to simulate a real sub-path environment
|
|
||||||
http.Error(resp, "404 page not found, sub-path is: "+setting.AppSubURL, http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: it's not quite clear about how req.URL and rctx.RoutePath work together.
|
|
||||||
// Fortunately, it is only used for debug purpose, we have enough time to figure it out in the future.
|
|
||||||
req.URL.RawPath = normalizedPath
|
|
||||||
req.URL.Path = normalizedPath
|
|
||||||
}
|
|
||||||
|
|
||||||
if rctx == nil {
|
|
||||||
req.URL.Path = normalizedPath
|
|
||||||
} else {
|
|
||||||
rctx.RoutePath = normalizedPath
|
|
||||||
}
|
|
||||||
next.ServeHTTP(resp, req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Sessioner() func(next http.Handler) http.Handler {
|
func Sessioner() func(next http.Handler) http.Handler {
|
||||||
return session.Sessioner(session.Options{
|
return session.Sessioner(session.Options{
|
||||||
Provider: setting.SessionConfig.Provider,
|
Provider: setting.SessionConfig.Provider,
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
// 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 := normalizeRequestPathMiddleware(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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -191,7 +191,8 @@ func NormalRoutes() *web.Route {
|
|||||||
if setting.Packages.Enabled {
|
if setting.Packages.Enabled {
|
||||||
// This implements package support for most package managers
|
// This implements package support for most package managers
|
||||||
r.Mount("/api/packages", packages_router.CommonRoutes())
|
r.Mount("/api/packages", packages_router.CommonRoutes())
|
||||||
// This implements the OCI API (Note this is not preceded by /api but is instead /v2)
|
// This implements the OCI API, this container registry "/v2" endpoint must be in the root of the site.
|
||||||
|
// If site admin deploys Gitea in a sub-path, they must configure their reverse proxy to map the "https://host/v2" endpoint to Gitea.
|
||||||
r.Mount("/v2", packages_router.ContainerRoutes())
|
r.Mount("/v2", packages_router.ContainerRoutes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
|
||||||
@ -142,23 +143,27 @@ func (b *Base) RemoteAddr() string {
|
|||||||
return b.Req.RemoteAddr
|
return b.Req.RemoteAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params returns the param on route
|
// Params returns the param in request path, eg: "/{var}" => "/a%2fb", then `var == "a/b"`
|
||||||
func (b *Base) Params(p string) string {
|
func (b *Base) Params(name string) string {
|
||||||
s, _ := url.PathUnescape(chi.URLParam(b.Req, strings.TrimPrefix(p, ":")))
|
s, err := url.PathUnescape(b.PathParamRaw(name))
|
||||||
|
if err != nil && !setting.IsProd {
|
||||||
|
panic("Failed to unescape path param: " + err.Error() + ", there seems to be a double-unescaping bug")
|
||||||
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Base) PathParamRaw(p string) string {
|
// PathParamRaw returns the raw param in request path, eg: "/{var}" => "/a%2fb", then `var == "a%2fb"`
|
||||||
return chi.URLParam(b.Req, strings.TrimPrefix(p, ":"))
|
func (b *Base) PathParamRaw(name string) string {
|
||||||
|
return chi.URLParam(b.Req, strings.TrimPrefix(name, ":"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParamsInt64 returns the param on route as int64
|
// ParamsInt64 returns the param in request path as int64
|
||||||
func (b *Base) ParamsInt64(p string) int64 {
|
func (b *Base) ParamsInt64(p string) int64 {
|
||||||
v, _ := strconv.ParseInt(b.Params(p), 10, 64)
|
v, _ := strconv.ParseInt(b.Params(p), 10, 64)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetParams set params into routes
|
// SetParams set request path params into routes
|
||||||
func (b *Base) SetParams(k, v string) {
|
func (b *Base) SetParams(k, v string) {
|
||||||
chiCtx := chi.RouteContext(b)
|
chiCtx := chi.RouteContext(b)
|
||||||
chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
|
chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
|
||||||
|
@ -1006,12 +1006,12 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||||||
if refType == RepoRefLegacy {
|
if refType == RepoRefLegacy {
|
||||||
// redirect from old URL scheme to new URL scheme
|
// redirect from old URL scheme to new URL scheme
|
||||||
prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*"))), strings.ToLower(ctx.Repo.RepoLink))
|
prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*"))), strings.ToLower(ctx.Repo.RepoLink))
|
||||||
|
redirect := path.Join(
|
||||||
ctx.Redirect(path.Join(
|
|
||||||
ctx.Repo.RepoLink,
|
ctx.Repo.RepoLink,
|
||||||
util.PathEscapeSegments(prefix),
|
util.PathEscapeSegments(prefix),
|
||||||
ctx.Repo.BranchNameSubURL(),
|
ctx.Repo.BranchNameSubURL(),
|
||||||
util.PathEscapeSegments(ctx.Repo.TreePath)))
|
util.PathEscapeSegments(ctx.Repo.TreePath))
|
||||||
|
ctx.Redirect(redirect)
|
||||||
return cancel
|
return cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
@ -14,22 +15,6 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testSrcRouteRedirect(t *testing.T, session *TestSession, user, repo, route, expectedLocation string, expectedStatus int) {
|
|
||||||
prefix := path.Join("/", user, repo, "src")
|
|
||||||
|
|
||||||
// Make request
|
|
||||||
req := NewRequest(t, "GET", path.Join(prefix, route))
|
|
||||||
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
|
||||||
|
|
||||||
// Check Location header
|
|
||||||
location := resp.Header().Get("Location")
|
|
||||||
assert.Equal(t, path.Join(prefix, expectedLocation), location)
|
|
||||||
|
|
||||||
// Perform redirect
|
|
||||||
req = NewRequest(t, "GET", location)
|
|
||||||
session.MakeRequest(t, req, expectedStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setDefaultBranch(t *testing.T, session *TestSession, user, repo, branch string) {
|
func setDefaultBranch(t *testing.T, session *TestSession, user, repo, branch string) {
|
||||||
location := path.Join("/", user, repo, "settings/branches")
|
location := path.Join("/", user, repo, "settings/branches")
|
||||||
csrf := GetCSRF(t, session, location)
|
csrf := GetCSRF(t, session, location)
|
||||||
@ -41,7 +26,7 @@ func setDefaultBranch(t *testing.T, session *TestSession, user, repo, branch str
|
|||||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNonasciiBranches(t *testing.T) {
|
func TestNonAsciiBranches(t *testing.T) {
|
||||||
testRedirects := []struct {
|
testRedirects := []struct {
|
||||||
from string
|
from string
|
||||||
to string
|
to string
|
||||||
@ -98,6 +83,7 @@ func TestNonasciiBranches(t *testing.T) {
|
|||||||
to: "branch/%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81",
|
to: "branch/%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81",
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
{
|
{
|
||||||
from: "Тэг",
|
from: "Тэг",
|
||||||
@ -119,6 +105,7 @@ func TestNonasciiBranches(t *testing.T) {
|
|||||||
to: "tag/%E3%82%BF%E3%82%B0/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md",
|
to: "tag/%E3%82%BF%E3%82%B0/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md",
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
{
|
{
|
||||||
from: "README.md",
|
from: "README.md",
|
||||||
@ -135,6 +122,7 @@ func TestNonasciiBranches(t *testing.T) {
|
|||||||
to: "branch/Plus+Is+Not+Space/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md",
|
to: "branch/Plus+Is+Not+Space/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md",
|
||||||
status: http.StatusNotFound, // it's not on default branch
|
status: http.StatusNotFound, // it's not on default branch
|
||||||
},
|
},
|
||||||
|
|
||||||
// Same but url-encoded (few tests)
|
// Same but url-encoded (few tests)
|
||||||
{
|
{
|
||||||
from: "%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81",
|
from: "%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81",
|
||||||
@ -205,10 +193,23 @@ func TestNonasciiBranches(t *testing.T) {
|
|||||||
session := loginUser(t, user)
|
session := loginUser(t, user)
|
||||||
|
|
||||||
setDefaultBranch(t, session, user, repo, "Plus+Is+Not+Space")
|
setDefaultBranch(t, session, user, repo, "Plus+Is+Not+Space")
|
||||||
|
defer setDefaultBranch(t, session, user, repo, "master")
|
||||||
|
|
||||||
for _, test := range testRedirects {
|
for _, test := range testRedirects {
|
||||||
testSrcRouteRedirect(t, session, user, repo, test.from, test.to, test.status)
|
t.Run(test.from, func(t *testing.T) {
|
||||||
}
|
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/src/%s", user, repo, test.from))
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
if resp.Code != http.StatusSeeOther {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setDefaultBranch(t, session, user, repo, "master")
|
redirectLocation := resp.Header().Get("Location")
|
||||||
|
if !assert.Equal(t, fmt.Sprintf("/%s/%s/src/%s", user, repo, test.to), redirectLocation) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", redirectLocation)
|
||||||
|
session.MakeRequest(t, req, test.status)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user