From 459a2656bfb557a54d334c5650d98cc2087e2076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20=22BKC=22=20Carlb=C3=A4cker?= Date: Tue, 26 Jun 2018 23:55:00 +0200 Subject: [PATCH] Backport #4312 to v1.4 (#4320) --- modules/util/util.go | 44 ++++++++++++++++++++++ modules/util/util_test.go | 79 +++++++++++++++++++++++++++++++++++++++ routers/user/auth.go | 3 +- 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 modules/util/util_test.go diff --git a/modules/util/util.go b/modules/util/util.go index e99f951f2..5dcbe448f 100644 --- a/modules/util/util.go +++ b/modules/util/util.go @@ -4,6 +4,15 @@ package util +import ( + "net/url" + "path" + "strings" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + // OptionalBool a boolean that can be "null" type OptionalBool byte @@ -47,6 +56,41 @@ func Max(a, b int) int { return a } +// URLJoin joins url components, like path.Join, but preserving contents +func URLJoin(base string, elems ...string) string { + if !strings.HasSuffix(base, "/") { + base += "/" + } + baseURL, err := url.Parse(base) + if err != nil { + log.Error(4, "URLJoin: Invalid base URL %s", base) + return "" + } + joinedPath := path.Join(elems...) + argURL, err := url.Parse(joinedPath) + if err != nil { + log.Error(4, "URLJoin: Invalid arg %s", joinedPath) + return "" + } + joinedURL := baseURL.ResolveReference(argURL).String() + if !baseURL.IsAbs() && !strings.HasPrefix(base, "/") { + return joinedURL[1:] // Removing leading '/' if needed + } + return joinedURL +} + +// IsExternalURL checks if rawURL points to an external URL like http://example.com +func IsExternalURL(rawURL string) bool { + parsed, err := url.Parse(rawURL) + if err != nil { + return true + } + if len(parsed.Host) != 0 && strings.Replace(parsed.Host, "www.", "", 1) != strings.Replace(setting.Domain, "www.", "", 1) { + return true + } + return false +} + // Min min of two ints func Min(a, b int) int { if a > b { diff --git a/modules/util/util_test.go b/modules/util/util_test.go new file mode 100644 index 000000000..d9357ffa3 --- /dev/null +++ b/modules/util/util_test.go @@ -0,0 +1,79 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package util + +import ( + "testing" + + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +func TestURLJoin(t *testing.T) { + type test struct { + Expected string + Base string + Elements []string + } + newTest := func(expected, base string, elements ...string) test { + return test{Expected: expected, Base: base, Elements: elements} + } + for _, test := range []test{ + newTest("https://try.gitea.io/a/b/c", + "https://try.gitea.io", "a/b", "c"), + newTest("https://try.gitea.io/a/b/c", + "https://try.gitea.io/", "/a/b/", "/c/"), + newTest("https://try.gitea.io/a/c", + "https://try.gitea.io/", "/a/./b/", "../c/"), + newTest("a/b/c", + "a", "b/c/"), + newTest("a/b/d", + "a/", "b/c/", "/../d/"), + newTest("https://try.gitea.io/a/b/c#d", + "https://try.gitea.io", "a/b", "c#d"), + newTest("/a/b/d", + "/a/", "b/c/", "/../d/"), + newTest("/a/b/c", + "/a", "b/c/"), + newTest("/a/b/c#hash", + "/a", "b/c#hash"), + } { + assert.Equal(t, test.Expected, URLJoin(test.Base, test.Elements...)) + } +} + +func TestIsExternalURL(t *testing.T) { + setting.Domain = "try.gitea.io" + type test struct { + Expected bool + RawURL string + } + newTest := func(expected bool, rawURL string) test { + return test{Expected: expected, RawURL: rawURL} + } + for _, test := range []test{ + newTest(false, + "https://try.gitea.io"), + newTest(true, + "https://example.com/"), + newTest(true, + "//example.com"), + newTest(true, + "http://example.com"), + newTest(false, + "a/"), + newTest(false, + "https://try.gitea.io/test?param=false"), + newTest(false, + "test?param=false"), + newTest(false, + "//try.gitea.io/test?param=false"), + newTest(false, + "/hey/hey/hey#3244"), + } { + assert.Equal(t, test.Expected, IsExternalURL(test.RawURL)) + } +} diff --git a/routers/user/auth.go b/routers/user/auth.go index d44939f50..8e4aca7cd 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/go-macaron/captcha" "github.com/markbates/goth" @@ -343,7 +344,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR return } - if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 { + if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) { ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL) if obeyRedirect { ctx.RedirectToFirst(redirectTo)