dompurify 2.2.3 → 2.2.7

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.2.3.
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.2.7.
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 [15 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 v12, v13, v14.0.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 [15 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.
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
 
@@ -37,10 +37,15 @@ It's easy. Just include DOMPurify on your website.
37
37
  Afterwards you can sanitize strings by executing the following code:
38
38
 
39
39
  ```js
40
- var clean = DOMPurify.sanitize(dirty);
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
+ 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
+
46
+ ```js
47
+ let clean = DOMPurify.sanitize( dirty , {USE_PROFILES: {html: true}} );
48
+ ```
44
49
 
45
50
  ### Is there any foot-gun potential?
46
51
 
@@ -155,6 +160,8 @@ var clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b']});
155
160
  var clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b', 'q'], ALLOWED_ATTR: ['style']});
156
161
 
157
162
  // allow all safe HTML elements but neither SVG nor MathML
163
+ // note that the USE_PROFILES setting will override the ALLOWED_TAGS setting
164
+ // so don't use them together
158
165
  var clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {html: true}});
159
166
 
160
167
  // allow all safe SVG elements and SVG Filters, no HTML or MathML
@@ -283,7 +290,7 @@ DOMPurify.addHook('beforeSanitizeElements', function (
283
290
 
284
291
  ## Continuous Integration
285
292
 
286
- We are currently using Travis CI in combination with BrowserStack. This gives us the possibility to confirm for each and every commit that all is going according to plan in all supported browsers. Check out the build logs here: https://travis-ci.org/cure53/DOMPurify
293
+ We are currently using Github Actions in combination with BrowserStack. This gives us the possibility to confirm for each and every commit that all is going according to plan in all supported browsers. Check out the build logs here: https://github.com/cure53/DOMPurify/actions
287
294
 
288
295
  You can further run local tests by executing `npm test`. The tests work fine with Node.js v0.6.2 and jsdom@8.5.0.
289
296
 
@@ -327,7 +334,7 @@ Feature releases will not be announced to this list.
327
334
 
328
335
  Many people helped and help DOMPurify become what it is and need to be acknowledged here!
329
336
 
330
- [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), [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) and especially [@masatokinugawa](https://twitter.com/masatokinugawa)
337
+ [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), [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)
331
338
 
332
339
  ## Testing powered by
333
340
  <a target="_blank" href="https://www.browserstack.com/"><img width="200" src="https://www.browserstack.com/images/layout/browserstack-logo-600x315.png"></a><br>
@@ -6,7 +6,9 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
6
6
 
7
7
  var hasOwnProperty = Object.hasOwnProperty,
8
8
  setPrototypeOf = Object.setPrototypeOf,
9
- isFrozen = Object.isFrozen;
9
+ isFrozen = Object.isFrozen,
10
+ getPrototypeOf = Object.getPrototypeOf,
11
+ getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
10
12
  var freeze = Object.freeze,
11
13
  seal = Object.seal,
12
14
  create = Object.create; // eslint-disable-line import/no-mutable-exports
@@ -117,15 +119,53 @@ function clone(object) {
117
119
  return newObject;
118
120
  }
119
121
 
122
+ /* IE10 doesn't support __lookupGetter__ so lets'
123
+ * simulate it. It also automatically checks
124
+ * if the prop is function or getter and behaves
125
+ * accordingly. */
126
+ function lookupGetter(object, prop) {
127
+ while (object !== null) {
128
+ var desc = getOwnPropertyDescriptor(object, prop);
129
+ if (desc) {
130
+ if (desc.get) {
131
+ return unapply(desc.get);
132
+ }
133
+
134
+ if (typeof desc.value === 'function') {
135
+ return unapply(desc.value);
136
+ }
137
+ }
138
+
139
+ object = getPrototypeOf(object);
140
+ }
141
+
142
+ function fallbackValue(element) {
143
+ console.warn('fallback value for', element);
144
+ return null;
145
+ }
146
+
147
+ return fallbackValue;
148
+ }
149
+
120
150
  var html = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
121
151
 
122
152
  // SVG
123
- var svg = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'audio', 'canvas', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'video', 'view', 'vkern']);
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']);
124
154
 
125
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']);
126
156
 
157
+ // List of SVG elements that are disallowed by default.
158
+ // We still need to know them so that we can do namespace
159
+ // checks properly in case one wants to add them to
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']);
162
+
127
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']);
128
164
 
165
+ // Similarly to SVG, we want to know all MathML elements,
166
+ // even those that we disallow by default.
167
+ var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
168
+
129
169
  var text = freeze(['#text']);
130
170
 
131
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']);
@@ -205,7 +245,7 @@ function createDOMPurify() {
205
245
  * Version label, exposed for easier checks
206
246
  * if DOMPurify is up to date or not
207
247
  */
208
- DOMPurify.version = '2.2.3';
248
+ DOMPurify.version = '2.2.7';
209
249
 
210
250
  /**
211
251
  * Array of elements that DOMPurify removed during sanitation.
@@ -227,6 +267,7 @@ function createDOMPurify() {
227
267
  var DocumentFragment = window.DocumentFragment,
228
268
  HTMLTemplateElement = window.HTMLTemplateElement,
229
269
  Node = window.Node,
270
+ Element = window.Element,
230
271
  NodeFilter = window.NodeFilter,
231
272
  _window$NamedNodeMap = window.NamedNodeMap,
232
273
  NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
@@ -235,13 +276,20 @@ function createDOMPurify() {
235
276
  DOMParser = window.DOMParser,
236
277
  trustedTypes = window.trustedTypes;
237
278
 
279
+
280
+ var ElementPrototype = Element.prototype;
281
+
282
+ var cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
283
+ var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
284
+ var getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
285
+ var getParentNode = lookupGetter(ElementPrototype, 'parentNode');
286
+
238
287
  // As per issue #47, the web-components registry is inherited by a
239
288
  // new document created via createHTMLDocument. As per the spec
240
289
  // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
241
290
  // a new empty registry is used when creating a template contents owner
242
291
  // document, so we use that as our parent document to ensure nothing
243
292
  // is inherited.
244
-
245
293
  if (typeof HTMLTemplateElement === 'function') {
246
294
  var template = document.createElement('template');
247
295
  if (template.content && template.content.ownerDocument) {
@@ -270,7 +318,7 @@ function createDOMPurify() {
270
318
  /**
271
319
  * Expose whether this browser supports running the full DOMPurify.
272
320
  */
273
- DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
321
+ DOMPurify.isSupported = typeof getParentNode === 'function' && implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
274
322
 
275
323
  var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR,
276
324
  ERB_EXPR$$1 = ERB_EXPR,
@@ -363,7 +411,7 @@ function createDOMPurify() {
363
411
  var USE_PROFILES = {};
364
412
 
365
413
  /* Tags to ignore content of when KEEP_CONTENT is true */
366
- var FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
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']);
367
415
 
368
416
  /* Tags that are safe for data: URIs */
369
417
  var DATA_URI_TAGS = null;
@@ -504,6 +552,115 @@ function createDOMPurify() {
504
552
  CONFIG = cfg;
505
553
  };
506
554
 
555
+ var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
556
+
557
+ var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
558
+
559
+ /* Keep track of all possible SVG and MathML tags
560
+ * so that we can perform the namespace checks
561
+ * correctly. */
562
+ var ALL_SVG_TAGS = addToSet({}, svg);
563
+ addToSet(ALL_SVG_TAGS, svgFilters);
564
+ addToSet(ALL_SVG_TAGS, svgDisallowed);
565
+
566
+ var ALL_MATHML_TAGS = addToSet({}, mathMl);
567
+ addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
568
+
569
+ var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
570
+ var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
571
+ var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
572
+
573
+ /**
574
+ *
575
+ *
576
+ * @param {Element} element a DOM element whose namespace is being checked
577
+ * @returns {boolean} Return false if the element has a
578
+ * namespace that a spec-compliant parser would never
579
+ * return. Return true otherwise.
580
+ */
581
+ var _checkValidNamespace = function _checkValidNamespace(element) {
582
+ var parent = getParentNode(element);
583
+
584
+ // In JSDOM, if we're inside shadow DOM, then parentNode
585
+ // can be null. We just simulate parent in this case.
586
+ if (!parent || !parent.tagName) {
587
+ parent = {
588
+ namespaceURI: HTML_NAMESPACE,
589
+ tagName: 'template'
590
+ };
591
+ }
592
+
593
+ var tagName = stringToLowerCase(element.tagName);
594
+ var parentTagName = stringToLowerCase(parent.tagName);
595
+
596
+ if (element.namespaceURI === SVG_NAMESPACE) {
597
+ // The only way to switch from HTML namespace to SVG
598
+ // is via <svg>. If it happens via any other tag, then
599
+ // it should be killed.
600
+ if (parent.namespaceURI === HTML_NAMESPACE) {
601
+ return tagName === 'svg';
602
+ }
603
+
604
+ // The only way to switch from MathML to SVG is via
605
+ // svg if parent is either <annotation-xml> or MathML
606
+ // text integration points.
607
+ if (parent.namespaceURI === MATHML_NAMESPACE) {
608
+ return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
609
+ }
610
+
611
+ // We only allow elements that are defined in SVG
612
+ // spec. All others are disallowed in SVG namespace.
613
+ return Boolean(ALL_SVG_TAGS[tagName]);
614
+ }
615
+
616
+ if (element.namespaceURI === MATHML_NAMESPACE) {
617
+ // The only way to switch from HTML namespace to MathML
618
+ // is via <math>. If it happens via any other tag, then
619
+ // it should be killed.
620
+ if (parent.namespaceURI === HTML_NAMESPACE) {
621
+ return tagName === 'math';
622
+ }
623
+
624
+ // The only way to switch from SVG to MathML is via
625
+ // <math> and HTML integration points
626
+ if (parent.namespaceURI === SVG_NAMESPACE) {
627
+ return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
628
+ }
629
+
630
+ // We only allow elements that are defined in MathML
631
+ // spec. All others are disallowed in MathML namespace.
632
+ return Boolean(ALL_MATHML_TAGS[tagName]);
633
+ }
634
+
635
+ if (element.namespaceURI === HTML_NAMESPACE) {
636
+ // The only way to switch from SVG to HTML is via
637
+ // HTML integration points, and from MathML to HTML
638
+ // is via MathML text integration points
639
+ if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
640
+ return false;
641
+ }
642
+
643
+ if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
644
+ return false;
645
+ }
646
+
647
+ // Certain elements are allowed in both SVG and HTML
648
+ // namespace. We need to specify them explicitly
649
+ // so that they don't get erronously deleted from
650
+ // HTML namespace.
651
+ var commonSvgAndHTMLElements = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
652
+
653
+ // We disallow tags that are specific for MathML
654
+ // or SVG and should never appear in HTML namespace
655
+ return !ALL_MATHML_TAGS[tagName] && (commonSvgAndHTMLElements[tagName] || !ALL_SVG_TAGS[tagName]);
656
+ }
657
+
658
+ // The code should never reach this place (this means
659
+ // that the element somehow got namespace that is not
660
+ // HTML, SVG or MathML). Return false just in case.
661
+ return false;
662
+ };
663
+
507
664
  /**
508
665
  * _forceRemove
509
666
  *
@@ -514,7 +671,11 @@ function createDOMPurify() {
514
671
  try {
515
672
  node.parentNode.removeChild(node);
516
673
  } catch (_) {
517
- node.outerHTML = emptyHTML;
674
+ try {
675
+ node.outerHTML = emptyHTML;
676
+ } catch (_) {
677
+ node.remove();
678
+ }
518
679
  }
519
680
  };
520
681
 
@@ -538,6 +699,19 @@ function createDOMPurify() {
538
699
  }
539
700
 
540
701
  node.removeAttribute(name);
702
+
703
+ // We void attribute values for unremovable "is"" attributes
704
+ if (name === 'is' && !ALLOWED_ATTR[name]) {
705
+ if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
706
+ try {
707
+ _forceRemove(node);
708
+ } catch (_) {}
709
+ } else {
710
+ try {
711
+ node.setAttribute(name, '');
712
+ } catch (_) {}
713
+ }
714
+ }
541
715
  };
542
716
 
543
717
  /**
@@ -606,7 +780,7 @@ function createDOMPurify() {
606
780
  return false;
607
781
  }
608
782
 
609
- 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') {
783
+ 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') {
610
784
  return true;
611
785
  }
612
786
 
@@ -678,12 +852,6 @@ function createDOMPurify() {
678
852
  allowedTags: ALLOWED_TAGS
679
853
  });
680
854
 
681
- /* Take care of an mXSS pattern using p, br inside svg, math */
682
- if ((tagName === 'svg' || tagName === 'math') && currentNode.querySelectorAll('p, br, form, table, h1, h2, h3, h4, h5, h6').length !== 0) {
683
- _forceRemove(currentNode);
684
- return true;
685
- }
686
-
687
855
  /* Detect mXSS attempts abusing namespace confusion */
688
856
  if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
689
857
  _forceRemove(currentNode);
@@ -693,18 +861,29 @@ function createDOMPurify() {
693
861
  /* Remove element if anything forbids its presence */
694
862
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
695
863
  /* Keep content except for bad-listed elements */
696
- if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && typeof currentNode.insertAdjacentHTML === 'function') {
697
- try {
698
- var htmlToInsert = currentNode.innerHTML;
699
- currentNode.insertAdjacentHTML('AfterEnd', trustedTypesPolicy ? trustedTypesPolicy.createHTML(htmlToInsert) : htmlToInsert);
700
- } catch (_) {}
864
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
865
+ var parentNode = getParentNode(currentNode);
866
+ var childNodes = getChildNodes(currentNode);
867
+
868
+ if (childNodes && parentNode) {
869
+ var childCount = childNodes.length;
870
+
871
+ for (var i = childCount - 1; i >= 0; --i) {
872
+ parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
873
+ }
874
+ }
701
875
  }
702
876
 
703
877
  _forceRemove(currentNode);
704
878
  return true;
705
879
  }
706
880
 
707
- /* Remove in case a noscript/noembed XSS is suspected */
881
+ /* Check whether element has a valid namespace */
882
+ if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
883
+ _forceRemove(currentNode);
884
+ return true;
885
+ }
886
+
708
887
  if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
709
888
  _forceRemove(currentNode);
710
889
  return true;