- 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 同源校验
55 lines
1.6 KiB
Go
55 lines
1.6 KiB
Go
// 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
|
|
}
|