dompurify 3.0.6 → 3.0.8

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