55package markup
66
77import (
8- "io"
9- "net/url"
108 "regexp"
119 "sync"
1210
13- "code.gitea.io/gitea/modules/setting"
14-
1511 "github.com/microcosm-cc/bluemonday"
1612)
1713
@@ -21,211 +17,35 @@ type Sanitizer struct {
2117 defaultPolicy * bluemonday.Policy
2218 descriptionPolicy * bluemonday.Policy
2319 rendererPolicies map [string ]* bluemonday.Policy
24- init sync. Once
20+ allowAllRegex * regexp. Regexp
2521}
2622
2723var (
28- sanitizer = & Sanitizer {}
29- allowAllRegex = regexp . MustCompile ( ".+" )
24+ defaultSanitizer * Sanitizer
25+ defaultSanitizerOnce sync. Once
3026)
3127
32- // NewSanitizer initializes sanitizer with allowed attributes based on settings.
33- // Multiple calls to this function will only create one instance of Sanitizer during
34- // entire application lifecycle.
35- func NewSanitizer () {
36- sanitizer .init .Do (func () {
37- InitializeSanitizer ()
38- })
39- }
40-
41- // InitializeSanitizer (re)initializes the current sanitizer to account for changes in settings
42- func InitializeSanitizer () {
43- sanitizer .rendererPolicies = map [string ]* bluemonday.Policy {}
44- sanitizer .defaultPolicy = createDefaultPolicy ()
45- sanitizer .descriptionPolicy = createRepoDescriptionPolicy ()
46-
47- for name , renderer := range renderers {
48- sanitizerRules := renderer .SanitizerRules ()
49- if len (sanitizerRules ) > 0 {
50- policy := createDefaultPolicy ()
51- addSanitizerRules (policy , sanitizerRules )
52- sanitizer .rendererPolicies [name ] = policy
53- }
54- }
55- }
56-
57- func createDefaultPolicy () * bluemonday.Policy {
58- policy := bluemonday .UGCPolicy ()
59-
60- // For JS code copy and Mermaid loading state
61- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^code-block( is-loading)?$` )).OnElements ("pre" )
62-
63- // For code preview
64- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^code-preview-[-\w]+( file-content)?$` )).Globally ()
65- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^lines-num$` )).OnElements ("td" )
66- policy .AllowAttrs ("data-line-number" ).OnElements ("span" )
67- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^lines-code chroma$` )).OnElements ("td" )
68- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^code-inner$` )).OnElements ("div" )
69-
70- // For code preview (unicode escape)
71- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^file-view( unicode-escaped)?$` )).OnElements ("table" )
72- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^lines-escape$` )).OnElements ("td" )
73- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^toggle-escape-button btn interact-bg$` )).OnElements ("a" ) // don't use button, button might submit a form
74- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^(ambiguous-code-point|escaped-code-point|broken-code-point)$` )).OnElements ("span" )
75- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^char$` )).OnElements ("span" )
76- policy .AllowAttrs ("data-tooltip-content" , "data-escaped" ).OnElements ("span" )
77-
78- // For color preview
79- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^color-preview$` )).OnElements ("span" )
80-
81- // For attention
82- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^attention-header attention-\w+$` )).OnElements ("blockquote" )
83- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^attention-\w+$` )).OnElements ("strong" )
84- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^attention-icon attention-\w+ svg octicon-[\w-]+$` )).OnElements ("svg" )
85- policy .AllowAttrs ("viewBox" , "width" , "height" , "aria-hidden" ).OnElements ("svg" )
86- policy .AllowAttrs ("fill-rule" , "d" ).OnElements ("path" )
87-
88- // For Chroma markdown plugin
89- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^(chroma )?language-[\w-]+( display)?( is-loading)?$` )).OnElements ("code" )
90-
91- // Checkboxes
92- policy .AllowAttrs ("type" ).Matching (regexp .MustCompile (`^checkbox$` )).OnElements ("input" )
93- policy .AllowAttrs ("checked" , "disabled" , "data-source-position" ).OnElements ("input" )
94-
95- // Custom URL-Schemes
96- if len (setting .Markdown .CustomURLSchemes ) > 0 {
97- policy .AllowURLSchemes (setting .Markdown .CustomURLSchemes ... )
98- } else {
99- policy .AllowURLSchemesMatching (allowAllRegex )
100-
101- // Even if every scheme is allowed, these three are blocked for security reasons
102- disallowScheme := func (* url.URL ) bool {
103- return false
28+ func GetDefaultSanitizer () * Sanitizer {
29+ defaultSanitizerOnce .Do (func () {
30+ defaultSanitizer = & Sanitizer {
31+ rendererPolicies : map [string ]* bluemonday.Policy {},
32+ allowAllRegex : regexp .MustCompile (".+" ),
10433 }
105- policy .AllowURLSchemeWithCustomPolicy ("javascript" , disallowScheme )
106- policy .AllowURLSchemeWithCustomPolicy ("vbscript" , disallowScheme )
107- policy .AllowURLSchemeWithCustomPolicy ("data" , disallowScheme )
108- }
109-
110- // Allow classes for anchors
111- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`ref-issue( ref-external-issue)?` )).OnElements ("a" )
112-
113- // Allow classes for task lists
114- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`task-list-item` )).OnElements ("li" )
115-
116- // Allow classes for org mode list item status.
117- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^(unchecked|checked|indeterminate)$` )).OnElements ("li" )
118-
119- // Allow icons
120- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^icon(\s+[\p{L}\p{N}_-]+)+$` )).OnElements ("i" )
121-
122- // Allow classes for emojis
123- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`emoji` )).OnElements ("img" )
124-
125- // Allow icons, emojis, chroma syntax and keyword markup on span
126- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji)|(language-math display)|(language-math inline))$|^([a-z][a-z0-9]{0,2})$|^` + keywordClass + `$` )).OnElements ("span" )
127-
128- // Allow 'color' and 'background-color' properties for the style attribute on text elements.
129- policy .AllowStyles ("color" , "background-color" ).OnElements ("span" , "p" )
130-
131- // Allow generally safe attributes
132- generalSafeAttrs := []string {
133- "abbr" , "accept" , "accept-charset" ,
134- "accesskey" , "action" , "align" , "alt" ,
135- "aria-describedby" , "aria-hidden" , "aria-label" , "aria-labelledby" ,
136- "axis" , "border" , "cellpadding" , "cellspacing" , "char" ,
137- "charoff" , "charset" , "checked" ,
138- "clear" , "cols" , "colspan" , "color" ,
139- "compact" , "coords" , "datetime" , "dir" ,
140- "disabled" , "enctype" , "for" , "frame" ,
141- "headers" , "height" , "hreflang" ,
142- "hspace" , "ismap" , "label" , "lang" ,
143- "maxlength" , "media" , "method" ,
144- "multiple" , "name" , "nohref" , "noshade" ,
145- "nowrap" , "open" , "prompt" , "readonly" , "rel" , "rev" ,
146- "rows" , "rowspan" , "rules" , "scope" ,
147- "selected" , "shape" , "size" , "span" ,
148- "start" , "summary" , "tabindex" , "target" ,
149- "title" , "type" , "usemap" , "valign" , "value" ,
150- "vspace" , "width" , "itemprop" ,
151- }
152-
153- generalSafeElements := []string {
154- "h1" , "h2" , "h3" , "h4" , "h5" , "h6" , "h7" , "h8" , "br" , "b" , "i" , "strong" , "em" , "a" , "pre" , "code" , "img" , "tt" ,
155- "div" , "ins" , "del" , "sup" , "sub" , "p" , "ol" , "ul" , "table" , "thead" , "tbody" , "tfoot" , "blockquote" , "label" ,
156- "dl" , "dt" , "dd" , "kbd" , "q" , "samp" , "var" , "hr" , "ruby" , "rt" , "rp" , "li" , "tr" , "td" , "th" , "s" , "strike" , "summary" ,
157- "details" , "caption" , "figure" , "figcaption" ,
158- "abbr" , "bdo" , "cite" , "dfn" , "mark" , "small" , "span" , "time" , "video" , "wbr" ,
159- }
160-
161- policy .AllowAttrs (generalSafeAttrs ... ).OnElements (generalSafeElements ... )
162-
163- policy .AllowAttrs ("src" , "autoplay" , "controls" ).OnElements ("video" )
164-
165- policy .AllowAttrs ("itemscope" , "itemtype" ).OnElements ("div" )
166-
167- // FIXME: Need to handle longdesc in img but there is no easy way to do it
168-
169- // Custom keyword markup
170- addSanitizerRules (policy , setting .ExternalSanitizerRules )
171-
172- return policy
173- }
174-
175- // createRepoDescriptionPolicy returns a minimal more strict policy that is used for
176- // repository descriptions.
177- func createRepoDescriptionPolicy () * bluemonday.Policy {
178- policy := bluemonday .NewPolicy ()
179-
180- // Allow italics and bold.
181- policy .AllowElements ("i" , "b" , "em" , "strong" )
182-
183- // Allow code.
184- policy .AllowElements ("code" )
185-
186- // Allow links
187- policy .AllowAttrs ("href" , "target" , "rel" ).OnElements ("a" )
188-
189- // Allow classes for emojis
190- policy .AllowAttrs ("class" ).Matching (regexp .MustCompile (`^emoji$` )).OnElements ("img" , "span" )
191- policy .AllowAttrs ("aria-label" ).OnElements ("span" )
192-
193- return policy
194- }
195-
196- func addSanitizerRules (policy * bluemonday.Policy , rules []setting.MarkupSanitizerRule ) {
197- for _ , rule := range rules {
198- if rule .AllowDataURIImages {
199- policy .AllowDataURIImages ()
200- }
201- if rule .Element != "" {
202- if rule .Regexp != nil {
203- policy .AllowAttrs (rule .AllowAttr ).Matching (rule .Regexp ).OnElements (rule .Element )
204- } else {
205- policy .AllowAttrs (rule .AllowAttr ).OnElements (rule .Element )
34+ for name , renderer := range renderers {
35+ sanitizerRules := renderer .SanitizerRules ()
36+ if len (sanitizerRules ) > 0 {
37+ policy := defaultSanitizer .createDefaultPolicy ()
38+ defaultSanitizer .addSanitizerRules (policy , sanitizerRules )
39+ defaultSanitizer .rendererPolicies [name ] = policy
20640 }
20741 }
208- }
209- }
210-
211- // SanitizeDescription sanitizes the HTML generated for a repository description.
212- func SanitizeDescription (s string ) string {
213- NewSanitizer ()
214- return sanitizer .descriptionPolicy .Sanitize (s )
215- }
216-
217- // Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
218- func Sanitize (s string ) string {
219- NewSanitizer ()
220- return sanitizer .defaultPolicy .Sanitize (s )
42+ defaultSanitizer .defaultPolicy = defaultSanitizer .createDefaultPolicy ()
43+ defaultSanitizer .descriptionPolicy = defaultSanitizer .createRepoDescriptionPolicy ()
44+ })
45+ return defaultSanitizer
22146}
22247
223- // SanitizeReader sanitizes a Reader
224- func SanitizeReader (r io.Reader , renderer string , w io.Writer ) error {
225- NewSanitizer ()
226- policy , exist := sanitizer .rendererPolicies [renderer ]
227- if ! exist {
228- policy = sanitizer .defaultPolicy
229- }
230- return policy .SanitizeReaderToWriter (r , w )
48+ func ResetDefaultSanitizerForTesting () {
49+ defaultSanitizer = nil
50+ defaultSanitizerOnce = sync.Once {}
23151}
0 commit comments