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