修复跨平台构建导致微信授权页样式摘要失配
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-10-03 23:29:04 +08:00
parent d62ad62330
commit 8d9cae7014
2 changed files with 43 additions and 1 deletions

View File

@ -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))

View File

@ -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()