A vibe coded tangled fork which supports pijul.
1package markup
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)
18
19func init() {
20 sharedDefaultPolicy = buildDefaultPolicy()
21 sharedDescriptionPolicy = buildDescriptionPolicy()
22}
23
24type Sanitizer struct {
25 defaultPolicy *bluemonday.Policy
26 descriptionPolicy *bluemonday.Policy
27}
28
29func NewSanitizer() Sanitizer {
30 return Sanitizer{
31 defaultPolicy: sharedDefaultPolicy,
32 descriptionPolicy: sharedDescriptionPolicy,
33 }
34}
35
36func (s *Sanitizer) SanitizeDefault(html string) string {
37 return s.defaultPolicy.Sanitize(html)
38}
39func (s *Sanitizer) SanitizeDescription(html string) string {
40 return s.descriptionPolicy.Sanitize(html)
41}
42
43func buildDefaultPolicy() *bluemonday.Policy {
44 policy := bluemonday.UGCPolicy()
45
46 // Allow generally safe attributes
47 generalSafeAttrs := []string{
48 "abbr", "accept", "accept-charset",
49 "accesskey", "action", "align", "alt",
50 "aria-describedby", "aria-hidden", "aria-label", "aria-labelledby",
51 "axis", "border", "cellpadding", "cellspacing", "char",
52 "charoff", "charset", "checked",
53 "clear", "cols", "colspan", "color",
54 "compact", "coords", "datetime", "dir",
55 "disabled", "enctype", "for", "frame",
56 "headers", "height", "hreflang",
57 "hspace", "ismap", "label", "lang",
58 "maxlength", "media", "method",
59 "multiple", "name", "nohref", "noshade",
60 "nowrap", "open", "prompt", "readonly", "rel", "rev",
61 "rows", "rowspan", "rules", "scope",
62 "selected", "shape", "size", "span",
63 "start", "summary", "tabindex", "target",
64 "title", "type", "usemap", "valign", "value",
65 "vspace", "width", "itemprop",
66 }
67
68 generalSafeElements := []string{
69 "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "br", "b", "i", "strong", "em", "a", "pre", "code", "img", "tt",
70 "div", "ins", "del", "sup", "sub", "p", "ol", "ul", "table", "thead", "tbody", "tfoot", "blockquote", "label",
71 "dl", "dt", "dd", "kbd", "q", "samp", "var", "hr", "ruby", "rt", "rp", "li", "tr", "td", "th", "s", "strike", "summary",
72 "details", "caption", "figure", "figcaption",
73 "abbr", "bdo", "cite", "dfn", "mark", "small", "span", "time", "video", "wbr",
74 }
75
76 policy.AllowAttrs(generalSafeAttrs...).OnElements(generalSafeElements...)
77
78 // video
79 policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
80
81 // checkboxes
82 policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
83 policy.AllowAttrs("checked", "disabled", "data-source-position").OnElements("input")
84
85 // for code blocks
86 policy.AllowAttrs("class").Matching(regexp.MustCompile(`chroma|mermaid`)).OnElements("pre")
87 policy.AllowAttrs("class").Matching(regexp.MustCompile(`anchor|footnote-ref|footnote-backref`)).OnElements("a")
88 policy.AllowAttrs("class").Matching(regexp.MustCompile(`heading`)).OnElements("h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8")
89 policy.AllowAttrs("class").Matching(regexp.MustCompile(strings.Join(slices.Collect(maps.Values(chroma.StandardTypes)), "|"))).OnElements("span")
90
91 // at-mentions
92 policy.AllowAttrs("class").Matching(regexp.MustCompile(`mention`)).OnElements("a")
93
94 // centering content
95 policy.AllowElements("center")
96
97 policy.AllowAttrs("align", "style", "width", "height").Globally()
98 policy.AllowStyles(
99 "margin",
100 "padding",
101 "text-align",
102 "font-weight",
103 "text-decoration",
104 "padding-left",
105 "padding-right",
106 "padding-top",
107 "padding-bottom",
108 "margin-left",
109 "margin-right",
110 "margin-top",
111 "margin-bottom",
112 )
113
114 // math
115 mathAttrs := []string{
116 "accent", "columnalign", "columnlines", "columnspan", "dir", "display",
117 "displaystyle", "encoding", "fence", "form", "largeop", "linebreak",
118 "linethickness", "lspace", "mathcolor", "mathsize", "mathvariant", "minsize",
119 "movablelimits", "notation", "rowalign", "rspace", "rowspacing", "rowspan",
120 "scriptlevel", "stretchy", "symmetric", "title", "voffset", "width",
121 }
122 mathElements := []string{
123 "annotation", "math", "menclose", "merror", "mfrac", "mi", "mmultiscripts",
124 "mn", "mo", "mover", "mpadded", "mprescripts", "mroot", "mrow", "mspace",
125 "msqrt", "mstyle", "msub", "msubsup", "msup", "mtable", "mtd", "mtext",
126 "mtr", "munder", "munderover", "semantics",
127 }
128 policy.AllowNoAttrs().OnElements(mathElements...)
129 policy.AllowAttrs(mathAttrs...).OnElements(mathElements...)
130
131 // goldmark-callout
132 policy.AllowAttrs("data-callout").OnElements("details")
133
134 return policy
135}
136
137func buildDescriptionPolicy() *bluemonday.Policy {
138 policy := bluemonday.NewPolicy()
139 policy.AllowStandardURLs()
140
141 // allow italics and bold.
142 policy.AllowElements("i", "b", "em", "strong")
143
144 // allow code.
145 policy.AllowElements("code")
146
147 // allow links
148 policy.AllowAttrs("href", "target", "rel").OnElements("a")
149
150 return policy
151}