Skip to content

Commit ce8696e

Browse files
jonasfranzbkcsoft
authored andcommitted
Backport #4312 to v1.4
1 parent a3b1053 commit ce8696e

File tree

3 files changed

+125
-1
lines changed

3 files changed

+125
-1
lines changed

modules/util/util.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44

55
package util
66

7+
import (
8+
"net/url"
9+
"path"
10+
"strings"
11+
12+
"code.gitea.io/gitea/modules/log"
13+
"code.gitea.io/gitea/modules/setting"
14+
)
15+
716
// OptionalBool a boolean that can be "null"
817
type OptionalBool byte
918

@@ -47,6 +56,41 @@ func Max(a, b int) int {
4756
return a
4857
}
4958

59+
// URLJoin joins url components, like path.Join, but preserving contents
60+
func URLJoin(base string, elems ...string) string {
61+
if !strings.HasSuffix(base, "/") {
62+
base += "/"
63+
}
64+
baseURL, err := url.Parse(base)
65+
if err != nil {
66+
log.Error(4, "URLJoin: Invalid base URL %s", base)
67+
return ""
68+
}
69+
joinedPath := path.Join(elems...)
70+
argURL, err := url.Parse(joinedPath)
71+
if err != nil {
72+
log.Error(4, "URLJoin: Invalid arg %s", joinedPath)
73+
return ""
74+
}
75+
joinedURL := baseURL.ResolveReference(argURL).String()
76+
if !baseURL.IsAbs() && !strings.HasPrefix(base, "/") {
77+
return joinedURL[1:] // Removing leading '/' if needed
78+
}
79+
return joinedURL
80+
}
81+
82+
// IsExternalURL checks if rawURL points to an external URL like http://example.com
83+
func IsExternalURL(rawURL string) bool {
84+
parsed, err := url.Parse(rawURL)
85+
if err != nil {
86+
return true
87+
}
88+
if len(parsed.Host) != 0 && strings.Replace(parsed.Host, "www.", "", 1) != strings.Replace(setting.Domain, "www.", "", 1) {
89+
return true
90+
}
91+
return false
92+
}
93+
5094
// Min min of two ints
5195
func Min(a, b int) int {
5296
if a > b {

modules/util/util_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2018 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package util
6+
7+
import (
8+
"testing"
9+
10+
"code.gitea.io/gitea/modules/setting"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestURLJoin(t *testing.T) {
16+
type test struct {
17+
Expected string
18+
Base string
19+
Elements []string
20+
}
21+
newTest := func(expected, base string, elements ...string) test {
22+
return test{Expected: expected, Base: base, Elements: elements}
23+
}
24+
for _, test := range []test{
25+
newTest("https://try.gitea.io/a/b/c",
26+
"https://try.gitea.io", "a/b", "c"),
27+
newTest("https://try.gitea.io/a/b/c",
28+
"https://try.gitea.io/", "/a/b/", "/c/"),
29+
newTest("https://try.gitea.io/a/c",
30+
"https://try.gitea.io/", "/a/./b/", "../c/"),
31+
newTest("a/b/c",
32+
"a", "b/c/"),
33+
newTest("a/b/d",
34+
"a/", "b/c/", "/../d/"),
35+
newTest("https://try.gitea.io/a/b/c#d",
36+
"https://try.gitea.io", "a/b", "c#d"),
37+
newTest("/a/b/d",
38+
"/a/", "b/c/", "/../d/"),
39+
newTest("/a/b/c",
40+
"/a", "b/c/"),
41+
newTest("/a/b/c#hash",
42+
"/a", "b/c#hash"),
43+
} {
44+
assert.Equal(t, test.Expected, URLJoin(test.Base, test.Elements...))
45+
}
46+
}
47+
48+
func TestIsExternalURL(t *testing.T) {
49+
setting.Domain = "try.gitea.io"
50+
type test struct {
51+
Expected bool
52+
RawURL string
53+
}
54+
newTest := func(expected bool, rawURL string) test {
55+
return test{Expected: expected, RawURL: rawURL}
56+
}
57+
for _, test := range []test{
58+
newTest(false,
59+
"https://try.gitea.io"),
60+
newTest(true,
61+
"https://example.com/"),
62+
newTest(true,
63+
"//example.com"),
64+
newTest(true,
65+
"http://example.com"),
66+
newTest(false,
67+
"a/"),
68+
newTest(false,
69+
"https://try.gitea.io/test?param=false"),
70+
newTest(false,
71+
"test?param=false"),
72+
newTest(false,
73+
"//try.gitea.io/test?param=false"),
74+
newTest(false,
75+
"/hey/hey/hey#3244"),
76+
} {
77+
assert.Equal(t, test.Expected, IsExternalURL(test.RawURL))
78+
}
79+
}

routers/user/auth.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"code.gitea.io/gitea/modules/context"
1919
"code.gitea.io/gitea/modules/log"
2020
"code.gitea.io/gitea/modules/setting"
21+
"code.gitea.io/gitea/modules/util"
2122

2223
"github.com/go-macaron/captcha"
2324
"github.com/markbates/goth"
@@ -343,7 +344,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
343344
return
344345
}
345346

346-
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
347+
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) {
347348
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL)
348349
if obeyRedirect {
349350
ctx.RedirectToFirst(redirectTo)

0 commit comments

Comments
 (0)