mirror of
https://github.com/go-gitea/gitea
synced 2025-07-15 23:17:19 +00:00
Make public URL generation configurable (#34250)
Follow up #32564 Co-authored-by: Jannis Pohl <838818+jannispl@users.noreply.github.com> Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
This commit is contained in:
@@ -53,30 +53,31 @@ func getRequestScheme(req *http.Request) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 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
|
||||
// GuessCurrentAppURL tries to guess the current full public URL (with sub-path) by http headers. It always has a '/' suffix, exactly the same as setting.AppURL
|
||||
// TODO: should rename it to GuessCurrentPublicURL in the future
|
||||
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)
|
||||
if !ok {
|
||||
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
|
||||
}
|
||||
// If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one.
|
||||
// Try the best guess to get the current host URL (will be used for public URL) by http headers.
|
||||
// At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong.
|
||||
// There are some cases:
|
||||
// 1. The reverse proxy is configured correctly, it passes "X-Forwarded-Proto/Host" headers. Perfect, Gitea can handle it correctly.
|
||||
// 2. The reverse proxy is not configured correctly, doesn't pass "X-Forwarded-Proto/Host" headers, eg: only one "proxy_pass http://gitea:3000" in Nginx.
|
||||
// 3. There is no reverse proxy.
|
||||
// Without more information, Gitea is impossible to distinguish between case 2 and case 3, then case 2 would result in
|
||||
// wrong guess like guessed AppURL becomes "http://gitea:3000/" behind a "https" reverse proxy, which is not accessible by end users.
|
||||
// So we introduced "UseHostHeader" option, it could be enabled by setting "ROOT_URL" to empty
|
||||
// wrong guess like guessed public URL becomes "http://gitea:3000/" behind a "https" reverse proxy, which is not accessible by end users.
|
||||
// So we introduced "PUBLIC_URL_DETECTION" option, to control the guessing behavior to satisfy different use cases.
|
||||
req, ok := ctx.Value(RequestContextKey).(*http.Request)
|
||||
if !ok {
|
||||
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
|
||||
}
|
||||
reqScheme := getRequestScheme(req)
|
||||
if reqScheme == "" {
|
||||
// if no reverse proxy header, try to use "Host" header for absolute URL
|
||||
if setting.UseHostHeader && req.Host != "" {
|
||||
if setting.PublicURLDetection == setting.PublicURLAuto && req.Host != "" {
|
||||
return util.Iif(req.TLS == nil, "http://", "https://") + req.Host
|
||||
}
|
||||
// fall back to default AppURL
|
||||
@@ -93,8 +94,8 @@ func GuessCurrentHostDomain(ctx context.Context) string {
|
||||
return util.IfZero(domain, host)
|
||||
}
|
||||
|
||||
// MakeAbsoluteURL tries to make a link to an absolute URL:
|
||||
// * If link is empty, it returns the current app URL.
|
||||
// MakeAbsoluteURL tries to make a link to an absolute public URL:
|
||||
// * If link is empty, it returns the current public URL.
|
||||
// * 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 {
|
||||
|
@@ -43,20 +43,37 @@ func TestIsRelativeURL(t *testing.T) {
|
||||
func TestGuessCurrentHostURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||
defer test.MockVariableValue(&setting.UseHostHeader, false)()
|
||||
headersWithProto := http.Header{"X-Forwarded-Proto": {"https"}}
|
||||
|
||||
ctx := t.Context()
|
||||
assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx))
|
||||
t.Run("Legacy", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.PublicURLDetection, setting.PublicURLLegacy)()
|
||||
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{Host: "localhost:3000"})
|
||||
assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx))
|
||||
assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(t.Context()))
|
||||
|
||||
defer test.MockVariableValue(&setting.UseHostHeader, true)()
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{Host: "http-host:3000"})
|
||||
assert.Equal(t, "http://http-host:3000", GuessCurrentHostURL(ctx))
|
||||
// legacy: "Host" is not used when there is no "X-Forwarded-Proto" header
|
||||
ctx := context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000"})
|
||||
assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx))
|
||||
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{Host: "http-host", TLS: &tls.ConnectionState{}})
|
||||
assert.Equal(t, "https://http-host", GuessCurrentHostURL(ctx))
|
||||
// if "X-Forwarded-Proto" exists, then use it and "Host" header
|
||||
ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: headersWithProto})
|
||||
assert.Equal(t, "https://req-host:3000", GuessCurrentHostURL(ctx))
|
||||
})
|
||||
|
||||
t.Run("Auto", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.PublicURLDetection, setting.PublicURLAuto)()
|
||||
|
||||
assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(t.Context()))
|
||||
|
||||
// auto: always use "Host" header, the scheme is determined by "X-Forwarded-Proto" header, or TLS config if no "X-Forwarded-Proto" header
|
||||
ctx := context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000"})
|
||||
assert.Equal(t, "http://req-host:3000", GuessCurrentHostURL(ctx))
|
||||
|
||||
ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host", TLS: &tls.ConnectionState{}})
|
||||
assert.Equal(t, "https://req-host", GuessCurrentHostURL(ctx))
|
||||
|
||||
ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: headersWithProto})
|
||||
assert.Equal(t, "https://req-host:3000", GuessCurrentHostURL(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMakeAbsoluteURL(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user