mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-14 05:39:08 +02:00
Bluemonday operates on raw text, so characters like && and <> inside markdown code blocks/inline code were being HTML-escaped (e.g. && → &&), causing them to render incorrectly in the frontend. Now extracts fenced code blocks and inline code spans before sanitization, runs bluemonday on the remaining content, then restores the code verbatim.
72 lines
2.5 KiB
Go
72 lines
2.5 KiB
Go
package sanitize
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/microcosm-cc/bluemonday"
|
|
)
|
|
|
|
// httpURL matches only http:// and https:// URLs — blocks javascript:, data:, etc.
|
|
var httpURL = regexp.MustCompile(`^https?://`)
|
|
|
|
// policy is a shared bluemonday policy that allows safe Markdown HTML while
|
|
// stripping dangerous elements (script, iframe, object, embed, style, on*).
|
|
var policy *bluemonday.Policy
|
|
|
|
func init() {
|
|
policy = bluemonday.UGCPolicy()
|
|
policy.AllowElements("div", "span")
|
|
// Allow file-card data attributes, but restrict data-href to http(s) only
|
|
// to prevent javascript: and other dangerous URL schemes.
|
|
policy.AllowAttrs("data-type", "data-filename").OnElements("div")
|
|
policy.AllowAttrs("data-href").Matching(httpURL).OnElements("div")
|
|
policy.AllowAttrs("class").OnElements("code", "div", "span", "pre")
|
|
}
|
|
|
|
// fencedCodeBlock matches ``` or ~~~ fenced code blocks (with optional language tag).
|
|
var fencedCodeBlock = regexp.MustCompile("(?m)^(```|~~~)[^\n]*\n[\\s\\S]*?\n(```|~~~)[ \t]*$")
|
|
|
|
// inlineCode matches backtick-delimited inline code spans.
|
|
// Ordered longest-delimiter-first so triple backticks match before doubles/singles.
|
|
var inlineCode = regexp.MustCompile("```[^`]+```|``[^`]+``|`[^`]+`")
|
|
|
|
// HTML sanitizes user-provided HTML/Markdown content, stripping dangerous
|
|
// tags (script, iframe, object, embed, etc.) and event-handler attributes.
|
|
//
|
|
// Code blocks and inline code spans are preserved verbatim so that bluemonday
|
|
// does not HTML-escape their contents (e.g. && → &&).
|
|
func HTML(input string) string {
|
|
// 1. Extract fenced code blocks, replacing with unique placeholders.
|
|
var blocks []string
|
|
placeholder := func(i int) string { return fmt.Sprintf("\x00CODEBLOCK_%d\x00", i) }
|
|
result := fencedCodeBlock.ReplaceAllStringFunc(input, func(m string) string {
|
|
idx := len(blocks)
|
|
blocks = append(blocks, m)
|
|
return placeholder(idx)
|
|
})
|
|
|
|
// 2. Extract inline code spans.
|
|
var inlines []string
|
|
inlinePH := func(i int) string { return fmt.Sprintf("\x00INLINE_%d\x00", i) }
|
|
result = inlineCode.ReplaceAllStringFunc(result, func(m string) string {
|
|
idx := len(inlines)
|
|
inlines = append(inlines, m)
|
|
return inlinePH(idx)
|
|
})
|
|
|
|
// 3. Sanitize the non-code portions.
|
|
result = policy.Sanitize(result)
|
|
|
|
// 4. Restore inline code spans, then fenced code blocks.
|
|
for i, code := range inlines {
|
|
result = strings.Replace(result, inlinePH(i), code, 1)
|
|
}
|
|
for i, block := range blocks {
|
|
result = strings.Replace(result, placeholder(i), block, 1)
|
|
}
|
|
|
|
return result
|
|
}
|