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.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.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 (global, factory) {
4
4
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
@@ -10,7 +10,9 @@
10
10
 
11
11
  var hasOwnProperty = Object.hasOwnProperty,
12
12
  setPrototypeOf = Object.setPrototypeOf,
13
- isFrozen = Object.isFrozen;
13
+ isFrozen = Object.isFrozen,
14
+ getPrototypeOf = Object.getPrototypeOf,
15
+ getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
14
16
  var freeze = Object.freeze,
15
17
  seal = Object.seal,
16
18
  create = Object.create; // eslint-disable-line import/no-mutable-exports
@@ -121,15 +123,48 @@
121
123
  return newObject;
122
124
  }
123
125
 
126
+ /* IE10 doesn't support __lookupGetter__ so lets'
127
+ * simulate it. It also automatically checks
128
+ * if the prop is function or getter and behaves
129
+ * accordingly. */
130
+ function lookupGetter(object, prop) {
131
+ while (object !== null) {
132
+ var desc = getOwnPropertyDescriptor(object, prop);
133
+ if (desc) {
134
+ if (desc.get) {
135
+ return unapply(desc.get);
136
+ }
137
+
138
+ if (typeof desc.value === 'function') {
139
+ return unapply(desc.value);
140
+ }
141
+ }
142
+
143
+ object = getPrototypeOf(object);
144
+ }
145
+
146
+ return null;
147
+ }
148
+
124
149
  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']);
125
150
 
126
151
  // SVG
127
- 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']);
152
+ 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']);
128
153
 
129
154
  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']);
130
155
 
156
+ // List of SVG elements that are disallowed by default.
157
+ // We still need to know them so that we can do namespace
158
+ // checks properly in case one wants to add them to
159
+ // allow-list.
160
+ 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']);
161
+
131
162
  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']);
132
163
 
164
+ // Similarly to SVG, we want to know all MathML elements,
165
+ // even those that we disallow by default.
166
+ var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
167
+
133
168
  var text = freeze(['#text']);
134
169
 
135
170
  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']);
@@ -209,7 +244,7 @@
209
244
  * Version label, exposed for easier checks
210
245
  * if DOMPurify is up to date or not
211
246
  */
212
- DOMPurify.version = '2.2.2';
247
+ DOMPurify.version = '2.2.6';
213
248
 
214
249
  /**
215
250
  * Array of elements that DOMPurify removed during sanitation.
@@ -231,6 +266,7 @@
231
266
  var DocumentFragment = window.DocumentFragment,
232
267
  HTMLTemplateElement = window.HTMLTemplateElement,
233
268
  Node = window.Node,
269
+ Element = window.Element,
234
270
  NodeFilter = window.NodeFilter,
235
271
  _window$NamedNodeMap = window.NamedNodeMap,
236
272
  NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
@@ -239,13 +275,20 @@
239
275
  DOMParser = window.DOMParser,
240
276
  trustedTypes = window.trustedTypes;
241
277
 
278
+
279
+ var ElementPrototype = Element.prototype;
280
+
281
+ var cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
282
+ var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
283
+ var getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
284
+ var getParentNode = lookupGetter(ElementPrototype, 'parentNode');
285
+
242
286
  // As per issue #47, the web-components registry is inherited by a
243
287
  // new document created via createHTMLDocument. As per the spec
244
288
  // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
245
289
  // a new empty registry is used when creating a template contents owner
246
290
  // document, so we use that as our parent document to ensure nothing
247
291
  // is inherited.
248
-
249
292
  if (typeof HTMLTemplateElement === 'function') {
250
293
  var template = document.createElement('template');
251
294
  if (template.content && template.content.ownerDocument) {
@@ -367,7 +410,7 @@
367
410
  var USE_PROFILES = {};
368
411
 
369
412
  /* Tags to ignore content of when KEEP_CONTENT is true */
370
- 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']);
413
+ 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']);
371
414
 
372
415
  /* Tags that are safe for data: URIs */
373
416
  var DATA_URI_TAGS = null;
@@ -508,6 +551,115 @@
508
551
  CONFIG = cfg;
509
552
  };
510
553
 
554
+ var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
555
+
556
+ var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
557
+
558
+ /* Keep track of all possible SVG and MathML tags
559
+ * so that we can perform the namespace checks
560
+ * correctly. */
561
+ var ALL_SVG_TAGS = addToSet({}, svg);
562
+ addToSet(ALL_SVG_TAGS, svgFilters);
563
+ addToSet(ALL_SVG_TAGS, svgDisallowed);
564
+
565
+ var ALL_MATHML_TAGS = addToSet({}, mathMl);
566
+ addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
567
+
568
+ var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
569
+ var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
570
+ var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
571
+
572
+ /**
573
+ *
574
+ *
575
+ * @param {Element} element a DOM element whose namespace is being checked
576
+ * @returns {boolean} Return false if the element has a
577
+ * namespace that a spec-compliant parser would never
578
+ * return. Return true otherwise.
579
+ */
580
+ var _checkValidNamespace = function _checkValidNamespace(element) {
581
+ var parent = getParentNode(element);
582
+
583
+ // In JSDOM, if we're inside shadow DOM, then parentNode
584
+ // can be null. We just simulate parent in this case.
585
+ if (!parent || !parent.tagName) {
586
+ parent = {
587
+ namespaceURI: HTML_NAMESPACE,
588
+ tagName: 'template'
589
+ };
590
+ }
591
+
592
+ var tagName = stringToLowerCase(element.tagName);
593
+ var parentTagName = stringToLowerCase(parent.tagName);
594
+
595
+ if (element.namespaceURI === SVG_NAMESPACE) {
596
+ // The only way to switch from HTML namespace to SVG
597
+ // is via <svg>. If it happens via any other tag, then
598
+ // it should be killed.
599
+ if (parent.namespaceURI === HTML_NAMESPACE) {
600
+ return tagName === 'svg';
601
+ }
602
+
603
+ // The only way to switch from MathML to SVG is via
604
+ // svg if parent is either <annotation-xml> or MathML
605
+ // text integration points.
606
+ if (parent.namespaceURI === MATHML_NAMESPACE) {
607
+ return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
608
+ }
609
+
610
+ // We only allow elements that are defined in SVG
611
+ // spec. All others are disallowed in SVG namespace.
612
+ return Boolean(ALL_SVG_TAGS[tagName]);
613
+ }
614
+
615
+ if (element.namespaceURI === MATHML_NAMESPACE) {
616
+ // The only way to switch from HTML namespace to MathML
617
+ // is via <math>. If it happens via any other tag, then
618
+ // it should be killed.
619
+ if (parent.namespaceURI === HTML_NAMESPACE) {
620
+ return tagName === 'math';
621
+ }
622
+
623
+ // The only way to switch from SVG to MathML is via
624
+ // <math> and HTML integration points
625
+ if (parent.namespaceURI === SVG_NAMESPACE) {
626
+ return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
627
+ }
628
+
629
+ // We only allow elements that are defined in MathML
630
+ // spec. All others are disallowed in MathML namespace.
631
+ return Boolean(ALL_MATHML_TAGS[tagName]);
632
+ }
633
+
634
+ if (element.namespaceURI === HTML_NAMESPACE) {
635
+ // The only way to switch from SVG to HTML is via
636
+ // HTML integration points, and from MathML to HTML
637
+ // is via MathML text integration points
638
+ if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
639
+ return false;
640
+ }
641
+
642
+ if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
643
+ return false;
644
+ }
645
+
646
+ // Certain elements are allowed in both SVG and HTML
647
+ // namespace. We need to specify them explicitly
648
+ // so that they don't get erronously deleted from
649
+ // HTML namespace.
650
+ var commonSvgAndHTMLElements = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
651
+
652
+ // We disallow tags that are specific for MathML
653
+ // or SVG and should never appear in HTML namespace
654
+ return !ALL_MATHML_TAGS[tagName] && (commonSvgAndHTMLElements[tagName] || !ALL_SVG_TAGS[tagName]);
655
+ }
656
+
657
+ // The code should never reach this place (this means
658
+ // that the element somehow got namespace that is not
659
+ // HTML, SVG or MathML). Return false just in case.
660
+ return false;
661
+ };
662
+
511
663
  /**
512
664
  * _forceRemove
513
665
  *
@@ -518,7 +670,11 @@
518
670
  try {
519
671
  node.parentNode.removeChild(node);
520
672
  } catch (_) {
521
- node.outerHTML = emptyHTML;
673
+ try {
674
+ node.outerHTML = emptyHTML;
675
+ } catch (_) {
676
+ node.remove();
677
+ }
522
678
  }
523
679
  };
524
680
 
@@ -610,7 +766,7 @@
610
766
  return false;
611
767
  }
612
768
 
613
- 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') {
769
+ 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') {
614
770
  return true;
615
771
  }
616
772
 
@@ -682,14 +838,8 @@
682
838
  allowedTags: ALLOWED_TAGS
683
839
  });
684
840
 
685
- /* Take care of an mXSS pattern using p, br inside svg, math */
686
- if ((tagName === 'svg' || tagName === 'math') && currentNode.querySelectorAll('p, br, form, table').length !== 0) {
687
- _forceRemove(currentNode);
688
- return true;
689
- }
690
-
691
841
  /* Detect mXSS attempts abusing namespace confusion */
692
- if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[!/\w]/g, currentNode.innerHTML) && regExpTest(/<[!/\w]/g, currentNode.textContent)) {
842
+ if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
693
843
  _forceRemove(currentNode);
694
844
  return true;
695
845
  }
@@ -697,18 +847,25 @@
697
847
  /* Remove element if anything forbids its presence */
698
848
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
699
849
  /* Keep content except for bad-listed elements */
700
- if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && typeof currentNode.insertAdjacentHTML === 'function') {
701
- try {
702
- var htmlToInsert = currentNode.innerHTML;
703
- currentNode.insertAdjacentHTML('AfterEnd', trustedTypesPolicy ? trustedTypesPolicy.createHTML(htmlToInsert) : htmlToInsert);
704
- } catch (_) {}
850
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
851
+ var parentNode = getParentNode(currentNode);
852
+ var childNodes = getChildNodes(currentNode);
853
+ var childCount = childNodes.length;
854
+ for (var i = childCount - 1; i >= 0; --i) {
855
+ parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
856
+ }
705
857
  }
706
858
 
707
859
  _forceRemove(currentNode);
708
860
  return true;
709
861
  }
710
862
 
711
- /* Remove in case a noscript/noembed XSS is suspected */
863
+ /* Check whether element has a valid namespace */
864
+ if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
865
+ _forceRemove(currentNode);
866
+ return true;
867
+ }
868
+
712
869
  if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
713
870
  _forceRemove(currentNode);
714
871
  return true;