dompurify 2.4.9 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/purify.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @license DOMPurify 2.4.9 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.4.9/LICENSE */
1
+ /*! @license DOMPurify 2.5.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.5.1/LICENSE */
2
2
 
3
3
  (function (global, factory) {
4
4
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
@@ -15,21 +15,17 @@
15
15
  return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
16
16
  }, _typeof(obj);
17
17
  }
18
-
19
18
  function _setPrototypeOf(o, p) {
20
19
  _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
21
20
  o.__proto__ = p;
22
21
  return o;
23
22
  };
24
-
25
23
  return _setPrototypeOf(o, p);
26
24
  }
27
-
28
25
  function _isNativeReflectConstruct() {
29
26
  if (typeof Reflect === "undefined" || !Reflect.construct) return false;
30
27
  if (Reflect.construct.sham) return false;
31
28
  if (typeof Proxy === "function") return true;
32
-
33
29
  try {
34
30
  Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
35
31
  return true;
@@ -37,7 +33,6 @@
37
33
  return false;
38
34
  }
39
35
  }
40
-
41
36
  function _construct(Parent, args, Class) {
42
37
  if (_isNativeReflectConstruct()) {
43
38
  _construct = Reflect.construct;
@@ -51,22 +46,17 @@
51
46
  return instance;
52
47
  };
53
48
  }
54
-
55
49
  return _construct.apply(null, arguments);
56
50
  }
57
-
58
51
  function _toConsumableArray(arr) {
59
52
  return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
60
53
  }
61
-
62
54
  function _arrayWithoutHoles(arr) {
63
55
  if (Array.isArray(arr)) return _arrayLikeToArray(arr);
64
56
  }
65
-
66
57
  function _iterableToArray(iter) {
67
58
  if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
68
59
  }
69
-
70
60
  function _unsupportedIterableToArray(o, minLen) {
71
61
  if (!o) return;
72
62
  if (typeof o === "string") return _arrayLikeToArray(o, minLen);
@@ -75,56 +65,46 @@
75
65
  if (n === "Map" || n === "Set") return Array.from(o);
76
66
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
77
67
  }
78
-
79
68
  function _arrayLikeToArray(arr, len) {
80
69
  if (len == null || len > arr.length) len = arr.length;
81
-
82
70
  for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
83
-
84
71
  return arr2;
85
72
  }
86
-
87
73
  function _nonIterableSpread() {
88
74
  throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
89
75
  }
90
76
 
91
77
  var hasOwnProperty = Object.hasOwnProperty,
92
- setPrototypeOf = Object.setPrototypeOf,
93
- isFrozen = Object.isFrozen,
94
- getPrototypeOf = Object.getPrototypeOf,
95
- getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
78
+ setPrototypeOf = Object.setPrototypeOf,
79
+ isFrozen = Object.isFrozen,
80
+ getPrototypeOf = Object.getPrototypeOf,
81
+ getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
96
82
  var freeze = Object.freeze,
97
- seal = Object.seal,
98
- create = Object.create; // eslint-disable-line import/no-mutable-exports
99
-
83
+ seal = Object.seal,
84
+ create = Object.create; // eslint-disable-line import/no-mutable-exports
100
85
  var _ref = typeof Reflect !== 'undefined' && Reflect,
101
- apply = _ref.apply,
102
- construct = _ref.construct;
103
-
86
+ apply = _ref.apply,
87
+ construct = _ref.construct;
104
88
  if (!apply) {
105
89
  apply = function apply(fun, thisValue, args) {
106
90
  return fun.apply(thisValue, args);
107
91
  };
108
92
  }
109
-
110
93
  if (!freeze) {
111
94
  freeze = function freeze(x) {
112
95
  return x;
113
96
  };
114
97
  }
115
-
116
98
  if (!seal) {
117
99
  seal = function seal(x) {
118
100
  return x;
119
101
  };
120
102
  }
121
-
122
103
  if (!construct) {
123
104
  construct = function construct(Func, args) {
124
105
  return _construct(Func, _toConsumableArray(args));
125
106
  };
126
107
  }
127
-
128
108
  var arrayForEach = unapply(Array.prototype.forEach);
129
109
  var arrayPop = unapply(Array.prototype.pop);
130
110
  var arrayPush = unapply(Array.prototype.push);
@@ -141,7 +121,6 @@
141
121
  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
142
122
  args[_key - 1] = arguments[_key];
143
123
  }
144
-
145
124
  return apply(func, thisArg, args);
146
125
  };
147
126
  }
@@ -150,103 +129,89 @@
150
129
  for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
151
130
  args[_key2] = arguments[_key2];
152
131
  }
153
-
154
132
  return construct(func, args);
155
133
  };
156
134
  }
157
- /* Add properties to a lookup table */
158
135
 
136
+ /* Add properties to a lookup table */
159
137
  function addToSet(set, array, transformCaseFunc) {
160
138
  var _transformCaseFunc;
161
-
162
139
  transformCaseFunc = (_transformCaseFunc = transformCaseFunc) !== null && _transformCaseFunc !== void 0 ? _transformCaseFunc : stringToLowerCase;
163
-
164
140
  if (setPrototypeOf) {
165
141
  // Make 'in' and truthy checks like Boolean(set.constructor)
166
142
  // independent of any properties defined on Object.prototype.
167
143
  // Prevent prototype setters from intercepting set as a this value.
168
144
  setPrototypeOf(set, null);
169
145
  }
170
-
171
146
  var l = array.length;
172
-
173
147
  while (l--) {
174
148
  var element = array[l];
175
-
176
149
  if (typeof element === 'string') {
177
150
  var lcElement = transformCaseFunc(element);
178
-
179
151
  if (lcElement !== element) {
180
152
  // Config presets (e.g. tags.js, attrs.js) are immutable.
181
153
  if (!isFrozen(array)) {
182
154
  array[l] = lcElement;
183
155
  }
184
-
185
156
  element = lcElement;
186
157
  }
187
158
  }
188
-
189
159
  set[element] = true;
190
160
  }
191
-
192
161
  return set;
193
162
  }
194
- /* Shallow clone an object */
195
163
 
164
+ /* Shallow clone an object */
196
165
  function clone(object) {
197
166
  var newObject = create(null);
198
167
  var property;
199
-
200
168
  for (property in object) {
201
169
  if (apply(hasOwnProperty, object, [property]) === true) {
202
170
  newObject[property] = object[property];
203
171
  }
204
172
  }
205
-
206
173
  return newObject;
207
174
  }
175
+
208
176
  /* IE10 doesn't support __lookupGetter__ so lets'
209
177
  * simulate it. It also automatically checks
210
178
  * if the prop is function or getter and behaves
211
179
  * accordingly. */
212
-
213
180
  function lookupGetter(object, prop) {
214
181
  while (object !== null) {
215
182
  var desc = getOwnPropertyDescriptor(object, prop);
216
-
217
183
  if (desc) {
218
184
  if (desc.get) {
219
185
  return unapply(desc.get);
220
186
  }
221
-
222
187
  if (typeof desc.value === 'function') {
223
188
  return unapply(desc.value);
224
189
  }
225
190
  }
226
-
227
191
  object = getPrototypeOf(object);
228
192
  }
229
-
230
193
  function fallbackValue(element) {
231
194
  console.warn('fallback value for', element);
232
195
  return null;
233
196
  }
234
-
235
197
  return fallbackValue;
236
198
  }
237
199
 
238
- var html$1 = 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']); // SVG
200
+ var html$1 = 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']);
239
201
 
202
+ // SVG
240
203
  var svg$1 = 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']);
241
- 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']); // List of SVG elements that are disallowed by default.
204
+ 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']);
205
+
206
+ // List of SVG elements that are disallowed by default.
242
207
  // We still need to know them so that we can do namespace
243
208
  // checks properly in case one wants to add them to
244
209
  // allow-list.
245
-
246
210
  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']);
247
- var mathMl$1 = 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']); // Similarly to SVG, we want to know all MathML elements,
248
- // even those that we disallow by default.
211
+ var mathMl$1 = 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']);
249
212
 
213
+ // Similarly to SVG, we want to know all MathML elements,
214
+ // even those that we disallow by default.
250
215
  var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
251
216
  var text = freeze(['#text']);
252
217
 
@@ -255,14 +220,12 @@
255
220
  var mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
256
221
  var xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
257
222
 
223
+ // eslint-disable-next-line unicorn/better-regex
258
224
  var MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
259
-
260
225
  var ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
261
226
  var TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
262
227
  var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
263
-
264
228
  var ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
265
-
266
229
  var IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
267
230
  );
268
231
  var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
@@ -274,6 +237,7 @@
274
237
  var getGlobal = function getGlobal() {
275
238
  return typeof window === 'undefined' ? null : window;
276
239
  };
240
+
277
241
  /**
278
242
  * Creates a no-op policy for internal use only.
279
243
  * Don't export this function outside this module!
@@ -282,25 +246,20 @@
282
246
  * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types
283
247
  * are not supported).
284
248
  */
285
-
286
-
287
249
  var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) {
288
250
  if (_typeof(trustedTypes) !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
289
251
  return null;
290
- } // Allow the callers to control the unique policy name
252
+ }
253
+
254
+ // Allow the callers to control the unique policy name
291
255
  // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
292
256
  // Policy creation with duplicate names throws in Trusted Types.
293
-
294
-
295
257
  var suffix = null;
296
258
  var ATTR_NAME = 'data-tt-policy-suffix';
297
-
298
259
  if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) {
299
260
  suffix = document.currentScript.getAttribute(ATTR_NAME);
300
261
  }
301
-
302
262
  var policyName = 'dompurify' + (suffix ? '#' + suffix : '');
303
-
304
263
  try {
305
264
  return trustedTypes.createPolicy(policyName, {
306
265
  createHTML: function createHTML(html) {
@@ -318,115 +277,106 @@
318
277
  return null;
319
278
  }
320
279
  };
321
-
322
280
  function createDOMPurify() {
323
281
  var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
324
-
325
282
  var DOMPurify = function DOMPurify(root) {
326
283
  return createDOMPurify(root);
327
284
  };
285
+
328
286
  /**
329
287
  * Version label, exposed for easier checks
330
288
  * if DOMPurify is up to date or not
331
289
  */
290
+ DOMPurify.version = '2.5.1';
332
291
 
333
-
334
- DOMPurify.version = '2.4.9';
335
292
  /**
336
293
  * Array of elements that DOMPurify removed during sanitation.
337
294
  * Empty if nothing was removed.
338
295
  */
339
-
340
296
  DOMPurify.removed = [];
341
-
342
297
  if (!window || !window.document || window.document.nodeType !== 9) {
343
298
  // Not running in a browser, provide a factory function
344
299
  // so that you can pass your own Window
345
300
  DOMPurify.isSupported = false;
346
301
  return DOMPurify;
347
302
  }
348
-
349
303
  var originalDocument = window.document;
350
304
  var document = window.document;
351
305
  var DocumentFragment = window.DocumentFragment,
352
- HTMLTemplateElement = window.HTMLTemplateElement,
353
- Node = window.Node,
354
- Element = window.Element,
355
- NodeFilter = window.NodeFilter,
356
- _window$NamedNodeMap = window.NamedNodeMap,
357
- NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
358
- HTMLFormElement = window.HTMLFormElement,
359
- DOMParser = window.DOMParser,
360
- trustedTypes = window.trustedTypes;
306
+ HTMLTemplateElement = window.HTMLTemplateElement,
307
+ Node = window.Node,
308
+ Element = window.Element,
309
+ NodeFilter = window.NodeFilter,
310
+ _window$NamedNodeMap = window.NamedNodeMap,
311
+ NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
312
+ HTMLFormElement = window.HTMLFormElement,
313
+ DOMParser = window.DOMParser,
314
+ trustedTypes = window.trustedTypes;
361
315
  var ElementPrototype = Element.prototype;
362
316
  var cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
363
317
  var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
364
318
  var getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
365
- var getParentNode = lookupGetter(ElementPrototype, 'parentNode'); // As per issue #47, the web-components registry is inherited by a
319
+ var getParentNode = lookupGetter(ElementPrototype, 'parentNode');
320
+
321
+ // As per issue #47, the web-components registry is inherited by a
366
322
  // new document created via createHTMLDocument. As per the spec
367
323
  // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
368
324
  // a new empty registry is used when creating a template contents owner
369
325
  // document, so we use that as our parent document to ensure nothing
370
326
  // is inherited.
371
-
372
327
  if (typeof HTMLTemplateElement === 'function') {
373
328
  var template = document.createElement('template');
374
-
375
329
  if (template.content && template.content.ownerDocument) {
376
330
  document = template.content.ownerDocument;
377
331
  }
378
332
  }
379
-
380
333
  var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument);
381
-
382
334
  var emptyHTML = trustedTypesPolicy ? trustedTypesPolicy.createHTML('') : '';
383
335
  var _document = document,
384
- implementation = _document.implementation,
385
- createNodeIterator = _document.createNodeIterator,
386
- createDocumentFragment = _document.createDocumentFragment,
387
- getElementsByTagName = _document.getElementsByTagName;
336
+ implementation = _document.implementation,
337
+ createNodeIterator = _document.createNodeIterator,
338
+ createDocumentFragment = _document.createDocumentFragment,
339
+ getElementsByTagName = _document.getElementsByTagName;
388
340
  var importNode = originalDocument.importNode;
389
341
  var documentMode = {};
390
-
391
342
  try {
392
343
  documentMode = clone(document).documentMode ? document.documentMode : {};
393
344
  } catch (_) {}
394
-
395
345
  var hooks = {};
346
+
396
347
  /**
397
348
  * Expose whether this browser supports running the full DOMPurify.
398
349
  */
399
-
400
350
  DOMPurify.isSupported = typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined && documentMode !== 9;
401
351
  var MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
402
- ERB_EXPR$1 = ERB_EXPR,
403
- TMPLIT_EXPR$1 = TMPLIT_EXPR,
404
- DATA_ATTR$1 = DATA_ATTR,
405
- ARIA_ATTR$1 = ARIA_ATTR,
406
- IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
407
- ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
408
- CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
352
+ ERB_EXPR$1 = ERB_EXPR,
353
+ TMPLIT_EXPR$1 = TMPLIT_EXPR,
354
+ DATA_ATTR$1 = DATA_ATTR,
355
+ ARIA_ATTR$1 = ARIA_ATTR,
356
+ IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
357
+ ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
358
+ CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
409
359
  var IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
360
+
410
361
  /**
411
362
  * We consider the elements and attributes below to be safe. Ideally
412
363
  * don't add any new ones but feel free to remove unwanted ones.
413
364
  */
414
365
 
415
366
  /* allowed element names */
416
-
417
367
  var ALLOWED_TAGS = null;
418
368
  var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(svgFilters), _toConsumableArray(mathMl$1), _toConsumableArray(text)));
419
- /* Allowed attribute names */
420
369
 
370
+ /* Allowed attribute names */
421
371
  var ALLOWED_ATTR = null;
422
372
  var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(mathMl), _toConsumableArray(xml)));
373
+
423
374
  /*
424
375
  * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
425
376
  * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
426
377
  * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
427
378
  * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
428
379
  */
429
-
430
380
  var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
431
381
  tagNameCheck: {
432
382
  writable: true,
@@ -447,59 +397,65 @@
447
397
  value: false
448
398
  }
449
399
  }));
450
- /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
451
400
 
401
+ /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
452
402
  var FORBID_TAGS = null;
453
- /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
454
403
 
404
+ /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
455
405
  var FORBID_ATTR = null;
456
- /* Decide if ARIA attributes are okay */
457
406
 
407
+ /* Decide if ARIA attributes are okay */
458
408
  var ALLOW_ARIA_ATTR = true;
459
- /* Decide if custom data attributes are okay */
460
409
 
410
+ /* Decide if custom data attributes are okay */
461
411
  var ALLOW_DATA_ATTR = true;
462
- /* Decide if unknown protocols are okay */
463
412
 
413
+ /* Decide if unknown protocols are okay */
464
414
  var ALLOW_UNKNOWN_PROTOCOLS = false;
415
+
465
416
  /* Decide if self-closing tags in attributes are allowed.
466
417
  * Usually removed due to a mXSS issue in jQuery 3.0 */
467
-
468
418
  var ALLOW_SELF_CLOSE_IN_ATTR = true;
419
+
469
420
  /* Output should be safe for common template engines.
470
421
  * This means, DOMPurify removes data attributes, mustaches and ERB
471
422
  */
472
-
473
423
  var SAFE_FOR_TEMPLATES = false;
474
- /* Decide if document with <html>... should be returned */
475
424
 
425
+ /* Output should be safe even for XML used within HTML and alike.
426
+ * This means, DOMPurify removes comments when containing risky content.
427
+ */
428
+ var SAFE_FOR_XML = true;
429
+
430
+ /* Decide if document with <html>... should be returned */
476
431
  var WHOLE_DOCUMENT = false;
477
- /* Track whether config is already set on this instance of DOMPurify. */
478
432
 
433
+ /* Track whether config is already set on this instance of DOMPurify. */
479
434
  var SET_CONFIG = false;
435
+
480
436
  /* Decide if all elements (e.g. style, script) must be children of
481
437
  * document.body. By default, browsers might move them to document.head */
482
-
483
438
  var FORCE_BODY = false;
439
+
484
440
  /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
485
441
  * string (or a TrustedHTML object if Trusted Types are supported).
486
442
  * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
487
443
  */
488
-
489
444
  var RETURN_DOM = false;
445
+
490
446
  /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
491
447
  * string (or a TrustedHTML object if Trusted Types are supported) */
492
-
493
448
  var RETURN_DOM_FRAGMENT = false;
449
+
494
450
  /* Try to return a Trusted Type object instead of a string, return a string in
495
451
  * case Trusted Types are not supported */
496
-
497
452
  var RETURN_TRUSTED_TYPE = false;
453
+
498
454
  /* Output should be free from DOM clobbering attacks?
499
455
  * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
500
456
  */
501
-
502
457
  var SANITIZE_DOM = true;
458
+
503
459
  /* Achieve full DOM Clobbering protection by isolating the namespace of named
504
460
  * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
505
461
  *
@@ -513,98 +469,101 @@
513
469
  * Namespace isolation is implemented by prefixing `id` and `name` attributes
514
470
  * with a constant string, i.e., `user-content-`
515
471
  */
516
-
517
472
  var SANITIZE_NAMED_PROPS = false;
518
473
  var SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
519
- /* Keep element content when removing element? */
520
474
 
475
+ /* Keep element content when removing element? */
521
476
  var KEEP_CONTENT = true;
477
+
522
478
  /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
523
479
  * of importing it into a new Document and returning a sanitized copy */
524
-
525
480
  var IN_PLACE = false;
526
- /* Allow usage of profiles like html, svg and mathMl */
527
481
 
482
+ /* Allow usage of profiles like html, svg and mathMl */
528
483
  var USE_PROFILES = {};
529
- /* Tags to ignore content of when KEEP_CONTENT is true */
530
484
 
485
+ /* Tags to ignore content of when KEEP_CONTENT is true */
531
486
  var FORBID_CONTENTS = null;
532
487
  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']);
533
- /* Tags that are safe for data: URIs */
534
488
 
489
+ /* Tags that are safe for data: URIs */
535
490
  var DATA_URI_TAGS = null;
536
491
  var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
537
- /* Attributes safe for values like "javascript:" */
538
492
 
493
+ /* Attributes safe for values like "javascript:" */
539
494
  var URI_SAFE_ATTRIBUTES = null;
540
495
  var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
541
496
  var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
542
497
  var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
543
498
  var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
544
499
  /* Document namespace */
545
-
546
500
  var NAMESPACE = HTML_NAMESPACE;
547
501
  var IS_EMPTY_INPUT = false;
548
- /* Allowed XHTML+XML namespaces */
549
502
 
503
+ /* Allowed XHTML+XML namespaces */
550
504
  var ALLOWED_NAMESPACES = null;
551
505
  var DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
552
- /* Parsing of strict XHTML documents */
553
506
 
507
+ /* Parsing of strict XHTML documents */
554
508
  var PARSER_MEDIA_TYPE;
555
509
  var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
556
510
  var DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
557
511
  var transformCaseFunc;
558
- /* Keep a reference to config to pass to hooks */
559
512
 
513
+ /* Keep a reference to config to pass to hooks */
560
514
  var CONFIG = null;
561
- /* Ideally, do not touch anything below this line */
562
515
 
516
+ /* Specify the maximum element nesting depth to prevent mXSS */
517
+ var MAX_NESTING_DEPTH = 255;
518
+
519
+ /* Ideally, do not touch anything below this line */
563
520
  /* ______________________________________________ */
564
521
 
565
522
  var formElement = document.createElement('form');
566
-
567
523
  var isRegexOrFunction = function isRegexOrFunction(testValue) {
568
524
  return testValue instanceof RegExp || testValue instanceof Function;
569
525
  };
526
+
570
527
  /**
571
528
  * _parseConfig
572
529
  *
573
530
  * @param {Object} cfg optional config literal
574
531
  */
575
532
  // eslint-disable-next-line complexity
576
-
577
-
578
533
  var _parseConfig = function _parseConfig(cfg) {
579
534
  if (CONFIG && CONFIG === cfg) {
580
535
  return;
581
536
  }
582
- /* Shield configuration object from tampering */
583
-
584
537
 
538
+ /* Shield configuration object from tampering */
585
539
  if (!cfg || _typeof(cfg) !== 'object') {
586
540
  cfg = {};
587
541
  }
588
- /* Shield configuration object from prototype pollution */
589
-
590
542
 
543
+ /* Shield configuration object from prototype pollution */
591
544
  cfg = clone(cfg);
592
- PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes
593
- SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE; // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
545
+ PARSER_MEDIA_TYPE =
546
+ // eslint-disable-next-line unicorn/prefer-includes
547
+ SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE;
594
548
 
549
+ // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
595
550
  transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
596
- /* Set configuration parameters */
597
551
 
552
+ /* Set configuration parameters */
598
553
  ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
599
554
  ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
600
555
  ALLOWED_NAMESPACES = 'ALLOWED_NAMESPACES' in cfg ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
601
- URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), // eslint-disable-line indent
602
- cfg.ADD_URI_SAFE_ATTR, // eslint-disable-line indent
556
+ URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES),
557
+ // eslint-disable-line indent
558
+ cfg.ADD_URI_SAFE_ATTR,
559
+ // eslint-disable-line indent
603
560
  transformCaseFunc // eslint-disable-line indent
604
561
  ) // eslint-disable-line indent
605
562
  : DEFAULT_URI_SAFE_ATTRIBUTES;
606
- DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent
607
- cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent
563
+ DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS),
564
+ // eslint-disable-line indent
565
+ cfg.ADD_DATA_URI_TAGS,
566
+ // eslint-disable-line indent
608
567
  transformCaseFunc // eslint-disable-line indent
609
568
  ) // eslint-disable-line indent
610
569
  : DEFAULT_DATA_URI_TAGS;
@@ -613,161 +572,128 @@
613
572
  FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
614
573
  USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
615
574
  ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
616
-
617
575
  ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
618
-
619
576
  ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
620
-
621
577
  ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
622
-
623
578
  SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
624
-
579
+ SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
625
580
  WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
626
-
627
581
  RETURN_DOM = cfg.RETURN_DOM || false; // Default false
628
-
629
582
  RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
630
-
631
583
  RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
632
-
633
584
  FORCE_BODY = cfg.FORCE_BODY || false; // Default false
634
-
635
585
  SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
636
-
637
586
  SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
638
-
639
587
  KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
640
-
641
588
  IN_PLACE = cfg.IN_PLACE || false; // Default false
642
-
643
589
  IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$1;
644
590
  NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
645
591
  CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
646
-
647
592
  if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
648
593
  CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
649
594
  }
650
-
651
595
  if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
652
596
  CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
653
597
  }
654
-
655
598
  if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
656
599
  CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
657
600
  }
658
-
659
601
  if (SAFE_FOR_TEMPLATES) {
660
602
  ALLOW_DATA_ATTR = false;
661
603
  }
662
-
663
604
  if (RETURN_DOM_FRAGMENT) {
664
605
  RETURN_DOM = true;
665
606
  }
666
- /* Parse profile info */
667
-
668
607
 
608
+ /* Parse profile info */
669
609
  if (USE_PROFILES) {
670
610
  ALLOWED_TAGS = addToSet({}, _toConsumableArray(text));
671
611
  ALLOWED_ATTR = [];
672
-
673
612
  if (USE_PROFILES.html === true) {
674
613
  addToSet(ALLOWED_TAGS, html$1);
675
614
  addToSet(ALLOWED_ATTR, html);
676
615
  }
677
-
678
616
  if (USE_PROFILES.svg === true) {
679
617
  addToSet(ALLOWED_TAGS, svg$1);
680
618
  addToSet(ALLOWED_ATTR, svg);
681
619
  addToSet(ALLOWED_ATTR, xml);
682
620
  }
683
-
684
621
  if (USE_PROFILES.svgFilters === true) {
685
622
  addToSet(ALLOWED_TAGS, svgFilters);
686
623
  addToSet(ALLOWED_ATTR, svg);
687
624
  addToSet(ALLOWED_ATTR, xml);
688
625
  }
689
-
690
626
  if (USE_PROFILES.mathMl === true) {
691
627
  addToSet(ALLOWED_TAGS, mathMl$1);
692
628
  addToSet(ALLOWED_ATTR, mathMl);
693
629
  addToSet(ALLOWED_ATTR, xml);
694
630
  }
695
631
  }
696
- /* Merge configuration parameters */
697
-
698
632
 
633
+ /* Merge configuration parameters */
699
634
  if (cfg.ADD_TAGS) {
700
635
  if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
701
636
  ALLOWED_TAGS = clone(ALLOWED_TAGS);
702
637
  }
703
-
704
638
  addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
705
639
  }
706
-
707
640
  if (cfg.ADD_ATTR) {
708
641
  if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
709
642
  ALLOWED_ATTR = clone(ALLOWED_ATTR);
710
643
  }
711
-
712
644
  addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
713
645
  }
714
-
715
646
  if (cfg.ADD_URI_SAFE_ATTR) {
716
647
  addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
717
648
  }
718
-
719
649
  if (cfg.FORBID_CONTENTS) {
720
650
  if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
721
651
  FORBID_CONTENTS = clone(FORBID_CONTENTS);
722
652
  }
723
-
724
653
  addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
725
654
  }
726
- /* Add #text in case KEEP_CONTENT is set to true */
727
-
728
655
 
656
+ /* Add #text in case KEEP_CONTENT is set to true */
729
657
  if (KEEP_CONTENT) {
730
658
  ALLOWED_TAGS['#text'] = true;
731
659
  }
732
- /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
733
-
734
660
 
661
+ /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
735
662
  if (WHOLE_DOCUMENT) {
736
663
  addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
737
664
  }
738
- /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
739
-
740
665
 
666
+ /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
741
667
  if (ALLOWED_TAGS.table) {
742
668
  addToSet(ALLOWED_TAGS, ['tbody']);
743
669
  delete FORBID_TAGS.tbody;
744
- } // Prevent further manipulation of configuration.
745
- // Not available in IE8, Safari 5, etc.
746
-
670
+ }
747
671
 
672
+ // Prevent further manipulation of configuration.
673
+ // Not available in IE8, Safari 5, etc.
748
674
  if (freeze) {
749
675
  freeze(cfg);
750
676
  }
751
-
752
677
  CONFIG = cfg;
753
678
  };
754
-
755
679
  var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
756
- var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']); // Certain elements are allowed in both SVG and HTML
680
+ var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
681
+
682
+ // Certain elements are allowed in both SVG and HTML
757
683
  // namespace. We need to specify them explicitly
758
684
  // so that they don't get erroneously deleted from
759
685
  // HTML namespace.
760
-
761
686
  var COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
687
+
762
688
  /* Keep track of all possible SVG and MathML tags
763
689
  * so that we can perform the namespace checks
764
690
  * correctly. */
765
-
766
691
  var ALL_SVG_TAGS = addToSet({}, svg$1);
767
692
  addToSet(ALL_SVG_TAGS, svgFilters);
768
693
  addToSet(ALL_SVG_TAGS, svgDisallowed);
769
694
  var ALL_MATHML_TAGS = addToSet({}, mathMl$1);
770
695
  addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
696
+
771
697
  /**
772
698
  *
773
699
  *
@@ -776,64 +702,59 @@
776
702
  * namespace that a spec-compliant parser would never
777
703
  * return. Return true otherwise.
778
704
  */
779
-
780
705
  var _checkValidNamespace = function _checkValidNamespace(element) {
781
- var parent = getParentNode(element); // In JSDOM, if we're inside shadow DOM, then parentNode
782
- // can be null. We just simulate parent in this case.
706
+ var parent = getParentNode(element);
783
707
 
708
+ // In JSDOM, if we're inside shadow DOM, then parentNode
709
+ // can be null. We just simulate parent in this case.
784
710
  if (!parent || !parent.tagName) {
785
711
  parent = {
786
712
  namespaceURI: NAMESPACE,
787
713
  tagName: 'template'
788
714
  };
789
715
  }
790
-
791
716
  var tagName = stringToLowerCase(element.tagName);
792
717
  var parentTagName = stringToLowerCase(parent.tagName);
793
-
794
718
  if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
795
719
  return false;
796
720
  }
797
-
798
721
  if (element.namespaceURI === SVG_NAMESPACE) {
799
722
  // The only way to switch from HTML namespace to SVG
800
723
  // is via <svg>. If it happens via any other tag, then
801
724
  // it should be killed.
802
725
  if (parent.namespaceURI === HTML_NAMESPACE) {
803
726
  return tagName === 'svg';
804
- } // The only way to switch from MathML to SVG is via`
727
+ }
728
+
729
+ // The only way to switch from MathML to SVG is via`
805
730
  // svg if parent is either <annotation-xml> or MathML
806
731
  // text integration points.
807
-
808
-
809
732
  if (parent.namespaceURI === MATHML_NAMESPACE) {
810
733
  return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
811
- } // We only allow elements that are defined in SVG
812
- // spec. All others are disallowed in SVG namespace.
813
-
734
+ }
814
735
 
736
+ // We only allow elements that are defined in SVG
737
+ // spec. All others are disallowed in SVG namespace.
815
738
  return Boolean(ALL_SVG_TAGS[tagName]);
816
739
  }
817
-
818
740
  if (element.namespaceURI === MATHML_NAMESPACE) {
819
741
  // The only way to switch from HTML namespace to MathML
820
742
  // is via <math>. If it happens via any other tag, then
821
743
  // it should be killed.
822
744
  if (parent.namespaceURI === HTML_NAMESPACE) {
823
745
  return tagName === 'math';
824
- } // The only way to switch from SVG to MathML is via
825
- // <math> and HTML integration points
826
-
746
+ }
827
747
 
748
+ // The only way to switch from SVG to MathML is via
749
+ // <math> and HTML integration points
828
750
  if (parent.namespaceURI === SVG_NAMESPACE) {
829
751
  return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
830
- } // We only allow elements that are defined in MathML
831
- // spec. All others are disallowed in MathML namespace.
832
-
752
+ }
833
753
 
754
+ // We only allow elements that are defined in MathML
755
+ // spec. All others are disallowed in MathML namespace.
834
756
  return Boolean(ALL_MATHML_TAGS[tagName]);
835
757
  }
836
-
837
758
  if (element.namespaceURI === HTML_NAMESPACE) {
838
759
  // The only way to switch from SVG to HTML is via
839
760
  // HTML integration points, and from MathML to HTML
@@ -841,39 +762,36 @@
841
762
  if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
842
763
  return false;
843
764
  }
844
-
845
765
  if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
846
766
  return false;
847
- } // We disallow tags that are specific for MathML
848
- // or SVG and should never appear in HTML namespace
849
-
767
+ }
850
768
 
769
+ // We disallow tags that are specific for MathML
770
+ // or SVG and should never appear in HTML namespace
851
771
  return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
852
- } // For XHTML and XML documents that support custom namespaces
853
-
772
+ }
854
773
 
774
+ // For XHTML and XML documents that support custom namespaces
855
775
  if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
856
776
  return true;
857
- } // The code should never reach this place (this means
777
+ }
778
+
779
+ // The code should never reach this place (this means
858
780
  // that the element somehow got namespace that is not
859
781
  // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
860
782
  // Return false just in case.
861
-
862
-
863
783
  return false;
864
784
  };
785
+
865
786
  /**
866
787
  * _forceRemove
867
788
  *
868
789
  * @param {Node} node a DOM node
869
790
  */
870
-
871
-
872
791
  var _forceRemove = function _forceRemove(node) {
873
792
  arrayPush(DOMPurify.removed, {
874
793
  element: node
875
794
  });
876
-
877
795
  try {
878
796
  // eslint-disable-next-line unicorn/prefer-dom-node-remove
879
797
  node.parentNode.removeChild(node);
@@ -885,14 +803,13 @@
885
803
  }
886
804
  }
887
805
  };
806
+
888
807
  /**
889
808
  * _removeAttribute
890
809
  *
891
810
  * @param {String} name an Attribute name
892
811
  * @param {Node} node a DOM node
893
812
  */
894
-
895
-
896
813
  var _removeAttribute = function _removeAttribute(name, node) {
897
814
  try {
898
815
  arrayPush(DOMPurify.removed, {
@@ -905,9 +822,9 @@
905
822
  from: node
906
823
  });
907
824
  }
825
+ node.removeAttribute(name);
908
826
 
909
- node.removeAttribute(name); // We void attribute values for unremovable "is"" attributes
910
-
827
+ // We void attribute values for unremovable "is"" attributes
911
828
  if (name === 'is' && !ALLOWED_ATTR[name]) {
912
829
  if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
913
830
  try {
@@ -920,19 +837,17 @@
920
837
  }
921
838
  }
922
839
  };
840
+
923
841
  /**
924
842
  * _initDocument
925
843
  *
926
844
  * @param {String} dirty a string of dirty markup
927
845
  * @return {Document} a DOM, filled with the dirty markup
928
846
  */
929
-
930
-
931
847
  var _initDocument = function _initDocument(dirty) {
932
848
  /* Create a HTML document */
933
849
  var doc;
934
850
  var leadingWhitespace;
935
-
936
851
  if (FORCE_BODY) {
937
852
  dirty = '<remove></remove>' + dirty;
938
853
  } else {
@@ -940,83 +855,74 @@
940
855
  var matches = stringMatch(dirty, /^[\r\n\t ]+/);
941
856
  leadingWhitespace = matches && matches[0];
942
857
  }
943
-
944
858
  if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
945
859
  // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
946
860
  dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
947
861
  }
948
-
949
862
  var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
950
863
  /*
951
864
  * Use the DOMParser API by default, fallback later if needs be
952
865
  * DOMParser not work for svg when has multiple root element.
953
866
  */
954
-
955
867
  if (NAMESPACE === HTML_NAMESPACE) {
956
868
  try {
957
869
  doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
958
870
  } catch (_) {}
959
871
  }
960
- /* Use createHTMLDocument in case DOMParser is not available */
961
-
962
872
 
873
+ /* Use createHTMLDocument in case DOMParser is not available */
963
874
  if (!doc || !doc.documentElement) {
964
875
  doc = implementation.createDocument(NAMESPACE, 'template', null);
965
-
966
876
  try {
967
877
  doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
968
- } catch (_) {// Syntax error if dirtyPayload is invalid xml
878
+ } catch (_) {
879
+ // Syntax error if dirtyPayload is invalid xml
969
880
  }
970
881
  }
971
-
972
882
  var body = doc.body || doc.documentElement;
973
-
974
883
  if (dirty && leadingWhitespace) {
975
884
  body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
976
885
  }
977
- /* Work on whole document or just its body */
978
-
979
886
 
887
+ /* Work on whole document or just its body */
980
888
  if (NAMESPACE === HTML_NAMESPACE) {
981
889
  return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
982
890
  }
983
-
984
891
  return WHOLE_DOCUMENT ? doc.documentElement : body;
985
892
  };
893
+
986
894
  /**
987
895
  * _createIterator
988
896
  *
989
897
  * @param {Document} root document/fragment to create iterator for
990
898
  * @return {Iterator} iterator instance
991
899
  */
992
-
993
-
994
900
  var _createIterator = function _createIterator(root) {
995
- return createNodeIterator.call(root.ownerDocument || root, root, // eslint-disable-next-line no-bitwise
901
+ return createNodeIterator.call(root.ownerDocument || root, root,
902
+ // eslint-disable-next-line no-bitwise
996
903
  NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null, false);
997
904
  };
905
+
998
906
  /**
999
907
  * _isClobbered
1000
908
  *
1001
909
  * @param {Node} elm element to check for clobbering attacks
1002
910
  * @return {Boolean} true if clobbered, false if safe
1003
911
  */
1004
-
1005
-
1006
912
  var _isClobbered = function _isClobbered(elm) {
1007
- 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' || typeof elm.hasChildNodes !== 'function');
913
+ return elm instanceof HTMLFormElement && (typeof elm.__depth !== 'undefined' && typeof elm.__depth !== 'number' || typeof elm.__removalCount !== 'undefined' && typeof elm.__removalCount !== 'number' || 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' || typeof elm.hasChildNodes !== 'function');
1008
914
  };
915
+
1009
916
  /**
1010
917
  * _isNode
1011
918
  *
1012
919
  * @param {Node} obj object to check whether it's a DOM node
1013
920
  * @return {Boolean} true is object is a DOM node
1014
921
  */
1015
-
1016
-
1017
922
  var _isNode = function _isNode(object) {
1018
923
  return _typeof(Node) === 'object' ? object instanceof Node : object && _typeof(object) === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string';
1019
924
  };
925
+
1020
926
  /**
1021
927
  * _executeHook
1022
928
  * Execute user configurable hooks
@@ -1025,17 +931,15 @@
1025
931
  * @param {Node} currentNode node to work on with the hook
1026
932
  * @param {Object} data additional hook parameters
1027
933
  */
1028
-
1029
-
1030
934
  var _executeHook = function _executeHook(entryPoint, currentNode, data) {
1031
935
  if (!hooks[entryPoint]) {
1032
936
  return;
1033
937
  }
1034
-
1035
938
  arrayForEach(hooks[entryPoint], function (hook) {
1036
939
  hook.call(DOMPurify, currentNode, data, CONFIG);
1037
940
  });
1038
941
  };
942
+
1039
943
  /**
1040
944
  * _sanitizeElements
1041
945
  *
@@ -1046,118 +950,101 @@
1046
950
  * @param {Node} currentNode to check for permission to exist
1047
951
  * @return {Boolean} true if node was killed, false if left alive
1048
952
  */
1049
-
1050
-
1051
953
  var _sanitizeElements = function _sanitizeElements(currentNode) {
1052
954
  var content;
1053
- /* Execute a hook if present */
1054
955
 
956
+ /* Execute a hook if present */
1055
957
  _executeHook('beforeSanitizeElements', currentNode, null);
1056
- /* Check if element is clobbered or can clobber */
1057
-
1058
958
 
959
+ /* Check if element is clobbered or can clobber */
1059
960
  if (_isClobbered(currentNode)) {
1060
961
  _forceRemove(currentNode);
1061
-
1062
962
  return true;
1063
963
  }
1064
- /* Check if tagname contains Unicode */
1065
-
1066
964
 
965
+ /* Check if tagname contains Unicode */
1067
966
  if (regExpTest(/[\u0080-\uFFFF]/, currentNode.nodeName)) {
1068
967
  _forceRemove(currentNode);
1069
-
1070
968
  return true;
1071
969
  }
1072
- /* Now let's check the element's type and name */
1073
-
1074
970
 
971
+ /* Now let's check the element's type and name */
1075
972
  var tagName = transformCaseFunc(currentNode.nodeName);
1076
- /* Execute a hook if present */
1077
973
 
974
+ /* Execute a hook if present */
1078
975
  _executeHook('uponSanitizeElement', currentNode, {
1079
976
  tagName: tagName,
1080
977
  allowedTags: ALLOWED_TAGS
1081
978
  });
1082
- /* Detect mXSS attempts abusing namespace confusion */
1083
-
1084
979
 
980
+ /* Detect mXSS attempts abusing namespace confusion */
1085
981
  if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
1086
982
  _forceRemove(currentNode);
1087
-
1088
983
  return true;
1089
984
  }
1090
- /* Mitigate a problem with templates inside select */
1091
-
1092
985
 
986
+ /* Mitigate a problem with templates inside select */
1093
987
  if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) {
1094
988
  _forceRemove(currentNode);
1095
-
1096
989
  return true;
1097
990
  }
1098
- /* Remove any ocurrence of processing instructions */
1099
-
1100
991
 
992
+ /* Remove any ocurrence of processing instructions */
1101
993
  if (currentNode.nodeType === 7) {
1102
994
  _forceRemove(currentNode);
1103
-
1104
995
  return true;
1105
996
  }
1106
- /* Remove element if anything forbids its presence */
1107
997
 
998
+ /* Remove any kind of possibly harmful comments */
999
+ if (SAFE_FOR_XML && currentNode.nodeType === 8 && regExpTest(/<[/\w]/g, currentNode.data)) {
1000
+ _forceRemove(currentNode);
1001
+ return true;
1002
+ }
1108
1003
 
1004
+ /* Remove element if anything forbids its presence */
1109
1005
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1110
1006
  /* Check if we have a custom element to handle */
1111
1007
  if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {
1112
1008
  if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
1113
1009
  if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
1114
1010
  }
1115
- /* Keep content except for bad-listed elements */
1116
-
1117
1011
 
1012
+ /* Keep content except for bad-listed elements */
1118
1013
  if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
1119
1014
  var parentNode = getParentNode(currentNode) || currentNode.parentNode;
1120
1015
  var childNodes = getChildNodes(currentNode) || currentNode.childNodes;
1121
-
1122
1016
  if (childNodes && parentNode) {
1123
1017
  var childCount = childNodes.length;
1124
-
1125
1018
  for (var i = childCount - 1; i >= 0; --i) {
1126
- parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
1019
+ var childClone = cloneNode(childNodes[i], true);
1020
+ childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
1021
+ parentNode.insertBefore(childClone, getNextSibling(currentNode));
1127
1022
  }
1128
1023
  }
1129
1024
  }
1130
-
1131
1025
  _forceRemove(currentNode);
1132
-
1133
1026
  return true;
1134
1027
  }
1135
- /* Check whether element has a valid namespace */
1136
-
1137
1028
 
1029
+ /* Check whether element has a valid namespace */
1138
1030
  if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
1139
1031
  _forceRemove(currentNode);
1140
-
1141
1032
  return true;
1142
1033
  }
1143
- /* Make sure that older browsers don't get fallback-tag mXSS */
1144
-
1145
1034
 
1035
+ /* Make sure that older browsers don't get fallback-tag mXSS */
1146
1036
  if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
1147
1037
  _forceRemove(currentNode);
1148
-
1149
1038
  return true;
1150
1039
  }
1151
- /* Sanitize element content to be template-safe */
1152
-
1153
1040
 
1041
+ /* Sanitize element content to be template-safe */
1154
1042
  if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
1155
1043
  /* Get the element's text content */
1156
1044
  content = currentNode.textContent;
1157
1045
  content = stringReplace(content, MUSTACHE_EXPR$1, ' ');
1158
1046
  content = stringReplace(content, ERB_EXPR$1, ' ');
1159
1047
  content = stringReplace(content, TMPLIT_EXPR$1, ' ');
1160
-
1161
1048
  if (currentNode.textContent !== content) {
1162
1049
  arrayPush(DOMPurify.removed, {
1163
1050
  element: currentNode.cloneNode()
@@ -1165,13 +1052,12 @@
1165
1052
  currentNode.textContent = content;
1166
1053
  }
1167
1054
  }
1168
- /* Execute a hook if present */
1169
-
1170
1055
 
1056
+ /* Execute a hook if present */
1171
1057
  _executeHook('afterSanitizeElements', currentNode, null);
1172
-
1173
1058
  return false;
1174
1059
  };
1060
+
1175
1061
  /**
1176
1062
  * _isValidAttribute
1177
1063
  *
@@ -1181,47 +1067,44 @@
1181
1067
  * @return {Boolean} Returns true if `value` is valid, otherwise false.
1182
1068
  */
1183
1069
  // eslint-disable-next-line complexity
1184
-
1185
-
1186
1070
  var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
1187
1071
  /* Make sure attribute cannot clobber */
1188
1072
  if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
1189
1073
  return false;
1190
1074
  }
1075
+
1191
1076
  /* Allow valid data-* attributes: At least one character after "-"
1192
1077
  (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
1193
1078
  XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
1194
1079
  We don't need to check the value; it's always URI safe. */
1195
-
1196
-
1197
1080
  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]) {
1198
- if ( // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1081
+ if (
1082
+ // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1199
1083
  // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1200
1084
  // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1201
- _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)) || // Alternative, second condition checks if it's an `is`-attribute, AND
1085
+ _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)) ||
1086
+ // Alternative, second condition checks if it's an `is`-attribute, AND
1202
1087
  // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1203
1088
  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 {
1204
1089
  return false;
1205
1090
  }
1206
1091
  /* Check value is safe. First, is attr inert? If so, is safe */
1207
-
1208
1092
  } 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) {
1209
1093
  return false;
1210
1094
  } else ;
1211
-
1212
1095
  return true;
1213
1096
  };
1097
+
1214
1098
  /**
1215
1099
  * _basicCustomElementCheck
1216
1100
  * checks if at least one dash is included in tagName, and it's not the first char
1217
1101
  * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1218
1102
  * @param {string} tagName name of the tag of the node to sanitize
1219
1103
  */
1220
-
1221
-
1222
1104
  var _basicCustomElementTest = function _basicCustomElementTest(tagName) {
1223
1105
  return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT$1);
1224
1106
  };
1107
+
1225
1108
  /**
1226
1109
  * _sanitizeAttributes
1227
1110
  *
@@ -1232,24 +1115,19 @@
1232
1115
  *
1233
1116
  * @param {Node} currentNode to sanitize
1234
1117
  */
1235
-
1236
-
1237
1118
  var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
1238
1119
  var attr;
1239
1120
  var value;
1240
1121
  var lcName;
1241
1122
  var l;
1242
1123
  /* Execute a hook if present */
1243
-
1244
1124
  _executeHook('beforeSanitizeAttributes', currentNode, null);
1245
-
1246
1125
  var attributes = currentNode.attributes;
1247
- /* Check if we have attributes; if not we might have a text node */
1248
1126
 
1127
+ /* Check if we have attributes; if not we might have a text node */
1249
1128
  if (!attributes) {
1250
1129
  return;
1251
1130
  }
1252
-
1253
1131
  var hookEvent = {
1254
1132
  attrName: '',
1255
1133
  attrValue: '',
@@ -1257,79 +1135,67 @@
1257
1135
  allowedAttributes: ALLOWED_ATTR
1258
1136
  };
1259
1137
  l = attributes.length;
1260
- /* Go backwards over all attributes; safely remove bad ones */
1261
1138
 
1139
+ /* Go backwards over all attributes; safely remove bad ones */
1262
1140
  while (l--) {
1263
1141
  attr = attributes[l];
1264
1142
  var _attr = attr,
1265
- name = _attr.name,
1266
- namespaceURI = _attr.namespaceURI;
1143
+ name = _attr.name,
1144
+ namespaceURI = _attr.namespaceURI;
1267
1145
  value = name === 'value' ? attr.value : stringTrim(attr.value);
1268
1146
  lcName = transformCaseFunc(name);
1269
- /* Execute a hook if present */
1270
1147
 
1148
+ /* Execute a hook if present */
1271
1149
  hookEvent.attrName = lcName;
1272
1150
  hookEvent.attrValue = value;
1273
1151
  hookEvent.keepAttr = true;
1274
1152
  hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
1275
-
1276
1153
  _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
1277
-
1278
1154
  value = hookEvent.attrValue;
1279
1155
  /* Did the hooks approve of the attribute? */
1280
-
1281
1156
  if (hookEvent.forceKeepAttr) {
1282
1157
  continue;
1283
1158
  }
1284
- /* Remove attribute */
1285
-
1286
1159
 
1160
+ /* Remove attribute */
1287
1161
  _removeAttribute(name, currentNode);
1288
- /* Did the hooks approve of the attribute? */
1289
-
1290
1162
 
1163
+ /* Did the hooks approve of the attribute? */
1291
1164
  if (!hookEvent.keepAttr) {
1292
1165
  continue;
1293
1166
  }
1294
- /* Work around a security issue in jQuery 3.0 */
1295
-
1296
1167
 
1168
+ /* Work around a security issue in jQuery 3.0 */
1297
1169
  if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
1298
1170
  _removeAttribute(name, currentNode);
1299
-
1300
1171
  continue;
1301
1172
  }
1302
- /* Sanitize attribute content to be template-safe */
1303
-
1304
1173
 
1174
+ /* Sanitize attribute content to be template-safe */
1305
1175
  if (SAFE_FOR_TEMPLATES) {
1306
1176
  value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
1307
1177
  value = stringReplace(value, ERB_EXPR$1, ' ');
1308
1178
  value = stringReplace(value, TMPLIT_EXPR$1, ' ');
1309
1179
  }
1310
- /* Is `value` valid for this attribute? */
1311
-
1312
1180
 
1181
+ /* Is `value` valid for this attribute? */
1313
1182
  var lcTag = transformCaseFunc(currentNode.nodeName);
1314
-
1315
1183
  if (!_isValidAttribute(lcTag, lcName, value)) {
1316
1184
  continue;
1317
1185
  }
1186
+
1318
1187
  /* Full DOM Clobbering protection via namespace isolation,
1319
1188
  * Prefix id and name attributes with `user-content-`
1320
1189
  */
1321
-
1322
-
1323
1190
  if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
1324
1191
  // Remove the attribute with this value
1325
- _removeAttribute(name, currentNode); // Prefix the value and later re-create the attribute with the sanitized value
1326
-
1192
+ _removeAttribute(name, currentNode);
1327
1193
 
1194
+ // Prefix the value and later re-create the attribute with the sanitized value
1328
1195
  value = SANITIZE_NAMED_PROPS_PREFIX + value;
1329
1196
  }
1330
- /* Handle attributes that require Trusted Types */
1331
-
1332
1197
 
1198
+ /* Handle attributes that require Trusted Types */
1333
1199
  if (trustedTypesPolicy && _typeof(trustedTypes) === 'object' && typeof trustedTypes.getAttributeType === 'function') {
1334
1200
  if (namespaceURI) ; else {
1335
1201
  switch (trustedTypes.getAttributeType(lcTag, lcName)) {
@@ -1338,7 +1204,6 @@
1338
1204
  value = trustedTypesPolicy.createHTML(value);
1339
1205
  break;
1340
1206
  }
1341
-
1342
1207
  case 'TrustedScriptURL':
1343
1208
  {
1344
1209
  value = trustedTypesPolicy.createScriptURL(value);
@@ -1347,9 +1212,8 @@
1347
1212
  }
1348
1213
  }
1349
1214
  }
1350
- /* Handle invalid data-* attribute set by try-catching it */
1351
-
1352
1215
 
1216
+ /* Handle invalid data-* attribute set by try-catching it */
1353
1217
  try {
1354
1218
  if (namespaceURI) {
1355
1219
  currentNode.setAttributeNS(namespaceURI, name, value);
@@ -1357,56 +1221,66 @@
1357
1221
  /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1358
1222
  currentNode.setAttribute(name, value);
1359
1223
  }
1360
-
1361
1224
  arrayPop(DOMPurify.removed);
1362
1225
  } catch (_) {}
1363
1226
  }
1364
- /* Execute a hook if present */
1365
-
1366
1227
 
1228
+ /* Execute a hook if present */
1367
1229
  _executeHook('afterSanitizeAttributes', currentNode, null);
1368
1230
  };
1231
+
1369
1232
  /**
1370
1233
  * _sanitizeShadowDOM
1371
1234
  *
1372
1235
  * @param {DocumentFragment} fragment to iterate over recursively
1373
1236
  */
1374
-
1375
-
1376
1237
  var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
1377
1238
  var shadowNode;
1378
-
1379
1239
  var shadowIterator = _createIterator(fragment);
1380
- /* Execute a hook if present */
1381
-
1382
1240
 
1241
+ /* Execute a hook if present */
1383
1242
  _executeHook('beforeSanitizeShadowDOM', fragment, null);
1384
-
1385
1243
  while (shadowNode = shadowIterator.nextNode()) {
1386
1244
  /* Execute a hook if present */
1387
1245
  _executeHook('uponSanitizeShadowNode', shadowNode, null);
1388
- /* Sanitize tags and elements */
1389
-
1390
1246
 
1247
+ /* Sanitize tags and elements */
1391
1248
  if (_sanitizeElements(shadowNode)) {
1392
1249
  continue;
1393
1250
  }
1394
- /* Deep shadow DOM detected */
1395
1251
 
1252
+ /* Set the nesting depth of an element */
1253
+ if (shadowNode.nodeType === 1) {
1254
+ if (shadowNode.parentNode && shadowNode.parentNode.__depth) {
1255
+ /*
1256
+ We want the depth of the node in the original tree, which can
1257
+ change when it's removed from its parent.
1258
+ */
1259
+ shadowNode.__depth = (shadowNode.__removalCount || 0) + shadowNode.parentNode.__depth + 1;
1260
+ } else {
1261
+ shadowNode.__depth = 1;
1262
+ }
1263
+ }
1264
+
1265
+ /* Remove an element if nested too deeply to avoid mXSS */
1266
+ if (shadowNode.__depth >= MAX_NESTING_DEPTH) {
1267
+ _forceRemove(shadowNode);
1268
+ }
1396
1269
 
1270
+ /* Deep shadow DOM detected */
1397
1271
  if (shadowNode.content instanceof DocumentFragment) {
1272
+ shadowNode.content.__depth = shadowNode.__depth;
1398
1273
  _sanitizeShadowDOM(shadowNode.content);
1399
1274
  }
1400
- /* Check attributes, sanitize if necessary */
1401
-
1402
1275
 
1276
+ /* Check attributes, sanitize if necessary */
1403
1277
  _sanitizeAttributes(shadowNode);
1404
1278
  }
1405
- /* Execute a hook if present */
1406
-
1407
1279
 
1280
+ /* Execute a hook if present */
1408
1281
  _executeHook('afterSanitizeShadowDOM', fragment, null);
1409
1282
  };
1283
+
1410
1284
  /**
1411
1285
  * Sanitize
1412
1286
  * Public method providing core sanitation functionality
@@ -1415,8 +1289,6 @@
1415
1289
  * @param {Object} configuration object
1416
1290
  */
1417
1291
  // eslint-disable-next-line complexity
1418
-
1419
-
1420
1292
  DOMPurify.sanitize = function (dirty) {
1421
1293
  var cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1422
1294
  var body;
@@ -1427,19 +1299,15 @@
1427
1299
  /* Make sure we have a string to sanitize.
1428
1300
  DO NOT return early, as this will return the wrong type if
1429
1301
  the user has requested a DOM object rather than a string */
1430
-
1431
1302
  IS_EMPTY_INPUT = !dirty;
1432
-
1433
1303
  if (IS_EMPTY_INPUT) {
1434
1304
  dirty = '<!-->';
1435
1305
  }
1436
- /* Stringify, in case dirty is an object */
1437
-
1438
1306
 
1307
+ /* Stringify, in case dirty is an object */
1439
1308
  if (typeof dirty !== 'string' && !_isNode(dirty)) {
1440
1309
  if (typeof dirty.toString === 'function') {
1441
1310
  dirty = dirty.toString();
1442
-
1443
1311
  if (typeof dirty !== 'string') {
1444
1312
  throw typeErrorCreate('dirty is not a string, aborting');
1445
1313
  }
@@ -1447,43 +1315,36 @@
1447
1315
  throw typeErrorCreate('toString is not a function');
1448
1316
  }
1449
1317
  }
1450
- /* Check we can run. Otherwise fall back or ignore */
1451
-
1452
1318
 
1319
+ /* Check we can run. Otherwise fall back or ignore */
1453
1320
  if (!DOMPurify.isSupported) {
1454
1321
  if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') {
1455
1322
  if (typeof dirty === 'string') {
1456
1323
  return window.toStaticHTML(dirty);
1457
1324
  }
1458
-
1459
1325
  if (_isNode(dirty)) {
1460
1326
  return window.toStaticHTML(dirty.outerHTML);
1461
1327
  }
1462
1328
  }
1463
-
1464
1329
  return dirty;
1465
1330
  }
1466
- /* Assign config vars */
1467
-
1468
1331
 
1332
+ /* Assign config vars */
1469
1333
  if (!SET_CONFIG) {
1470
1334
  _parseConfig(cfg);
1471
1335
  }
1472
- /* Clean up removed elements */
1473
-
1474
1336
 
1337
+ /* Clean up removed elements */
1475
1338
  DOMPurify.removed = [];
1476
- /* Check if dirty is correctly typed for IN_PLACE */
1477
1339
 
1340
+ /* Check if dirty is correctly typed for IN_PLACE */
1478
1341
  if (typeof dirty === 'string') {
1479
1342
  IN_PLACE = false;
1480
1343
  }
1481
-
1482
1344
  if (IN_PLACE) {
1483
1345
  /* Do some early pre-sanitization to avoid unsafe root nodes */
1484
1346
  if (dirty.nodeName) {
1485
1347
  var tagName = transformCaseFunc(dirty.nodeName);
1486
-
1487
1348
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1488
1349
  throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1489
1350
  }
@@ -1493,7 +1354,6 @@
1493
1354
  elements being stripped by the parser */
1494
1355
  body = _initDocument('<!---->');
1495
1356
  importedNode = body.ownerDocument.importNode(dirty, true);
1496
-
1497
1357
  if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
1498
1358
  /* Node is already a body, use as is */
1499
1359
  body = importedNode;
@@ -1505,71 +1365,80 @@
1505
1365
  }
1506
1366
  } else {
1507
1367
  /* Exit directly if we have nothing to do */
1508
- if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && // eslint-disable-next-line unicorn/prefer-includes
1368
+ if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
1369
+ // eslint-disable-next-line unicorn/prefer-includes
1509
1370
  dirty.indexOf('<') === -1) {
1510
1371
  return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
1511
1372
  }
1512
- /* Initialize the document to work on */
1513
-
1514
1373
 
1374
+ /* Initialize the document to work on */
1515
1375
  body = _initDocument(dirty);
1516
- /* Check we have a DOM node from the data */
1517
1376
 
1377
+ /* Check we have a DOM node from the data */
1518
1378
  if (!body) {
1519
1379
  return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
1520
1380
  }
1521
1381
  }
1522
- /* Remove first element node (ours) if FORCE_BODY is set */
1523
-
1524
1382
 
1383
+ /* Remove first element node (ours) if FORCE_BODY is set */
1525
1384
  if (body && FORCE_BODY) {
1526
1385
  _forceRemove(body.firstChild);
1527
1386
  }
1528
- /* Get node iterator */
1529
-
1530
1387
 
1388
+ /* Get node iterator */
1531
1389
  var nodeIterator = _createIterator(IN_PLACE ? dirty : body);
1532
- /* Now start iterating over the created document */
1533
-
1534
1390
 
1391
+ /* Now start iterating over the created document */
1535
1392
  while (currentNode = nodeIterator.nextNode()) {
1536
1393
  /* Fix IE's strange behavior with manipulated textNodes #89 */
1537
1394
  if (currentNode.nodeType === 3 && currentNode === oldNode) {
1538
1395
  continue;
1539
1396
  }
1540
- /* Sanitize tags and elements */
1541
-
1542
1397
 
1398
+ /* Sanitize tags and elements */
1543
1399
  if (_sanitizeElements(currentNode)) {
1544
1400
  continue;
1545
1401
  }
1546
- /* Shadow DOM detected, sanitize it */
1547
1402
 
1403
+ /* Set the nesting depth of an element */
1404
+ if (currentNode.nodeType === 1) {
1405
+ if (currentNode.parentNode && currentNode.parentNode.__depth) {
1406
+ /*
1407
+ We want the depth of the node in the original tree, which can
1408
+ change when it's removed from its parent.
1409
+ */
1410
+ currentNode.__depth = (currentNode.__removalCount || 0) + currentNode.parentNode.__depth + 1;
1411
+ } else {
1412
+ currentNode.__depth = 1;
1413
+ }
1414
+ }
1415
+
1416
+ /* Remove an element if nested too deeply to avoid mXSS */
1417
+ if (currentNode.__depth >= MAX_NESTING_DEPTH) {
1418
+ _forceRemove(currentNode);
1419
+ }
1548
1420
 
1421
+ /* Shadow DOM detected, sanitize it */
1549
1422
  if (currentNode.content instanceof DocumentFragment) {
1423
+ currentNode.content.__depth = currentNode.__depth;
1550
1424
  _sanitizeShadowDOM(currentNode.content);
1551
1425
  }
1552
- /* Check attributes, sanitize if necessary */
1553
-
1554
1426
 
1427
+ /* Check attributes, sanitize if necessary */
1555
1428
  _sanitizeAttributes(currentNode);
1556
-
1557
1429
  oldNode = currentNode;
1558
1430
  }
1559
-
1560
1431
  oldNode = null;
1561
- /* If we sanitized `dirty` in-place, return it. */
1562
1432
 
1433
+ /* If we sanitized `dirty` in-place, return it. */
1563
1434
  if (IN_PLACE) {
1564
1435
  return dirty;
1565
1436
  }
1566
- /* Return sanitized string or DOM */
1567
-
1568
1437
 
1438
+ /* Return sanitized string or DOM */
1569
1439
  if (RETURN_DOM) {
1570
1440
  if (RETURN_DOM_FRAGMENT) {
1571
1441
  returnNode = createDocumentFragment.call(body.ownerDocument);
1572
-
1573
1442
  while (body.firstChild) {
1574
1443
  // eslint-disable-next-line unicorn/prefer-dom-node-append
1575
1444
  returnNode.appendChild(body.firstChild);
@@ -1577,7 +1446,6 @@
1577
1446
  } else {
1578
1447
  returnNode = body;
1579
1448
  }
1580
-
1581
1449
  if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmod) {
1582
1450
  /*
1583
1451
  AdoptNode() is not used because internal state is not reset
@@ -1588,51 +1456,45 @@
1588
1456
  */
1589
1457
  returnNode = importNode.call(originalDocument, returnNode, true);
1590
1458
  }
1591
-
1592
1459
  return returnNode;
1593
1460
  }
1594
-
1595
1461
  var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1596
- /* Serialize doctype if allowed */
1597
1462
 
1463
+ /* Serialize doctype if allowed */
1598
1464
  if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
1599
1465
  serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
1600
1466
  }
1601
- /* Sanitize final string template-safe */
1602
-
1603
1467
 
1468
+ /* Sanitize final string template-safe */
1604
1469
  if (SAFE_FOR_TEMPLATES) {
1605
1470
  serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR$1, ' ');
1606
1471
  serializedHTML = stringReplace(serializedHTML, ERB_EXPR$1, ' ');
1607
1472
  serializedHTML = stringReplace(serializedHTML, TMPLIT_EXPR$1, ' ');
1608
1473
  }
1609
-
1610
1474
  return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
1611
1475
  };
1476
+
1612
1477
  /**
1613
1478
  * Public method to set the configuration once
1614
1479
  * setConfig
1615
1480
  *
1616
1481
  * @param {Object} cfg configuration object
1617
1482
  */
1618
-
1619
-
1620
1483
  DOMPurify.setConfig = function (cfg) {
1621
1484
  _parseConfig(cfg);
1622
-
1623
1485
  SET_CONFIG = true;
1624
1486
  };
1487
+
1625
1488
  /**
1626
1489
  * Public method to remove the configuration
1627
1490
  * clearConfig
1628
1491
  *
1629
1492
  */
1630
-
1631
-
1632
1493
  DOMPurify.clearConfig = function () {
1633
1494
  CONFIG = null;
1634
1495
  SET_CONFIG = false;
1635
1496
  };
1497
+
1636
1498
  /**
1637
1499
  * Public method to check if an attribute value is valid.
1638
1500
  * Uses last set config, if any. Otherwise, uses config defaults.
@@ -1643,18 +1505,16 @@
1643
1505
  * @param {string} value Attribute value.
1644
1506
  * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
1645
1507
  */
1646
-
1647
-
1648
1508
  DOMPurify.isValidAttribute = function (tag, attr, value) {
1649
1509
  /* Initialize shared config vars if necessary. */
1650
1510
  if (!CONFIG) {
1651
1511
  _parseConfig({});
1652
1512
  }
1653
-
1654
1513
  var lcTag = transformCaseFunc(tag);
1655
1514
  var lcName = transformCaseFunc(attr);
1656
1515
  return _isValidAttribute(lcTag, lcName, value);
1657
1516
  };
1517
+
1658
1518
  /**
1659
1519
  * AddHook
1660
1520
  * Public method to add DOMPurify hooks
@@ -1662,16 +1522,14 @@
1662
1522
  * @param {String} entryPoint entry point for the hook to add
1663
1523
  * @param {Function} hookFunction function to execute
1664
1524
  */
1665
-
1666
-
1667
1525
  DOMPurify.addHook = function (entryPoint, hookFunction) {
1668
1526
  if (typeof hookFunction !== 'function') {
1669
1527
  return;
1670
1528
  }
1671
-
1672
1529
  hooks[entryPoint] = hooks[entryPoint] || [];
1673
1530
  arrayPush(hooks[entryPoint], hookFunction);
1674
1531
  };
1532
+
1675
1533
  /**
1676
1534
  * RemoveHook
1677
1535
  * Public method to remove a DOMPurify hook at a given entryPoint
@@ -1680,40 +1538,34 @@
1680
1538
  * @param {String} entryPoint entry point for the hook to remove
1681
1539
  * @return {Function} removed(popped) hook
1682
1540
  */
1683
-
1684
-
1685
1541
  DOMPurify.removeHook = function (entryPoint) {
1686
1542
  if (hooks[entryPoint]) {
1687
1543
  return arrayPop(hooks[entryPoint]);
1688
1544
  }
1689
1545
  };
1546
+
1690
1547
  /**
1691
1548
  * RemoveHooks
1692
1549
  * Public method to remove all DOMPurify hooks at a given entryPoint
1693
1550
  *
1694
1551
  * @param {String} entryPoint entry point for the hooks to remove
1695
1552
  */
1696
-
1697
-
1698
1553
  DOMPurify.removeHooks = function (entryPoint) {
1699
1554
  if (hooks[entryPoint]) {
1700
1555
  hooks[entryPoint] = [];
1701
1556
  }
1702
1557
  };
1558
+
1703
1559
  /**
1704
1560
  * RemoveAllHooks
1705
1561
  * Public method to remove all DOMPurify hooks
1706
1562
  *
1707
1563
  */
1708
-
1709
-
1710
1564
  DOMPurify.removeAllHooks = function () {
1711
1565
  hooks = {};
1712
1566
  };
1713
-
1714
1567
  return DOMPurify;
1715
1568
  }
1716
-
1717
1569
  var purify = createDOMPurify();
1718
1570
 
1719
1571
  return purify;