dompurify 2.3.1 → 2.3.5

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 2.3.1 | (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.1/LICENSE */
1
+ /*! @license DOMPurify 2.3.5 | (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.5/LICENSE */
2
2
 
3
3
  (function (global, factory) {
4
4
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
@@ -156,13 +156,13 @@
156
156
  // SVG
157
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']);
158
158
 
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']);
159
+ 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']);
160
160
 
161
161
  // List of SVG elements that are disallowed by default.
162
162
  // We still need to know them so that we can do namespace
163
163
  // checks properly in case one wants to add them to
164
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']);
165
+ 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']);
166
166
 
167
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']);
168
168
 
@@ -172,9 +172,9 @@
172
172
 
173
173
  var text = freeze(['#text']);
174
174
 
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', 'slot']);
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', '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']);
176
176
 
177
- 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']);
177
+ 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']);
178
178
 
179
179
  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']);
180
180
 
@@ -249,7 +249,7 @@
249
249
  * Version label, exposed for easier checks
250
250
  * if DOMPurify is up to date or not
251
251
  */
252
- DOMPurify.version = '2.3.1';
252
+ DOMPurify.version = '2.3.5';
253
253
 
254
254
  /**
255
255
  * Array of elements that DOMPurify removed during sanitation.
@@ -275,8 +275,7 @@
275
275
  NodeFilter = window.NodeFilter,
276
276
  _window$NamedNodeMap = window.NamedNodeMap,
277
277
  NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
278
- Text = window.Text,
279
- Comment = window.Comment,
278
+ HTMLFormElement = window.HTMLFormElement,
280
279
  DOMParser = window.DOMParser,
281
280
  trustedTypes = window.trustedTypes;
282
281
 
@@ -302,7 +301,7 @@
302
301
  }
303
302
 
304
303
  var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument);
305
- var emptyHTML = trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML('') : '';
304
+ var emptyHTML = trustedTypesPolicy ? trustedTypesPolicy.createHTML('') : '';
306
305
 
307
306
  var _document = document,
308
307
  implementation = _document.implementation,
@@ -346,6 +345,33 @@
346
345
  var ALLOWED_ATTR = null;
347
346
  var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray$1(html$1), _toConsumableArray$1(svg$1), _toConsumableArray$1(mathMl$1), _toConsumableArray$1(xml)));
348
347
 
348
+ /*
349
+ * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
350
+ * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
351
+ * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
352
+ * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
353
+ */
354
+ var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
355
+ tagNameCheck: {
356
+ writable: true,
357
+ configurable: false,
358
+ enumerable: true,
359
+ value: null
360
+ },
361
+ attributeNameCheck: {
362
+ writable: true,
363
+ configurable: false,
364
+ enumerable: true,
365
+ value: null
366
+ },
367
+ allowCustomizedBuiltInElements: {
368
+ writable: true,
369
+ configurable: false,
370
+ enumerable: true,
371
+ value: false
372
+ }
373
+ }));
374
+
349
375
  /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
350
376
  var FORBID_TAGS = null;
351
377
 
@@ -386,17 +412,6 @@
386
412
  * string (or a TrustedHTML object if Trusted Types are supported) */
387
413
  var RETURN_DOM_FRAGMENT = false;
388
414
 
389
- /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
390
- * `Node` is imported into the current `Document`. If this flag is not enabled the
391
- * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
392
- * DOMPurify.
393
- *
394
- * This defaults to `true` starting DOMPurify 2.2.0. Note that setting it to `false`
395
- * might cause XSS from attacks hidden in closed shadowroots in case the browser
396
- * supports Declarative Shadow: DOM https://web.dev/declarative-shadow-dom/
397
- */
398
- var RETURN_DOM_IMPORT = true;
399
-
400
415
  /* Try to return a Trusted Type object instead of a string, return a string in
401
416
  * case Trusted Types are not supported */
402
417
  var RETURN_TRUSTED_TYPE = false;
@@ -433,6 +448,12 @@
433
448
  var NAMESPACE = HTML_NAMESPACE;
434
449
  var IS_EMPTY_INPUT = false;
435
450
 
451
+ /* Parsing of strict XHTML documents */
452
+ var PARSER_MEDIA_TYPE = void 0;
453
+ var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
454
+ var DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
455
+ var transformCaseFunc = void 0;
456
+
436
457
  /* Keep a reference to config to pass to hooks */
437
458
  var CONFIG = null;
438
459
 
@@ -441,6 +462,10 @@
441
462
 
442
463
  var formElement = document.createElement('form');
443
464
 
465
+ var isRegexOrFunction = function isRegexOrFunction(testValue) {
466
+ return testValue instanceof RegExp || testValue instanceof Function;
467
+ };
468
+
444
469
  /**
445
470
  * _parseConfig
446
471
  *
@@ -476,7 +501,6 @@
476
501
  WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
477
502
  RETURN_DOM = cfg.RETURN_DOM || false; // Default false
478
503
  RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
479
- RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT !== false; // Default true
480
504
  RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
481
505
  FORCE_BODY = cfg.FORCE_BODY || false; // Default false
482
506
  SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
@@ -484,6 +508,27 @@
484
508
  IN_PLACE = cfg.IN_PLACE || false; // Default false
485
509
  IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
486
510
  NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
511
+ if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
512
+ CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
513
+ }
514
+
515
+ if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
516
+ CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
517
+ }
518
+
519
+ if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
520
+ CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
521
+ }
522
+
523
+ PARSER_MEDIA_TYPE =
524
+ // eslint-disable-next-line unicorn/prefer-includes
525
+ SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE;
526
+
527
+ // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
528
+ transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? function (x) {
529
+ return x;
530
+ } : stringToLowerCase;
531
+
487
532
  if (SAFE_FOR_TEMPLATES) {
488
533
  ALLOW_DATA_ATTR = false;
489
534
  }
@@ -752,6 +797,11 @@
752
797
  leadingWhitespace = matches && matches[0];
753
798
  }
754
799
 
800
+ if (PARSER_MEDIA_TYPE === 'application/xhtml+xml') {
801
+ // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
802
+ dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
803
+ }
804
+
755
805
  var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
756
806
  /*
757
807
  * Use the DOMParser API by default, fallback later if needs be
@@ -759,7 +809,7 @@
759
809
  */
760
810
  if (NAMESPACE === HTML_NAMESPACE) {
761
811
  try {
762
- doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
812
+ doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
763
813
  } catch (_) {}
764
814
  }
765
815
 
@@ -804,15 +854,7 @@
804
854
  * @return {Boolean} true if clobbered, false if safe
805
855
  */
806
856
  var _isClobbered = function _isClobbered(elm) {
807
- if (elm instanceof Text || elm instanceof Comment) {
808
- return false;
809
- }
810
-
811
- 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') {
812
- return true;
813
- }
814
-
815
- return false;
857
+ 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');
816
858
  };
817
859
 
818
860
  /**
@@ -872,7 +914,7 @@
872
914
  }
873
915
 
874
916
  /* Now let's check the element's type and name */
875
- var tagName = stringToLowerCase(currentNode.nodeName);
917
+ var tagName = transformCaseFunc(currentNode.nodeName);
876
918
 
877
919
  /* Execute a hook if present */
878
920
  _executeHook('uponSanitizeElement', currentNode, {
@@ -908,6 +950,11 @@
908
950
  }
909
951
  }
910
952
 
953
+ if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {
954
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
955
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
956
+ }
957
+
911
958
  _forceRemove(currentNode);
912
959
  return true;
913
960
  }
@@ -961,8 +1008,16 @@
961
1008
  XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
962
1009
  We don't need to check the value; it's always URI safe. */
963
1010
  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]) {
964
- return false;
965
-
1011
+ if (
1012
+ // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1013
+ // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1014
+ // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1015
+ _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)) ||
1016
+ // Alternative, second condition checks if it's an `is`-attribute, AND
1017
+ // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1018
+ 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 {
1019
+ return false;
1020
+ }
966
1021
  /* Check value is safe. First, is attr inert? If so, is safe */
967
1022
  } 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 {
968
1023
  return false;
@@ -971,6 +1026,16 @@
971
1026
  return true;
972
1027
  };
973
1028
 
1029
+ /**
1030
+ * _basicCustomElementCheck
1031
+ * checks if at least one dash is included in tagName, and it's not the first char
1032
+ * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1033
+ * @param {string} tagName name of the tag of the node to sanitize
1034
+ */
1035
+ var _basicCustomElementTest = function _basicCustomElementTest(tagName) {
1036
+ return tagName.indexOf('-') > 0;
1037
+ };
1038
+
974
1039
  /**
975
1040
  * _sanitizeAttributes
976
1041
  *
@@ -1013,7 +1078,7 @@
1013
1078
  namespaceURI = _attr.namespaceURI;
1014
1079
 
1015
1080
  value = stringTrim(attr.value);
1016
- lcName = stringToLowerCase(name);
1081
+ lcName = transformCaseFunc(name);
1017
1082
 
1018
1083
  /* Execute a hook if present */
1019
1084
  hookEvent.attrName = lcName;
@@ -1048,7 +1113,7 @@
1048
1113
  }
1049
1114
 
1050
1115
  /* Is `value` valid for this attribute? */
1051
- var lcTag = currentNode.nodeName.toLowerCase();
1116
+ var lcTag = transformCaseFunc(currentNode.nodeName);
1052
1117
  if (!_isValidAttribute(lcTag, lcName, value)) {
1053
1118
  continue;
1054
1119
  }
@@ -1167,7 +1232,15 @@
1167
1232
  IN_PLACE = false;
1168
1233
  }
1169
1234
 
1170
- if (IN_PLACE) ; else if (dirty instanceof Node) {
1235
+ if (IN_PLACE) {
1236
+ /* Do some early pre-sanitization to avoid unsafe root nodes */
1237
+ if (dirty.nodeName) {
1238
+ var tagName = transformCaseFunc(dirty.nodeName);
1239
+ if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1240
+ throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1241
+ }
1242
+ }
1243
+ } else if (dirty instanceof Node) {
1171
1244
  /* If dirty is a DOM element, append to an empty document to avoid
1172
1245
  elements being stripped by the parser */
1173
1246
  body = _initDocument('<!---->');
@@ -1194,7 +1267,7 @@
1194
1267
 
1195
1268
  /* Check we have a DOM node from the data */
1196
1269
  if (!body) {
1197
- return RETURN_DOM ? null : emptyHTML;
1270
+ return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
1198
1271
  }
1199
1272
  }
1200
1273
 
@@ -1249,7 +1322,7 @@
1249
1322
  returnNode = body;
1250
1323
  }
1251
1324
 
1252
- if (RETURN_DOM_IMPORT) {
1325
+ if (ALLOWED_ATTR.shadowroot) {
1253
1326
  /*
1254
1327
  AdoptNode() is not used because internal state is not reset
1255
1328
  (e.g. the past names map of a HTMLFormElement), this is safe
@@ -1311,8 +1384,8 @@
1311
1384
  _parseConfig({});
1312
1385
  }
1313
1386
 
1314
- var lcTag = stringToLowerCase(tag);
1315
- var lcName = stringToLowerCase(attr);
1387
+ var lcTag = transformCaseFunc(tag);
1388
+ var lcName = transformCaseFunc(attr);
1316
1389
  return _isValidAttribute(lcTag, lcName, value);
1317
1390
  };
1318
1391