This repository has no description
1package sanitizer
2
3import (
4 "maps"
5 "regexp"
6 "slices"
7 "strings"
8
9 "github.com/alecthomas/chroma/v2"
10 "github.com/microcosm-cc/bluemonday"
11)
12
13// shared policies built once at init; safe for concurrent use per bluemonday docs
14var (
15 sharedDefaultPolicy *bluemonday.Policy
16 sharedDescriptionPolicy *bluemonday.Policy
17 sharedLogsPolicy *bluemonday.Policy
18)
19
20func init() {
21 sharedDefaultPolicy = buildDefaultPolicy()
22 sharedDescriptionPolicy = buildDescriptionPolicy()
23 sharedLogsPolicy = buildLogsPolicy()
24}
25
26func SanitizeDefault(html string) string {
27 return sharedDefaultPolicy.Sanitize(html)
28}
29func SanitizeDescription(html string) string {
30 return sharedDescriptionPolicy.Sanitize(html)
31}
32func SanitizeLogs(html string) string {
33 return sharedLogsPolicy.Sanitize(html)
34}
35
36func buildDefaultPolicy() *bluemonday.Policy {
37 policy := bluemonday.UGCPolicy()
38
39 // Allow generally safe attributes
40 generalSafeAttrs := []string{
41 "abbr", "accept", "accept-charset",
42 "accesskey", "action", "align", "alt",
43 "aria-describedby", "aria-hidden", "aria-label", "aria-labelledby",
44 "axis", "border", "cellpadding", "cellspacing", "char",
45 "charoff", "charset", "checked",
46 "clear", "cols", "colspan", "color",
47 "compact", "coords", "datetime", "dir",
48 "disabled", "enctype", "for", "frame",
49 "headers", "height", "hreflang",
50 "hspace", "ismap", "label", "lang",
51 "maxlength", "media", "method",
52 "multiple", "name", "nohref", "noshade",
53 "nowrap", "open", "prompt", "readonly", "rel", "rev",
54 "rows", "rowspan", "rules", "scope",
55 "selected", "shape", "size", "span",
56 "start", "summary", "tabindex", "target",
57 "title", "type", "usemap", "valign", "value",
58 "vspace", "width", "itemprop",
59 }
60
61 generalSafeElements := []string{
62 "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "br", "b", "i", "strong", "em", "a", "pre", "code", "img", "tt",
63 "div", "ins", "del", "sup", "sub", "p", "ol", "ul", "table", "thead", "tbody", "tfoot", "blockquote", "label",
64 "dl", "dt", "dd", "kbd", "q", "samp", "var", "hr", "ruby", "rt", "rp", "li", "tr", "td", "th", "s", "strike", "summary",
65 "details", "caption", "figure", "figcaption",
66 "abbr", "bdo", "cite", "dfn", "mark", "small", "span", "time", "video", "wbr",
67 }
68
69 policy.AllowAttrs(generalSafeAttrs...).OnElements(generalSafeElements...)
70
71 // video
72 policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
73
74 // picture/source for modern image formats (avif, webp, etc.)
75 policy.AllowAttrs("srcset", "type", "media").OnElements("source")
76
77 // marker the editor uses to preview an uncommitted blob image; see markdown.go
78 policy.AllowAttrs("data-blob-cid").OnElements("img", "source")
79
80 // checkboxes
81 policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
82 policy.AllowAttrs("checked", "disabled", "data-source-position").OnElements("input")
83
84 // for code blocks
85 policy.AllowAttrs("class").Matching(regexp.MustCompile(`chroma|mermaid`)).OnElements("pre")
86 policy.AllowAttrs("class").Matching(regexp.MustCompile(`anchor|footnote-ref|footnote-backref`)).OnElements("a")
87 policy.AllowAttrs("class").Matching(regexp.MustCompile(`heading`)).OnElements("h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8")
88 policy.AllowAttrs("class").Matching(regexp.MustCompile(strings.Join(slices.Collect(maps.Values(chroma.StandardTypes)), "|"))).OnElements("span")
89
90 // at-mentions
91 policy.AllowAttrs("class").Matching(regexp.MustCompile(`mention`)).OnElements("a")
92
93 // centering content
94 policy.AllowElements("center")
95
96 policy.AllowAttrs("align", "style", "width", "height").Globally()
97 policy.AllowStyles(
98 "margin",
99 "padding",
100 "text-align",
101 "font-weight",
102 "text-decoration",
103 "padding-left",
104 "padding-right",
105 "padding-top",
106 "padding-bottom",
107 "margin-left",
108 "margin-right",
109 "margin-top",
110 "margin-bottom",
111 )
112
113 // math: the math extension emits <span class="math inline|display"> wrapping
114 // the raw LaTeX (delimited by \( \) / \[ \]). MathJax renders it client-side,
115 // so the sanitizer only needs to preserve these carrier spans.
116 policy.AllowAttrs("class").Matching(regexp.MustCompile(`^math (inline|display)$`)).OnElements("span")
117
118 // raw MathML: markdown is rendered with html.WithUnsafe(), so hand-authored
119 // <math>...</math> in source passes through to here. Browsers render
120 // presentation MathML natively, so preserve the elements and their attributes.
121 mathAttrs := []string{
122 "accent", "columnalign", "columnlines", "columnspan", "dir", "display",
123 "displaystyle", "encoding", "fence", "form", "largeop", "linebreak",
124 "linethickness", "lspace", "mathcolor", "mathsize", "mathvariant", "minsize",
125 "movablelimits", "notation", "rowalign", "rspace", "rowspacing", "rowspan",
126 "scriptlevel", "stretchy", "symmetric", "title", "voffset", "width",
127 }
128 mathElements := []string{
129 "annotation", "math", "menclose", "merror", "mfrac", "mi", "mmultiscripts",
130 "mn", "mo", "mover", "mpadded", "mprescripts", "mroot", "mrow", "mspace",
131 "msqrt", "mstyle", "msub", "msubsup", "msup", "mtable", "mtd", "mtext",
132 "mtr", "munder", "munderover", "semantics",
133 }
134 policy.AllowNoAttrs().OnElements(mathElements...)
135 policy.AllowAttrs(mathAttrs...).OnElements(mathElements...)
136
137 // goldmark-callout
138 policy.AllowAttrs("data-callout").OnElements("details")
139
140 return policy
141}
142
143func buildDescriptionPolicy() *bluemonday.Policy {
144 policy := bluemonday.NewPolicy()
145 policy.AllowStandardURLs()
146
147 // allow italics and bold.
148 policy.AllowElements("i", "b", "em", "strong")
149
150 // allow code.
151 policy.AllowElements("code")
152
153 // allow links
154 policy.AllowAttrs("href", "target", "rel").OnElements("a")
155
156 return policy
157}
158
159func buildLogsPolicy() *bluemonday.Policy {
160 policy := bluemonday.NewPolicy()
161
162 policy.AllowElements("p", "span")
163
164 // allow italics and bold
165 policy.AllowElements("i", "b", "em", "strong")
166
167 // allow fg/bg classes from terminal-to-html
168 policy.AllowAttrs("class").Matching(regexp.MustCompile(`term-*`)).OnElements("span")
169
170 return policy
171}