dompurify 2.2.8 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/purify.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @license DOMPurify | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.2.2/LICENSE */
1
+ /*! @license DOMPurify 2.3.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.2/LICENSE */
2
2
 
3
3
  (function (global, factory) {
4
4
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
@@ -249,7 +249,7 @@
249
249
  * Version label, exposed for easier checks
250
250
  * if DOMPurify is up to date or not
251
251
  */
252
- DOMPurify.version = '2.2.8';
252
+ DOMPurify.version = '2.3.2';
253
253
 
254
254
  /**
255
255
  * Array of elements that DOMPurify removed during sanitation.
@@ -307,7 +307,8 @@
307
307
  var _document = document,
308
308
  implementation = _document.implementation,
309
309
  createNodeIterator = _document.createNodeIterator,
310
- createDocumentFragment = _document.createDocumentFragment;
310
+ createDocumentFragment = _document.createDocumentFragment,
311
+ getElementsByTagName = _document.getElementsByTagName;
311
312
  var importNode = originalDocument.importNode;
312
313
 
313
314
 
@@ -414,7 +415,8 @@
414
415
  var USE_PROFILES = {};
415
416
 
416
417
  /* Tags to ignore content of when KEEP_CONTENT is true */
417
- var FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
418
+ var FORBID_CONTENTS = null;
419
+ var DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
418
420
 
419
421
  /* Tags that are safe for data: URIs */
420
422
  var DATA_URI_TAGS = null;
@@ -422,13 +424,20 @@
422
424
 
423
425
  /* Attributes safe for values like "javascript:" */
424
426
  var URI_SAFE_ATTRIBUTES = null;
425
- var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']);
427
+ var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
426
428
 
427
429
  var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
428
430
  var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
429
431
  var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
430
432
  /* Document namespace */
431
433
  var NAMESPACE = HTML_NAMESPACE;
434
+ var IS_EMPTY_INPUT = false;
435
+
436
+ /* Parsing of strict XHTML documents */
437
+ var PARSER_MEDIA_TYPE = void 0;
438
+ var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
439
+ var DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
440
+ var transformCaseFunc = void 0;
432
441
 
433
442
  /* Keep a reference to config to pass to hooks */
434
443
  var CONFIG = null;
@@ -462,6 +471,7 @@
462
471
  ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
463
472
  URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) : DEFAULT_URI_SAFE_ATTRIBUTES;
464
473
  DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS;
474
+ FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS) : DEFAULT_FORBID_CONTENTS;
465
475
  FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {};
466
476
  FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};
467
477
  USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
@@ -479,7 +489,13 @@
479
489
  KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
480
490
  IN_PLACE = cfg.IN_PLACE || false; // Default false
481
491
  IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
482
- NAMESPACE = cfg.NAMESPACE || NAMESPACE;
492
+ NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
493
+ PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE in SUPPORTED_PARSER_MEDIA_TYPES ? cfg.PARSER_MEDIA_TYPE : DEFAULT_PARSER_MEDIA_TYPE;
494
+ // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
495
+ transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? function (x) {
496
+ return x;
497
+ } : stringToLowerCase;
498
+
483
499
  if (SAFE_FOR_TEMPLATES) {
484
500
  ALLOW_DATA_ATTR = false;
485
501
  }
@@ -537,6 +553,14 @@
537
553
  addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
538
554
  }
539
555
 
556
+ if (cfg.FORBID_CONTENTS) {
557
+ if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
558
+ FORBID_CONTENTS = clone(FORBID_CONTENTS);
559
+ }
560
+
561
+ addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS);
562
+ }
563
+
540
564
  /* Add #text in case KEEP_CONTENT is set to true */
541
565
  if (KEEP_CONTENT) {
542
566
  ALLOWED_TAGS['#text'] = true;
@@ -675,6 +699,7 @@
675
699
  var _forceRemove = function _forceRemove(node) {
676
700
  arrayPush(DOMPurify.removed, { element: node });
677
701
  try {
702
+ // eslint-disable-next-line unicorn/prefer-dom-node-remove
678
703
  node.parentNode.removeChild(node);
679
704
  } catch (_) {
680
705
  try {
@@ -739,6 +764,11 @@
739
764
  leadingWhitespace = matches && matches[0];
740
765
  }
741
766
 
767
+ if (PARSER_MEDIA_TYPE === 'application/xhtml+xml') {
768
+ // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
769
+ dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
770
+ }
771
+
742
772
  var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
743
773
  /*
744
774
  * Use the DOMParser API by default, fallback later if needs be
@@ -746,14 +776,18 @@
746
776
  */
747
777
  if (NAMESPACE === HTML_NAMESPACE) {
748
778
  try {
749
- doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
779
+ doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
750
780
  } catch (_) {}
751
781
  }
752
782
 
753
783
  /* Use createHTMLDocument in case DOMParser is not available */
754
784
  if (!doc || !doc.documentElement) {
755
785
  doc = implementation.createDocument(NAMESPACE, 'template', null);
756
- doc.documentElement.innerHTML = dirtyPayload;
786
+ try {
787
+ doc.documentElement.innerHTML = IS_EMPTY_INPUT ? '' : dirtyPayload;
788
+ } catch (_) {
789
+ // Syntax error if dirtyPayload is invalid xml
790
+ }
757
791
  }
758
792
 
759
793
  var body = doc.body || doc.documentElement;
@@ -763,6 +797,10 @@
763
797
  }
764
798
 
765
799
  /* Work on whole document or just its body */
800
+ if (NAMESPACE === HTML_NAMESPACE) {
801
+ return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
802
+ }
803
+
766
804
  return WHOLE_DOCUMENT ? doc.documentElement : body;
767
805
  };
768
806
 
@@ -773,9 +811,7 @@
773
811
  * @return {Iterator} iterator instance
774
812
  */
775
813
  var _createIterator = function _createIterator(root) {
776
- return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, function () {
777
- return NodeFilter.FILTER_ACCEPT;
778
- }, false);
814
+ return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
779
815
  };
780
816
 
781
817
  /**
@@ -853,7 +889,7 @@
853
889
  }
854
890
 
855
891
  /* Now let's check the element's type and name */
856
- var tagName = stringToLowerCase(currentNode.nodeName);
892
+ var tagName = transformCaseFunc(currentNode.nodeName);
857
893
 
858
894
  /* Execute a hook if present */
859
895
  _executeHook('uponSanitizeElement', currentNode, {
@@ -867,6 +903,12 @@
867
903
  return true;
868
904
  }
869
905
 
906
+ /* Mitigate a problem with templates inside select */
907
+ if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) {
908
+ _forceRemove(currentNode);
909
+ return true;
910
+ }
911
+
870
912
  /* Remove element if anything forbids its presence */
871
913
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
872
914
  /* Keep content except for bad-listed elements */
@@ -935,7 +977,7 @@
935
977
  (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
936
978
  XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
937
979
  We don't need to check the value; it's always URI safe. */
938
- if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
980
+ if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
939
981
  return false;
940
982
 
941
983
  /* Check value is safe. First, is attr inert? If so, is safe */
@@ -988,7 +1030,7 @@
988
1030
  namespaceURI = _attr.namespaceURI;
989
1031
 
990
1032
  value = stringTrim(attr.value);
991
- lcName = stringToLowerCase(name);
1033
+ lcName = transformCaseFunc(name);
992
1034
 
993
1035
  /* Execute a hook if present */
994
1036
  hookEvent.attrName = lcName;
@@ -1023,7 +1065,7 @@
1023
1065
  }
1024
1066
 
1025
1067
  /* Is `value` valid for this attribute? */
1026
- var lcTag = currentNode.nodeName.toLowerCase();
1068
+ var lcTag = transformCaseFunc(currentNode.nodeName);
1027
1069
  if (!_isValidAttribute(lcTag, lcName, value)) {
1028
1070
  continue;
1029
1071
  }
@@ -1096,7 +1138,8 @@
1096
1138
  /* Make sure we have a string to sanitize.
1097
1139
  DO NOT return early, as this will return the wrong type if
1098
1140
  the user has requested a DOM object rather than a string */
1099
- if (!dirty) {
1141
+ IS_EMPTY_INPUT = !dirty;
1142
+ if (IS_EMPTY_INPUT) {
1100
1143
  dirty = '<!-->';
1101
1144
  }
1102
1145
 
@@ -1152,7 +1195,7 @@
1152
1195
  } else if (importedNode.nodeName === 'HTML') {
1153
1196
  body = importedNode;
1154
1197
  } else {
1155
- // eslint-disable-next-line unicorn/prefer-node-append
1198
+ // eslint-disable-next-line unicorn/prefer-dom-node-append
1156
1199
  body.appendChild(importedNode);
1157
1200
  }
1158
1201
  } else {
@@ -1216,7 +1259,7 @@
1216
1259
  returnNode = createDocumentFragment.call(body.ownerDocument);
1217
1260
 
1218
1261
  while (body.firstChild) {
1219
- // eslint-disable-next-line unicorn/prefer-node-append
1262
+ // eslint-disable-next-line unicorn/prefer-dom-node-append
1220
1263
  returnNode.appendChild(body.firstChild);
1221
1264
  }
1222
1265
  } else {
@@ -1285,8 +1328,8 @@
1285
1328
  _parseConfig({});
1286
1329
  }
1287
1330
 
1288
- var lcTag = stringToLowerCase(tag);
1289
- var lcName = stringToLowerCase(attr);
1331
+ var lcTag = transformCaseFunc(tag);
1332
+ var lcName = transformCaseFunc(attr);
1290
1333
  return _isValidAttribute(lcTag, lcName, value);
1291
1334
  };
1292
1335