Skip to content

Commit 10c27df

Browse files
committed
fix
1 parent 3ee39db commit 10c27df

File tree

4 files changed

+84
-31
lines changed

4 files changed

+84
-31
lines changed

modules/httplib/url.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,40 @@ 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 isRelativeURL(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
1919
}
20+
return u != nil && u.Scheme == "" && u.Host == ""
21+
}
2022

23+
// IsRelativeURL detects if a URL is relative (no scheme or host)
24+
func IsRelativeURL(s string) bool {
2125
u, err := url.Parse(s)
22-
if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(s), strings.ToLower(setting.AppURL))) {
23-
return true
24-
}
26+
return err == nil && isRelativeURL(s, u)
27+
}
2528

26-
return false
29+
func IsCurrentGiteaSiteURL(s string) bool {
30+
u, err := url.Parse(s)
31+
if err != nil {
32+
return false
33+
}
34+
if u.Path != "" {
35+
u.Path = "/" + util.PathJoinRelX(u.Path)
36+
if !strings.HasSuffix(u.Path, "/") {
37+
u.Path += "/"
38+
}
39+
}
40+
if isRelativeURL(s, u) {
41+
return u.Path == "" || strings.HasPrefix(strings.ToLower(u.Path), strings.ToLower(setting.AppSubURL+"/"))
42+
}
43+
if u.Path == "" {
44+
u.Path = "/"
45+
}
46+
return strings.HasPrefix(strings.ToLower(u.String()), strings.ToLower(setting.AppURL))
2747
}

modules/httplib/url_test.go

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,65 @@ 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+
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
17+
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
18+
rel := []string{
19+
"",
20+
"foo",
21+
"/",
22+
"/foo?k=%20#abc",
23+
}
24+
for _, s := range rel {
25+
assert.True(t, IsRelativeURL(s), "rel = %q", s)
26+
}
27+
abs := []string{
28+
"//",
29+
"\\\\",
30+
"/\\",
31+
"\\/",
32+
33+
"https://test.com",
34+
}
35+
for _, s := range abs {
36+
assert.False(t, IsRelativeURL(s), "abs = %q", s)
37+
}
38+
}
2439

25-
{"//", true},
26-
{"\\\\", true},
27-
{"/\\", true},
28-
{"\\/", true},
29-
{"mail:[email protected]", 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-
})
40+
func TestIsCurrentGiteaSiteURL(t *testing.T) {
41+
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
42+
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
43+
good := []string{
44+
"?key=val",
45+
"/sub",
46+
"/sub/",
47+
"/sub/foo",
48+
"/sub/foo/",
49+
"http://localhost:3000/sub?key=val",
50+
"http://localhost:3000/sub/",
3751
}
52+
for _, s := range good {
53+
assert.True(t, IsCurrentGiteaSiteURL(s), "good = %q", s)
54+
}
55+
bad := []string{
56+
"/",
57+
"//",
58+
"\\\\",
59+
"/foo",
60+
"http://localhost:3000/sub/..",
61+
"http://localhost:3000/other",
62+
"http://other/",
63+
}
64+
for _, s := range bad {
65+
assert.False(t, IsCurrentGiteaSiteURL(s), "bad = %q", s)
66+
}
67+
68+
setting.AppURL = "http://localhost:3000/"
69+
setting.AppSubURL = ""
70+
assert.True(t, IsCurrentGiteaSiteURL("http://localhost:3000?key=val"))
3871
}

routers/common/redirect.go

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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)