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