dompurify 2.2.2 → 2.2.6

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.es.js CHANGED
@@ -1,10 +1,12 @@
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.0.8/LICENSE */
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 */
2
2
 
3
3
  function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
4
4
 
5
5
  var hasOwnProperty = Object.hasOwnProperty,
6
6
  setPrototypeOf = Object.setPrototypeOf,
7
- isFrozen = Object.isFrozen;
7
+ isFrozen = Object.isFrozen,
8
+ getPrototypeOf = Object.getPrototypeOf,
9
+ getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
8
10
  var freeze = Object.freeze,
9
11
  seal = Object.seal,
10
12
  create = Object.create; // eslint-disable-line import/no-mutable-exports
@@ -115,15 +117,48 @@ function clone(object) {
115
117
  return newObject;
116
118
  }
117
119
 
120
+ /* IE10 doesn't support __lookupGetter__ so lets'
121
+ * simulate it. It also automatically checks
122
+ * if the prop is function or getter and behaves
123
+ * accordingly. */
124
+ function lookupGetter(object, prop) {
125
+ while (object !== null) {
126
+ var desc = getOwnPropertyDescriptor(object, prop);
127
+ if (desc) {
128
+ if (desc.get) {
129
+ return unapply(desc.get);
130
+ }
131
+
132
+ if (typeof desc.value === 'function') {
133
+ return unapply(desc.value);
134
+ }
135
+ }
136
+
137
+ object = getPrototypeOf(object);
138
+ }
139
+
140
+ return null;
141
+ }
142
+
118
143
  var html = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
119
144
 
120
145
  // SVG
121
- var svg = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'audio', 'canvas', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'video', 'view', 'vkern']);
146
+ var svg = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
122
147
 
123
148
  var svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
124
149
 
150
+ // List of SVG elements that are disallowed by default.
151
+ // We still need to know them so that we can do namespace
152
+ // checks properly in case one wants to add them to
153
+ // allow-list.
154
+ var svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'fedropshadow', 'feimage', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
155
+
125
156
  var mathMl = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover']);
126
157
 
158
+ // Similarly to SVG, we want to know all MathML elements,
159
+ // even those that we disallow by default.
160
+ var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
161
+
127
162
  var text = freeze(['#text']);
128
163
 
129
164
  var html$1 = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns']);
@@ -203,7 +238,7 @@ function createDOMPurify() {
203
238
  * Version label, exposed for easier checks
204
239
  * if DOMPurify is up to date or not
205
240
  */
206
- DOMPurify.version = '2.2.2';
241
+ DOMPurify.version = '2.2.6';
207
242
 
208
243
  /**
209
244
  * Array of elements that DOMPurify removed during sanitation.
@@ -225,6 +260,7 @@ function createDOMPurify() {
225
260
  var DocumentFragment = window.DocumentFragment,
226
261
  HTMLTemplateElement = window.HTMLTemplateElement,
227
262
  Node = window.Node,
263
+ Element = window.Element,
228
264
  NodeFilter = window.NodeFilter,
229
265
  _window$NamedNodeMap = window.NamedNodeMap,
230
266
  NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
@@ -233,13 +269,20 @@ function createDOMPurify() {
233
269
  DOMParser = window.DOMParser,
234
270
  trustedTypes = window.trustedTypes;
235
271
 
272
+
273
+ var ElementPrototype = Element.prototype;
274
+
275
+ var cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
276
+ var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
277
+ var getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
278
+ var getParentNode = lookupGetter(ElementPrototype, 'parentNode');
279
+
236
280
  // As per issue #47, the web-components registry is inherited by a
237
281
  // new document created via createHTMLDocument. As per the spec
238
282
  // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
239
283
  // a new empty registry is used when creating a template contents owner
240
284
  // document, so we use that as our parent document to ensure nothing
241
285
  // is inherited.
242
-
243
286
  if (typeof HTMLTemplateElement === 'function') {
244
287
  var template = document.createElement('template');
245
288
  if (template.content && template.content.ownerDocument) {
@@ -361,7 +404,7 @@ function createDOMPurify() {
361
404
  var USE_PROFILES = {};
362
405
 
363
406
  /* Tags to ignore content of when KEEP_CONTENT is true */
364
- var FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
407
+ 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']);
365
408
 
366
409
  /* Tags that are safe for data: URIs */
367
410
  var DATA_URI_TAGS = null;
@@ -502,6 +545,115 @@ function createDOMPurify() {
502
545
  CONFIG = cfg;
503
546
  };
504
547
 
548
+ var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
549
+
550
+ var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
551
+
552
+ /* Keep track of all possible SVG and MathML tags
553
+ * so that we can perform the namespace checks
554
+ * correctly. */
555
+ var ALL_SVG_TAGS = addToSet({}, svg);
556
+ addToSet(ALL_SVG_TAGS, svgFilters);
557
+ addToSet(ALL_SVG_TAGS, svgDisallowed);
558
+
559
+ var ALL_MATHML_TAGS = addToSet({}, mathMl);
560
+ addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
561
+
562
+ var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
563
+ var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
564
+ var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
565
+
566
+ /**
567
+ *
568
+ *
569
+ * @param {Element} element a DOM element whose namespace is being checked
570
+ * @returns {boolean} Return false if the element has a
571
+ * namespace that a spec-compliant parser would never
572
+ * return. Return true otherwise.
573
+ */
574
+ var _checkValidNamespace = function _checkValidNamespace(element) {
575
+ var parent = getParentNode(element);
576
+
577
+ // In JSDOM, if we're inside shadow DOM, then parentNode
578
+ // can be null. We just simulate parent in this case.
579
+ if (!parent || !parent.tagName) {
580
+ parent = {
581
+ namespaceURI: HTML_NAMESPACE,
582
+ tagName: 'template'
583
+ };
584
+ }
585
+
586
+ var tagName = stringToLowerCase(element.tagName);
587
+ var parentTagName = stringToLowerCase(parent.tagName);
588
+
589
+ if (element.namespaceURI === SVG_NAMESPACE) {
590
+ // The only way to switch from HTML namespace to SVG
591
+ // is via <svg>. If it happens via any other tag, then
592
+ // it should be killed.
593
+ if (parent.namespaceURI === HTML_NAMESPACE) {
594
+ return tagName === 'svg';
595
+ }
596
+
597
+ // The only way to switch from MathML to SVG is via
598
+ // svg if parent is either <annotation-xml> or MathML
599
+ // text integration points.
600
+ if (parent.namespaceURI === MATHML_NAMESPACE) {
601
+ return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
602
+ }
603
+
604
+ // We only allow elements that are defined in SVG
605
+ // spec. All others are disallowed in SVG namespace.
606
+ return Boolean(ALL_SVG_TAGS[tagName]);
607
+ }
608
+
609
+ if (element.namespaceURI === MATHML_NAMESPACE) {
610
+ // The only way to switch from HTML namespace to MathML
611
+ // is via <math>. If it happens via any other tag, then
612
+ // it should be killed.
613
+ if (parent.namespaceURI === HTML_NAMESPACE) {
614
+ return tagName === 'math';
615
+ }
616
+
617
+ // The only way to switch from SVG to MathML is via
618
+ // <math> and HTML integration points
619
+ if (parent.namespaceURI === SVG_NAMESPACE) {
620
+ return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
621
+ }
622
+
623
+ // We only allow elements that are defined in MathML
624
+ // spec. All others are disallowed in MathML namespace.
625
+ return Boolean(ALL_MATHML_TAGS[tagName]);
626
+ }
627
+
628
+ if (element.namespaceURI === HTML_NAMESPACE) {
629
+ // The only way to switch from SVG to HTML is via
630
+ // HTML integration points, and from MathML to HTML
631
+ // is via MathML text integration points
632
+ if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
633
+ return false;
634
+ }
635
+
636
+ if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
637
+ return false;
638
+ }
639
+
640
+ // Certain elements are allowed in both SVG and HTML
641
+ // namespace. We need to specify them explicitly
642
+ // so that they don't get erronously deleted from
643
+ // HTML namespace.
644
+ var commonSvgAndHTMLElements = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
645
+
646
+ // We disallow tags that are specific for MathML
647
+ // or SVG and should never appear in HTML namespace
648
+ return !ALL_MATHML_TAGS[tagName] && (commonSvgAndHTMLElements[tagName] || !ALL_SVG_TAGS[tagName]);
649
+ }
650
+
651
+ // The code should never reach this place (this means
652
+ // that the element somehow got namespace that is not
653
+ // HTML, SVG or MathML). Return false just in case.
654
+ return false;
655
+ };
656
+
505
657
  /**
506
658
  * _forceRemove
507
659
  *
@@ -512,7 +664,11 @@ function createDOMPurify() {
512
664
  try {
513
665
  node.parentNode.removeChild(node);
514
666
  } catch (_) {
515
- node.outerHTML = emptyHTML;
667
+ try {
668
+ node.outerHTML = emptyHTML;
669
+ } catch (_) {
670
+ node.remove();
671
+ }
516
672
  }
517
673
  };
518
674
 
@@ -604,7 +760,7 @@ function createDOMPurify() {
604
760
  return false;
605
761
  }
606
762
 
607
- if (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string') {
763
+ if (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function') {
608
764
  return true;
609
765
  }
610
766
 
@@ -676,14 +832,8 @@ function createDOMPurify() {
676
832
  allowedTags: ALLOWED_TAGS
677
833
  });
678
834
 
679
- /* Take care of an mXSS pattern using p, br inside svg, math */
680
- if ((tagName === 'svg' || tagName === 'math') && currentNode.querySelectorAll('p, br, form, table').length !== 0) {
681
- _forceRemove(currentNode);
682
- return true;
683
- }
684
-
685
835
  /* Detect mXSS attempts abusing namespace confusion */
686
- if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[!/\w]/g, currentNode.innerHTML) && regExpTest(/<[!/\w]/g, currentNode.textContent)) {
836
+ if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
687
837
  _forceRemove(currentNode);
688
838
  return true;
689
839
  }
@@ -691,18 +841,25 @@ function createDOMPurify() {
691
841
  /* Remove element if anything forbids its presence */
692
842
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
693
843
  /* Keep content except for bad-listed elements */
694
- if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && typeof currentNode.insertAdjacentHTML === 'function') {
695
- try {
696
- var htmlToInsert = currentNode.innerHTML;
697
- currentNode.insertAdjacentHTML('AfterEnd', trustedTypesPolicy ? trustedTypesPolicy.createHTML(htmlToInsert) : htmlToInsert);
698
- } catch (_) {}
844
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
845
+ var parentNode = getParentNode(currentNode);
846
+ var childNodes = getChildNodes(currentNode);
847
+ var childCount = childNodes.length;
848
+ for (var i = childCount - 1; i >= 0; --i) {
849
+ parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
850
+ }
699
851
  }
700
852
 
701
853
  _forceRemove(currentNode);
702
854
  return true;
703
855
  }
704
856
 
705
- /* Remove in case a noscript/noembed XSS is suspected */
857
+ /* Check whether element has a valid namespace */
858
+ if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
859
+ _forceRemove(currentNode);
860
+ return true;
861
+ }
862
+
706
863
  if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
707
864
  _forceRemove(currentNode);
708
865
  return true;