dompurify 2.3.2 → 2.3.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,4 +1,4 @@
1
- /*! @license DOMPurify 2.3.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.2/LICENSE */
1
+ /*! @license DOMPurify 2.3.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.6/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
 
@@ -150,13 +150,13 @@ var html = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside'
150
150
  // SVG
151
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']);
152
152
 
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']);
153
+ var svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
154
154
 
155
155
  // List of SVG elements that are disallowed by default.
156
156
  // We still need to know them so that we can do namespace
157
157
  // checks properly in case one wants to add them to
158
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']);
159
+ var svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'fedropshadow', '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
160
 
161
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']);
162
162
 
@@ -166,9 +166,9 @@ var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv
166
166
 
167
167
  var text = freeze(['#text']);
168
168
 
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']);
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', 'nonce', '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']);
170
170
 
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']);
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', 'transform-origin', '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']);
172
172
 
173
173
  var mathMl$1 = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
174
174
 
@@ -184,6 +184,7 @@ var IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-
184
184
  var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
185
185
  var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
186
186
  );
187
+ var DOCTYPE_NAME = seal(/^html$/i);
187
188
 
188
189
  var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
189
190
 
@@ -243,7 +244,7 @@ function createDOMPurify() {
243
244
  * Version label, exposed for easier checks
244
245
  * if DOMPurify is up to date or not
245
246
  */
246
- DOMPurify.version = '2.3.2';
247
+ DOMPurify.version = '2.3.6';
247
248
 
248
249
  /**
249
250
  * Array of elements that DOMPurify removed during sanitation.
@@ -269,8 +270,7 @@ function createDOMPurify() {
269
270
  NodeFilter = window.NodeFilter,
270
271
  _window$NamedNodeMap = window.NamedNodeMap,
271
272
  NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
272
- Text = window.Text,
273
- Comment = window.Comment,
273
+ HTMLFormElement = window.HTMLFormElement,
274
274
  DOMParser = window.DOMParser,
275
275
  trustedTypes = window.trustedTypes;
276
276
 
@@ -296,7 +296,7 @@ function createDOMPurify() {
296
296
  }
297
297
 
298
298
  var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument);
299
- var emptyHTML = trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML('') : '';
299
+ var emptyHTML = trustedTypesPolicy ? trustedTypesPolicy.createHTML('') : '';
300
300
 
301
301
  var _document = document,
302
302
  implementation = _document.implementation,
@@ -340,6 +340,33 @@ function createDOMPurify() {
340
340
  var ALLOWED_ATTR = null;
341
341
  var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray$1(html$1), _toConsumableArray$1(svg$1), _toConsumableArray$1(mathMl$1), _toConsumableArray$1(xml)));
342
342
 
343
+ /*
344
+ * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
345
+ * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
346
+ * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
347
+ * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
348
+ */
349
+ var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
350
+ tagNameCheck: {
351
+ writable: true,
352
+ configurable: false,
353
+ enumerable: true,
354
+ value: null
355
+ },
356
+ attributeNameCheck: {
357
+ writable: true,
358
+ configurable: false,
359
+ enumerable: true,
360
+ value: null
361
+ },
362
+ allowCustomizedBuiltInElements: {
363
+ writable: true,
364
+ configurable: false,
365
+ enumerable: true,
366
+ value: false
367
+ }
368
+ }));
369
+
343
370
  /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
344
371
  var FORBID_TAGS = null;
345
372
 
@@ -380,17 +407,6 @@ function createDOMPurify() {
380
407
  * string (or a TrustedHTML object if Trusted Types are supported) */
381
408
  var RETURN_DOM_FRAGMENT = false;
382
409
 
383
- /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
384
- * `Node` is imported into the current `Document`. If this flag is not enabled the
385
- * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
386
- * DOMPurify.
387
- *
388
- * This defaults to `true` starting DOMPurify 2.2.0. Note that setting it to `false`
389
- * might cause XSS from attacks hidden in closed shadowroots in case the browser
390
- * supports Declarative Shadow: DOM https://web.dev/declarative-shadow-dom/
391
- */
392
- var RETURN_DOM_IMPORT = true;
393
-
394
410
  /* Try to return a Trusted Type object instead of a string, return a string in
395
411
  * case Trusted Types are not supported */
396
412
  var RETURN_TRUSTED_TYPE = false;
@@ -441,6 +457,10 @@ function createDOMPurify() {
441
457
 
442
458
  var formElement = document.createElement('form');
443
459
 
460
+ var isRegexOrFunction = function isRegexOrFunction(testValue) {
461
+ return testValue instanceof RegExp || testValue instanceof Function;
462
+ };
463
+
444
464
  /**
445
465
  * _parseConfig
446
466
  *
@@ -476,7 +496,6 @@ function createDOMPurify() {
476
496
  WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
477
497
  RETURN_DOM = cfg.RETURN_DOM || false; // Default false
478
498
  RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
479
- RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT !== false; // Default true
480
499
  RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
481
500
  FORCE_BODY = cfg.FORCE_BODY || false; // Default false
482
501
  SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
@@ -484,7 +503,22 @@ function createDOMPurify() {
484
503
  IN_PLACE = cfg.IN_PLACE || false; // Default false
485
504
  IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
486
505
  NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
487
- PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE in SUPPORTED_PARSER_MEDIA_TYPES ? cfg.PARSER_MEDIA_TYPE : DEFAULT_PARSER_MEDIA_TYPE;
506
+ if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
507
+ CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
508
+ }
509
+
510
+ if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
511
+ CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
512
+ }
513
+
514
+ if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
515
+ CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
516
+ }
517
+
518
+ PARSER_MEDIA_TYPE =
519
+ // eslint-disable-next-line unicorn/prefer-includes
520
+ SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE;
521
+
488
522
  // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
489
523
  transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? function (x) {
490
524
  return x;
@@ -805,7 +839,9 @@ function createDOMPurify() {
805
839
  * @return {Iterator} iterator instance
806
840
  */
807
841
  var _createIterator = function _createIterator(root) {
808
- return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
842
+ return createNodeIterator.call(root.ownerDocument || root, root,
843
+ // eslint-disable-next-line no-bitwise
844
+ NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
809
845
  };
810
846
 
811
847
  /**
@@ -815,15 +851,7 @@ function createDOMPurify() {
815
851
  * @return {Boolean} true if clobbered, false if safe
816
852
  */
817
853
  var _isClobbered = function _isClobbered(elm) {
818
- if (elm instanceof Text || elm instanceof Comment) {
819
- return false;
820
- }
821
-
822
- 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') {
823
- return true;
824
- }
825
-
826
- return false;
854
+ return elm instanceof HTMLFormElement && (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');
827
855
  };
828
856
 
829
857
  /**
@@ -905,6 +933,12 @@ function createDOMPurify() {
905
933
 
906
934
  /* Remove element if anything forbids its presence */
907
935
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
936
+ /* Check if we have a custom element to handle */
937
+ if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {
938
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
939
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
940
+ }
941
+
908
942
  /* Keep content except for bad-listed elements */
909
943
  if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
910
944
  var parentNode = getParentNode(currentNode) || currentNode.parentNode;
@@ -972,8 +1006,16 @@ function createDOMPurify() {
972
1006
  XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
973
1007
  We don't need to check the value; it's always URI safe. */
974
1008
  if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
975
- return false;
976
-
1009
+ if (
1010
+ // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1011
+ // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1012
+ // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1013
+ _basicCustomElementTest(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) ||
1014
+ // Alternative, second condition checks if it's an `is`-attribute, AND
1015
+ // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1016
+ lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
1017
+ return false;
1018
+ }
977
1019
  /* Check value is safe. First, is attr inert? If so, is safe */
978
1020
  } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$$1, stringReplace(value, ATTR_WHITESPACE$$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$$1, stringReplace(value, ATTR_WHITESPACE$$1, ''))) ; else if (!value) ; else {
979
1021
  return false;
@@ -982,6 +1024,16 @@ function createDOMPurify() {
982
1024
  return true;
983
1025
  };
984
1026
 
1027
+ /**
1028
+ * _basicCustomElementCheck
1029
+ * checks if at least one dash is included in tagName, and it's not the first char
1030
+ * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1031
+ * @param {string} tagName name of the tag of the node to sanitize
1032
+ */
1033
+ var _basicCustomElementTest = function _basicCustomElementTest(tagName) {
1034
+ return tagName.indexOf('-') > 0;
1035
+ };
1036
+
985
1037
  /**
986
1038
  * _sanitizeAttributes
987
1039
  *
@@ -1178,7 +1230,15 @@ function createDOMPurify() {
1178
1230
  IN_PLACE = false;
1179
1231
  }
1180
1232
 
1181
- if (IN_PLACE) ; else if (dirty instanceof Node) {
1233
+ if (IN_PLACE) {
1234
+ /* Do some early pre-sanitization to avoid unsafe root nodes */
1235
+ if (dirty.nodeName) {
1236
+ var tagName = transformCaseFunc(dirty.nodeName);
1237
+ if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1238
+ throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1239
+ }
1240
+ }
1241
+ } else if (dirty instanceof Node) {
1182
1242
  /* If dirty is a DOM element, append to an empty document to avoid
1183
1243
  elements being stripped by the parser */
1184
1244
  body = _initDocument('<!---->');
@@ -1205,7 +1265,7 @@ function createDOMPurify() {
1205
1265
 
1206
1266
  /* Check we have a DOM node from the data */
1207
1267
  if (!body) {
1208
- return RETURN_DOM ? null : emptyHTML;
1268
+ return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
1209
1269
  }
1210
1270
  }
1211
1271
 
@@ -1260,7 +1320,7 @@ function createDOMPurify() {
1260
1320
  returnNode = body;
1261
1321
  }
1262
1322
 
1263
- if (RETURN_DOM_IMPORT) {
1323
+ if (ALLOWED_ATTR.shadowroot) {
1264
1324
  /*
1265
1325
  AdoptNode() is not used because internal state is not reset
1266
1326
  (e.g. the past names map of a HTMLFormElement), this is safe
@@ -1276,6 +1336,11 @@ function createDOMPurify() {
1276
1336
 
1277
1337
  var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1278
1338
 
1339
+ /* Serialize doctype if allowed */
1340
+ if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
1341
+ serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
1342
+ }
1343
+
1279
1344
  /* Sanitize final string template-safe */
1280
1345
  if (SAFE_FOR_TEMPLATES) {
1281
1346
  serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR$$1, ' ');