dompurify 2.2.3 → 2.2.7

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
@@ -4,7 +4,9 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, 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,53 @@ 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
+ function fallbackValue(element) {
141
+ console.warn('fallback value for', element);
142
+ return null;
143
+ }
144
+
145
+ return fallbackValue;
146
+ }
147
+
118
148
  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
149
 
120
150
  // 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']);
151
+ 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
152
 
123
153
  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
154
 
155
+ // List of SVG elements that are disallowed by default.
156
+ // We still need to know them so that we can do namespace
157
+ // checks properly in case one wants to add them to
158
+ // allow-list.
159
+ 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']);
160
+
125
161
  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
162
 
163
+ // Similarly to SVG, we want to know all MathML elements,
164
+ // even those that we disallow by default.
165
+ var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
166
+
127
167
  var text = freeze(['#text']);
128
168
 
129
169
  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 +243,7 @@ function createDOMPurify() {
203
243
  * Version label, exposed for easier checks
204
244
  * if DOMPurify is up to date or not
205
245
  */
206
- DOMPurify.version = '2.2.3';
246
+ DOMPurify.version = '2.2.7';
207
247
 
208
248
  /**
209
249
  * Array of elements that DOMPurify removed during sanitation.
@@ -225,6 +265,7 @@ function createDOMPurify() {
225
265
  var DocumentFragment = window.DocumentFragment,
226
266
  HTMLTemplateElement = window.HTMLTemplateElement,
227
267
  Node = window.Node,
268
+ Element = window.Element,
228
269
  NodeFilter = window.NodeFilter,
229
270
  _window$NamedNodeMap = window.NamedNodeMap,
230
271
  NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
@@ -233,13 +274,20 @@ function createDOMPurify() {
233
274
  DOMParser = window.DOMParser,
234
275
  trustedTypes = window.trustedTypes;
235
276
 
277
+
278
+ var ElementPrototype = Element.prototype;
279
+
280
+ var cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
281
+ var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
282
+ var getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
283
+ var getParentNode = lookupGetter(ElementPrototype, 'parentNode');
284
+
236
285
  // As per issue #47, the web-components registry is inherited by a
237
286
  // new document created via createHTMLDocument. As per the spec
238
287
  // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
239
288
  // a new empty registry is used when creating a template contents owner
240
289
  // document, so we use that as our parent document to ensure nothing
241
290
  // is inherited.
242
-
243
291
  if (typeof HTMLTemplateElement === 'function') {
244
292
  var template = document.createElement('template');
245
293
  if (template.content && template.content.ownerDocument) {
@@ -268,7 +316,7 @@ function createDOMPurify() {
268
316
  /**
269
317
  * Expose whether this browser supports running the full DOMPurify.
270
318
  */
271
- DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
319
+ DOMPurify.isSupported = typeof getParentNode === 'function' && implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
272
320
 
273
321
  var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR,
274
322
  ERB_EXPR$$1 = ERB_EXPR,
@@ -361,7 +409,7 @@ function createDOMPurify() {
361
409
  var USE_PROFILES = {};
362
410
 
363
411
  /* 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']);
412
+ 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
413
 
366
414
  /* Tags that are safe for data: URIs */
367
415
  var DATA_URI_TAGS = null;
@@ -502,6 +550,115 @@ function createDOMPurify() {
502
550
  CONFIG = cfg;
503
551
  };
504
552
 
553
+ var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
554
+
555
+ var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
556
+
557
+ /* Keep track of all possible SVG and MathML tags
558
+ * so that we can perform the namespace checks
559
+ * correctly. */
560
+ var ALL_SVG_TAGS = addToSet({}, svg);
561
+ addToSet(ALL_SVG_TAGS, svgFilters);
562
+ addToSet(ALL_SVG_TAGS, svgDisallowed);
563
+
564
+ var ALL_MATHML_TAGS = addToSet({}, mathMl);
565
+ addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
566
+
567
+ var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
568
+ var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
569
+ var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
570
+
571
+ /**
572
+ *
573
+ *
574
+ * @param {Element} element a DOM element whose namespace is being checked
575
+ * @returns {boolean} Return false if the element has a
576
+ * namespace that a spec-compliant parser would never
577
+ * return. Return true otherwise.
578
+ */
579
+ var _checkValidNamespace = function _checkValidNamespace(element) {
580
+ var parent = getParentNode(element);
581
+
582
+ // In JSDOM, if we're inside shadow DOM, then parentNode
583
+ // can be null. We just simulate parent in this case.
584
+ if (!parent || !parent.tagName) {
585
+ parent = {
586
+ namespaceURI: HTML_NAMESPACE,
587
+ tagName: 'template'
588
+ };
589
+ }
590
+
591
+ var tagName = stringToLowerCase(element.tagName);
592
+ var parentTagName = stringToLowerCase(parent.tagName);
593
+
594
+ if (element.namespaceURI === SVG_NAMESPACE) {
595
+ // The only way to switch from HTML namespace to SVG
596
+ // is via <svg>. If it happens via any other tag, then
597
+ // it should be killed.
598
+ if (parent.namespaceURI === HTML_NAMESPACE) {
599
+ return tagName === 'svg';
600
+ }
601
+
602
+ // The only way to switch from MathML to SVG is via
603
+ // svg if parent is either <annotation-xml> or MathML
604
+ // text integration points.
605
+ if (parent.namespaceURI === MATHML_NAMESPACE) {
606
+ return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
607
+ }
608
+
609
+ // We only allow elements that are defined in SVG
610
+ // spec. All others are disallowed in SVG namespace.
611
+ return Boolean(ALL_SVG_TAGS[tagName]);
612
+ }
613
+
614
+ if (element.namespaceURI === MATHML_NAMESPACE) {
615
+ // The only way to switch from HTML namespace to MathML
616
+ // is via <math>. If it happens via any other tag, then
617
+ // it should be killed.
618
+ if (parent.namespaceURI === HTML_NAMESPACE) {
619
+ return tagName === 'math';
620
+ }
621
+
622
+ // The only way to switch from SVG to MathML is via
623
+ // <math> and HTML integration points
624
+ if (parent.namespaceURI === SVG_NAMESPACE) {
625
+ return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
626
+ }
627
+
628
+ // We only allow elements that are defined in MathML
629
+ // spec. All others are disallowed in MathML namespace.
630
+ return Boolean(ALL_MATHML_TAGS[tagName]);
631
+ }
632
+
633
+ if (element.namespaceURI === HTML_NAMESPACE) {
634
+ // The only way to switch from SVG to HTML is via
635
+ // HTML integration points, and from MathML to HTML
636
+ // is via MathML text integration points
637
+ if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
638
+ return false;
639
+ }
640
+
641
+ if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
642
+ return false;
643
+ }
644
+
645
+ // Certain elements are allowed in both SVG and HTML
646
+ // namespace. We need to specify them explicitly
647
+ // so that they don't get erronously deleted from
648
+ // HTML namespace.
649
+ var commonSvgAndHTMLElements = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
650
+
651
+ // We disallow tags that are specific for MathML
652
+ // or SVG and should never appear in HTML namespace
653
+ return !ALL_MATHML_TAGS[tagName] && (commonSvgAndHTMLElements[tagName] || !ALL_SVG_TAGS[tagName]);
654
+ }
655
+
656
+ // The code should never reach this place (this means
657
+ // that the element somehow got namespace that is not
658
+ // HTML, SVG or MathML). Return false just in case.
659
+ return false;
660
+ };
661
+
505
662
  /**
506
663
  * _forceRemove
507
664
  *
@@ -512,7 +669,11 @@ function createDOMPurify() {
512
669
  try {
513
670
  node.parentNode.removeChild(node);
514
671
  } catch (_) {
515
- node.outerHTML = emptyHTML;
672
+ try {
673
+ node.outerHTML = emptyHTML;
674
+ } catch (_) {
675
+ node.remove();
676
+ }
516
677
  }
517
678
  };
518
679
 
@@ -536,6 +697,19 @@ function createDOMPurify() {
536
697
  }
537
698
 
538
699
  node.removeAttribute(name);
700
+
701
+ // We void attribute values for unremovable "is"" attributes
702
+ if (name === 'is' && !ALLOWED_ATTR[name]) {
703
+ if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
704
+ try {
705
+ _forceRemove(node);
706
+ } catch (_) {}
707
+ } else {
708
+ try {
709
+ node.setAttribute(name, '');
710
+ } catch (_) {}
711
+ }
712
+ }
539
713
  };
540
714
 
541
715
  /**
@@ -604,7 +778,7 @@ function createDOMPurify() {
604
778
  return false;
605
779
  }
606
780
 
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') {
781
+ 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
782
  return true;
609
783
  }
610
784
 
@@ -676,12 +850,6 @@ function createDOMPurify() {
676
850
  allowedTags: ALLOWED_TAGS
677
851
  });
678
852
 
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, h1, h2, h3, h4, h5, h6').length !== 0) {
681
- _forceRemove(currentNode);
682
- return true;
683
- }
684
-
685
853
  /* Detect mXSS attempts abusing namespace confusion */
686
854
  if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
687
855
  _forceRemove(currentNode);
@@ -691,18 +859,29 @@ function createDOMPurify() {
691
859
  /* Remove element if anything forbids its presence */
692
860
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
693
861
  /* 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 (_) {}
862
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
863
+ var parentNode = getParentNode(currentNode);
864
+ var childNodes = getChildNodes(currentNode);
865
+
866
+ if (childNodes && parentNode) {
867
+ var childCount = childNodes.length;
868
+
869
+ for (var i = childCount - 1; i >= 0; --i) {
870
+ parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
871
+ }
872
+ }
699
873
  }
700
874
 
701
875
  _forceRemove(currentNode);
702
876
  return true;
703
877
  }
704
878
 
705
- /* Remove in case a noscript/noembed XSS is suspected */
879
+ /* Check whether element has a valid namespace */
880
+ if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
881
+ _forceRemove(currentNode);
882
+ return true;
883
+ }
884
+
706
885
  if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
707
886
  _forceRemove(currentNode);
708
887
  return true;