Fix duplicate sub-path for avatars (#31365)
Fix #31361, and add tests And this PR introduces an undocumented & debug-purpose-only config option: `USE_SUB_URL_PATH`. It does nothing for end users, it only helps the development of sub-path related problems. And also fix #31366 Co-authored-by: @ExplodingDragon
This commit is contained in:
parent
23147494a7
commit
84cbb6c4d2
@ -81,6 +81,10 @@ RUN_USER = ; git
|
|||||||
;; Overwrite the automatically generated public URL. Necessary for proxies and docker.
|
;; Overwrite the automatically generated public URL. Necessary for proxies and docker.
|
||||||
;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||||
;;
|
;;
|
||||||
|
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
|
||||||
|
;; DO NOT USE IT IN PRODUCTION!!!
|
||||||
|
;USE_SUB_URL_PATH = false
|
||||||
|
;;
|
||||||
;; when STATIC_URL_PREFIX is empty it will follow ROOT_URL
|
;; when STATIC_URL_PREFIX is empty it will follow ROOT_URL
|
||||||
;STATIC_URL_PREFIX =
|
;STATIC_URL_PREFIX =
|
||||||
;;
|
;;
|
||||||
|
28
models/repo/avatar_test.go
Normal file
28
models/repo/avatar_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoAvatarLink(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||||
|
|
||||||
|
repo := &Repository{ID: 1, Avatar: "avatar.png"}
|
||||||
|
link := repo.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/repo-avatars/avatar.png", link)
|
||||||
|
|
||||||
|
setting.AppURL = "https://localhost/sub-path/"
|
||||||
|
setting.AppSubURL = "/sub-path"
|
||||||
|
link = repo.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/sub-path/repo-avatars/avatar.png", link)
|
||||||
|
}
|
@ -89,9 +89,11 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
|||||||
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
|
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvatarLink returns the full avatar url with http host. TODO: refactor it to a relative URL, but it is still used in API response at the moment
|
// AvatarLink returns the full avatar url with http host.
|
||||||
|
// TODO: refactor it to a relative URL, but it is still used in API response at the moment
|
||||||
func (u *User) AvatarLink(ctx context.Context) string {
|
func (u *User) AvatarLink(ctx context.Context) string {
|
||||||
return httplib.MakeAbsoluteURL(ctx, u.AvatarLinkWithSize(ctx, 0))
|
relLink := u.AvatarLinkWithSize(ctx, 0) // it can't be empty
|
||||||
|
return httplib.MakeAbsoluteURL(ctx, relLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
||||||
|
28
models/user/avatar_test.go
Normal file
28
models/user/avatar_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserAvatarLink(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||||
|
|
||||||
|
u := &User{ID: 1, Avatar: "avatar.png"}
|
||||||
|
link := u.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/avatars/avatar.png", link)
|
||||||
|
|
||||||
|
setting.AppURL = "https://localhost/sub-path/"
|
||||||
|
setting.AppSubURL = "/sub-path"
|
||||||
|
link = u.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
|
||||||
|
}
|
@ -57,11 +57,16 @@ func getForwardedHost(req *http.Request) string {
|
|||||||
return req.Header.Get("X-Forwarded-Host")
|
return req.Header.Get("X-Forwarded-Host")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GuessCurrentAppURL tries to guess the current full URL by http headers. It always has a '/' suffix, exactly the same as setting.AppURL
|
// GuessCurrentAppURL tries to guess the current full app URL (with sub-path) by http headers. It always has a '/' suffix, exactly the same as setting.AppURL
|
||||||
func GuessCurrentAppURL(ctx context.Context) string {
|
func GuessCurrentAppURL(ctx context.Context) string {
|
||||||
|
return GuessCurrentHostURL(ctx) + setting.AppSubURL + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GuessCurrentHostURL tries to guess the current full host URL (no sub-path) by http headers, there is no trailing slash.
|
||||||
|
func GuessCurrentHostURL(ctx context.Context) string {
|
||||||
req, ok := ctx.Value(RequestContextKey).(*http.Request)
|
req, ok := ctx.Value(RequestContextKey).(*http.Request)
|
||||||
if !ok {
|
if !ok {
|
||||||
return setting.AppURL
|
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
|
||||||
}
|
}
|
||||||
// If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one.
|
// If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one.
|
||||||
// At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong.
|
// At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong.
|
||||||
@ -74,20 +79,27 @@ func GuessCurrentAppURL(ctx context.Context) string {
|
|||||||
// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
|
// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
|
||||||
reqScheme := getRequestScheme(req)
|
reqScheme := getRequestScheme(req)
|
||||||
if reqScheme == "" {
|
if reqScheme == "" {
|
||||||
return setting.AppURL
|
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
|
||||||
}
|
}
|
||||||
reqHost := getForwardedHost(req)
|
reqHost := getForwardedHost(req)
|
||||||
if reqHost == "" {
|
if reqHost == "" {
|
||||||
reqHost = req.Host
|
reqHost = req.Host
|
||||||
}
|
}
|
||||||
return reqScheme + "://" + reqHost + setting.AppSubURL + "/"
|
return reqScheme + "://" + reqHost
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeAbsoluteURL(ctx context.Context, s string) string {
|
// MakeAbsoluteURL tries to make a link to an absolute URL:
|
||||||
if IsRelativeURL(s) {
|
// * If link is empty, it returns the current app URL.
|
||||||
return GuessCurrentAppURL(ctx) + strings.TrimPrefix(s, "/")
|
// * If link is absolute, it returns the link.
|
||||||
|
// * Otherwise, it returns the current host URL + link, the link itself should have correct sub-path (AppSubURL) if needed.
|
||||||
|
func MakeAbsoluteURL(ctx context.Context, link string) string {
|
||||||
|
if link == "" {
|
||||||
|
return GuessCurrentAppURL(ctx)
|
||||||
}
|
}
|
||||||
return s
|
if !IsRelativeURL(link) {
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
return GuessCurrentHostURL(ctx) + "/" + strings.TrimPrefix(link, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
|
func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
|
||||||
|
@ -46,14 +46,14 @@ func TestMakeAbsoluteURL(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
assert.Equal(t, "http://cfg-host/sub/", MakeAbsoluteURL(ctx, ""))
|
assert.Equal(t, "http://cfg-host/sub/", MakeAbsoluteURL(ctx, ""))
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "foo"))
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
|
assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||||
Host: "user-host",
|
Host: "user-host",
|
||||||
})
|
})
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||||
Host: "user-host",
|
Host: "user-host",
|
||||||
@ -61,7 +61,7 @@ func TestMakeAbsoluteURL(t *testing.T) {
|
|||||||
"X-Forwarded-Host": {"forwarded-host"},
|
"X-Forwarded-Host": {"forwarded-host"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||||
Host: "user-host",
|
Host: "user-host",
|
||||||
@ -70,7 +70,7 @@ func TestMakeAbsoluteURL(t *testing.T) {
|
|||||||
"X-Forwarded-Proto": {"https"},
|
"X-Forwarded-Proto": {"https"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "https://forwarded-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
||||||
|
18
modules/setting/global.go
Normal file
18
modules/setting/global.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
// Global settings
|
||||||
|
var (
|
||||||
|
// RunUser is the OS user that Gitea is running as. ini:"RUN_USER"
|
||||||
|
RunUser string
|
||||||
|
// RunMode is the running mode of Gitea, it only accepts two values: "dev" and "prod".
|
||||||
|
// Non-dev values will be replaced by "prod". ini: "RUN_MODE"
|
||||||
|
RunMode string
|
||||||
|
// IsProd is true if RunMode is not "dev"
|
||||||
|
IsProd bool
|
||||||
|
|
||||||
|
// AppName is the Application name, used in the page title. ini: "APP_NAME"
|
||||||
|
AppName string
|
||||||
|
)
|
@ -40,16 +40,16 @@ const (
|
|||||||
LandingPageLogin LandingPage = "/user/login"
|
LandingPageLogin LandingPage = "/user/login"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Server settings
|
||||||
var (
|
var (
|
||||||
// AppName is the Application name, used in the page title.
|
|
||||||
// It maps to ini:"APP_NAME"
|
|
||||||
AppName string
|
|
||||||
// AppURL is the Application ROOT_URL. It always has a '/' suffix
|
// AppURL is the Application ROOT_URL. It always has a '/' suffix
|
||||||
// It maps to ini:"ROOT_URL"
|
// It maps to ini:"ROOT_URL"
|
||||||
AppURL string
|
AppURL string
|
||||||
// AppSubURL represents the sub-url mounting point for gitea. It is either "" or starts with '/' and ends without '/', such as '/{subpath}'.
|
// AppSubURL represents the sub-url mounting point for gitea. It is either "" or starts with '/' and ends without '/', such as '/{subpath}'.
|
||||||
// This value is empty if site does not have sub-url.
|
// This value is empty if site does not have sub-url.
|
||||||
AppSubURL string
|
AppSubURL string
|
||||||
|
// UseSubURLPath makes Gitea handle requests with sub-path like "/sub-path/owner/repo/...", to make it easier to debug sub-path related problems without a reverse proxy.
|
||||||
|
UseSubURLPath bool
|
||||||
// AppDataPath is the default path for storing data.
|
// AppDataPath is the default path for storing data.
|
||||||
// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
|
// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
|
||||||
AppDataPath string
|
AppDataPath string
|
||||||
@ -59,8 +59,6 @@ var (
|
|||||||
// AssetVersion holds a opaque value that is used for cache-busting assets
|
// AssetVersion holds a opaque value that is used for cache-busting assets
|
||||||
AssetVersion string
|
AssetVersion string
|
||||||
|
|
||||||
// Server settings
|
|
||||||
|
|
||||||
Protocol Scheme
|
Protocol Scheme
|
||||||
UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"`
|
UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"`
|
||||||
ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
|
ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
|
||||||
@ -275,9 +273,10 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||||||
// This should be TrimRight to ensure that there is only a single '/' at the end of AppURL.
|
// This should be TrimRight to ensure that there is only a single '/' at the end of AppURL.
|
||||||
AppURL = strings.TrimRight(appURL.String(), "/") + "/"
|
AppURL = strings.TrimRight(appURL.String(), "/") + "/"
|
||||||
|
|
||||||
// Suburl should start with '/' and end without '/', such as '/{subpath}'.
|
// AppSubURL should start with '/' and end without '/', such as '/{subpath}'.
|
||||||
// This value is empty if site does not have sub-url.
|
// This value is empty if site does not have sub-url.
|
||||||
AppSubURL = strings.TrimSuffix(appURL.Path, "/")
|
AppSubURL = strings.TrimSuffix(appURL.Path, "/")
|
||||||
|
UseSubURLPath = sec.Key("USE_SUB_URL_PATH").MustBool(false)
|
||||||
StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
|
StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
|
||||||
|
|
||||||
// Check if Domain differs from AppURL domain than update it to AppURL's domain
|
// Check if Domain differs from AppURL domain than update it to AppURL's domain
|
||||||
|
@ -25,12 +25,7 @@ var (
|
|||||||
// AppStartTime store time gitea has started
|
// AppStartTime store time gitea has started
|
||||||
AppStartTime time.Time
|
AppStartTime time.Time
|
||||||
|
|
||||||
// Other global setting objects
|
|
||||||
|
|
||||||
CfgProvider ConfigProvider
|
CfgProvider ConfigProvider
|
||||||
RunMode string
|
|
||||||
RunUser string
|
|
||||||
IsProd bool
|
|
||||||
IsWindows bool
|
IsWindows bool
|
||||||
|
|
||||||
// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing
|
// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing
|
||||||
|
@ -117,7 +117,7 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
|
|||||||
|
|
||||||
func apiUnauthorizedError(ctx *context.Context) {
|
func apiUnauthorizedError(ctx *context.Context) {
|
||||||
// container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed
|
// container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed
|
||||||
realmURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), setting.AppSubURL+"/") + "/v2/token"
|
realmURL := httplib.GuessCurrentHostURL(ctx) + "/v2/token"
|
||||||
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+realmURL+`",service="container_registry",scope="*"`)
|
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+realmURL+`",service="container_registry",scope="*"`)
|
||||||
apiErrorDefined(ctx, errUnauthorized)
|
apiErrorDefined(ctx, errUnauthorized)
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
// 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
|
// first, normalize the URL path
|
||||||
handlers = append(handlers, stripSlashesMiddleware)
|
handlers = append(handlers, normalizeRequestPathMiddleware)
|
||||||
|
|
||||||
// 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,9 +75,9 @@ func ProtocolMiddlewares() (handlers []any) {
|
|||||||
return handlers
|
return handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
func stripSlashesMiddleware(next http.Handler) http.Handler {
|
func normalizeRequestPathMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
// First of all escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
|
// escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
|
||||||
req.URL.RawPath = req.URL.EscapedPath()
|
req.URL.RawPath = req.URL.EscapedPath()
|
||||||
|
|
||||||
urlPath := req.URL.RawPath
|
urlPath := req.URL.RawPath
|
||||||
@ -86,19 +86,42 @@ func stripSlashesMiddleware(next http.Handler) http.Handler {
|
|||||||
urlPath = rctx.RoutePath
|
urlPath = rctx.RoutePath
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizedPath := &strings.Builder{}
|
normalizedPath := strings.TrimRight(urlPath, "/")
|
||||||
prevWasSlash := false
|
// the following code block is a slow-path for replacing all repeated slashes "//" to one single "/"
|
||||||
for _, chr := range strings.TrimRight(urlPath, "/") {
|
// if the path doesn't have repeated slashes, then no need to execute it
|
||||||
if chr != '/' || !prevWasSlash {
|
if strings.Contains(normalizedPath, "//") {
|
||||||
sanitizedPath.WriteRune(chr)
|
buf := &strings.Builder{}
|
||||||
|
prevWasSlash := false
|
||||||
|
for _, chr := range normalizedPath {
|
||||||
|
if chr != '/' || !prevWasSlash {
|
||||||
|
buf.WriteRune(chr)
|
||||||
|
}
|
||||||
|
prevWasSlash = 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 {
|
if rctx == nil {
|
||||||
req.URL.Path = sanitizedPath.String()
|
req.URL.Path = normalizedPath
|
||||||
} else {
|
} else {
|
||||||
rctx.RoutePath = sanitizedPath.String()
|
rctx.RoutePath = normalizedPath
|
||||||
}
|
}
|
||||||
next.ServeHTTP(resp, req)
|
next.ServeHTTP(resp, req)
|
||||||
})
|
})
|
||||||
|
@ -61,7 +61,7 @@ func TestStripSlashesMiddleware(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// pass the test middleware to validate the changes
|
// pass the test middleware to validate the changes
|
||||||
handlerToTest := stripSlashesMiddleware(testMiddleware)
|
handlerToTest := normalizeRequestPathMiddleware(testMiddleware)
|
||||||
// create a mock request to use
|
// create a mock request to use
|
||||||
req := httptest.NewRequest("GET", tt.inputPath, nil)
|
req := httptest.NewRequest("GET", tt.inputPath, nil)
|
||||||
// call the handler using a mock response recorder
|
// call the handler using a mock response recorder
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
@ -56,7 +57,7 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
|
|||||||
issueReference = "!"
|
issueReference = "!"
|
||||||
}
|
}
|
||||||
|
|
||||||
reviewedOn := fmt.Sprintf("Reviewed-on: %s/%s", setting.AppURL, pr.Issue.Link())
|
reviewedOn := fmt.Sprintf("Reviewed-on: %s", httplib.MakeAbsoluteURL(ctx, pr.Issue.Link()))
|
||||||
reviewedBy := pr.GetApprovers(ctx)
|
reviewedBy := pr.GetApprovers(ctx)
|
||||||
|
|
||||||
if mergeStyle != "" {
|
if mergeStyle != "" {
|
||||||
|
Loading…
Reference in New Issue
Block a user