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