Skip to content

Commit 3cea2cb

Browse files
committed
fix
1 parent 3ee39db commit 3cea2cb

File tree

4 files changed

+72
-30
lines changed

4 files changed

+72
-30
lines changed

modules/httplib/url.go

+21-6
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,35 @@ import (
88
"strings"
99

1010
"code.gitea.io/gitea/modules/setting"
11+
"code.gitea.io/gitea/modules/util"
1112
)
1213

13-
// IsRiskyRedirectURL returns true if the URL is considered risky for redirects
14-
func IsRiskyRedirectURL(s string) bool {
14+
func detectRelativeURL(s string, u *url.URL) bool {
1515
// Unfortunately browsers consider a redirect Location with preceding "//", "\\", "/\" and "\/" as meaning redirect to "http(s)://REST_OF_PATH"
1616
// Therefore we should ignore these redirect locations to prevent open redirects
1717
if len(s) > 1 && (s[0] == '/' || s[0] == '\\') && (s[1] == '/' || s[1] == '\\') {
18-
return true
18+
return false
19+
}
20+
if u.Path != "" {
21+
u.Path = "/" + util.PathJoinRelX(u.Path)
1922
}
23+
return u != nil && u.Scheme == "" && u.Host == "" &&
24+
(u.Path == "" || strings.HasPrefix(strings.ToLower(u.Path+"/"), strings.ToLower(setting.AppSubURL+"/")))
25+
}
26+
27+
// IsRelativeURL detects if a URL is relative (no scheme or host)
28+
func IsRelativeURL(s string) bool {
29+
u, err := url.Parse(s)
30+
return err == nil && detectRelativeURL(s, u)
31+
}
2032

33+
func IsCurrentGiteaSiteURL(s string) bool {
2134
u, err := url.Parse(s)
22-
if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(s), strings.ToLower(setting.AppURL))) {
35+
if err != nil {
36+
return false
37+
}
38+
if detectRelativeURL(s, u) {
2339
return true
2440
}
25-
26-
return false
41+
return strings.HasPrefix(strings.ToLower(u.String()+"/"), strings.ToLower(setting.AppURL))
2742
}

modules/httplib/url_test.go

+49-22
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,59 @@ import (
77
"testing"
88

99
"code.gitea.io/gitea/modules/setting"
10+
"code.gitea.io/gitea/modules/test"
1011

1112
"github.com/stretchr/testify/assert"
1213
)
1314

14-
func TestIsRiskyRedirectURL(t *testing.T) {
15-
setting.AppURL = "http://localhost:3000/"
16-
tests := []struct {
17-
input string
18-
want bool
19-
}{
20-
{"", false},
21-
{"foo", false},
22-
{"/", false},
23-
{"/foo?k=%20#abc", false},
15+
func TestIsRelativeURL(t *testing.T) {
16+
rel := []string{
17+
"",
18+
"foo",
19+
"/",
20+
"/foo?k=%20#abc",
21+
}
22+
for _, s := range rel {
23+
assert.True(t, IsRelativeURL(s), "rel = %q", s)
24+
}
25+
abs := []string{
26+
"//",
27+
"\\\\",
28+
"/\\",
29+
"\\/",
30+
"mailto:a@b.com",
31+
"https://test.com",
32+
}
33+
for _, s := range abs {
34+
assert.False(t, IsRelativeURL(s), "abs = %q", s)
35+
}
36+
}
2437

25-
{"//", true},
26-
{"\\\\", true},
27-
{"/\\", true},
28-
{"\\/", true},
29-
{"mail:a@b.com", true},
30-
{"https://test.com", true},
31-
{setting.AppURL + "/foo", false},
32-
}
33-
for _, tt := range tests {
34-
t.Run(tt.input, func(t *testing.T) {
35-
assert.Equal(t, tt.want, IsRiskyRedirectURL(tt.input))
36-
})
38+
func TestIsCurrentGiteaSiteURL(t *testing.T) {
39+
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
40+
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
41+
good := []string{
42+
"?key=val",
43+
"/sub",
44+
"/sub/",
45+
"/sub/foo",
46+
"/sub/foo/",
47+
"http://localhost:3000/sub",
48+
"http://localhost:3000/sub/",
49+
}
50+
for _, s := range good {
51+
assert.True(t, IsCurrentGiteaSiteURL(s), "good = %q", s)
52+
}
53+
bad := []string{
54+
"/",
55+
"//",
56+
"\\\\",
57+
"/foo",
58+
"http://localhost:3000/sub/..",
59+
"http://localhost:3000/other",
60+
"http://other/",
61+
}
62+
for _, s := range bad {
63+
assert.False(t, IsCurrentGiteaSiteURL(s), "bad = %q", s)
3764
}
3865
}

routers/common/redirect.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func FetchRedirectDelegate(resp http.ResponseWriter, req *http.Request) {
1717
// The typical page is "issue comment" page. The backend responds "/owner/repo/issues/1#comment-2",
1818
// then frontend needs this delegate to redirect to the new location with hash correctly.
1919
redirect := req.PostFormValue("redirect")
20-
if httplib.IsRiskyRedirectURL(redirect) {
20+
if !httplib.IsCurrentGiteaSiteURL(redirect) {
2121
resp.WriteHeader(http.StatusBadRequest)
2222
return
2323
}

services/context/context_response.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (ctx *Context) RedirectToFirst(location ...string) {
5151
continue
5252
}
5353

54-
if httplib.IsRiskyRedirectURL(loc) {
54+
if !httplib.IsCurrentGiteaSiteURL(loc) {
5555
continue
5656
}
5757

0 commit comments

Comments
 (0)