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/README.md CHANGED
@@ -6,11 +6,11 @@
6
6
 
7
7
  DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
8
8
 
9
- It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version 2.3.0.
9
+ It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version 2.3.4.
10
10
 
11
11
  DOMPurify is written in JavaScript and works in all modern browsers (Safari (10+), Opera (15+), Internet Explorer (10+), Edge, Firefox and Chrome - as well as almost anything else using Blink or WebKit). It doesn't break on MSIE6 or other legacy browsers. It either uses [a fall-back](#what-about-older-browsers-like-msie8) or simply does nothing.
12
12
 
13
- Our automated tests cover [17 different browsers](https://github.com/cure53/DOMPurify/blob/main/test/karma.custom-launchers.config.js#L5) right now, more to come. We also cover Node.js v14.15.1, v15.4.0, running DOMPurify on [jsdom](https://github.com/tmpvar/jsdom). Older Node.js versions are known to work as well.
13
+ Our automated tests cover [19 different browsers](https://github.com/cure53/DOMPurify/blob/main/test/karma.custom-launchers.config.js#L5) right now, more to come. We also cover Node.js v14.15.1, v15.4.0, v16.13.0, v17.0.0, running DOMPurify on [jsdom](https://github.com/tmpvar/jsdom). Older Node.js versions are known to work as well.
14
14
 
15
15
  DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not. For more details please also read about our [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model). Please, read it. Like, really.
16
16
 
@@ -40,7 +40,7 @@ Afterwards you can sanitize strings by executing the following code:
40
40
  let clean = DOMPurify.sanitize( dirty );
41
41
  ```
42
42
 
43
- The resulting HTML can be written into a DOM element using `innerHTML` or the DOM using `document.write()`. That is fully up to you.
43
+ The resulting HTML can be written into a DOM element using `innerHTML` or the DOM using `document.write()`. That is fully up to you.
44
44
  Note that by default, we permit HTML, SVG **and** MathML. If you only need HTML, which might be a very common use-case, you can easily set that up as well:
45
45
 
46
46
  ```js
@@ -133,7 +133,7 @@ DOMPurify also exposes a property called `isSupported`, which tells you whether
133
133
  In version 1.0.9, support for [Trusted Types API](https://github.com/WICG/trusted-types) was added to DOMPurify.
134
134
  In version 2.0.0, a config flag was added to control DOMPurify's behavior regarding this.
135
135
 
136
- When `DOMPurify.sanitize` is used in an environment where the Trusted Types API is available and `RETURN_TRUSTED_TYPE` is set to `true`, it tries to return a `TrustedHTML` value instead of a string (the behavior for `RETURN_DOM`, `RETURN_DOM_FRAGMENT`, and `RETURN_DOM_IMPORT` config options does not change).
136
+ When `DOMPurify.sanitize` is used in an environment where the Trusted Types API is available and `RETURN_TRUSTED_TYPE` is set to `true`, it tries to return a `TrustedHTML` value instead of a string (the behavior for `RETURN_DOM` and `RETURN_DOM_FRAGMENT` config options does not change).
137
137
 
138
138
  ## Can I configure DOMPurify?
139
139
 
@@ -188,6 +188,52 @@ var clean = DOMPurify.sanitize(dirty, {ADD_ATTR: ['my-attr']});
188
188
  // prohibit HTML5 data attributes, leave other safe HTML as is (default is true)
189
189
  var clean = DOMPurify.sanitize(dirty, {ALLOW_DATA_ATTR: false});
190
190
 
191
+ /**
192
+ * Control behavior relating to Custom Elements
193
+ */
194
+
195
+ // DOMPurify allows to define rules for Custom Elements. When using the CUSTOM_ELEMENT_HANDLING
196
+ // literal, it is possible to define exactly what elements you wish to allow (by default, none are allowed).
197
+ //
198
+ // The same goes for their attributes. By default, the built-in or configured allow.list is used.
199
+ //
200
+ // You can use a RegExp literal to specify what is allowed or a predicate, examples for both can be seen below.
201
+ // The default values are very restrictive to prevent accidental XSS bypasses. Handle with great care!
202
+
203
+
204
+ var clean = DOMPurify.sanitize(
205
+ '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
206
+ {
207
+ CUSTOM_ELEMENT_HANDLING: {
208
+ tagNameCheck: null, // no custom elements are allowed
209
+ attributeNameCheck: null, // default / standard attribute allow-list is used
210
+ allowCustomizedBuiltInElements: false, // no customized built-ins allowed
211
+ },
212
+ }
213
+ ); // <div is=""></div>
214
+
215
+ var clean = DOMPurify.sanitize(
216
+ '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
217
+ {
218
+ CUSTOM_ELEMENT_HANDLING: {
219
+ tagNameCheck: /^foo-/, // allow all tags starting with "foo-"
220
+ attributeNameCheck: /baz/, // allow all attributes containing "baz"
221
+ allowCustomizedBuiltInElements: false, // customized built-ins are allowed
222
+ },
223
+ }
224
+ ); // <foo-bar baz="foobar"></foo-bar><div is=""></div>
225
+
226
+ var clean = DOMPurify.sanitize(
227
+ '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
228
+ {
229
+ CUSTOM_ELEMENT_HANDLING: {
230
+ tagNameCheck: (tagName) => tagName.match(/^foo-/), // allow all tags starting with "foo-"
231
+ attributeNameCheck: (attr) => attr.match(/baz/), // allow all containing "baz"
232
+ allowCustomizedBuiltInElements: true, // allow customized built-ins
233
+ },
234
+ }
235
+ ); // <foo-bar baz="foobar"></foo-bar><div is="foo-baz"></div>
236
+
191
237
  /**
192
238
  * Control behavior relating to URI values
193
239
  */
@@ -211,9 +257,6 @@ var clean = DOMPurify.sanitize(dirty, {ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|
211
257
 
212
258
  /**
213
259
  * Influence the return-type
214
- *
215
- * Careful, this setting has foot-gun potential! If you set RETURN_DOM or RETURN_DOM_FRAGMENT to true, don't set RETURN_DOM_IMPORT to false!
216
- * By default, our settings are secure - we believe - but returning a DOM *and* manually setting RETURN_DOM_IMPORT to false will give you XSS in some situations.
217
260
  */
218
261
  // return a DOM HTMLBodyElement instead of an HTML string (default is false)
219
262
  var clean = DOMPurify.sanitize(dirty, {RETURN_DOM: true});
@@ -221,13 +264,6 @@ var clean = DOMPurify.sanitize(dirty, {RETURN_DOM: true});
221
264
  // return a DOM DocumentFragment instead of an HTML string (default is false)
222
265
  var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true});
223
266
 
224
- // return a DOM DocumentFragment instead of an HTML string (default is false)
225
- // also import it into the current document (default is false).
226
- // RETURN_DOM_IMPORT must be set if you would like to append
227
- // the returned node to the current document (default is true)
228
- var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true, RETURN_DOM_IMPORT: true});
229
- document.body.appendChild(clean);
230
-
231
267
  // use the RETURN_TRUSTED_TYPE flag to turn on Trusted Types support if available
232
268
  var clean = DOMPurify.sanitize(dirty, {RETURN_TRUSTED_TYPE: true}); // will return a TrustedHTML object instead of a string if possible
233
269
 
@@ -246,6 +282,9 @@ var clean = DOMPurify.sanitize(dirty, {KEEP_CONTENT: false});
246
282
  // glue elements like style, script or others to document.body and prevent unintuitive browser behavior in several edge-cases (default is false)
247
283
  var clean = DOMPurify.sanitize(dirty, {FORCE_BODY: true});
248
284
 
285
+ // change the parser type so sanitized data is treated as XML and not as HTML, which is the default
286
+ var clean = DOMPurify.sanitize(dirty, {PARSER_MEDIA_TYPE: 'application/xhtml+xml'});
287
+
249
288
  /**
250
289
  * Influence where we sanitize
251
290
  */
@@ -337,9 +376,9 @@ Feature releases will not be announced to this list.
337
376
 
338
377
  Many people helped and help DOMPurify become what it is and need to be acknowledged here!
339
378
 
340
- [granlem 💸](https://twitter.com/MaximeVeit), [oreoshake 💸](https://github.com/oreoshake), [dcramer 💸](https://github.com/dcramer),[tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [NateScarlet](https://github.com/NateScarlet), [neilj](https://github.com/neilj), [fhemberger](https://github.com/fhemberger), [Joris-van-der-Wel](https://github.com/Joris-van-der-Wel), [ydaniv](https://github.com/ydaniv), [terjanq](https://twitter.com/terjanq), [filedescriptor](https://github.com/filedescriptor), [ConradIrwin](https://github.com/ConradIrwin), [gibson042](https://github.com/gibson042), [choumx](https://github.com/choumx), [0xSobky](https://github.com/0xSobky), [styfle](https://github.com/styfle), [koto](https://github.com/koto), [tlau88](https://github.com/tlau88), [strugee](https://github.com/strugee), [oparoz](https://github.com/oparoz), [mathiasbynens](https://github.com/mathiasbynens), [edg2s](https://github.com/edg2s), [dnkolegov](https://github.com/dnkolegov), [dhardtke](https://github.com/dhardtke), [wirehead](https://github.com/wirehead), [thorn0](https://github.com/thorn0), [styu](https://github.com/styu), [mozfreddyb](https://github.com/mozfreddyb), [mikesamuel](https://github.com/mikesamuel), [jorangreef](https://github.com/jorangreef), [jimmyhchan](https://github.com/jimmyhchan), [jameydeorio](https://github.com/jameydeorio), [jameskraus](https://github.com/jameskraus), [hyderali](https://github.com/hyderali), [hansottowirtz](https://github.com/hansottowirtz), [hackvertor](https://github.com/hackvertor), [freddyb](https://github.com/freddyb), [flavorjones](https://github.com/flavorjones), [djfarrelly](https://github.com/djfarrelly), [devd](https://github.com/devd), [camerondunford](https://github.com/camerondunford), [buu700](https://github.com/buu700), [buildog](https://github.com/buildog), [alabiaga](https://github.com/alabiaga), [Vector919](https://github.com/Vector919), [Robbert](https://github.com/Robbert), [GreLI](https://github.com/GreLI), [FuzzySockets](https://github.com/FuzzySockets), [ArtemBernatskyy](https://github.com/ArtemBernatskyy), [@garethheyes](https://twitter.com/garethheyes), [@shafigullin](https://twitter.com/shafigullin), [@mmrupp](https://twitter.com/mmrupp), [@irsdl](https://twitter.com/irsdl),[ShikariSenpai](https://github.com/ShikariSenpai), [ansjdnakjdnajkd](https://github.com/ansjdnakjdnajkd), [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro), [@CmdEngineer_](https://twitter.com/CmdEngineer_), [@avr4mit](https://twitter.com/avr4mit) and especially [@securitymb ❤️](https://twitter.com/securitymb) & [@masatokinugawa ❤️](https://twitter.com/masatokinugawa)
379
+ [GrantGryczan 💸](https://github.com/GrantGryczan), [lowdefy 💸](https://twitter.com/lowdefy), [granlem 💸](https://twitter.com/MaximeVeit), [oreoshake 💸](https://github.com/oreoshake), [dcramer 💸](https://github.com/dcramer),[tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [franktopel](https://github.com/franktopel), [NateScarlet](https://github.com/NateScarlet), [neilj](https://github.com/neilj), [fhemberger](https://github.com/fhemberger), [Joris-van-der-Wel](https://github.com/Joris-van-der-Wel), [ydaniv](https://github.com/ydaniv), [terjanq](https://twitter.com/terjanq), [filedescriptor](https://github.com/filedescriptor), [ConradIrwin](https://github.com/ConradIrwin), [gibson042](https://github.com/gibson042), [choumx](https://github.com/choumx), [0xSobky](https://github.com/0xSobky), [styfle](https://github.com/styfle), [koto](https://github.com/koto), [tlau88](https://github.com/tlau88), [strugee](https://github.com/strugee), [oparoz](https://github.com/oparoz), [mathiasbynens](https://github.com/mathiasbynens), [edg2s](https://github.com/edg2s), [dnkolegov](https://github.com/dnkolegov), [dhardtke](https://github.com/dhardtke), [wirehead](https://github.com/wirehead), [thorn0](https://github.com/thorn0), [styu](https://github.com/styu), [mozfreddyb](https://github.com/mozfreddyb), [mikesamuel](https://github.com/mikesamuel), [jorangreef](https://github.com/jorangreef), [jimmyhchan](https://github.com/jimmyhchan), [jameydeorio](https://github.com/jameydeorio), [jameskraus](https://github.com/jameskraus), [hyderali](https://github.com/hyderali), [hansottowirtz](https://github.com/hansottowirtz), [hackvertor](https://github.com/hackvertor), [freddyb](https://github.com/freddyb), [flavorjones](https://github.com/flavorjones), [djfarrelly](https://github.com/djfarrelly), [devd](https://github.com/devd), [camerondunford](https://github.com/camerondunford), [buu700](https://github.com/buu700), [buildog](https://github.com/buildog), [alabiaga](https://github.com/alabiaga), [Vector919](https://github.com/Vector919), [Robbert](https://github.com/Robbert), [GreLI](https://github.com/GreLI), [FuzzySockets](https://github.com/FuzzySockets), [ArtemBernatskyy](https://github.com/ArtemBernatskyy), [@garethheyes](https://twitter.com/garethheyes), [@shafigullin](https://twitter.com/shafigullin), [@mmrupp](https://twitter.com/mmrupp), [@irsdl](https://twitter.com/irsdl),[ShikariSenpai](https://github.com/ShikariSenpai), [ansjdnakjdnajkd](https://github.com/ansjdnakjdnajkd), [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro), [@CmdEngineer_](https://twitter.com/CmdEngineer_), [@avr4mit](https://twitter.com/avr4mit) and especially [@securitymb ❤️](https://twitter.com/securitymb) & [@masatokinugawa ❤️](https://twitter.com/masatokinugawa)
341
380
 
342
- ## Testing powered by
381
+ ## Testing powered by
343
382
  <a target="_blank" href="https://www.browserstack.com/"><img width="200" src="https://www.browserstack.com/images/layout/browserstack-logo-600x315.png"></a><br>
344
383
 
345
384
  And last but not least, thanks to [BrowserStack Open-Source Program](https://www.browserstack.com/open-source) for supporting this project with their services for free and delivering excellent, dedicated and very professional support on top of that.
@@ -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
  'use strict';
4
4
 
@@ -152,13 +152,13 @@ var html = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside'
152
152
  // SVG
153
153
  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']);
154
154
 
155
- 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']);
155
+ 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']);
156
156
 
157
157
  // List of SVG elements that are disallowed by default.
158
158
  // We still need to know them so that we can do namespace
159
159
  // checks properly in case one wants to add them to
160
160
  // allow-list.
161
- 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']);
161
+ 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']);
162
162
 
163
163
  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']);
164
164
 
@@ -168,7 +168,7 @@ var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv
168
168
 
169
169
  var text = freeze(['#text']);
170
170
 
171
- 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']);
171
+ 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']);
172
172
 
173
173
  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']);
174
174
 
@@ -245,7 +245,7 @@ function createDOMPurify() {
245
245
  * Version label, exposed for easier checks
246
246
  * if DOMPurify is up to date or not
247
247
  */
248
- DOMPurify.version = '2.3.0';
248
+ DOMPurify.version = '2.3.4';
249
249
 
250
250
  /**
251
251
  * Array of elements that DOMPurify removed during sanitation.
@@ -271,8 +271,7 @@ function createDOMPurify() {
271
271
  NodeFilter = window.NodeFilter,
272
272
  _window$NamedNodeMap = window.NamedNodeMap,
273
273
  NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
274
- Text = window.Text,
275
- Comment = window.Comment,
274
+ HTMLFormElement = window.HTMLFormElement,
276
275
  DOMParser = window.DOMParser,
277
276
  trustedTypes = window.trustedTypes;
278
277
 
@@ -342,6 +341,33 @@ function createDOMPurify() {
342
341
  var ALLOWED_ATTR = null;
343
342
  var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray$1(html$1), _toConsumableArray$1(svg$1), _toConsumableArray$1(mathMl$1), _toConsumableArray$1(xml)));
344
343
 
344
+ /*
345
+ * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
346
+ * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
347
+ * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
348
+ * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
349
+ */
350
+ var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
351
+ tagNameCheck: {
352
+ writable: true,
353
+ configurable: false,
354
+ enumerable: true,
355
+ value: null
356
+ },
357
+ attributeNameCheck: {
358
+ writable: true,
359
+ configurable: false,
360
+ enumerable: true,
361
+ value: null
362
+ },
363
+ allowCustomizedBuiltInElements: {
364
+ writable: true,
365
+ configurable: false,
366
+ enumerable: true,
367
+ value: false
368
+ }
369
+ }));
370
+
345
371
  /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
346
372
  var FORBID_TAGS = null;
347
373
 
@@ -382,17 +408,6 @@ function createDOMPurify() {
382
408
  * string (or a TrustedHTML object if Trusted Types are supported) */
383
409
  var RETURN_DOM_FRAGMENT = false;
384
410
 
385
- /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
386
- * `Node` is imported into the current `Document`. If this flag is not enabled the
387
- * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
388
- * DOMPurify.
389
- *
390
- * This defaults to `true` starting DOMPurify 2.2.0. Note that setting it to `false`
391
- * might cause XSS from attacks hidden in closed shadowroots in case the browser
392
- * supports Declarative Shadow: DOM https://web.dev/declarative-shadow-dom/
393
- */
394
- var RETURN_DOM_IMPORT = true;
395
-
396
411
  /* Try to return a Trusted Type object instead of a string, return a string in
397
412
  * case Trusted Types are not supported */
398
413
  var RETURN_TRUSTED_TYPE = false;
@@ -411,7 +426,8 @@ function createDOMPurify() {
411
426
  var USE_PROFILES = {};
412
427
 
413
428
  /* Tags to ignore content of when KEEP_CONTENT is true */
414
- 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']);
429
+ var FORBID_CONTENTS = null;
430
+ 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']);
415
431
 
416
432
  /* Tags that are safe for data: URIs */
417
433
  var DATA_URI_TAGS = null;
@@ -419,7 +435,7 @@ function createDOMPurify() {
419
435
 
420
436
  /* Attributes safe for values like "javascript:" */
421
437
  var URI_SAFE_ATTRIBUTES = null;
422
- var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']);
438
+ var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
423
439
 
424
440
  var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
425
441
  var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
@@ -428,6 +444,12 @@ function createDOMPurify() {
428
444
  var NAMESPACE = HTML_NAMESPACE;
429
445
  var IS_EMPTY_INPUT = false;
430
446
 
447
+ /* Parsing of strict XHTML documents */
448
+ var PARSER_MEDIA_TYPE = void 0;
449
+ var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
450
+ var DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
451
+ var transformCaseFunc = void 0;
452
+
431
453
  /* Keep a reference to config to pass to hooks */
432
454
  var CONFIG = null;
433
455
 
@@ -436,6 +458,10 @@ function createDOMPurify() {
436
458
 
437
459
  var formElement = document.createElement('form');
438
460
 
461
+ var isRegexOrFunction = function isRegexOrFunction(testValue) {
462
+ return testValue instanceof RegExp || testValue instanceof Function;
463
+ };
464
+
439
465
  /**
440
466
  * _parseConfig
441
467
  *
@@ -460,6 +486,7 @@ function createDOMPurify() {
460
486
  ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
461
487
  URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) : DEFAULT_URI_SAFE_ATTRIBUTES;
462
488
  DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS;
489
+ FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS) : DEFAULT_FORBID_CONTENTS;
463
490
  FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {};
464
491
  FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};
465
492
  USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
@@ -470,7 +497,6 @@ function createDOMPurify() {
470
497
  WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
471
498
  RETURN_DOM = cfg.RETURN_DOM || false; // Default false
472
499
  RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
473
- RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT !== false; // Default true
474
500
  RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
475
501
  FORCE_BODY = cfg.FORCE_BODY || false; // Default false
476
502
  SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
@@ -478,6 +504,27 @@ function createDOMPurify() {
478
504
  IN_PLACE = cfg.IN_PLACE || false; // Default false
479
505
  IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
480
506
  NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
507
+ if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
508
+ CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
509
+ }
510
+
511
+ if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
512
+ CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
513
+ }
514
+
515
+ if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
516
+ CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
517
+ }
518
+
519
+ PARSER_MEDIA_TYPE =
520
+ // eslint-disable-next-line unicorn/prefer-includes
521
+ SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE;
522
+
523
+ // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
524
+ transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? function (x) {
525
+ return x;
526
+ } : stringToLowerCase;
527
+
481
528
  if (SAFE_FOR_TEMPLATES) {
482
529
  ALLOW_DATA_ATTR = false;
483
530
  }
@@ -535,6 +582,14 @@ function createDOMPurify() {
535
582
  addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
536
583
  }
537
584
 
585
+ if (cfg.FORBID_CONTENTS) {
586
+ if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
587
+ FORBID_CONTENTS = clone(FORBID_CONTENTS);
588
+ }
589
+
590
+ addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS);
591
+ }
592
+
538
593
  /* Add #text in case KEEP_CONTENT is set to true */
539
594
  if (KEEP_CONTENT) {
540
595
  ALLOWED_TAGS['#text'] = true;
@@ -738,6 +793,11 @@ function createDOMPurify() {
738
793
  leadingWhitespace = matches && matches[0];
739
794
  }
740
795
 
796
+ if (PARSER_MEDIA_TYPE === 'application/xhtml+xml') {
797
+ // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
798
+ dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
799
+ }
800
+
741
801
  var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
742
802
  /*
743
803
  * Use the DOMParser API by default, fallback later if needs be
@@ -745,7 +805,7 @@ function createDOMPurify() {
745
805
  */
746
806
  if (NAMESPACE === HTML_NAMESPACE) {
747
807
  try {
748
- doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
808
+ doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
749
809
  } catch (_) {}
750
810
  }
751
811
 
@@ -790,15 +850,7 @@ function createDOMPurify() {
790
850
  * @return {Boolean} true if clobbered, false if safe
791
851
  */
792
852
  var _isClobbered = function _isClobbered(elm) {
793
- if (elm instanceof Text || elm instanceof Comment) {
794
- return false;
795
- }
796
-
797
- 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') {
798
- return true;
799
- }
800
-
801
- return false;
853
+ 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');
802
854
  };
803
855
 
804
856
  /**
@@ -858,7 +910,7 @@ function createDOMPurify() {
858
910
  }
859
911
 
860
912
  /* Now let's check the element's type and name */
861
- var tagName = stringToLowerCase(currentNode.nodeName);
913
+ var tagName = transformCaseFunc(currentNode.nodeName);
862
914
 
863
915
  /* Execute a hook if present */
864
916
  _executeHook('uponSanitizeElement', currentNode, {
@@ -872,6 +924,12 @@ function createDOMPurify() {
872
924
  return true;
873
925
  }
874
926
 
927
+ /* Mitigate a problem with templates inside select */
928
+ if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) {
929
+ _forceRemove(currentNode);
930
+ return true;
931
+ }
932
+
875
933
  /* Remove element if anything forbids its presence */
876
934
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
877
935
  /* Keep content except for bad-listed elements */
@@ -888,6 +946,11 @@ function createDOMPurify() {
888
946
  }
889
947
  }
890
948
 
949
+ if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {
950
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
951
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
952
+ }
953
+
891
954
  _forceRemove(currentNode);
892
955
  return true;
893
956
  }
@@ -941,8 +1004,16 @@ function createDOMPurify() {
941
1004
  XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
942
1005
  We don't need to check the value; it's always URI safe. */
943
1006
  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]) {
944
- return false;
945
-
1007
+ if (
1008
+ // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1009
+ // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1010
+ // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1011
+ _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)) ||
1012
+ // Alternative, second condition checks if it's an `is`-attribute, AND
1013
+ // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1014
+ 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 {
1015
+ return false;
1016
+ }
946
1017
  /* Check value is safe. First, is attr inert? If so, is safe */
947
1018
  } 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 {
948
1019
  return false;
@@ -951,6 +1022,16 @@ function createDOMPurify() {
951
1022
  return true;
952
1023
  };
953
1024
 
1025
+ /**
1026
+ * _basicCustomElementCheck
1027
+ * checks if at least one dash is included in tagName, and it's not the first char
1028
+ * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1029
+ * @param {string} tagName name of the tag of the node to sanitize
1030
+ */
1031
+ var _basicCustomElementTest = function _basicCustomElementTest(tagName) {
1032
+ return tagName.indexOf('-') > 0;
1033
+ };
1034
+
954
1035
  /**
955
1036
  * _sanitizeAttributes
956
1037
  *
@@ -993,7 +1074,7 @@ function createDOMPurify() {
993
1074
  namespaceURI = _attr.namespaceURI;
994
1075
 
995
1076
  value = stringTrim(attr.value);
996
- lcName = stringToLowerCase(name);
1077
+ lcName = transformCaseFunc(name);
997
1078
 
998
1079
  /* Execute a hook if present */
999
1080
  hookEvent.attrName = lcName;
@@ -1028,7 +1109,7 @@ function createDOMPurify() {
1028
1109
  }
1029
1110
 
1030
1111
  /* Is `value` valid for this attribute? */
1031
- var lcTag = currentNode.nodeName.toLowerCase();
1112
+ var lcTag = transformCaseFunc(currentNode.nodeName);
1032
1113
  if (!_isValidAttribute(lcTag, lcName, value)) {
1033
1114
  continue;
1034
1115
  }
@@ -1229,7 +1310,7 @@ function createDOMPurify() {
1229
1310
  returnNode = body;
1230
1311
  }
1231
1312
 
1232
- if (RETURN_DOM_IMPORT) {
1313
+ if (ALLOWED_ATTR.shadowroot) {
1233
1314
  /*
1234
1315
  AdoptNode() is not used because internal state is not reset
1235
1316
  (e.g. the past names map of a HTMLFormElement), this is safe
@@ -1291,8 +1372,8 @@ function createDOMPurify() {
1291
1372
  _parseConfig({});
1292
1373
  }
1293
1374
 
1294
- var lcTag = stringToLowerCase(tag);
1295
- var lcName = stringToLowerCase(attr);
1375
+ var lcTag = transformCaseFunc(tag);
1376
+ var lcName = transformCaseFunc(attr);
1296
1377
  return _isValidAttribute(lcTag, lcName, value);
1297
1378
  };
1298
1379