dompurify 3.1.7 → 3.2.1

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