Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177package markup
import ( "maps" "regexp" "slices" "strings"
"github.com/alecthomas/chroma/v2" "github.com/microcosm-cc/bluemonday")
// shared policies built once at init; safe for concurrent use per bluemonday docsvar ( sharedDefaultPolicy *bluemonday.Policy sharedDescriptionPolicy *bluemonday.Policy sharedLogsPolicy *bluemonday.Policy)
func init() { sharedDefaultPolicy = buildDefaultPolicy() sharedDescriptionPolicy = buildDescriptionPolicy() sharedLogsPolicy = buildLogsPolicy()}
type Sanitizer struct { defaultPolicy *bluemonday.Policy descriptionPolicy *bluemonday.Policy logsPolicy *bluemonday.Policy}
func NewSanitizer() Sanitizer { return Sanitizer{ defaultPolicy: sharedDefaultPolicy, descriptionPolicy: sharedDescriptionPolicy, logsPolicy: sharedLogsPolicy, }}
func (s *Sanitizer) SanitizeDefault(html string) string { return s.defaultPolicy.Sanitize(html)}func (s *Sanitizer) SanitizeDescription(html string) string { return s.descriptionPolicy.Sanitize(html)}func (s *Sanitizer) SanitizeLogs(html string) string { return s.logsPolicy.Sanitize(html)}
func buildDefaultPolicy() *bluemonday.Policy { policy := bluemonday.UGCPolicy()
// Allow generally safe attributes generalSafeAttrs := []string{ "abbr", "accept", "accept-charset", "accesskey", "action", "align", "alt", "aria-describedby", "aria-hidden", "aria-label", "aria-labelledby", "axis", "border", "cellpadding", "cellspacing", "char", "charoff", "charset", "checked", "clear", "cols", "colspan", "color", "compact", "coords", "datetime", "dir", "disabled", "enctype", "for", "frame", "headers", "height", "hreflang", "hspace", "ismap", "label", "lang", "maxlength", "media", "method", "multiple", "name", "nohref", "noshade", "nowrap", "open", "prompt", "readonly", "rel", "rev", "rows", "rowspan", "rules", "scope", "selected", "shape", "size", "span", "start", "summary", "tabindex", "target", "title", "type", "usemap", "valign", "value", "vspace", "width", "itemprop", }
generalSafeElements := []string{ "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "br", "b", "i", "strong", "em", "a", "pre", "code", "img", "tt", "div", "ins", "del", "sup", "sub", "p", "ol", "ul", "table", "thead", "tbody", "tfoot", "blockquote", "label", "dl", "dt", "dd", "kbd", "q", "samp", "var", "hr", "ruby", "rt", "rp", "li", "tr", "td", "th", "s", "strike", "summary", "details", "caption", "figure", "figcaption", "abbr", "bdo", "cite", "dfn", "mark", "small", "span", "time", "video", "wbr", "picture", "source", }
policy.AllowAttrs(generalSafeAttrs...).OnElements(generalSafeElements...)
// video policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
// picture/source for modern image formats (avif, webp, etc.) policy.AllowAttrs("srcset", "type", "media").OnElements("source")
// checkboxes policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input") policy.AllowAttrs("checked", "disabled", "data-source-position").OnElements("input")
// for code blocks policy.AllowAttrs("class").Matching(regexp.MustCompile(`chroma|mermaid`)).OnElements("pre") policy.AllowAttrs("class").Matching(regexp.MustCompile(`anchor|footnote-ref|footnote-backref`)).OnElements("a") policy.AllowAttrs("class").Matching(regexp.MustCompile(`heading`)).OnElements("h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8") policy.AllowAttrs("class").Matching(regexp.MustCompile(strings.Join(slices.Collect(maps.Values(chroma.StandardTypes)), "|"))).OnElements("span")
// at-mentions policy.AllowAttrs("class").Matching(regexp.MustCompile(`mention`)).OnElements("a")
// centering content policy.AllowElements("center")
policy.AllowAttrs("align", "style", "width", "height").Globally() policy.AllowStyles( "margin", "padding", "text-align", "font-weight", "text-decoration", "padding-left", "padding-right", "padding-top", "padding-bottom", "margin-left", "margin-right", "margin-top", "margin-bottom", )
// math mathAttrs := []string{ "accent", "columnalign", "columnlines", "columnspan", "dir", "display", "displaystyle", "encoding", "fence", "form", "largeop", "linebreak", "linethickness", "lspace", "mathcolor", "mathsize", "mathvariant", "minsize", "movablelimits", "notation", "rowalign", "rspace", "rowspacing", "rowspan", "scriptlevel", "stretchy", "symmetric", "title", "voffset", "width", } mathElements := []string{ "annotation", "math", "menclose", "merror", "mfrac", "mi", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mprescripts", "mroot", "mrow", "mspace", "msqrt", "mstyle", "msub", "msubsup", "msup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "semantics", } policy.AllowNoAttrs().OnElements(mathElements...) policy.AllowAttrs(mathAttrs...).OnElements(mathElements...)
// goldmark-callout policy.AllowAttrs("data-callout").OnElements("details")
return policy}
func buildDescriptionPolicy() *bluemonday.Policy { policy := bluemonday.NewPolicy() policy.AllowStandardURLs()
// allow italics and bold. policy.AllowElements("i", "b", "em", "strong")
// allow code. policy.AllowElements("code")
// allow links policy.AllowAttrs("href", "target", "rel").OnElements("a")
return policy}
func buildLogsPolicy() *bluemonday.Policy { policy := bluemonday.NewPolicy()
policy.AllowElements("p", "span")
// allow italics and bold policy.AllowElements("i", "b", "em", "strong")
// allow fg/bg classes from terminal-to-html policy.AllowAttrs("class").Matching(regexp.MustCompile(`term-*`)).OnElements("span")
return policy}