From 699eb41e7cf30ae89560562b180f139f1989457e Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 14 Apr 2026 21:11:08 +0800 Subject: [PATCH] Add test for "fetch redirect", add CSS value validation for external render (#37207) By the way, fix the checkAppUrl message for #37212 --- routers/common/redirect.go | 5 ++- routers/common/redirect_test.go | 48 +++++++++++++++++++++++ web_src/js/external-render-helper.test.ts | 25 ++++++++++++ web_src/js/external-render-helper.ts | 17 +++++++- web_src/js/features/common-page.ts | 4 +- web_src/js/globals.d.ts | 9 +++++ web_src/js/vitest.setup.ts | 2 + 7 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 routers/common/redirect_test.go create mode 100644 web_src/js/external-render-helper.test.ts diff --git a/routers/common/redirect.go b/routers/common/redirect.go index 2e1f315f2d8..e9f14a6eb1e 100644 --- a/routers/common/redirect.go +++ b/routers/common/redirect.go @@ -18,9 +18,10 @@ func FetchRedirectDelegate(resp http.ResponseWriter, req *http.Request) { // then frontend needs this delegate to redirect to the new location with hash correctly. redirect := req.FormValue("redirect") if req.Method != http.MethodPost || !httplib.IsCurrentGiteaSiteURL(req.Context(), redirect) { - resp.WriteHeader(http.StatusBadRequest) + http.Error(resp, "Bad Request", http.StatusBadRequest) return } - resp.Header().Add("Location", redirect) + // no OpenRedirect, the "redirect" is validated by "IsCurrentGiteaSiteURL" above + resp.Header().Set("Location", redirect) resp.WriteHeader(http.StatusSeeOther) } diff --git a/routers/common/redirect_test.go b/routers/common/redirect_test.go new file mode 100644 index 00000000000..c4e6d2e6c75 --- /dev/null +++ b/routers/common/redirect_test.go @@ -0,0 +1,48 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +func TestFetchRedirectDelegate(t *testing.T) { + defer test.MockVariableValue(&setting.AppURL, "https://gitea/")() + + cases := []struct { + method string + input string + status int + }{ + {method: "POST", input: "/foo?k=v", status: http.StatusSeeOther}, + {method: "GET", input: "/foo?k=v", status: http.StatusBadRequest}, + {method: "POST", input: `\/foo?k=v`, status: http.StatusBadRequest}, + {method: "POST", input: `\\/foo?k=v`, status: http.StatusBadRequest}, + {method: "POST", input: "https://gitea/xxx", status: http.StatusSeeOther}, + {method: "POST", input: "https://other/xxx", status: http.StatusBadRequest}, + } + for _, c := range cases { + t.Run(c.method+" "+c.input, func(t *testing.T) { + resp := httptest.NewRecorder() + req := httptest.NewRequest(c.method, "/?redirect="+url.QueryEscape(c.input), nil) + FetchRedirectDelegate(resp, req) + assert.Equal(t, c.status, resp.Code) + if c.status == http.StatusSeeOther { + assert.Equal(t, c.input, resp.Header().Get("Location")) + } else { + assert.Empty(t, resp.Header().Get("Location")) + assert.Equal(t, "Bad Request", strings.TrimSpace(resp.Body.String())) + } + }) + } +} diff --git a/web_src/js/external-render-helper.test.ts b/web_src/js/external-render-helper.test.ts new file mode 100644 index 00000000000..452d7f8f2d5 --- /dev/null +++ b/web_src/js/external-render-helper.test.ts @@ -0,0 +1,25 @@ +import './external-render-helper.ts'; + +test('isValidCssColor', async () => { + const isValidCssColor = window.testModules.externalRenderHelper!.isValidCssColor; + expect(isValidCssColor(null)).toBe(false); + expect(isValidCssColor('')).toBe(false); + + expect(isValidCssColor('#123')).toBe(true); + expect(isValidCssColor('#1234')).toBe(true); + expect(isValidCssColor('#abcabc')).toBe(true); + expect(isValidCssColor('#abcabc12')).toBe(true); + + expect(isValidCssColor('rgb(255 255 255)')).toBe(true); + expect(isValidCssColor('rgb(0, 255, 255)')).toBe(true); + + // examples from MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/rgb + expect(isValidCssColor('rgb(255 255 255 / 50%)')).toBe(true); + expect(isValidCssColor('rgb(from #123456 hwb(120deg 10% 20%) calc(g + 40) b / 0.5)')).toBe(true); + + expect(isValidCssColor('#123 ; other')).toBe(false); + expect(isValidCssColor('#123 : other')).toBe(false); + expect(isValidCssColor('#rgb(0, 255, 255); other')).toBe(false); + expect(isValidCssColor('#rgb(0, 255, 255)} other')).toBe(false); + expect(isValidCssColor('url(other)')).toBe(false); +}); diff --git a/web_src/js/external-render-helper.ts b/web_src/js/external-render-helper.ts index 3acac8db141..9162d0f550d 100644 --- a/web_src/js/external-render-helper.ts +++ b/web_src/js/external-render-helper.ts @@ -15,6 +15,17 @@ RENDER_COMMAND = `echo '