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.js CHANGED
@@ -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,53 @@
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
+ function fallbackValue(element) {
147
+ console.warn('fallback value for', element);
148
+ return null;
149
+ }
150
+
151
+ return fallbackValue;
152
+ }
153
+
124
154
  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
155
 
126
156
  // 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']);
157
+ 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
158
 
129
159
  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
160
 
161
+ // List of SVG elements that are disallowed by default.
162
+ // We still need to know them so that we can do namespace
163
+ // checks properly in case one wants to add them to
164
+ // allow-list.
165
+ 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']);
166
+
131
167
  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
168
 
169
+ // Similarly to SVG, we want to know all MathML elements,
170
+ // even those that we disallow by default.
171
+ var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
172
+
133
173
  var text = freeze(['#text']);
134
174
 
135
175
  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 +249,7 @@
209
249
  * Version label, exposed for easier checks
210
250
  * if DOMPurify is up to date or not
211
251
  */
212
- DOMPurify.version = '2.2.3';
252
+ DOMPurify.version = '2.2.7';
213
253
 
214
254
  /**
215
255
  * Array of elements that DOMPurify removed during sanitation.
@@ -231,6 +271,7 @@
231
271
  var DocumentFragment = window.DocumentFragment,
232
272
  HTMLTemplateElement = window.HTMLTemplateElement,
233
273
  Node = window.Node,
274
+ Element = window.Element,
234
275
  NodeFilter = window.NodeFilter,
235
276
  _window$NamedNodeMap = window.NamedNodeMap,
236
277
  NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
@@ -239,13 +280,20 @@
239
280
  DOMParser = window.DOMParser,
240
281
  trustedTypes = window.trustedTypes;
241
282
 
283
+
284
+ var ElementPrototype = Element.prototype;
285
+
286
+ var cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
287
+ var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
288
+ var getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
289
+ var getParentNode = lookupGetter(ElementPrototype, 'parentNode');
290
+
242
291
  // As per issue #47, the web-components registry is inherited by a
243
292
  // new document created via createHTMLDocument. As per the spec
244
293
  // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
245
294
  // a new empty registry is used when creating a template contents owner
246
295
  // document, so we use that as our parent document to ensure nothing
247
296
  // is inherited.
248
-
249
297
  if (typeof HTMLTemplateElement === 'function') {
250
298
  var template = document.createElement('template');
251
299
  if (template.content && template.content.ownerDocument) {
@@ -274,7 +322,7 @@
274
322
  /**
275
323
  * Expose whether this browser supports running the full DOMPurify.
276
324
  */
277
- DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
325
+ DOMPurify.isSupported = typeof getParentNode === 'function' && implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
278
326
 
279
327
  var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR,
280
328
  ERB_EXPR$$1 = ERB_EXPR,
@@ -367,7 +415,7 @@
367
415
  var USE_PROFILES = {};
368
416
 
369
417
  /* 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']);
418
+ 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
419
 
372
420
  /* Tags that are safe for data: URIs */
373
421
  var DATA_URI_TAGS = null;
@@ -508,6 +556,115 @@
508
556
  CONFIG = cfg;
509
557
  };
510
558
 
559
+ var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
560
+
561
+ var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
562
+
563
+ /* Keep track of all possible SVG and MathML tags
564
+ * so that we can perform the namespace checks
565
+ * correctly. */
566
+ var ALL_SVG_TAGS = addToSet({}, svg);
567
+ addToSet(ALL_SVG_TAGS, svgFilters);
568
+ addToSet(ALL_SVG_TAGS, svgDisallowed);
569
+
570
+ var ALL_MATHML_TAGS = addToSet({}, mathMl);
571
+ addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
572
+
573
+ var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
574
+ var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
575
+ var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
576
+
577
+ /**
578
+ *
579
+ *
580
+ * @param {Element} element a DOM element whose namespace is being checked
581
+ * @returns {boolean} Return false if the element has a
582
+ * namespace that a spec-compliant parser would never
583
+ * return. Return true otherwise.
584
+ */
585
+ var _checkValidNamespace = function _checkValidNamespace(element) {
586
+ var parent = getParentNode(element);
587
+
588
+ // In JSDOM, if we're inside shadow DOM, then parentNode
589
+ // can be null. We just simulate parent in this case.
590
+ if (!parent || !parent.tagName) {
591
+ parent = {
592
+ namespaceURI: HTML_NAMESPACE,
593
+ tagName: 'template'
594
+ };
595
+ }
596
+
597
+ var tagName = stringToLowerCase(element.tagName);
598
+ var parentTagName = stringToLowerCase(parent.tagName);
599
+
600
+ if (element.namespaceURI === SVG_NAMESPACE) {
601
+ // The only way to switch from HTML namespace to SVG
602
+ // is via <svg>. If it happens via any other tag, then
603
+ // it should be killed.
604
+ if (parent.namespaceURI === HTML_NAMESPACE) {
605
+ return tagName === 'svg';
606
+ }
607
+
608
+ // The only way to switch from MathML to SVG is via
609
+ // svg if parent is either <annotation-xml> or MathML
610
+ // text integration points.
611
+ if (parent.namespaceURI === MATHML_NAMESPACE) {
612
+ return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
613
+ }
614
+
615
+ // We only allow elements that are defined in SVG
616
+ // spec. All others are disallowed in SVG namespace.
617
+ return Boolean(ALL_SVG_TAGS[tagName]);
618
+ }
619
+
620
+ if (element.namespaceURI === MATHML_NAMESPACE) {
621
+ // The only way to switch from HTML namespace to MathML
622
+ // is via <math>. If it happens via any other tag, then
623
+ // it should be killed.
624
+ if (parent.namespaceURI === HTML_NAMESPACE) {
625
+ return tagName === 'math';
626
+ }
627
+
628
+ // The only way to switch from SVG to MathML is via
629
+ // <math> and HTML integration points
630
+ if (parent.namespaceURI === SVG_NAMESPACE) {
631
+ return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
632
+ }
633
+
634
+ // We only allow elements that are defined in MathML
635
+ // spec. All others are disallowed in MathML namespace.
636
+ return Boolean(ALL_MATHML_TAGS[tagName]);
637
+ }
638
+
639
+ if (element.namespaceURI === HTML_NAMESPACE) {
640
+ // The only way to switch from SVG to HTML is via
641
+ // HTML integration points, and from MathML to HTML
642
+ // is via MathML text integration points
643
+ if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
644
+ return false;
645
+ }
646
+
647
+ if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
648
+ return false;
649
+ }
650
+
651
+ // Certain elements are allowed in both SVG and HTML
652
+ // namespace. We need to specify them explicitly
653
+ // so that they don't get erronously deleted from
654
+ // HTML namespace.
655
+ var commonSvgAndHTMLElements = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
656
+
657
+ // We disallow tags that are specific for MathML
658
+ // or SVG and should never appear in HTML namespace
659
+ return !ALL_MATHML_TAGS[tagName] && (commonSvgAndHTMLElements[tagName] || !ALL_SVG_TAGS[tagName]);
660
+ }
661
+
662
+ // The code should never reach this place (this means
663
+ // that the element somehow got namespace that is not
664
+ // HTML, SVG or MathML). Return false just in case.
665
+ return false;
666
+ };
667
+
511
668
  /**
512
669
  * _forceRemove
513
670
  *
@@ -518,7 +675,11 @@
518
675
  try {
519
676
  node.parentNode.removeChild(node);
520
677
  } catch (_) {
521
- node.outerHTML = emptyHTML;
678
+ try {
679
+ node.outerHTML = emptyHTML;
680
+ } catch (_) {
681
+ node.remove();
682
+ }
522
683
  }
523
684
  };
524
685
 
@@ -542,6 +703,19 @@
542
703
  }
543
704
 
544
705
  node.removeAttribute(name);
706
+
707
+ // We void attribute values for unremovable "is"" attributes
708
+ if (name === 'is' && !ALLOWED_ATTR[name]) {
709
+ if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
710
+ try {
711
+ _forceRemove(node);
712
+ } catch (_) {}
713
+ } else {
714
+ try {
715
+ node.setAttribute(name, '');
716
+ } catch (_) {}
717
+ }
718
+ }
545
719
  };
546
720
 
547
721
  /**
@@ -610,7 +784,7 @@
610
784
  return false;
611
785
  }
612
786
 
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') {
787
+ 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
788
  return true;
615
789
  }
616
790
 
@@ -682,12 +856,6 @@
682
856
  allowedTags: ALLOWED_TAGS
683
857
  });
684
858
 
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, h1, h2, h3, h4, h5, h6').length !== 0) {
687
- _forceRemove(currentNode);
688
- return true;
689
- }
690
-
691
859
  /* Detect mXSS attempts abusing namespace confusion */
692
860
  if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
693
861
  _forceRemove(currentNode);
@@ -697,18 +865,29 @@
697
865
  /* Remove element if anything forbids its presence */
698
866
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
699
867
  /* 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 (_) {}
868
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
869
+ var parentNode = getParentNode(currentNode);
870
+ var childNodes = getChildNodes(currentNode);
871
+
872
+ if (childNodes && parentNode) {
873
+ var childCount = childNodes.length;
874
+
875
+ for (var i = childCount - 1; i >= 0; --i) {
876
+ parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
877
+ }
878
+ }
705
879
  }
706
880
 
707
881
  _forceRemove(currentNode);
708
882
  return true;
709
883
  }
710
884
 
711
- /* Remove in case a noscript/noembed XSS is suspected */
885
+ /* Check whether element has a valid namespace */
886
+ if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
887
+ _forceRemove(currentNode);
888
+ return true;
889
+ }
890
+
712
891
  if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
713
892
  _forceRemove(currentNode);
714
893
  return true;