summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorwxiaoguang <wxiaoguang@gmail.com>2024-05-08 21:34:43 +0800
committerGitHub <noreply@github.com>2024-05-08 13:34:43 +0000
commitd4c2db39bf239d071c6ac96815efa8fd8b03ad94 (patch)
tree158b7a40af6d1637d57cdde078d94bf1d7c95404 /modules
parentd410e2acce22e5b3518a9bf64a9152b32a91fe18 (diff)
downloadgitea-d4c2db39bf239d071c6ac96815efa8fd8b03ad94.tar.gz
gitea-d4c2db39bf239d071c6ac96815efa8fd8b03ad94.zip
Refactor AppURL usage (#30885) (#30891)
Backport #30885 Fix #30883 Fix #29591 Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Diffstat (limited to 'modules')
-rw-r--r--modules/httplib/url.go60
-rw-r--r--modules/httplib/url_test.go59
-rw-r--r--modules/markup/html_codepreview.go2
3 files changed, 112 insertions, 9 deletions
diff --git a/modules/httplib/url.go b/modules/httplib/url.go
index 903799cb68..541c4f325b 100644
--- a/modules/httplib/url.go
+++ b/modules/httplib/url.go
@@ -4,6 +4,8 @@
package httplib
import (
+ "context"
+ "net/http"
"net/url"
"strings"
@@ -11,6 +13,10 @@ import (
"code.gitea.io/gitea/modules/util"
)
+type RequestContextKeyStruct struct{}
+
+var RequestContextKey = RequestContextKeyStruct{}
+
func urlIsRelative(s string, u *url.URL) bool {
// Unfortunately browsers consider a redirect Location with preceding "//", "\\", "/\" and "\/" as meaning redirect to "http(s)://REST_OF_PATH"
// Therefore we should ignore these redirect locations to prevent open redirects
@@ -26,7 +32,56 @@ func IsRelativeURL(s string) bool {
return err == nil && urlIsRelative(s, u)
}
-func IsCurrentGiteaSiteURL(s string) bool {
+func guessRequestScheme(req *http.Request, def string) string {
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
+ if s := req.Header.Get("X-Forwarded-Proto"); s != "" {
+ return s
+ }
+ if s := req.Header.Get("X-Forwarded-Protocol"); s != "" {
+ return s
+ }
+ if s := req.Header.Get("X-Url-Scheme"); s != "" {
+ return s
+ }
+ if s := req.Header.Get("Front-End-Https"); s != "" {
+ return util.Iif(s == "on", "https", "http")
+ }
+ if s := req.Header.Get("X-Forwarded-Ssl"); s != "" {
+ return util.Iif(s == "on", "https", "http")
+ }
+ return def
+}
+
+func guessForwardedHost(req *http.Request) string {
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/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
+func GuessCurrentAppURL(ctx context.Context) string {
+ req, ok := ctx.Value(RequestContextKey).(*http.Request)
+ if !ok {
+ return setting.AppURL
+ }
+ if host := guessForwardedHost(req); host != "" {
+ // if it is behind a reverse proxy, use "https" as default scheme in case the site admin forgets to set the correct forwarded-protocol headers
+ return guessRequestScheme(req, "https") + "://" + host + setting.AppSubURL + "/"
+ } else if req.Host != "" {
+ // if it is not behind a reverse proxy, use the scheme from config options, meanwhile use "https" as much as possible
+ defaultScheme := util.Iif(setting.Protocol == "http", "http", "https")
+ return guessRequestScheme(req, defaultScheme) + "://" + req.Host + setting.AppSubURL + "/"
+ }
+ return setting.AppURL
+}
+
+func MakeAbsoluteURL(ctx context.Context, s string) string {
+ if IsRelativeURL(s) {
+ return GuessCurrentAppURL(ctx) + strings.TrimPrefix(s, "/")
+ }
+ return s
+}
+
+func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
u, err := url.Parse(s)
if err != nil {
return false
@@ -45,5 +100,6 @@ func IsCurrentGiteaSiteURL(s string) bool {
if u.Path == "" {
u.Path = "/"
}
- return strings.HasPrefix(strings.ToLower(u.String()), strings.ToLower(setting.AppURL))
+ urlLower := strings.ToLower(u.String())
+ return strings.HasPrefix(urlLower, strings.ToLower(setting.AppURL)) || strings.HasPrefix(urlLower, strings.ToLower(GuessCurrentAppURL(ctx)))
}
diff --git a/modules/httplib/url_test.go b/modules/httplib/url_test.go
index 9bf09bcf2f..e021cd610d 100644
--- a/modules/httplib/url_test.go
+++ b/modules/httplib/url_test.go
@@ -4,6 +4,8 @@
package httplib
import (
+ "context"
+ "net/http"
"testing"
"code.gitea.io/gitea/modules/setting"
@@ -37,9 +39,44 @@ func TestIsRelativeURL(t *testing.T) {
}
}
+func TestMakeAbsoluteURL(t *testing.T) {
+ defer test.MockVariableValue(&setting.Protocol, "http")()
+ defer test.MockVariableValue(&setting.AppURL, "http://the-host/sub/")()
+ defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
+
+ ctx := context.Background()
+ assert.Equal(t, "http://the-host/sub/", MakeAbsoluteURL(ctx, ""))
+ assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
+ assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
+ assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
+
+ ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
+ Host: "user-host",
+ })
+ assert.Equal(t, "http://user-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
+
+ ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
+ Host: "user-host",
+ Header: map[string][]string{
+ "X-Forwarded-Host": {"forwarded-host"},
+ },
+ })
+ assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
+
+ ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
+ Host: "user-host",
+ Header: map[string][]string{
+ "X-Forwarded-Host": {"forwarded-host"},
+ "X-Forwarded-Proto": {"https"},
+ },
+ })
+ assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
+}
+
func TestIsCurrentGiteaSiteURL(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
+ ctx := context.Background()
good := []string{
"?key=val",
"/sub",
@@ -50,7 +87,7 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) {
"http://localhost:3000/sub/",
}
for _, s := range good {
- assert.True(t, IsCurrentGiteaSiteURL(s), "good = %q", s)
+ assert.True(t, IsCurrentGiteaSiteURL(ctx, s), "good = %q", s)
}
bad := []string{
".",
@@ -64,13 +101,23 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) {
"http://other/",
}
for _, s := range bad {
- assert.False(t, IsCurrentGiteaSiteURL(s), "bad = %q", s)
+ assert.False(t, IsCurrentGiteaSiteURL(ctx, s), "bad = %q", s)
}
setting.AppURL = "http://localhost:3000/"
setting.AppSubURL = ""
- assert.False(t, IsCurrentGiteaSiteURL("//"))
- assert.False(t, IsCurrentGiteaSiteURL("\\\\"))
- assert.False(t, IsCurrentGiteaSiteURL("http://localhost"))
- assert.True(t, IsCurrentGiteaSiteURL("http://localhost:3000?key=val"))
+ assert.False(t, IsCurrentGiteaSiteURL(ctx, "//"))
+ assert.False(t, IsCurrentGiteaSiteURL(ctx, "\\\\"))
+ assert.False(t, IsCurrentGiteaSiteURL(ctx, "http://localhost"))
+ assert.True(t, IsCurrentGiteaSiteURL(ctx, "http://localhost:3000?key=val"))
+
+ ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
+ Host: "user-host",
+ Header: map[string][]string{
+ "X-Forwarded-Host": {"forwarded-host"},
+ "X-Forwarded-Proto": {"https"},
+ },
+ })
+ assert.True(t, IsCurrentGiteaSiteURL(ctx, "http://localhost:3000"))
+ assert.True(t, IsCurrentGiteaSiteURL(ctx, "https://forwarded-host"))
}
diff --git a/modules/markup/html_codepreview.go b/modules/markup/html_codepreview.go
index 5ef2217e3d..5ab9290b3e 100644
--- a/modules/markup/html_codepreview.go
+++ b/modules/markup/html_codepreview.go
@@ -42,7 +42,7 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
CommitID: node.Data[m[6]:m[7]],
FilePath: node.Data[m[8]:m[9]],
}
- if !httplib.IsCurrentGiteaSiteURL(opts.FullURL) {
+ if !httplib.IsCurrentGiteaSiteURL(ctx.Ctx, opts.FullURL) {
return 0, 0, "", nil
}
u, err := url.Parse(opts.FilePath)