From 8d9cae701483f2ea2ed9299f5f0aa981991a0952 Mon Sep 17 00:00:00 2001 From: smail Date: Sat, 3 Oct 2026 23:29:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B7=A8=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E5=AF=BC=E8=87=B4=E5=BE=AE=E4=BF=A1=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E9=A1=B5=E6=A0=B7=E5=BC=8F=E6=91=98=E8=A6=81=E5=A4=B1?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/controllers/wechatcallback/page.go | 10 +++++- .../controllers/wechatcallback/page_test.go | 34 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/backend/controllers/wechatcallback/page.go b/backend/controllers/wechatcallback/page.go index 91c4f28..8599eb4 100644 --- a/backend/controllers/wechatcallback/page.go +++ b/backend/controllers/wechatcallback/page.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "html/template" "net/http" + "strings" ) // 页面随主程序发布,无需外部字体、脚本或手机浏览器登录状态。 @@ -15,7 +16,14 @@ import ( var pageHTML string //go:embed page.css -var pageCSS string +var embeddedCSS string + +var pageCSS = normalizeStyle(embeddedCSS) + +// 与浏览器解析内嵌样式时的换行规则一致,避免跨平台构建导致摘要不匹配。 +func normalizeStyle(css string) string { + return strings.ReplaceAll(strings.ReplaceAll(css, "\r\n", "\n"), "\r", "\n") +} var resultTemplate = template.Must(template.New("wechat-result").Parse(pageHTML)) diff --git a/backend/controllers/wechatcallback/page_test.go b/backend/controllers/wechatcallback/page_test.go index 4d9f8d2..8bf0445 100644 --- a/backend/controllers/wechatcallback/page_test.go +++ b/backend/controllers/wechatcallback/page_test.go @@ -7,6 +7,8 @@ import ( "net/http/httptest" "strings" "testing" + + "golang.org/x/net/html" ) func TestWriteAuthorizationResult(t *testing.T) { @@ -57,6 +59,38 @@ func TestWriteAuthorizationResult(t *testing.T) { } } +func TestStyleHashAfterHTMLParsing(t *testing.T) { + for name, lineEnding := range map[string]string{"LF": "\n", "CRLF": "\r\n", "CR": "\r"} { + t.Run(name, func(t *testing.T) { + original := pageCSS + defer func() { pageCSS = original }() + pageCSS = normalizeStyle(strings.ReplaceAll(original, "\n", lineEnding)) + for _, authorized := range []bool{true, false} { + response := httptest.NewRecorder() + Write(response, authorized) + tokenizer := html.NewTokenizer(strings.NewReader(response.Body.String())) + var styles []string + for token := tokenizer.Next(); token != html.ErrorToken; token = tokenizer.Next() { + if token == html.StartTagToken && tokenizer.Token().Data == "style" { + if tokenizer.Next() != html.TextToken { + t.Fatal("内嵌样式必须包含有效文本") + } + styles = append(styles, tokenizer.Token().Data) + } + } + if len(styles) != 1 || styles[0] != original { + t.Fatal("浏览器解析后的样式必须与规范化样式一致") + } + digest := sha256.Sum256([]byte(styles[0])) + expected := "style-src 'sha256-" + base64.StdEncoding.EncodeToString(digest[:]) + "'" + if !strings.Contains(response.Header().Get("Content-Security-Policy"), expected) { + t.Fatal("安全策略必须允许浏览器实际解析出的样式摘要") + } + } + }) + } +} + func TestWriteResultSecurityHeaders(t *testing.T) { for _, authorized := range []bool{true, false} { response := httptest.NewRecorder()