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