mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-27 00:23:41 +09:00 
			
		
		
		
	Refactor external URL detection (#29973)
Follow #29960, `IsExternalURL` is not needed anymore. Add some tests for `RedirectToCurrentSite`
This commit is contained in:
		| @@ -32,9 +32,11 @@ func IsCurrentGiteaSiteURL(s string) bool { | |||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 	if u.Path != "" { | 	if u.Path != "" { | ||||||
| 		u.Path = "/" + util.PathJoinRelX(u.Path) | 		cleanedPath := util.PathJoinRelX(u.Path) | ||||||
| 		if !strings.HasSuffix(u.Path, "/") { | 		if cleanedPath == "" || cleanedPath == "." { | ||||||
| 			u.Path += "/" | 			u.Path = "/" | ||||||
|  | 		} else { | ||||||
|  | 			u.Path += "/" + cleanedPath + "/" | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if urlIsRelative(s, u) { | 	if urlIsRelative(s, u) { | ||||||
|   | |||||||
| @@ -53,6 +53,8 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) { | |||||||
| 		assert.True(t, IsCurrentGiteaSiteURL(s), "good = %q", s) | 		assert.True(t, IsCurrentGiteaSiteURL(s), "good = %q", s) | ||||||
| 	} | 	} | ||||||
| 	bad := []string{ | 	bad := []string{ | ||||||
|  | 		".", | ||||||
|  | 		"foo", | ||||||
| 		"/", | 		"/", | ||||||
| 		"//", | 		"//", | ||||||
| 		"\\\\", | 		"\\\\", | ||||||
| @@ -67,5 +69,8 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) { | |||||||
|  |  | ||||||
| 	setting.AppURL = "http://localhost:3000/" | 	setting.AppURL = "http://localhost:3000/" | ||||||
| 	setting.AppSubURL = "" | 	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.True(t, IsCurrentGiteaSiteURL("http://localhost:3000?key=val")) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,26 +5,10 @@ package utils | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"html" | 	"html" | ||||||
| 	"net/url" |  | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // SanitizeFlashErrorString will sanitize a flash error string | // SanitizeFlashErrorString will sanitize a flash error string | ||||||
| func SanitizeFlashErrorString(x string) string { | func SanitizeFlashErrorString(x string) string { | ||||||
| 	return strings.ReplaceAll(html.EscapeString(x), "\n", "<br>") | 	return strings.ReplaceAll(html.EscapeString(x), "\n", "<br>") | ||||||
| } | } | ||||||
|  |  | ||||||
| // 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 |  | ||||||
| 	} |  | ||||||
| 	appURL, _ := url.Parse(setting.AppURL) |  | ||||||
| 	if len(parsed.Host) != 0 && strings.Replace(parsed.Host, "www.", "", 1) != strings.Replace(appURL.Host, "www.", "", 1) { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -5,47 +5,8 @@ package utils | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" |  | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestIsExternalURL(t *testing.T) { |  | ||||||
| 	setting.AppURL = "https://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"), |  | ||||||
| 		newTest(true, |  | ||||||
| 			"://missing protocol scheme"), |  | ||||||
| 	} { |  | ||||||
| 		assert.Equal(t, test.Expected, IsExternalURL(test.RawURL)) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestSanitizeFlashErrorString(t *testing.T) { | func TestSanitizeFlashErrorString(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name string | 		name string | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/auth/password" | 	"code.gitea.io/gitea/modules/auth/password" | ||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/eventsource" | 	"code.gitea.io/gitea/modules/eventsource" | ||||||
|  | 	"code.gitea.io/gitea/modules/httplib" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/optional" | 	"code.gitea.io/gitea/modules/optional" | ||||||
| 	"code.gitea.io/gitea/modules/session" | 	"code.gitea.io/gitea/modules/session" | ||||||
| @@ -25,7 +26,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	"code.gitea.io/gitea/modules/web" | 	"code.gitea.io/gitea/modules/web" | ||||||
| 	"code.gitea.io/gitea/modules/web/middleware" | 	"code.gitea.io/gitea/modules/web/middleware" | ||||||
| 	"code.gitea.io/gitea/routers/utils" |  | ||||||
| 	auth_service "code.gitea.io/gitea/services/auth" | 	auth_service "code.gitea.io/gitea/services/auth" | ||||||
| 	"code.gitea.io/gitea/services/auth/source/oauth2" | 	"code.gitea.io/gitea/services/auth/source/oauth2" | ||||||
| 	"code.gitea.io/gitea/services/context" | 	"code.gitea.io/gitea/services/context" | ||||||
| @@ -368,7 +368,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe | |||||||
| 		return setting.AppSubURL + "/" | 		return setting.AppSubURL + "/" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) { | 	if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" && httplib.IsCurrentGiteaSiteURL(redirectTo) { | ||||||
| 		middleware.DeleteRedirectToCookie(ctx.Resp) | 		middleware.DeleteRedirectToCookie(ctx.Resp) | ||||||
| 		if obeyRedirect { | 		if obeyRedirect { | ||||||
| 			ctx.RedirectToCurrentSite(redirectTo) | 			ctx.RedirectToCurrentSite(redirectTo) | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 	"code.gitea.io/gitea/modules/web" | 	"code.gitea.io/gitea/modules/web" | ||||||
| 	"code.gitea.io/gitea/modules/web/middleware" | 	"code.gitea.io/gitea/modules/web/middleware" | ||||||
| 	"code.gitea.io/gitea/routers/utils" |  | ||||||
| 	"code.gitea.io/gitea/services/context" | 	"code.gitea.io/gitea/services/context" | ||||||
| 	"code.gitea.io/gitea/services/forms" | 	"code.gitea.io/gitea/services/forms" | ||||||
| 	"code.gitea.io/gitea/services/mailer" | 	"code.gitea.io/gitea/services/mailer" | ||||||
| @@ -312,7 +311,7 @@ func MustChangePasswordPost(ctx *context.Context) { | |||||||
|  |  | ||||||
| 	log.Trace("User updated password: %s", ctx.Doer.Name) | 	log.Trace("User updated password: %s", ctx.Doer.Name) | ||||||
|  |  | ||||||
| 	if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) { | 	if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" { | ||||||
| 		middleware.DeleteRedirectToCookie(ctx.Resp) | 		middleware.DeleteRedirectToCookie(ctx.Resp) | ||||||
| 		ctx.RedirectToCurrentSite(redirectTo) | 		ctx.RedirectToCurrentSite(redirectTo) | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -6,9 +6,11 @@ package context | |||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
|  | 	"net/url" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  | 	"code.gitea.io/gitea/modules/test" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
| @@ -22,3 +24,28 @@ func TestRemoveSessionCookieHeader(t *testing.T) { | |||||||
| 	assert.Len(t, w.Header().Values("Set-Cookie"), 1) | 	assert.Len(t, w.Header().Values("Set-Cookie"), 1) | ||||||
| 	assert.Contains(t, "other=bar", w.Header().Get("Set-Cookie")) | 	assert.Contains(t, "other=bar", w.Header().Get("Set-Cookie")) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestRedirectToCurrentSite(t *testing.T) { | ||||||
|  | 	defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")() | ||||||
|  | 	defer test.MockVariableValue(&setting.AppSubURL, "/sub")() | ||||||
|  | 	cases := []struct { | ||||||
|  | 		location string | ||||||
|  | 		want     string | ||||||
|  | 	}{ | ||||||
|  | 		{"/", "/sub/"}, | ||||||
|  | 		{"http://localhost:3000/sub?k=v", "http://localhost:3000/sub?k=v"}, | ||||||
|  | 		{"http://other", "/sub/"}, | ||||||
|  | 	} | ||||||
|  | 	for _, c := range cases { | ||||||
|  | 		t.Run(c.location, func(t *testing.T) { | ||||||
|  | 			req := &http.Request{URL: &url.URL{Path: "/"}} | ||||||
|  | 			resp := httptest.NewRecorder() | ||||||
|  | 			base, baseCleanUp := NewBaseContext(resp, req) | ||||||
|  | 			defer baseCleanUp() | ||||||
|  | 			ctx := NewWebContext(base, nil, nil) | ||||||
|  | 			ctx.RedirectToCurrentSite(c.location) | ||||||
|  | 			redirect := test.RedirectURL(resp) | ||||||
|  | 			assert.Equal(t, c.want, redirect) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user