dompurify 2.2.4 → 2.2.8

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,18 +117,56 @@ 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
- 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']);
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', 'slot']);
130
170
 
131
171
  var svg$1 = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'targetx', 'targety', 'transform', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
132
172
 
@@ -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.4';
246
+ DOMPurify.version = '2.2.8';
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) {
@@ -253,7 +301,6 @@ function createDOMPurify() {
253
301
  var _document = document,
254
302
  implementation = _document.implementation,
255
303
  createNodeIterator = _document.createNodeIterator,
256
- getElementsByTagName = _document.getElementsByTagName,
257
304
  createDocumentFragment = _document.createDocumentFragment;
258
305
  var importNode = originalDocument.importNode;
259
306
 
@@ -268,7 +315,7 @@ function createDOMPurify() {
268
315
  /**
269
316
  * Expose whether this browser supports running the full DOMPurify.
270
317
  */
271
- DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
318
+ DOMPurify.isSupported = typeof getParentNode === 'function' && implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
272
319
 
273
320
  var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR,
274
321
  ERB_EXPR$$1 = ERB_EXPR,
@@ -361,7 +408,7 @@ function createDOMPurify() {
361
408
  var USE_PROFILES = {};
362
409
 
363
410
  /* 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']);
411
+ 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
412
 
366
413
  /* Tags that are safe for data: URIs */
367
414
  var DATA_URI_TAGS = null;
@@ -371,6 +418,12 @@ function createDOMPurify() {
371
418
  var URI_SAFE_ATTRIBUTES = null;
372
419
  var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']);
373
420
 
421
+ var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
422
+ var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
423
+ var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
424
+ /* Document namespace */
425
+ var NAMESPACE = HTML_NAMESPACE;
426
+
374
427
  /* Keep a reference to config to pass to hooks */
375
428
  var CONFIG = null;
376
429
 
@@ -420,6 +473,7 @@ function createDOMPurify() {
420
473
  KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
421
474
  IN_PLACE = cfg.IN_PLACE || false; // Default false
422
475
  IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
476
+ NAMESPACE = cfg.NAMESPACE || NAMESPACE;
423
477
  if (SAFE_FOR_TEMPLATES) {
424
478
  ALLOW_DATA_ATTR = false;
425
479
  }
@@ -502,6 +556,111 @@ function createDOMPurify() {
502
556
  CONFIG = cfg;
503
557
  };
504
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
+ /**
574
+ *
575
+ *
576
+ * @param {Element} element a DOM element whose namespace is being checked
577
+ * @returns {boolean} Return false if the element has a
578
+ * namespace that a spec-compliant parser would never
579
+ * return. Return true otherwise.
580
+ */
581
+ var _checkValidNamespace = function _checkValidNamespace(element) {
582
+ var parent = getParentNode(element);
583
+
584
+ // In JSDOM, if we're inside shadow DOM, then parentNode
585
+ // can be null. We just simulate parent in this case.
586
+ if (!parent || !parent.tagName) {
587
+ parent = {
588
+ namespaceURI: HTML_NAMESPACE,
589
+ tagName: 'template'
590
+ };
591
+ }
592
+
593
+ var tagName = stringToLowerCase(element.tagName);
594
+ var parentTagName = stringToLowerCase(parent.tagName);
595
+
596
+ if (element.namespaceURI === SVG_NAMESPACE) {
597
+ // The only way to switch from HTML namespace to SVG
598
+ // is via <svg>. If it happens via any other tag, then
599
+ // it should be killed.
600
+ if (parent.namespaceURI === HTML_NAMESPACE) {
601
+ return tagName === 'svg';
602
+ }
603
+
604
+ // The only way to switch from MathML to SVG is via
605
+ // svg if parent is either <annotation-xml> or MathML
606
+ // text integration points.
607
+ if (parent.namespaceURI === MATHML_NAMESPACE) {
608
+ return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
609
+ }
610
+
611
+ // We only allow elements that are defined in SVG
612
+ // spec. All others are disallowed in SVG namespace.
613
+ return Boolean(ALL_SVG_TAGS[tagName]);
614
+ }
615
+
616
+ if (element.namespaceURI === MATHML_NAMESPACE) {
617
+ // The only way to switch from HTML namespace to MathML
618
+ // is via <math>. If it happens via any other tag, then
619
+ // it should be killed.
620
+ if (parent.namespaceURI === HTML_NAMESPACE) {
621
+ return tagName === 'math';
622
+ }
623
+
624
+ // The only way to switch from SVG to MathML is via
625
+ // <math> and HTML integration points
626
+ if (parent.namespaceURI === SVG_NAMESPACE) {
627
+ return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
628
+ }
629
+
630
+ // We only allow elements that are defined in MathML
631
+ // spec. All others are disallowed in MathML namespace.
632
+ return Boolean(ALL_MATHML_TAGS[tagName]);
633
+ }
634
+
635
+ if (element.namespaceURI === HTML_NAMESPACE) {
636
+ // The only way to switch from SVG to HTML is via
637
+ // HTML integration points, and from MathML to HTML
638
+ // is via MathML text integration points
639
+ if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
640
+ return false;
641
+ }
642
+
643
+ if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
644
+ return false;
645
+ }
646
+
647
+ // Certain elements are allowed in both SVG and HTML
648
+ // namespace. We need to specify them explicitly
649
+ // so that they don't get erronously deleted from
650
+ // HTML namespace.
651
+ var commonSvgAndHTMLElements = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
652
+
653
+ // We disallow tags that are specific for MathML
654
+ // or SVG and should never appear in HTML namespace
655
+ return !ALL_MATHML_TAGS[tagName] && (commonSvgAndHTMLElements[tagName] || !ALL_SVG_TAGS[tagName]);
656
+ }
657
+
658
+ // The code should never reach this place (this means
659
+ // that the element somehow got namespace that is not
660
+ // HTML, SVG or MathML). Return false just in case.
661
+ return false;
662
+ };
663
+
505
664
  /**
506
665
  * _forceRemove
507
666
  *
@@ -540,6 +699,19 @@ function createDOMPurify() {
540
699
  }
541
700
 
542
701
  node.removeAttribute(name);
702
+
703
+ // We void attribute values for unremovable "is"" attributes
704
+ if (name === 'is' && !ALLOWED_ATTR[name]) {
705
+ if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
706
+ try {
707
+ _forceRemove(node);
708
+ } catch (_) {}
709
+ } else {
710
+ try {
711
+ node.setAttribute(name, '');
712
+ } catch (_) {}
713
+ }
714
+ }
543
715
  };
544
716
 
545
717
  /**
@@ -562,27 +734,30 @@ function createDOMPurify() {
562
734
  }
563
735
 
564
736
  var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
565
- /* Use the DOMParser API by default, fallback later if needs be */
566
- try {
567
- doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
568
- } catch (_) {}
737
+ /*
738
+ * Use the DOMParser API by default, fallback later if needs be
739
+ * DOMParser not work for svg when has multiple root element.
740
+ */
741
+ if (NAMESPACE === HTML_NAMESPACE) {
742
+ try {
743
+ doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
744
+ } catch (_) {}
745
+ }
569
746
 
570
747
  /* Use createHTMLDocument in case DOMParser is not available */
571
748
  if (!doc || !doc.documentElement) {
572
- doc = implementation.createHTMLDocument('');
573
- var _doc = doc,
574
- body = _doc.body;
575
-
576
- body.parentNode.removeChild(body.parentNode.firstElementChild);
577
- body.outerHTML = dirtyPayload;
749
+ doc = implementation.createDocument(NAMESPACE, 'template', null);
750
+ doc.documentElement.innerHTML = dirtyPayload;
578
751
  }
579
752
 
753
+ var body = doc.body || doc.documentElement;
754
+
580
755
  if (dirty && leadingWhitespace) {
581
- doc.body.insertBefore(document.createTextNode(leadingWhitespace), doc.body.childNodes[0] || null);
756
+ body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
582
757
  }
583
758
 
584
759
  /* Work on whole document or just its body */
585
- return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
760
+ return WHOLE_DOCUMENT ? doc.documentElement : body;
586
761
  };
587
762
 
588
763
  /**
@@ -608,7 +783,7 @@ function createDOMPurify() {
608
783
  return false;
609
784
  }
610
785
 
611
- 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') {
786
+ 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') {
612
787
  return true;
613
788
  }
614
789
 
@@ -689,29 +864,30 @@ function createDOMPurify() {
689
864
  /* Remove element if anything forbids its presence */
690
865
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
691
866
  /* Keep content except for bad-listed elements */
692
- if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && typeof currentNode.insertAdjacentHTML === 'function') {
693
- try {
694
- var htmlToInsert = currentNode.innerHTML;
695
- currentNode.insertAdjacentHTML('AfterEnd', trustedTypesPolicy ? trustedTypesPolicy.createHTML(htmlToInsert) : htmlToInsert);
696
- } catch (_) {}
697
- }
867
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
868
+ var parentNode = getParentNode(currentNode) || currentNode.parentNode;
869
+ var childNodes = getChildNodes(currentNode) || currentNode.childNodes;
698
870
 
699
- _forceRemove(currentNode);
700
- return true;
701
- }
871
+ if (childNodes && parentNode) {
872
+ var childCount = childNodes.length;
873
+
874
+ for (var i = childCount - 1; i >= 0; --i) {
875
+ parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
876
+ }
877
+ }
878
+ }
702
879
 
703
- if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
704
880
  _forceRemove(currentNode);
705
881
  return true;
706
882
  }
707
883
 
708
- if (tagName === 'math' && _isNode(currentNode.firstElementChild) && currentNode.querySelectorAll(':not(' + mathMl.join('):not(') + ')').length > 0) {
884
+ /* Check whether element has a valid namespace */
885
+ if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
709
886
  _forceRemove(currentNode);
710
887
  return true;
711
888
  }
712
889
 
713
- /* Take care of an mXSS using HTML inside SVG affecting old Chrome */
714
- if (tagName === 'svg' && currentNode.querySelectorAll('p, br, table, form, noscript').length > 0) {
890
+ if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
715
891
  _forceRemove(currentNode);
716
892
  return true;
717
893
  }