后端基础层:模块定义、配置解析、SQLite 存储层、Argon2id 认证与 Markdown 渲染

- internal/config:serve/init/backup/gc 子命令参数解析(--dev 强制 loopback 守卫)
- internal/store:user_version 版本化迁移(仅追加式 + 越界拒启 + --allow-newer)、
  笔记/图片/引用/会话/设置 DAO、并集可见性查询、VACUUM INTO 在线备份、
  回收站 30 天 + 孤儿图 7 天宽限 gc
- internal/auth:Argon2id PHC 串(m=19456,t=2,p=1)、256bit token 与 SHA-256 摘要
- internal/markdown:goldmark(默认转义)+ bluemonday 双保险,摘要纯文本提取
- internal/middleware:安全头(CSP/HSTS/nosniff 等)、错误日志、
  有界令牌桶限流(per-IP 桶上限 + TTL 逐出)、Origin/Referer 同源校验
This commit is contained in:
2026-09-08 08:14:12 +08:00
parent 685f628e26
commit 247e88c4fb
17 changed files with 1786 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
// Package markdown 服务端 Markdown 渲染(goldmark + bluemonday),
// 仅用于 RSS / meta 摘要;库中始终只存 Markdown 原文(§7.5)。
package markdown
import (
"bytes"
"html"
"regexp"
"strings"
"unicode/utf8"
"github.com/microcosm-cc/bluemonday"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
)
var engine = goldmark.New(
goldmark.WithExtensions(extension.GFM),
// 默认 html.WithUnsafe=false:原始 HTML 与危险 URL 被转义(XSS 安全默认),
// 再经 bluemonday 白名单清洗双保险。
)
// ugcPolicy RSS description 用白名单清洗(双保险,与 goldmark 转义叠加)。
var ugcPolicy = bluemonday.UGCPolicy()
// strictPolicy 摘要提取用:剥掉全部标签。
var strictPolicy = bluemonday.StrictPolicy()
// Render 将 Markdown 渲染为清洗后的 HTML。
func Render(source string) string {
var buf bytes.Buffer
if err := engine.Convert([]byte(source), &buf); err != nil {
// goldmark 对任意输入不产生错误;兜底按纯文本转义。
return html.EscapeString(source)
}
return ugcPolicy.Sanitize(buf.String())
}
var wsRe = regexp.MustCompile(`\s+`)
// Summary 从 Markdown 源提取纯文本摘要:渲染→剥标签→反转义→压空白→截断。
func Summary(source string, limit int) string {
if limit <= 0 {
return ""
}
text := strictPolicy.Sanitize(Render(source))
text = html.UnescapeString(text)
text = strings.TrimSpace(wsRe.ReplaceAllString(text, " "))
if r := utf8.RuneCountInString(text); r > limit {
runes := []rune(text)
return string(runes[:limit]) + "…"
}
return text
}
+33
View File
@@ -0,0 +1,33 @@
package markdown
import (
"strings"
"testing"
)
// 服务端渲染防线(§9.1-T1):goldmark 转义 + bluemonday 白名单。
func TestRenderEscapesDangerousHTML(t *testing.T) {
out := Render("# 标题\n\n<script>alert(1)</script>\n\n[link](javascript:alert(1))\n\n**bold**")
if strings.Contains(out, "<script>") {
t.Errorf("script 应被转义: %s", out)
}
if strings.Contains(out, `href="javascript:`) {
t.Errorf("javascript: 协议应被剥除: %s", out)
}
if !strings.Contains(out, "<strong>bold</strong>") {
t.Errorf("正常 Markdown 应渲染: %s", out)
}
}
func TestSummary(t *testing.T) {
got := Summary("# 标题\n\n这是**正文**内容,包含 <b>HTML</b>。", 10)
if strings.ContainsAny(got, "<>#*") {
t.Errorf("摘要应为纯文本: %q", got)
}
if strings.Count(got, "")-1 > 11 { // 10 runes + 可能的省略号
t.Errorf("摘要应截断: %q", got)
}
if s := Summary("", 100); s != "" {
t.Errorf("空内容摘要应为空: %q", s)
}
}