chore: baseline snapshot before moderation-strictness feature

Repo .git was damaged (refs/ missing, index truncated, object store near-empty).
Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak,
then full worktree committed as a rollback baseline.
This commit is contained in:
tamakyi
2026-09-10 10:56:27 +08:00
commit e3e59e7c69
296 changed files with 67458 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
package template
import (
"strings"
"testing"
)
func TestMarkdownToHTML_Basic(t *testing.T) {
cases := []struct {
name string
in string
want string // 必须包含
}{
{"bold", "**bold**", "<strong>bold</strong>"},
{"italic", "*italic*", "<em>italic</em>"},
{"link", "[baidu](https://baidu.com)", `href="https://baidu.com"`},
{"list", "- item1\n- item2", "<li>item1</li>"},
{"code", "`code`", "<code>code</code>"},
{"newline", "line1\nline2", "<br>"},
{"font-red", `<font color="red">文本</font>`, `<font color="red">文本</font>`},
{"font-hex", `<font color="#ff0000">红</font>`, `<font color="#ff0000">红</font>`},
{"font-rgb", `<font color="rgb(255,0,0)">红</font>`, `<font color="rgb(255,0,0)">红</font>`},
{"font-mixed", `**粗体** 和 <font color="blue">蓝</font>`, `<font color="blue">蓝</font>`},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := string(MarkdownToHTML(tc.in))
if !strings.Contains(got, tc.want) {
t.Errorf("MarkdownToHTML(%q) = %q, 期望包含 %q", tc.in, got, tc.want)
}
})
}
}
func TestMarkdownToHTML_XSS(t *testing.T) {
cases := []string{
`&lt;script&gt;alert(1)&lt;/script&gt;`,
`&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;`,
`<script>alert(1)</script>`,
`<img src=x onerror=alert(1)>`,
`[x](javascript:alert(1))`,
`<a href="javascript:alert(1)">x</a>`,
`<iframe src="https://evil.com"></iframe>`,
"<svg onload=alert(1)>",
`<div onclick="alert(1)">x</div>`,
`<style>body{display:none}</style>`,
}
for _, in := range cases {
got := string(MarkdownToHTML(in))
if strings.Contains(got, "<script") || strings.Contains(got, " onerror") ||
strings.Contains(got, " onclick") || strings.Contains(got, " onload") ||
strings.Contains(got, "<iframe") || strings.Contains(got, "javascript:") ||
strings.Contains(got, "<style") || strings.Contains(got, "<svg") {
t.Errorf("XSS 防护失败: 输入 %q 输出 %q", in, got)
}
}
}
// TestMarkdownToHTML_FontColorInjection font 的 color 属性只放行合法颜色值,
// 注入尝试必须被丢弃(标签保留但 color 属性被剥离,且不产生新属性)。
func TestMarkdownToHTML_FontColorInjection(t *testing.T) {
cases := []struct {
name string
in string
want string // 输出必须包含(剥离后仅剩文本)
}{
{"css-inject", `<font color="red;position:fixed">x</font>`, "x"},
{"quote-inject", `<font color="red" onclick="alert(1)">x</font>`, "x"},
{"url-inject", `<font color="url(javascript:alert(1))">x</font>`, "x"},
{"onattr-inject", `<font color="x" style="background:url(javascript:1)">x</font>`, "x"},
{"long-value", `<font color="` + strings.Repeat("a", 50) + `">x</font>`, "x"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := string(MarkdownToHTML(tc.in))
if !strings.Contains(got, tc.want) {
t.Errorf("输出 %q 应包含 %q", got, tc.want)
}
// 注入的 color 值不得出现
for _, evil := range []string{";", "position:", "onclick", "javascript:", "<font color=\"red\" onclick", "<font color=\"x\" style"} {
if strings.Contains(got, evil) {
t.Errorf("注入残留: %q 包含 %q", got, evil)
}
}
})
}
}
func TestMarkdownToHTML_Empty(t *testing.T) {
if got := string(MarkdownToHTML("")); got != "" {
t.Errorf("空输入应输出空串, got %q", got)
}
}
func TestMarkdownToPlainText(t *testing.T) {
cases := []struct {
name string
in string
want string // 必须包含(忽略换行折叠)
}{
{"plain", "你好世界", "你好世界"},
{"bold", "**粗体**", "粗体"},
{"font", `<font color="red">红字</font>`, "红字"},
{"link", "[百度](https://baidu.com)", "百度"},
{"code", "`code`", "code"},
{"list", "- 第一项\n- 第二项", "第一项"},
{"block", "# 标题\n内容", "标题"},
{"mixed", "问题:**重要** 详情 <font color=\"blue\">蓝</font>", "重要"},
{"empty", "", ""},
// 危险标签被剥离:script 的内容是纯文本(绘制无风险),img 自闭合无文本则输出空
{"xss-script", "<script>alert(1)</script>", "alert(1)"},
{"xss-img", `<img src=x onerror=alert(1)>`, ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := strings.Join(strings.Fields(MarkdownToPlainText(tc.in)), " ")
if tc.want == "" {
if got != "" {
t.Errorf("MarkdownToPlainText(%q) = %q, 期望空", tc.in, got)
}
return
}
if !strings.Contains(got, tc.want) {
t.Errorf("MarkdownToPlainText(%q) = %q, 期望包含 %q", tc.in, got, tc.want)
}
// 输出不得残留任何标签(< 必须被剥离)
if strings.Contains(got, "<") {
t.Errorf("MarkdownToPlainText(%q) = %q, 不应包含标签字符", tc.in, got)
}
})
}
}
// TestSafeURL 是 P2-1 的回归测试:markdown 链接白名单必须拒绝协议相对 URL
// (//evil.com 等),否则会被误判为站内相对路径而渲染成指向外站的钓鱼 / 外链埋点链接。
// 同时验证 javascript:/data:/ftp: 等危险协议与大小写变体均被拒绝,http/https/mailto
// 与站内相对路径被放行。
func TestSafeURL(t *testing.T) {
allow := []string{
"https://example.com",
"http://example.com",
"HTTPS://EXAMPLE.COM", // 大小写不敏感
"mailto:[email protected]",
"/path/to/page",
"./relative",
"../up",
"#fragment",
}
for _, u := range allow {
if !safeURL(u) {
t.Errorf("safeURL(%q) = false, 期望放行", u)
}
}
deny := []string{
"//evil.com", // 协议相对 URL(核心回归点)
`\\evil.com`, // 反斜杠变体
`/\evil.com`, // 反斜杠变体
"javascript:alert(1)", // 危险协议
"JavaScript:alert(1)", // 大小写变体
"data:text/html,<script>",
"ftp://example.com",
"", // 空串
}
for _, u := range deny {
if safeURL(u) {
t.Errorf("safeURL(%q) = true, 期望拒绝", u)
}
}
}