Files
jiang13-forum/services/compose_body.go
freefire fde5f628ec feat: opaque session、安装/发帖 SSR 与最小 Admin 后台
浏览器登录改为 DB sessions(可吊销);敏感词与 OIDC PEM 入 settings;
落地安装向导、注册发帖与 /admin 仪表盘/板块/审核/设置。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-29 05:44:16 +08:00

98 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package services
import (
"html"
"regexp"
"strings"
)
var (
mdImageRe = regexp.MustCompile(`!\[([^\]]*)\]\(([^)]+)\)`)
mdLinkRe = regexp.MustCompile(`\[([^\]]+)\]\(([^)]+)\)`)
)
// ComposeBodyToHTML 将发帖表单正文转为可消毒 HTML。
// 若已含块级 HTML 标签则原样交 Sanitize否则按纯文本/轻量 Markdown 转段落。
func ComposeBodyToHTML(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" {
return ""
}
lower := strings.ToLower(raw)
if strings.Contains(lower, "<p") || strings.Contains(lower, "<div") ||
strings.Contains(lower, "<h1") || strings.Contains(lower, "<ul") ||
strings.Contains(lower, "<ol") || strings.Contains(lower, "<pre") ||
strings.Contains(lower, "<blockquote") {
return raw
}
// 先转义,再恢复轻量 md 图片/链接
esc := html.EscapeString(raw)
esc = mdImageRe.ReplaceAllStringFunc(esc, func(m string) string {
sub := mdImageRe.FindStringSubmatch(m)
if len(sub) != 3 {
return m
}
alt, src := sub[1], sub[2]
// 仅允许站内 uploads 或 http(s)
if !safeComposeURL(src) {
return m
}
return `<p><img src="` + html.EscapeString(src) + `" alt="` + alt + `"></p>`
})
esc = mdLinkRe.ReplaceAllStringFunc(esc, func(m string) string {
sub := mdLinkRe.FindStringSubmatch(m)
if len(sub) != 3 {
return m
}
text, href := sub[1], sub[2]
if !safeComposeURL(href) {
return m
}
return `<a href="` + html.EscapeString(href) + `" rel="noopener noreferrer">` + text + `</a>`
})
parts := strings.Split(esc, "\n\n")
var b strings.Builder
for _, p := range parts {
p = strings.TrimSpace(p)
if p == "" {
continue
}
// 已是独立 img 段
if strings.HasPrefix(p, "<p><img ") {
b.WriteString(p)
continue
}
p = strings.ReplaceAll(p, "\n", "<br>\n")
b.WriteString("<p>")
b.WriteString(p)
b.WriteString("</p>\n")
}
return b.String()
}
func safeComposeURL(u string) bool {
u = strings.TrimSpace(u)
if strings.HasPrefix(u, "/uploads/") || strings.HasPrefix(u, "/media/") {
return true
}
return strings.HasPrefix(u, "https://") || strings.HasPrefix(u, "http://")
}
// HTMLToComposePlain 编辑页回显:去掉简单标签便于 textarea 编辑(尽力而为)
func HTMLToComposePlain(htmlBody string) string {
s := strings.TrimSpace(htmlBody)
if s == "" {
return ""
}
// img → markdown
imgRe := regexp.MustCompile(`(?i)<img[^>]+src="([^"]+)"[^>]*>`)
s = imgRe.ReplaceAllString(s, "![]($1)")
s = regexp.MustCompile(`(?i)</p>\s*<p>`).ReplaceAllString(s, "\n\n")
s = regexp.MustCompile(`(?i)<br\s*/?>`).ReplaceAllString(s, "\n")
s = regexp.MustCompile(`(?i)</?p[^>]*>`).ReplaceAllString(s, "")
s = regexp.MustCompile(`(?i)<[^>]+>`).ReplaceAllString(s, "")
return strings.TrimSpace(html.UnescapeString(s))
}