From af5b1503ec131d750778618ffcb50618fe6e9ce7 Mon Sep 17 00:00:00 2001 From: jannispl <838818+jannispl@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:57:34 +0100 Subject: [PATCH] Add config option "USE_HOST_HEADER" Resolves #32554 This option can be set to make Gitea always use the "Host" request header for construction of absolute URLs. --- modules/httplib/url.go | 13 ++++++++++--- modules/httplib/url_test.go | 27 +++++++++++++++++++++++++++ modules/setting/server.go | 5 +++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/modules/httplib/url.go b/modules/httplib/url.go index e3bad1e5fb..155fa59901 100644 --- a/modules/httplib/url.go +++ b/modules/httplib/url.go @@ -69,11 +69,18 @@ func GuessCurrentHostURL(ctx context.Context) string { // 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 an extra config option, 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/", which is not accessible by end users. - // So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL. + // With the "USE_HOST_HEADER" config option disabled (default), Gitea is impossible to distinguish between case 2 and case 3, + // When enabling "USE_HOST_HEADER", any reverse proxies must be configured to properly pass "X-Forwarded-Proto/Host" headers, + // otherwise this would result in wrong guess like guessed AppURL becomes "http://gitea:3000/", which is not accessible by end users. reqScheme := getRequestScheme(req) if reqScheme == "" { + if setting.UseHostHeader && req.Host != "" { + if req.TLS != nil { + return "https://" + req.Host + } + return "http://" + req.Host + } + return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/") } // X-Forwarded-Host has many problems: non-standard, not well-defined (X-Forwarded-Port or not), conflicts with Host header. diff --git a/modules/httplib/url_test.go b/modules/httplib/url_test.go index fc6c91cd3a..3d19a507df 100644 --- a/modules/httplib/url_test.go +++ b/modules/httplib/url_test.go @@ -5,6 +5,7 @@ package httplib import ( "context" + "crypto/tls" "net/http" "testing" @@ -39,6 +40,32 @@ 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")() + + ctx := context.Background() + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx)) + + ctx = context.WithValue(ctx, RequestContextKey, &http.Request{ + Host: "localhost:3000", + }) + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx)) + + defer test.MockVariableValue(&setting.UseHostHeader, true)() + + ctx = context.WithValue(ctx, RequestContextKey, &http.Request{ + Host: "localhost:3000", + }) + assert.Equal(t, "http://localhost:3000", GuessCurrentHostURL(ctx)) + + ctx = context.WithValue(ctx, RequestContextKey, &http.Request{ + Host: "localhost", + TLS: &tls.ConnectionState{}, + }) + assert.Equal(t, "https://localhost", GuessCurrentHostURL(ctx)) +} + func TestMakeAbsoluteURL(t *testing.T) { defer test.MockVariableValue(&setting.Protocol, "http")() defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")() diff --git a/modules/setting/server.go b/modules/setting/server.go index d7a71578d4..66635ce25f 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -50,6 +50,10 @@ var ( 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 + // UseHostHeader makes Gitea always use the "Host" request header for construction of absolute URLs. + // This requires any reverse proxy to properly pass headers like "X-Forwarded-Proto" and "Host". + // It maps to ini:"USE_HOST_HEADER" in [server] and defaults to false + UseHostHeader bool // AppDataPath is the default path for storing data. // It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data" AppDataPath string @@ -277,6 +281,7 @@ func loadServerFrom(rootCfg ConfigProvider) { // This value is empty if site does not have sub-url. AppSubURL = strings.TrimSuffix(appURL.Path, "/") UseSubURLPath = sec.Key("USE_SUB_URL_PATH").MustBool(false) + UseHostHeader = sec.Key("USE_HOST_HEADER").MustBool(false) StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/") // Check if Domain differs from AppURL domain than update it to AppURL's domain