OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of dart.dom.html; | 5 part of dart.dom.html; |
6 | 6 |
7 | |
8 /** | 7 /** |
9 * Class which helps construct standard node validation policies. | 8 * Class which helps construct standard node validation policies. |
10 * | 9 * |
11 * By default this will not accept anything, but the 'allow*' functions can be | 10 * By default this will not accept anything, but the 'allow*' functions can be |
12 * used to expand what types of elements or attributes are allowed. | 11 * used to expand what types of elements or attributes are allowed. |
13 * | 12 * |
14 * All allow functions are additive- elements will be accepted if they are | 13 * All allow functions are additive- elements will be accepted if they are |
15 * accepted by any specific rule. | 14 * accepted by any specific rule. |
16 * | 15 * |
17 * It is important to remember that sanitization is not just intended to prevent | 16 * It is important to remember that sanitization is not just intended to prevent |
18 * cross-site scripting attacks, but also to prevent information from being | 17 * cross-site scripting attacks, but also to prevent information from being |
19 * displayed in unexpected ways. For example something displaying basic | 18 * displayed in unexpected ways. For example something displaying basic |
20 * formatted text may not expect `<video>` tags to appear. In this case an | 19 * formatted text may not expect `<video>` tags to appear. In this case an |
21 * empty NodeValidatorBuilder with just [allowTextElements] might be | 20 * empty NodeValidatorBuilder with just [allowTextElements] might be |
22 * appropriate. | 21 * appropriate. |
23 */ | 22 */ |
24 class NodeValidatorBuilder implements NodeValidator { | 23 class NodeValidatorBuilder implements NodeValidator { |
25 | |
26 final List<NodeValidator> _validators = <NodeValidator>[]; | 24 final List<NodeValidator> _validators = <NodeValidator>[]; |
27 | 25 |
28 NodeValidatorBuilder() { | 26 NodeValidatorBuilder() {} |
29 } | |
30 | 27 |
31 /** | 28 /** |
32 * Creates a new NodeValidatorBuilder which accepts common constructs. | 29 * Creates a new NodeValidatorBuilder which accepts common constructs. |
33 * | 30 * |
34 * By default this will accept HTML5 elements and attributes with the default | 31 * By default this will accept HTML5 elements and attributes with the default |
35 * [UriPolicy] and templating elements. | 32 * [UriPolicy] and templating elements. |
36 * | 33 * |
37 * Notable syntax which is filtered: | 34 * Notable syntax which is filtered: |
38 * | 35 * |
39 * * Only known-good HTML5 elements and attributes are allowed. | 36 * * Only known-good HTML5 elements and attributes are allowed. |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
148 * Allow custom elements with the specified tag name and specified attributes. | 145 * Allow custom elements with the specified tag name and specified attributes. |
149 * | 146 * |
150 * This will allow the elements as custom tags (such as <x-foo></x-foo>), | 147 * This will allow the elements as custom tags (such as <x-foo></x-foo>), |
151 * but will not allow tag extensions. Use [allowTagExtension] to allow | 148 * but will not allow tag extensions. Use [allowTagExtension] to allow |
152 * tag extensions. | 149 * tag extensions. |
153 */ | 150 */ |
154 void allowCustomElement(String tagName, | 151 void allowCustomElement(String tagName, |
155 {UriPolicy uriPolicy, | 152 {UriPolicy uriPolicy, |
156 Iterable<String> attributes, | 153 Iterable<String> attributes, |
157 Iterable<String> uriAttributes}) { | 154 Iterable<String> uriAttributes}) { |
158 | |
159 var tagNameUpper = tagName.toUpperCase(); | 155 var tagNameUpper = tagName.toUpperCase(); |
160 var attrs; | 156 var attrs = attributes != null |
161 if (attributes != null) { | 157 ? attributes |
162 attrs = | 158 .map /*<String>*/ ((name) => '$tagNameUpper::${name.toLowerCase()}') |
Alan Knight
2016/04/19 18:24:34
A null-aware operator to simplify this? I think it
Jacob
2016/04/19 21:50:46
Much better... Done.
| |
163 attributes.map((name) => '$tagNameUpper::${name.toLowerCase()}'); | 159 : null; |
164 } | 160 var uriAttrs = uriAttributes != null |
165 var uriAttrs; | 161 ? uriAttributes |
166 if (uriAttributes != null) { | 162 .map /*<String>*/ ((name) => '$tagNameUpper::${name.toLowerCase()}') |
167 uriAttrs = | 163 : null; |
168 uriAttributes.map((name) => '$tagNameUpper::${name.toLowerCase()}'); | |
169 } | |
170 if (uriPolicy == null) { | 164 if (uriPolicy == null) { |
171 uriPolicy = new UriPolicy(); | 165 uriPolicy = new UriPolicy(); |
172 } | 166 } |
173 | 167 |
174 add(new _CustomElementNodeValidator( | 168 add(new _CustomElementNodeValidator( |
175 uriPolicy, | 169 uriPolicy, [tagNameUpper], attrs, uriAttrs, false, true)); |
176 [tagNameUpper], | |
177 attrs, | |
178 uriAttrs, | |
179 false, | |
180 true)); | |
181 } | 170 } |
182 | 171 |
183 /** | 172 /** |
184 * Allow custom tag extensions with the specified type name and specified | 173 * Allow custom tag extensions with the specified type name and specified |
185 * attributes. | 174 * attributes. |
186 * | 175 * |
187 * This will allow tag extensions (such as <div is="x-foo"></div>), | 176 * This will allow tag extensions (such as <div is="x-foo"></div>), |
188 * but will not allow custom tags. Use [allowCustomElement] to allow | 177 * but will not allow custom tags. Use [allowCustomElement] to allow |
189 * custom tags. | 178 * custom tags. |
190 */ | 179 */ |
191 void allowTagExtension(String tagName, String baseName, | 180 void allowTagExtension(String tagName, String baseName, |
192 {UriPolicy uriPolicy, | 181 {UriPolicy uriPolicy, |
193 Iterable<String> attributes, | 182 Iterable<String> attributes, |
194 Iterable<String> uriAttributes}) { | 183 Iterable<String> uriAttributes}) { |
195 | |
196 var baseNameUpper = baseName.toUpperCase(); | 184 var baseNameUpper = baseName.toUpperCase(); |
197 var tagNameUpper = tagName.toUpperCase(); | 185 var tagNameUpper = tagName.toUpperCase(); |
198 var attrs; | 186 var attrs = attributes != null |
199 if (attributes != null) { | 187 ? attributes.map /*<String>*/ ( |
200 attrs = | 188 (name) => '$baseNameUpper::${name.toLowerCase()}') |
201 attributes.map((name) => '$baseNameUpper::${name.toLowerCase()}'); | 189 : null; |
202 } | 190 var uriAttrs = uriAttributes != null |
203 var uriAttrs; | 191 ? uriAttributes.map /*<String>*/ ( |
204 if (uriAttributes != null) { | 192 (name) => '$baseNameUpper::${name.toLowerCase()}') |
205 uriAttrs = | 193 : null; |
206 uriAttributes.map((name) => '$baseNameUpper::${name.toLowerCase()}'); | |
207 } | |
208 if (uriPolicy == null) { | 194 if (uriPolicy == null) { |
209 uriPolicy = new UriPolicy(); | 195 uriPolicy = new UriPolicy(); |
210 } | 196 } |
211 | 197 |
212 add(new _CustomElementNodeValidator( | 198 add(new _CustomElementNodeValidator(uriPolicy, |
213 uriPolicy, | 199 [tagNameUpper, baseNameUpper], attrs, uriAttrs, true, false)); |
214 [tagNameUpper, baseNameUpper], | |
215 attrs, | |
216 uriAttrs, | |
217 true, | |
218 false)); | |
219 } | 200 } |
220 | 201 |
221 void allowElement(String tagName, {UriPolicy uriPolicy, | 202 void allowElement(String tagName, |
222 Iterable<String> attributes, | 203 {UriPolicy uriPolicy, |
223 Iterable<String> uriAttributes}) { | 204 Iterable<String> attributes, |
224 | 205 Iterable<String> uriAttributes}) { |
225 allowCustomElement(tagName, uriPolicy: uriPolicy, | 206 allowCustomElement(tagName, |
207 uriPolicy: uriPolicy, | |
226 attributes: attributes, | 208 attributes: attributes, |
227 uriAttributes: uriAttributes); | 209 uriAttributes: uriAttributes); |
228 } | 210 } |
229 | 211 |
230 /** | 212 /** |
231 * Allow templating elements (such as <template> and template-related | 213 * Allow templating elements (such as <template> and template-related |
232 * attributes. | 214 * attributes. |
233 * | 215 * |
234 * This still requires other validators to allow regular attributes to be | 216 * This still requires other validators to allow regular attributes to be |
235 * bound (such as [allowHtml5]). | 217 * bound (such as [allowHtml5]). |
(...skipping 10 matching lines...) Expand all Loading... | |
246 */ | 228 */ |
247 void add(NodeValidator validator) { | 229 void add(NodeValidator validator) { |
248 _validators.add(validator); | 230 _validators.add(validator); |
249 } | 231 } |
250 | 232 |
251 bool allowsElement(Element element) { | 233 bool allowsElement(Element element) { |
252 return _validators.any((v) => v.allowsElement(element)); | 234 return _validators.any((v) => v.allowsElement(element)); |
253 } | 235 } |
254 | 236 |
255 bool allowsAttribute(Element element, String attributeName, String value) { | 237 bool allowsAttribute(Element element, String attributeName, String value) { |
256 return _validators.any( | 238 return _validators |
257 (v) => v.allowsAttribute(element, attributeName, value)); | 239 .any((v) => v.allowsAttribute(element, attributeName, value)); |
258 } | 240 } |
259 } | 241 } |
260 | 242 |
261 class _SimpleNodeValidator implements NodeValidator { | 243 class _SimpleNodeValidator implements NodeValidator { |
262 final Set<String> allowedElements = new Set<String>(); | 244 final Set<String> allowedElements = new Set<String>(); |
263 final Set<String> allowedAttributes = new Set<String>(); | 245 final Set<String> allowedAttributes = new Set<String>(); |
264 final Set<String> allowedUriAttributes = new Set<String>(); | 246 final Set<String> allowedUriAttributes = new Set<String>(); |
265 final UriPolicy uriPolicy; | 247 final UriPolicy uriPolicy; |
266 | 248 |
267 factory _SimpleNodeValidator.allowNavigation(UriPolicy uriPolicy) { | 249 factory _SimpleNodeValidator.allowNavigation(UriPolicy uriPolicy) { |
268 return new _SimpleNodeValidator(uriPolicy, | 250 return new _SimpleNodeValidator(uriPolicy, allowedElements: const [ |
269 allowedElements: const [ | 251 'A', |
270 'A', | 252 'FORM' |
271 'FORM'], | 253 ], allowedAttributes: const [ |
272 allowedAttributes: const [ | 254 'A::accesskey', |
273 'A::accesskey', | 255 'A::coords', |
274 'A::coords', | 256 'A::hreflang', |
275 'A::hreflang', | 257 'A::name', |
276 'A::name', | 258 'A::shape', |
277 'A::shape', | 259 'A::tabindex', |
278 'A::tabindex', | 260 'A::target', |
279 'A::target', | 261 'A::type', |
280 'A::type', | 262 'FORM::accept', |
281 'FORM::accept', | 263 'FORM::autocomplete', |
282 'FORM::autocomplete', | 264 'FORM::enctype', |
283 'FORM::enctype', | 265 'FORM::method', |
284 'FORM::method', | 266 'FORM::name', |
285 'FORM::name', | 267 'FORM::novalidate', |
286 'FORM::novalidate', | 268 'FORM::target', |
287 'FORM::target', | 269 ], allowedUriAttributes: const [ |
288 ], | 270 'A::href', |
289 allowedUriAttributes: const [ | 271 'FORM::action', |
290 'A::href', | 272 ]); |
291 'FORM::action', | |
292 ]); | |
293 } | 273 } |
294 | 274 |
295 factory _SimpleNodeValidator.allowImages(UriPolicy uriPolicy) { | 275 factory _SimpleNodeValidator.allowImages(UriPolicy uriPolicy) { |
296 return new _SimpleNodeValidator(uriPolicy, | 276 return new _SimpleNodeValidator(uriPolicy, allowedElements: const [ |
297 allowedElements: const [ | 277 'IMG' |
298 'IMG' | 278 ], allowedAttributes: const [ |
299 ], | 279 'IMG::align', |
300 allowedAttributes: const [ | 280 'IMG::alt', |
301 'IMG::align', | 281 'IMG::border', |
302 'IMG::alt', | 282 'IMG::height', |
303 'IMG::border', | 283 'IMG::hspace', |
304 'IMG::height', | 284 'IMG::ismap', |
305 'IMG::hspace', | 285 'IMG::name', |
306 'IMG::ismap', | 286 'IMG::usemap', |
307 'IMG::name', | 287 'IMG::vspace', |
308 'IMG::usemap', | 288 'IMG::width', |
309 'IMG::vspace', | 289 ], allowedUriAttributes: const [ |
310 'IMG::width', | 290 'IMG::src', |
311 ], | 291 ]); |
312 allowedUriAttributes: const [ | |
313 'IMG::src', | |
314 ]); | |
315 } | 292 } |
316 | 293 |
317 factory _SimpleNodeValidator.allowTextElements() { | 294 factory _SimpleNodeValidator.allowTextElements() { |
318 return new _SimpleNodeValidator(null, | 295 return new _SimpleNodeValidator(null, allowedElements: const [ |
319 allowedElements: const [ | 296 'B', |
320 'B', | 297 'BLOCKQUOTE', |
321 'BLOCKQUOTE', | 298 'BR', |
322 'BR', | 299 'EM', |
323 'EM', | 300 'H1', |
324 'H1', | 301 'H2', |
325 'H2', | 302 'H3', |
326 'H3', | 303 'H4', |
327 'H4', | 304 'H5', |
328 'H5', | 305 'H6', |
329 'H6', | 306 'HR', |
330 'HR', | 307 'I', |
331 'I', | 308 'LI', |
332 'LI', | 309 'OL', |
333 'OL', | 310 'P', |
334 'P', | 311 'SPAN', |
335 'SPAN', | 312 'UL', |
336 'UL', | 313 ]); |
337 ]); | |
338 } | 314 } |
339 | 315 |
340 /** | 316 /** |
341 * Elements must be uppercased tag names. For example `'IMG'`. | 317 * Elements must be uppercased tag names. For example `'IMG'`. |
342 * Attributes must be uppercased tag name followed by :: followed by | 318 * Attributes must be uppercased tag name followed by :: followed by |
343 * lowercase attribute name. For example `'IMG:src'`. | 319 * lowercase attribute name. For example `'IMG:src'`. |
344 */ | 320 */ |
345 _SimpleNodeValidator(this.uriPolicy, | 321 _SimpleNodeValidator(this.uriPolicy, |
346 {Iterable<String> allowedElements, Iterable<String> allowedAttributes, | 322 {Iterable<String> allowedElements, |
347 Iterable<String> allowedUriAttributes}) { | 323 Iterable<String> allowedAttributes, |
324 Iterable<String> allowedUriAttributes}) { | |
348 this.allowedElements.addAll(allowedElements ?? const []); | 325 this.allowedElements.addAll(allowedElements ?? const []); |
349 allowedAttributes = allowedAttributes ?? const []; | 326 allowedAttributes = allowedAttributes ?? const []; |
350 allowedUriAttributes = allowedUriAttributes ?? const []; | 327 allowedUriAttributes = allowedUriAttributes ?? const []; |
351 var legalAttributes = allowedAttributes.where( | 328 var legalAttributes = allowedAttributes |
352 (x) => !_Html5NodeValidator._uriAttributes.contains(x)); | 329 .where((x) => !_Html5NodeValidator._uriAttributes.contains(x)); |
353 var extraUriAttributes = allowedAttributes.where( | 330 var extraUriAttributes = allowedAttributes |
354 (x) => _Html5NodeValidator._uriAttributes.contains(x)); | 331 .where((x) => _Html5NodeValidator._uriAttributes.contains(x)); |
355 this.allowedAttributes.addAll(legalAttributes); | 332 this.allowedAttributes.addAll(legalAttributes); |
356 this.allowedUriAttributes.addAll(allowedUriAttributes); | 333 this.allowedUriAttributes.addAll(allowedUriAttributes); |
357 this.allowedUriAttributes.addAll(extraUriAttributes); | 334 this.allowedUriAttributes.addAll(extraUriAttributes); |
358 } | 335 } |
359 | 336 |
360 bool allowsElement(Element element) { | 337 bool allowsElement(Element element) { |
361 return allowedElements.contains(Element._safeTagName(element)); | 338 return allowedElements.contains(Element._safeTagName(element)); |
362 } | 339 } |
363 | 340 |
364 bool allowsAttribute(Element element, String attributeName, String value) { | 341 bool allowsAttribute(Element element, String attributeName, String value) { |
(...skipping 12 matching lines...) Expand all Loading... | |
377 return true; | 354 return true; |
378 } | 355 } |
379 return false; | 356 return false; |
380 } | 357 } |
381 } | 358 } |
382 | 359 |
383 class _CustomElementNodeValidator extends _SimpleNodeValidator { | 360 class _CustomElementNodeValidator extends _SimpleNodeValidator { |
384 final bool allowTypeExtension; | 361 final bool allowTypeExtension; |
385 final bool allowCustomTag; | 362 final bool allowCustomTag; |
386 | 363 |
387 _CustomElementNodeValidator(UriPolicy uriPolicy, | 364 _CustomElementNodeValidator( |
365 UriPolicy uriPolicy, | |
388 Iterable<String> allowedElements, | 366 Iterable<String> allowedElements, |
389 Iterable<String> allowedAttributes, | 367 Iterable<String> allowedAttributes, |
390 Iterable<String> allowedUriAttributes, | 368 Iterable<String> allowedUriAttributes, |
391 bool allowTypeExtension, | 369 bool allowTypeExtension, |
392 bool allowCustomTag): | 370 bool allowCustomTag) |
393 | 371 : this.allowTypeExtension = allowTypeExtension == true, |
394 super(uriPolicy, | 372 this.allowCustomTag = allowCustomTag == true, |
395 allowedElements: allowedElements, | 373 super(uriPolicy, |
396 allowedAttributes: allowedAttributes, | 374 allowedElements: allowedElements, |
397 allowedUriAttributes: allowedUriAttributes), | 375 allowedAttributes: allowedAttributes, |
398 this.allowTypeExtension = allowTypeExtension == true, | 376 allowedUriAttributes: allowedUriAttributes); |
399 this.allowCustomTag = allowCustomTag == true; | |
400 | 377 |
401 bool allowsElement(Element element) { | 378 bool allowsElement(Element element) { |
402 if (allowTypeExtension) { | 379 if (allowTypeExtension) { |
403 var isAttr = element.attributes['is']; | 380 var isAttr = element.attributes['is']; |
404 if (isAttr != null) { | 381 if (isAttr != null) { |
405 return allowedElements.contains(isAttr.toUpperCase()) && | 382 return allowedElements.contains(isAttr.toUpperCase()) && |
406 allowedElements.contains(Element._safeTagName(element)); | 383 allowedElements.contains(Element._safeTagName(element)); |
407 } | 384 } |
408 } | 385 } |
409 return allowCustomTag && allowedElements.contains(Element._safeTagName(eleme nt)); | 386 return allowCustomTag && |
387 allowedElements.contains(Element._safeTagName(element)); | |
410 } | 388 } |
411 | 389 |
412 bool allowsAttribute(Element element, String attributeName, String value) { | 390 bool allowsAttribute(Element element, String attributeName, String value) { |
413 if (allowsElement(element)) { | 391 if (allowsElement(element)) { |
414 if (allowTypeExtension && attributeName == 'is' && | 392 if (allowTypeExtension && |
393 attributeName == 'is' && | |
415 allowedElements.contains(value.toUpperCase())) { | 394 allowedElements.contains(value.toUpperCase())) { |
416 return true; | 395 return true; |
417 } | 396 } |
418 return super.allowsAttribute(element, attributeName, value); | 397 return super.allowsAttribute(element, attributeName, value); |
419 } | 398 } |
420 return false; | 399 return false; |
421 } | 400 } |
422 } | 401 } |
423 | 402 |
424 class _TemplatingNodeValidator extends _SimpleNodeValidator { | 403 class _TemplatingNodeValidator extends _SimpleNodeValidator { |
425 static const _TEMPLATE_ATTRS = | 404 static const _TEMPLATE_ATTRS = const <String>[ |
426 const <String>['bind', 'if', 'ref', 'repeat', 'syntax']; | 405 'bind', |
406 'if', | |
407 'ref', | |
408 'repeat', | |
409 'syntax' | |
410 ]; | |
427 | 411 |
428 final Set<String> _templateAttrs; | 412 final Set<String> _templateAttrs; |
429 | 413 |
430 _TemplatingNodeValidator(): | 414 _TemplatingNodeValidator() |
431 super(null, | 415 : _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS), |
432 allowedElements: [ | 416 super(null, |
433 'TEMPLATE' | 417 allowedElements: ['TEMPLATE'], |
434 ], | 418 allowedAttributes: |
435 allowedAttributes: _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')), | 419 _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')) {} |
436 _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS) { | |
437 } | |
438 | 420 |
439 bool allowsAttribute(Element element, String attributeName, String value) { | 421 bool allowsAttribute(Element element, String attributeName, String value) { |
440 if (super.allowsAttribute(element, attributeName, value)) { | 422 if (super.allowsAttribute(element, attributeName, value)) { |
441 return true; | 423 return true; |
442 } | 424 } |
443 | 425 |
444 if (attributeName == 'template' && value == "") { | 426 if (attributeName == 'template' && value == "") { |
445 return true; | 427 return true; |
446 } | 428 } |
447 | 429 |
448 if (element.attributes['template'] == "" ) { | 430 if (element.attributes['template'] == "") { |
449 return _templateAttrs.contains(attributeName); | 431 return _templateAttrs.contains(attributeName); |
450 } | 432 } |
451 return false; | 433 return false; |
452 } | 434 } |
453 } | 435 } |
454 | 436 |
455 | |
456 class _SvgNodeValidator implements NodeValidator { | 437 class _SvgNodeValidator implements NodeValidator { |
457 bool allowsElement(Element element) { | 438 bool allowsElement(Element element) { |
458 if (element is svg.ScriptElement) { | 439 if (element is svg.ScriptElement) { |
459 return false; | 440 return false; |
460 } | 441 } |
461 // Firefox 37 has issues with creating foreign elements inside a | 442 // Firefox 37 has issues with creating foreign elements inside a |
462 // foreignobject tag as SvgElement. We don't want foreignobject contents | 443 // foreignobject tag as SvgElement. We don't want foreignobject contents |
463 // anyway, so just remove the whole tree outright. And we can't rely | 444 // anyway, so just remove the whole tree outright. And we can't rely |
464 // on IE recognizing the SvgForeignObject type, so go by tagName. Bug 23144 | 445 // on IE recognizing the SvgForeignObject type, so go by tagName. Bug 23144 |
465 if (element is svg.SvgElement && Element._safeTagName(element) == 'foreignOb ject') { | 446 if (element is svg.SvgElement && |
447 Element._safeTagName(element) == 'foreignObject') { | |
466 return false; | 448 return false; |
467 } | 449 } |
468 if (element is svg.SvgElement) { | 450 if (element is svg.SvgElement) { |
469 return true; | 451 return true; |
470 } | 452 } |
471 return false; | 453 return false; |
472 } | 454 } |
473 | 455 |
474 bool allowsAttribute(Element element, String attributeName, String value) { | 456 bool allowsAttribute(Element element, String attributeName, String value) { |
475 if (attributeName == 'is' || attributeName.startsWith('on')) { | 457 if (attributeName == 'is' || attributeName.startsWith('on')) { |
476 return false; | 458 return false; |
477 } | 459 } |
478 return allowsElement(element); | 460 return allowsElement(element); |
479 } | 461 } |
480 } | 462 } |
OLD | NEW |