dompurify 2.3.0 → 2.3.4

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.0 | (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.0/LICENSE */
1
+ /*! @license DOMPurify 2.3.4 | (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.4/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,7 +166,7 @@ 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
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']);
172
172
 
@@ -243,7 +243,7 @@ function createDOMPurify() {
243
243
  * Version label, exposed for easier checks
244
244
  * if DOMPurify is up to date or not
245
245
  */
246
- DOMPurify.version = '2.3.0';
246
+ DOMPurify.version = '2.3.4';
247
247
 
248
248
  /**
249
249
  * Array of elements that DOMPurify removed during sanitation.
@@ -269,8 +269,7 @@ function createDOMPurify() {
269
269
  NodeFilter = window.NodeFilter,
270
270
  _window$NamedNodeMap = window.NamedNodeMap,
271
271
  NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
272
- Text = window.Text,
273
- Comment = window.Comment,
272
+ HTMLFormElement = window.HTMLFormElement,
274
273
  DOMParser = window.DOMParser,
275
274
  trustedTypes = window.trustedTypes;
276
275
 
@@ -340,6 +339,33 @@ function createDOMPurify() {
340
339
  var ALLOWED_ATTR = null;
341
340
  var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray$1(html$1), _toConsumableArray$1(svg$1), _toConsumableArray$1(mathMl$1), _toConsumableArray$1(xml)));
342
341
 
342
+ /*
343
+ * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
344
+ * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
345
+ * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
346
+ * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
347
+ */
348
+ var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
349
+ tagNameCheck: {
350
+ writable: true,
351
+ configurable: false,
352
+ enumerable: true,
353
+ value: null
354
+ },
355
+ attributeNameCheck: {
356
+ writable: true,
357
+ configurable: false,
358
+ enumerable: true,
359
+ value: null
360
+ },
361
+ allowCustomizedBuiltInElements: {
362
+ writable: true,
363
+ configurable: false,
364
+ enumerable: true,
365
+ value: false
366
+ }
367
+ }));
368
+
343
369
  /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
344
370
  var FORBID_TAGS = null;
345
371
 
@@ -380,17 +406,6 @@ function createDOMPurify() {
380
406
  * string (or a TrustedHTML object if Trusted Types are supported) */
381
407
  var RETURN_DOM_FRAGMENT = false;
382
408
 
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
409
  /* Try to return a Trusted Type object instead of a string, return a string in
395
410
  * case Trusted Types are not supported */
396
411
  var RETURN_TRUSTED_TYPE = false;
@@ -409,7 +424,8 @@ function createDOMPurify() {
409
424
  var USE_PROFILES = {};
410
425
 
411
426
  /* Tags to ignore content of when KEEP_CONTENT is true */
412
- var FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
427
+ var FORBID_CONTENTS = null;
428
+ var DEFAULT_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']);
413
429
 
414
430
  /* Tags that are safe for data: URIs */
415
431
  var DATA_URI_TAGS = null;
@@ -417,7 +433,7 @@ function createDOMPurify() {
417
433
 
418
434
  /* Attributes safe for values like "javascript:" */
419
435
  var URI_SAFE_ATTRIBUTES = null;
420
- var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']);
436
+ var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
421
437
 
422
438
  var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
423
439
  var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
@@ -426,6 +442,12 @@ function createDOMPurify() {
426
442
  var NAMESPACE = HTML_NAMESPACE;
427
443
  var IS_EMPTY_INPUT = false;
428
444
 
445
+ /* Parsing of strict XHTML documents */
446
+ var PARSER_MEDIA_TYPE = void 0;
447
+ var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
448
+ var DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
449
+ var transformCaseFunc = void 0;
450
+
429
451
  /* Keep a reference to config to pass to hooks */
430
452
  var CONFIG = null;
431
453
 
@@ -434,6 +456,10 @@ function createDOMPurify() {
434
456
 
435
457
  var formElement = document.createElement('form');
436
458
 
459
+ var isRegexOrFunction = function isRegexOrFunction(testValue) {
460
+ return testValue instanceof RegExp || testValue instanceof Function;
461
+ };
462
+
437
463
  /**
438
464
  * _parseConfig
439
465
  *
@@ -458,6 +484,7 @@ function createDOMPurify() {
458
484
  ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
459
485
  URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) : DEFAULT_URI_SAFE_ATTRIBUTES;
460
486
  DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS;
487
+ FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS) : DEFAULT_FORBID_CONTENTS;
461
488
  FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {};
462
489
  FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};
463
490
  USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
@@ -468,7 +495,6 @@ function createDOMPurify() {
468
495
  WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
469
496
  RETURN_DOM = cfg.RETURN_DOM || false; // Default false
470
497
  RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
471
- RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT !== false; // Default true
472
498
  RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
473
499
  FORCE_BODY = cfg.FORCE_BODY || false; // Default false
474
500
  SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
@@ -476,6 +502,27 @@ function createDOMPurify() {
476
502
  IN_PLACE = cfg.IN_PLACE || false; // Default false
477
503
  IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
478
504
  NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
505
+ if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
506
+ CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
507
+ }
508
+
509
+ if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
510
+ CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
511
+ }
512
+
513
+ if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
514
+ CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
515
+ }
516
+
517
+ PARSER_MEDIA_TYPE =
518
+ // eslint-disable-next-line unicorn/prefer-includes
519
+ SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE;
520
+
521
+ // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
522
+ transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? function (x) {
523
+ return x;
524
+ } : stringToLowerCase;
525
+
479
526
  if (SAFE_FOR_TEMPLATES) {
480
527
  ALLOW_DATA_ATTR = false;
481
528
  }
@@ -533,6 +580,14 @@ function createDOMPurify() {
533
580
  addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
534
581
  }
535
582
 
583
+ if (cfg.FORBID_CONTENTS) {
584
+ if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
585
+ FORBID_CONTENTS = clone(FORBID_CONTENTS);
586
+ }
587
+
588
+ addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS);
589
+ }
590
+
536
591
  /* Add #text in case KEEP_CONTENT is set to true */
537
592
  if (KEEP_CONTENT) {
538
593
  ALLOWED_TAGS['#text'] = true;
@@ -736,6 +791,11 @@ function createDOMPurify() {
736
791
  leadingWhitespace = matches && matches[0];
737
792
  }
738
793
 
794
+ if (PARSER_MEDIA_TYPE === 'application/xhtml+xml') {
795
+ // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
796
+ dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
797
+ }
798
+
739
799
  var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
740
800
  /*
741
801
  * Use the DOMParser API by default, fallback later if needs be
@@ -743,7 +803,7 @@ function createDOMPurify() {
743
803
  */
744
804
  if (NAMESPACE === HTML_NAMESPACE) {
745
805
  try {
746
- doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
806
+ doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
747
807
  } catch (_) {}
748
808
  }
749
809
 
@@ -788,15 +848,7 @@ function createDOMPurify() {
788
848
  * @return {Boolean} true if clobbered, false if safe
789
849
  */
790
850
  var _isClobbered = function _isClobbered(elm) {
791
- if (elm instanceof Text || elm instanceof Comment) {
792
- return false;
793
- }
794
-
795
- 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') {
796
- return true;
797
- }
798
-
799
- return false;
851
+ 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');
800
852
  };
801
853
 
802
854
  /**
@@ -856,7 +908,7 @@ function createDOMPurify() {
856
908
  }
857
909
 
858
910
  /* Now let's check the element's type and name */
859
- var tagName = stringToLowerCase(currentNode.nodeName);
911
+ var tagName = transformCaseFunc(currentNode.nodeName);
860
912
 
861
913
  /* Execute a hook if present */
862
914
  _executeHook('uponSanitizeElement', currentNode, {
@@ -870,6 +922,12 @@ function createDOMPurify() {
870
922
  return true;
871
923
  }
872
924
 
925
+ /* Mitigate a problem with templates inside select */
926
+ if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) {
927
+ _forceRemove(currentNode);
928
+ return true;
929
+ }
930
+
873
931
  /* Remove element if anything forbids its presence */
874
932
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
875
933
  /* Keep content except for bad-listed elements */
@@ -886,6 +944,11 @@ function createDOMPurify() {
886
944
  }
887
945
  }
888
946
 
947
+ if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {
948
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
949
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
950
+ }
951
+
889
952
  _forceRemove(currentNode);
890
953
  return true;
891
954
  }
@@ -939,8 +1002,16 @@ function createDOMPurify() {
939
1002
  XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
940
1003
  We don't need to check the value; it's always URI safe. */
941
1004
  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]) {
942
- return false;
943
-
1005
+ if (
1006
+ // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1007
+ // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1008
+ // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1009
+ _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)) ||
1010
+ // Alternative, second condition checks if it's an `is`-attribute, AND
1011
+ // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1012
+ 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 {
1013
+ return false;
1014
+ }
944
1015
  /* Check value is safe. First, is attr inert? If so, is safe */
945
1016
  } 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 {
946
1017
  return false;
@@ -949,6 +1020,16 @@ function createDOMPurify() {
949
1020
  return true;
950
1021
  };
951
1022
 
1023
+ /**
1024
+ * _basicCustomElementCheck
1025
+ * checks if at least one dash is included in tagName, and it's not the first char
1026
+ * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1027
+ * @param {string} tagName name of the tag of the node to sanitize
1028
+ */
1029
+ var _basicCustomElementTest = function _basicCustomElementTest(tagName) {
1030
+ return tagName.indexOf('-') > 0;
1031
+ };
1032
+
952
1033
  /**
953
1034
  * _sanitizeAttributes
954
1035
  *
@@ -991,7 +1072,7 @@ function createDOMPurify() {
991
1072
  namespaceURI = _attr.namespaceURI;
992
1073
 
993
1074
  value = stringTrim(attr.value);
994
- lcName = stringToLowerCase(name);
1075
+ lcName = transformCaseFunc(name);
995
1076
 
996
1077
  /* Execute a hook if present */
997
1078
  hookEvent.attrName = lcName;
@@ -1026,7 +1107,7 @@ function createDOMPurify() {
1026
1107
  }
1027
1108
 
1028
1109
  /* Is `value` valid for this attribute? */
1029
- var lcTag = currentNode.nodeName.toLowerCase();
1110
+ var lcTag = transformCaseFunc(currentNode.nodeName);
1030
1111
  if (!_isValidAttribute(lcTag, lcName, value)) {
1031
1112
  continue;
1032
1113
  }
@@ -1227,7 +1308,7 @@ function createDOMPurify() {
1227
1308
  returnNode = body;
1228
1309
  }
1229
1310
 
1230
- if (RETURN_DOM_IMPORT) {
1311
+ if (ALLOWED_ATTR.shadowroot) {
1231
1312
  /*
1232
1313
  AdoptNode() is not used because internal state is not reset
1233
1314
  (e.g. the past names map of a HTMLFormElement), this is safe
@@ -1289,8 +1370,8 @@ function createDOMPurify() {
1289
1370
  _parseConfig({});
1290
1371
  }
1291
1372
 
1292
- var lcTag = stringToLowerCase(tag);
1293
- var lcName = stringToLowerCase(attr);
1373
+ var lcTag = transformCaseFunc(tag);
1374
+ var lcName = transformCaseFunc(attr);
1294
1375
  return _isValidAttribute(lcTag, lcName, value);
1295
1376
  };
1296
1377