5
5
package markup
6
6
7
7
import (
8
- "io"
9
- "net/url"
10
8
"regexp"
11
9
"sync"
12
10
13
- "code.gitea.io/gitea/modules/setting"
14
-
15
11
"github.com/microcosm-cc/bluemonday"
16
12
)
17
13
@@ -21,211 +17,35 @@ type Sanitizer struct {
21
17
defaultPolicy * bluemonday.Policy
22
18
descriptionPolicy * bluemonday.Policy
23
19
rendererPolicies map [string ]* bluemonday.Policy
24
- init sync. Once
20
+ allowAllRegex * regexp. Regexp
25
21
}
26
22
27
23
var (
28
- sanitizer = & Sanitizer {}
29
- allowAllRegex = regexp . MustCompile ( ".+" )
24
+ defaultSanitizer * Sanitizer
25
+ defaultSanitizerOnce sync. Once
30
26
)
31
27
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 (".+" ),
104
33
}
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
206
40
}
207
41
}
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
221
46
}
222
47
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 {}
231
51
}
0 commit comments