loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[PORT] drop utils.IsExternalURL (and expand IsRiskyRedirectURL tests) (#3167)

Related to #2773
Related to Refactor URL detection [gitea#29960](https://github.com/go-gitea/gitea/pull/29960)
Related to Refactor external URL detection [gitea#29973](https://github.com/go-gitea/gitea/pull/29973)

I added a bunch of tests to `httplib.TestIsRiskyRedirectURL` and some cases should be better handled (however it is not an easy task).

I also ported the removal of `utils.IsExternalURL`, since it prevents duplicated (subtle) code.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3167
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: oliverpool <git@olivier.pfad.fr>
Co-committed-by: oliverpool <git@olivier.pfad.fr>

authored by

oliverpool
oliverpool
and committed by
Earl Warren
16879b07 20c0292b

+103 -74
+87 -2
modules/httplib/url_test.go
··· 7 7 "testing" 8 8 9 9 "code.gitea.io/gitea/modules/setting" 10 + "code.gitea.io/gitea/modules/test" 10 11 11 12 "github.com/stretchr/testify/assert" 12 13 ) 13 14 14 15 func TestIsRiskyRedirectURL(t *testing.T) { 15 - setting.AppURL = "http://localhost:3000/" 16 + defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")() 17 + defer test.MockVariableValue(&setting.AppSubURL, "/sub")() 18 + 16 19 tests := []struct { 17 20 input string 18 21 want bool 19 22 }{ 20 23 {"", false}, 21 24 {"foo", false}, 25 + {"./", false}, 26 + {"?key=val", false}, 27 + {"/sub/", false}, 28 + {"http://localhost:3000/sub/", false}, 29 + {"/sub/foo", false}, 30 + {"http://localhost:3000/sub/foo", false}, 31 + {"http://localhost:3000/sub/test?param=false", false}, 32 + // FIXME: should probably be true (would requires resolving references using setting.appURL.ResolveReference(u)) 33 + {"/sub/../", false}, 34 + {"http://localhost:3000/sub/../", false}, 35 + {"/sUb/", false}, 36 + {"http://localhost:3000/sUb/foo", false}, 37 + {"/sub", false}, 38 + {"/foo?k=%20#abc", false}, 22 39 {"/", false}, 40 + {"a/", false}, 41 + {"test?param=false", false}, 42 + {"/hey/hey/hey#3244", false}, 43 + 44 + {"//", true}, 45 + {"\\\\", true}, 46 + {"/\\", true}, 47 + {"\\/", true}, 48 + {"mail:a@b.com", true}, 49 + {"https://test.com", true}, 50 + {"http://localhost:3000/foo", true}, 51 + {"http://localhost:3000/sub", true}, 52 + {"http://localhost:3000/sub?key=val", true}, 53 + {"https://example.com/", true}, 54 + {"//example.com", true}, 55 + {"http://example.com", true}, 56 + {"http://localhost:3000/test?param=false", true}, 57 + {"//localhost:3000/test?param=false", true}, 58 + {"://missing protocol scheme", true}, 59 + // FIXME: should probably be false 60 + {"//localhost:3000/sub/test?param=false", true}, 61 + } 62 + for _, tt := range tests { 63 + t.Run(tt.input, func(t *testing.T) { 64 + assert.Equal(t, tt.want, IsRiskyRedirectURL(tt.input)) 65 + }) 66 + } 67 + } 68 + 69 + func TestIsRiskyRedirectURLWithoutSubURL(t *testing.T) { 70 + defer test.MockVariableValue(&setting.AppURL, "https://next.forgejo.org/")() 71 + defer test.MockVariableValue(&setting.AppSubURL, "")() 72 + 73 + tests := []struct { 74 + input string 75 + want bool 76 + }{ 77 + {"", false}, 78 + {"foo", false}, 79 + {"./", false}, 80 + {"?key=val", false}, 81 + {"/sub/", false}, 82 + {"https://next.forgejo.org/sub/", false}, 83 + {"/sub/foo", false}, 84 + {"https://next.forgejo.org/sub/foo", false}, 85 + {"https://next.forgejo.org/sub/test?param=false", false}, 86 + {"https://next.forgejo.org/sub/../", false}, 87 + {"/sub/../", false}, 88 + {"/sUb/", false}, 89 + {"https://next.forgejo.org/sUb/foo", false}, 90 + {"/sub", false}, 23 91 {"/foo?k=%20#abc", false}, 92 + {"/", false}, 93 + {"a/", false}, 94 + {"test?param=false", false}, 95 + {"/hey/hey/hey#3244", false}, 96 + {"https://next.forgejo.org/test?param=false", false}, 97 + {"https://next.forgejo.org/foo", false}, 98 + {"https://next.forgejo.org/sub", false}, 99 + {"https://next.forgejo.org/sub?key=val", false}, 24 100 25 101 {"//", true}, 26 102 {"\\\\", true}, ··· 28 104 {"\\/", true}, 29 105 {"mail:a@b.com", true}, 30 106 {"https://test.com", true}, 31 - {setting.AppURL + "/foo", false}, 107 + {"https://example.com/", true}, 108 + {"//example.com", true}, 109 + {"http://example.com", true}, 110 + {"://missing protocol scheme", true}, 111 + {"https://forgejo.org", true}, 112 + {"https://example.org?url=https://next.forgejo.org", true}, 113 + // FIXME: should probably be false 114 + {"https://next.forgejo.org", true}, 115 + {"//next.forgejo.org/test?param=false", true}, 116 + {"//next.forgejo.org/sub/test?param=false", true}, 32 117 } 33 118 for _, tt := range tests { 34 119 t.Run(tt.input, func(t *testing.T) {
-16
routers/utils/utils.go
··· 5 5 6 6 import ( 7 7 "html" 8 - "net/url" 9 8 "strings" 10 - 11 - "code.gitea.io/gitea/modules/setting" 12 9 ) 13 10 14 11 // SanitizeFlashErrorString will sanitize a flash error string 15 12 func SanitizeFlashErrorString(x string) string { 16 13 return strings.ReplaceAll(html.EscapeString(x), "\n", "<br>") 17 14 } 18 - 19 - // IsExternalURL checks if rawURL points to an external URL like http://example.com 20 - func IsExternalURL(rawURL string) bool { 21 - parsed, err := url.Parse(rawURL) 22 - if err != nil { 23 - return true 24 - } 25 - appURL, _ := url.Parse(setting.AppURL) 26 - if len(parsed.Host) != 0 && strings.Replace(parsed.Host, "www.", "", 1) != strings.Replace(appURL.Host, "www.", "", 1) { 27 - return true 28 - } 29 - return false 30 - }
-39
routers/utils/utils_test.go
··· 5 5 6 6 import ( 7 7 "testing" 8 - 9 - "code.gitea.io/gitea/modules/setting" 10 - 11 - "github.com/stretchr/testify/assert" 12 8 ) 13 - 14 - func TestIsExternalURL(t *testing.T) { 15 - setting.AppURL = "https://try.gitea.io/" 16 - type test struct { 17 - Expected bool 18 - RawURL string 19 - } 20 - newTest := func(expected bool, rawURL string) test { 21 - return test{Expected: expected, RawURL: rawURL} 22 - } 23 - for _, test := range []test{ 24 - newTest(false, 25 - "https://try.gitea.io"), 26 - newTest(true, 27 - "https://example.com/"), 28 - newTest(true, 29 - "//example.com"), 30 - newTest(true, 31 - "http://example.com"), 32 - newTest(false, 33 - "a/"), 34 - newTest(false, 35 - "https://try.gitea.io/test?param=false"), 36 - newTest(false, 37 - "test?param=false"), 38 - newTest(false, 39 - "//try.gitea.io/test?param=false"), 40 - newTest(false, 41 - "/hey/hey/hey#3244"), 42 - newTest(true, 43 - "://missing protocol scheme"), 44 - } { 45 - assert.Equal(t, test.Expected, IsExternalURL(test.RawURL)) 46 - } 47 - } 48 9 49 10 func TestSanitizeFlashErrorString(t *testing.T) { 50 11 tests := []struct {
+7 -8
routers/web/auth/auth.go
··· 18 18 "code.gitea.io/gitea/modules/auth/password" 19 19 "code.gitea.io/gitea/modules/base" 20 20 "code.gitea.io/gitea/modules/eventsource" 21 + "code.gitea.io/gitea/modules/httplib" 21 22 "code.gitea.io/gitea/modules/log" 22 23 "code.gitea.io/gitea/modules/optional" 23 24 "code.gitea.io/gitea/modules/session" ··· 26 27 "code.gitea.io/gitea/modules/util" 27 28 "code.gitea.io/gitea/modules/web" 28 29 "code.gitea.io/gitea/modules/web/middleware" 29 - "code.gitea.io/gitea/routers/utils" 30 30 auth_service "code.gitea.io/gitea/services/auth" 31 31 "code.gitea.io/gitea/services/auth/source/oauth2" 32 32 "code.gitea.io/gitea/services/context" ··· 372 372 return setting.AppSubURL + "/" 373 373 } 374 374 375 - if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) { 375 + redirectTo := ctx.GetSiteCookie("redirect_to") 376 + if redirectTo != "" { 376 377 middleware.DeleteRedirectToCookie(ctx.Resp) 377 - if obeyRedirect { 378 - ctx.RedirectToFirst(redirectTo) 379 - } 380 - return redirectTo 381 378 } 382 - 383 379 if obeyRedirect { 384 - ctx.Redirect(setting.AppSubURL + "/") 380 + return ctx.RedirectToFirst(redirectTo) 381 + } 382 + if !httplib.IsRiskyRedirectURL(redirectTo) { 383 + return redirectTo 385 384 } 386 385 return setting.AppSubURL + "/" 387 386 }
+3 -6
routers/web/auth/password.go
··· 18 18 "code.gitea.io/gitea/modules/timeutil" 19 19 "code.gitea.io/gitea/modules/web" 20 20 "code.gitea.io/gitea/modules/web/middleware" 21 - "code.gitea.io/gitea/routers/utils" 22 21 "code.gitea.io/gitea/services/context" 23 22 "code.gitea.io/gitea/services/forms" 24 23 "code.gitea.io/gitea/services/mailer" ··· 312 311 313 312 log.Trace("User updated password: %s", ctx.Doer.Name) 314 313 315 - if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) { 314 + redirectTo := ctx.GetSiteCookie("redirect_to") 315 + if redirectTo != "" { 316 316 middleware.DeleteRedirectToCookie(ctx.Resp) 317 - ctx.RedirectToFirst(redirectTo) 318 - return 319 317 } 320 - 321 - ctx.Redirect(setting.AppSubURL + "/") 318 + ctx.RedirectToFirst(redirectTo) 322 319 }
+6 -3
services/context/context_response.go
··· 44 44 ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect) 45 45 } 46 46 47 - // RedirectToFirst redirects to first not empty URL 48 - func (ctx *Context) RedirectToFirst(location ...string) { 47 + // RedirectToFirst redirects to first not empty URL which likely belongs to current site. 48 + // If no suitable redirection is found, it redirects to the home. 49 + // It returns the location it redirected to. 50 + func (ctx *Context) RedirectToFirst(location ...string) string { 49 51 for _, loc := range location { 50 52 if len(loc) == 0 { 51 53 continue ··· 56 58 } 57 59 58 60 ctx.Redirect(loc) 59 - return 61 + return loc 60 62 } 61 63 62 64 ctx.Redirect(setting.AppSubURL + "/") 65 + return setting.AppSubURL + "/" 63 66 } 64 67 65 68 const tplStatus500 base.TplName = "status/500"