opentiny 6.9.33 → 6.9.34

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.
@@ -8103,14 +8103,65 @@
8103
8103
  };
8104
8104
  };
8105
8105
 
8106
- const {entries, setPrototypeOf, isFrozen, getPrototypeOf, getOwnPropertyDescriptor} = Object;
8107
- let {freeze, seal, create: create$1} = Object;
8108
- let {apply, construct} = typeof Reflect !== 'undefined' && Reflect;
8109
- if (!apply) {
8110
- apply = function apply(fun, thisValue, args) {
8111
- return fun.apply(thisValue, args);
8112
- };
8106
+ /*! @license DOMPurify 3.4.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.5/LICENSE */
8107
+
8108
+ function _arrayLikeToArray(r, a) {
8109
+ (null == a || a > r.length) && (a = r.length);
8110
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
8111
+ return n;
8112
+ }
8113
+ function _arrayWithHoles(r) {
8114
+ if (Array.isArray(r)) return r;
8115
+ }
8116
+ function _iterableToArrayLimit(r, l) {
8117
+ var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
8118
+ if (null != t) {
8119
+ var e,
8120
+ n,
8121
+ i,
8122
+ u,
8123
+ a = [],
8124
+ f = true,
8125
+ o = false;
8126
+ try {
8127
+ if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
8128
+ } catch (r) {
8129
+ o = true, n = r;
8130
+ } finally {
8131
+ try {
8132
+ if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
8133
+ } finally {
8134
+ if (o) throw n;
8135
+ }
8136
+ }
8137
+ return a;
8138
+ }
8139
+ }
8140
+ function _nonIterableRest() {
8141
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
8113
8142
  }
8143
+ function _slicedToArray(r, e) {
8144
+ return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
8145
+ }
8146
+ function _unsupportedIterableToArray(r, a) {
8147
+ if (r) {
8148
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
8149
+ var t = {}.toString.call(r).slice(8, -1);
8150
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
8151
+ }
8152
+ }
8153
+
8154
+ const entries = Object.entries,
8155
+ setPrototypeOf = Object.setPrototypeOf,
8156
+ isFrozen = Object.isFrozen,
8157
+ getPrototypeOf = Object.getPrototypeOf,
8158
+ getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
8159
+ let freeze = Object.freeze,
8160
+ seal = Object.seal,
8161
+ create$1 = Object.create; // eslint-disable-line import/no-mutable-exports
8162
+ let _ref = typeof Reflect !== 'undefined' && Reflect,
8163
+ apply = _ref.apply,
8164
+ construct = _ref.construct;
8114
8165
  if (!freeze) {
8115
8166
  freeze = function freeze(x) {
8116
8167
  return x;
@@ -8121,50 +8172,99 @@
8121
8172
  return x;
8122
8173
  };
8123
8174
  }
8175
+ if (!apply) {
8176
+ apply = function apply(func, thisArg) {
8177
+ for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
8178
+ args[_key - 2] = arguments[_key];
8179
+ }
8180
+ return func.apply(thisArg, args);
8181
+ };
8182
+ }
8124
8183
  if (!construct) {
8125
- construct = function construct(Func, args) {
8184
+ construct = function construct(Func) {
8185
+ for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
8186
+ args[_key2 - 1] = arguments[_key2];
8187
+ }
8126
8188
  return new Func(...args);
8127
8189
  };
8128
8190
  }
8129
8191
  const arrayForEach = unapply(Array.prototype.forEach);
8192
+ const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
8130
8193
  const arrayPop = unapply(Array.prototype.pop);
8131
8194
  const arrayPush = unapply(Array.prototype.push);
8195
+ const arraySplice = unapply(Array.prototype.splice);
8196
+ const arrayIsArray = Array.isArray;
8132
8197
  const stringToLowerCase = unapply(String.prototype.toLowerCase);
8133
8198
  const stringToString = unapply(String.prototype.toString);
8134
8199
  const stringMatch = unapply(String.prototype.match);
8135
8200
  const stringReplace = unapply(String.prototype.replace);
8136
8201
  const stringIndexOf = unapply(String.prototype.indexOf);
8137
8202
  const stringTrim = unapply(String.prototype.trim);
8203
+ const numberToString = unapply(Number.prototype.toString);
8204
+ const booleanToString = unapply(Boolean.prototype.toString);
8205
+ const bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);
8206
+ const symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);
8207
+ const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
8208
+ const objectToString = unapply(Object.prototype.toString);
8138
8209
  const regExpTest = unapply(RegExp.prototype.test);
8139
8210
  const typeErrorCreate = unconstruct(TypeError);
8211
+ /**
8212
+ * Creates a new function that calls the given function with a specified thisArg and arguments.
8213
+ *
8214
+ * @param func - The function to be wrapped and called.
8215
+ * @returns A new function that calls the given function with a specified thisArg and arguments.
8216
+ */
8140
8217
  function unapply(func) {
8141
8218
  return function (thisArg) {
8142
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
8143
- args[_key - 1] = arguments[_key];
8219
+ if (thisArg instanceof RegExp) {
8220
+ thisArg.lastIndex = 0;
8221
+ }
8222
+ for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
8223
+ args[_key3 - 1] = arguments[_key3];
8144
8224
  }
8145
8225
  return apply(func, thisArg, args);
8146
8226
  };
8147
8227
  }
8148
- function unconstruct(func) {
8228
+ /**
8229
+ * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
8230
+ *
8231
+ * @param func - The constructor function to be wrapped and called.
8232
+ * @returns A new function that constructs an instance of the given constructor function with the provided arguments.
8233
+ */
8234
+ function unconstruct(Func) {
8149
8235
  return function () {
8150
- for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
8151
- args[_key2] = arguments[_key2];
8236
+ for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
8237
+ args[_key4] = arguments[_key4];
8152
8238
  }
8153
- return construct(func, args);
8239
+ return construct(Func, args);
8154
8240
  };
8155
8241
  }
8156
- function addToSet(set, array, transformCaseFunc) {
8157
- var _transformCaseFunc;
8158
- transformCaseFunc = (_transformCaseFunc = transformCaseFunc) !== null && _transformCaseFunc !== void 0 ? _transformCaseFunc : stringToLowerCase;
8242
+ /**
8243
+ * Add properties to a lookup table
8244
+ *
8245
+ * @param set - The set to which elements will be added.
8246
+ * @param array - The array containing elements to be added to the set.
8247
+ * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
8248
+ * @returns The modified set with added elements.
8249
+ */
8250
+ function addToSet(set, array) {
8251
+ let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
8159
8252
  if (setPrototypeOf) {
8253
+ // Make 'in' and truthy checks like Boolean(set.constructor)
8254
+ // independent of any properties defined on Object.prototype.
8255
+ // Prevent prototype setters from intercepting set as a this value.
8160
8256
  setPrototypeOf(set, null);
8161
8257
  }
8258
+ if (!arrayIsArray(array)) {
8259
+ return set;
8260
+ }
8162
8261
  let l = array.length;
8163
8262
  while (l--) {
8164
8263
  let element = array[l];
8165
8264
  if (typeof element === 'string') {
8166
8265
  const lcElement = transformCaseFunc(element);
8167
8266
  if (lcElement !== element) {
8267
+ // Config presets (e.g. tags.js, attrs.js) are immutable.
8168
8268
  if (!isFrozen(array)) {
8169
8269
  array[l] = lcElement;
8170
8270
  }
@@ -8175,13 +8275,105 @@
8175
8275
  }
8176
8276
  return set;
8177
8277
  }
8278
+ /**
8279
+ * Clean up an array to harden against CSPP
8280
+ *
8281
+ * @param array - The array to be cleaned.
8282
+ * @returns The cleaned version of the array
8283
+ */
8284
+ function cleanArray(array) {
8285
+ for (let index = 0; index < array.length; index++) {
8286
+ const isPropertyExist = objectHasOwnProperty(array, index);
8287
+ if (!isPropertyExist) {
8288
+ array[index] = null;
8289
+ }
8290
+ }
8291
+ return array;
8292
+ }
8293
+ /**
8294
+ * Shallow clone an object
8295
+ *
8296
+ * @param object - The object to be cloned.
8297
+ * @returns A new object that copies the original.
8298
+ */
8178
8299
  function clone(object) {
8179
8300
  const newObject = create$1(null);
8180
- for (const [property, value] of entries(object)) {
8181
- newObject[property] = value;
8301
+ for (const _ref2 of entries(object)) {
8302
+ var _ref3 = _slicedToArray(_ref2, 2);
8303
+ const property = _ref3[0];
8304
+ const value = _ref3[1];
8305
+ const isPropertyExist = objectHasOwnProperty(object, property);
8306
+ if (isPropertyExist) {
8307
+ if (arrayIsArray(value)) {
8308
+ newObject[property] = cleanArray(value);
8309
+ } else if (value && typeof value === 'object' && value.constructor === Object) {
8310
+ newObject[property] = clone(value);
8311
+ } else {
8312
+ newObject[property] = value;
8313
+ }
8314
+ }
8182
8315
  }
8183
8316
  return newObject;
8184
8317
  }
8318
+ /**
8319
+ * Convert non-node values into strings without depending on direct property access.
8320
+ *
8321
+ * @param value - The value to stringify.
8322
+ * @returns A string representation of the provided value.
8323
+ */
8324
+ function stringifyValue(value) {
8325
+ switch (typeof value) {
8326
+ case 'string':
8327
+ {
8328
+ return value;
8329
+ }
8330
+ case 'number':
8331
+ {
8332
+ return numberToString(value);
8333
+ }
8334
+ case 'boolean':
8335
+ {
8336
+ return booleanToString(value);
8337
+ }
8338
+ case 'bigint':
8339
+ {
8340
+ return bigintToString ? bigintToString(value) : '0';
8341
+ }
8342
+ case 'symbol':
8343
+ {
8344
+ return symbolToString ? symbolToString(value) : 'Symbol()';
8345
+ }
8346
+ case 'undefined':
8347
+ {
8348
+ return objectToString(value);
8349
+ }
8350
+ case 'function':
8351
+ case 'object':
8352
+ {
8353
+ if (value === null) {
8354
+ return objectToString(value);
8355
+ }
8356
+ const valueAsRecord = value;
8357
+ const valueToString = lookupGetter(valueAsRecord, 'toString');
8358
+ if (typeof valueToString === 'function') {
8359
+ const stringified = valueToString(valueAsRecord);
8360
+ return typeof stringified === 'string' ? stringified : objectToString(stringified);
8361
+ }
8362
+ return objectToString(value);
8363
+ }
8364
+ default:
8365
+ {
8366
+ return objectToString(value);
8367
+ }
8368
+ }
8369
+ }
8370
+ /**
8371
+ * This method automatically checks if the prop is function or getter and behaves accordingly.
8372
+ *
8373
+ * @param object - The object to look up the getter function in its prototype chain.
8374
+ * @param prop - The property name for which to find the getter function.
8375
+ * @returns The getter function found in the prototype chain or a fallback function.
8376
+ */
8185
8377
  function lookupGetter(object, prop) {
8186
8378
  while (object !== null) {
8187
8379
  const desc = getOwnPropertyDescriptor(object, prop);
@@ -8195,660 +8387,79 @@
8195
8387
  }
8196
8388
  object = getPrototypeOf(object);
8197
8389
  }
8198
- function fallbackValue(element) {
8199
- console.warn('fallback value for', element);
8390
+ function fallbackValue() {
8200
8391
  return null;
8201
8392
  }
8202
8393
  return fallbackValue;
8203
8394
  }
8204
- const html$1 = freeze([
8205
- 'a',
8206
- 'abbr',
8207
- 'acronym',
8208
- 'address',
8209
- 'area',
8210
- 'article',
8211
- 'aside',
8212
- 'audio',
8213
- 'b',
8214
- 'bdi',
8215
- 'bdo',
8216
- 'big',
8217
- 'blink',
8218
- 'blockquote',
8219
- 'body',
8220
- 'br',
8221
- 'button',
8222
- 'canvas',
8223
- 'caption',
8224
- 'center',
8225
- 'cite',
8226
- 'code',
8227
- 'col',
8228
- 'colgroup',
8229
- 'content',
8230
- 'data',
8231
- 'datalist',
8232
- 'dd',
8233
- 'decorator',
8234
- 'del',
8235
- 'details',
8236
- 'dfn',
8237
- 'dialog',
8238
- 'dir',
8239
- 'div',
8240
- 'dl',
8241
- 'dt',
8242
- 'element',
8243
- 'em',
8244
- 'fieldset',
8245
- 'figcaption',
8246
- 'figure',
8247
- 'font',
8248
- 'footer',
8249
- 'form',
8250
- 'h1',
8251
- 'h2',
8252
- 'h3',
8253
- 'h4',
8254
- 'h5',
8255
- 'h6',
8256
- 'head',
8257
- 'header',
8258
- 'hgroup',
8259
- 'hr',
8260
- 'html',
8261
- 'i',
8262
- 'img',
8263
- 'input',
8264
- 'ins',
8265
- 'kbd',
8266
- 'label',
8267
- 'legend',
8268
- 'li',
8269
- 'main',
8270
- 'map',
8271
- 'mark',
8272
- 'marquee',
8273
- 'menu',
8274
- 'menuitem',
8275
- 'meter',
8276
- 'nav',
8277
- 'nobr',
8278
- 'ol',
8279
- 'optgroup',
8280
- 'option',
8281
- 'output',
8282
- 'p',
8283
- 'picture',
8284
- 'pre',
8285
- 'progress',
8286
- 'q',
8287
- 'rp',
8288
- 'rt',
8289
- 'ruby',
8290
- 's',
8291
- 'samp',
8292
- 'section',
8293
- 'select',
8294
- 'shadow',
8295
- 'small',
8296
- 'source',
8297
- 'spacer',
8298
- 'span',
8299
- 'strike',
8300
- 'strong',
8301
- 'style',
8302
- 'sub',
8303
- 'summary',
8304
- 'sup',
8305
- 'table',
8306
- 'tbody',
8307
- 'td',
8308
- 'template',
8309
- 'textarea',
8310
- 'tfoot',
8311
- 'th',
8312
- 'thead',
8313
- 'time',
8314
- 'tr',
8315
- 'track',
8316
- 'tt',
8317
- 'u',
8318
- 'ul',
8319
- 'var',
8320
- 'video',
8321
- 'wbr'
8322
- ]);
8323
- const svg$1 = freeze([
8324
- 'svg',
8325
- 'a',
8326
- 'altglyph',
8327
- 'altglyphdef',
8328
- 'altglyphitem',
8329
- 'animatecolor',
8330
- 'animatemotion',
8331
- 'animatetransform',
8332
- 'circle',
8333
- 'clippath',
8334
- 'defs',
8335
- 'desc',
8336
- 'ellipse',
8337
- 'filter',
8338
- 'font',
8339
- 'g',
8340
- 'glyph',
8341
- 'glyphref',
8342
- 'hkern',
8343
- 'image',
8344
- 'line',
8345
- 'lineargradient',
8346
- 'marker',
8347
- 'mask',
8348
- 'metadata',
8349
- 'mpath',
8350
- 'path',
8351
- 'pattern',
8352
- 'polygon',
8353
- 'polyline',
8354
- 'radialgradient',
8355
- 'rect',
8356
- 'stop',
8357
- 'style',
8358
- 'switch',
8359
- 'symbol',
8360
- 'text',
8361
- 'textpath',
8362
- 'title',
8363
- 'tref',
8364
- 'tspan',
8365
- 'view',
8366
- 'vkern'
8367
- ]);
8368
- const svgFilters = freeze([
8369
- 'feBlend',
8370
- 'feColorMatrix',
8371
- 'feComponentTransfer',
8372
- 'feComposite',
8373
- 'feConvolveMatrix',
8374
- 'feDiffuseLighting',
8375
- 'feDisplacementMap',
8376
- 'feDistantLight',
8377
- 'feDropShadow',
8378
- 'feFlood',
8379
- 'feFuncA',
8380
- 'feFuncB',
8381
- 'feFuncG',
8382
- 'feFuncR',
8383
- 'feGaussianBlur',
8384
- 'feImage',
8385
- 'feMerge',
8386
- 'feMergeNode',
8387
- 'feMorphology',
8388
- 'feOffset',
8389
- 'fePointLight',
8390
- 'feSpecularLighting',
8391
- 'feSpotLight',
8392
- 'feTile',
8393
- 'feTurbulence'
8394
- ]);
8395
- const svgDisallowed = freeze([
8396
- 'animate',
8397
- 'color-profile',
8398
- 'cursor',
8399
- 'discard',
8400
- 'font-face',
8401
- 'font-face-format',
8402
- 'font-face-name',
8403
- 'font-face-src',
8404
- 'font-face-uri',
8405
- 'foreignobject',
8406
- 'hatch',
8407
- 'hatchpath',
8408
- 'mesh',
8409
- 'meshgradient',
8410
- 'meshpatch',
8411
- 'meshrow',
8412
- 'missing-glyph',
8413
- 'script',
8414
- 'set',
8415
- 'solidcolor',
8416
- 'unknown',
8417
- 'use'
8418
- ]);
8419
- const mathMl$1 = freeze([
8420
- 'math',
8421
- 'menclose',
8422
- 'merror',
8423
- 'mfenced',
8424
- 'mfrac',
8425
- 'mglyph',
8426
- 'mi',
8427
- 'mlabeledtr',
8428
- 'mmultiscripts',
8429
- 'mn',
8430
- 'mo',
8431
- 'mover',
8432
- 'mpadded',
8433
- 'mphantom',
8434
- 'mroot',
8435
- 'mrow',
8436
- 'ms',
8437
- 'mspace',
8438
- 'msqrt',
8439
- 'mstyle',
8440
- 'msub',
8441
- 'msup',
8442
- 'msubsup',
8443
- 'mtable',
8444
- 'mtd',
8445
- 'mtext',
8446
- 'mtr',
8447
- 'munder',
8448
- 'munderover',
8449
- 'mprescripts'
8450
- ]);
8451
- const mathMlDisallowed = freeze([
8452
- 'maction',
8453
- 'maligngroup',
8454
- 'malignmark',
8455
- 'mlongdiv',
8456
- 'mscarries',
8457
- 'mscarry',
8458
- 'msgroup',
8459
- 'mstack',
8460
- 'msline',
8461
- 'msrow',
8462
- 'semantics',
8463
- 'annotation',
8464
- 'annotation-xml',
8465
- 'mprescripts',
8466
- 'none'
8467
- ]);
8395
+ function isRegex(value) {
8396
+ try {
8397
+ regExpTest(value, '');
8398
+ return true;
8399
+ } catch (_unused) {
8400
+ return false;
8401
+ }
8402
+ }
8403
+
8404
+ 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', 'search', 'section', 'select', 'shadow', 'slot', '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']);
8405
+ const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
8406
+ 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']);
8407
+ // List of SVG elements that are disallowed by default.
8408
+ // We still need to know them so that we can do namespace
8409
+ // checks properly in case one wants to add them to
8410
+ // allow-list.
8411
+ 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']);
8412
+ 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']);
8413
+ // Similarly to SVG, we want to know all MathML elements,
8414
+ // even those that we disallow by default.
8415
+ const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
8468
8416
  const text$1 = freeze(['#text']);
8469
- const html = freeze([
8470
- 'accept',
8471
- 'action',
8472
- 'align',
8473
- 'alt',
8474
- 'autocapitalize',
8475
- 'autocomplete',
8476
- 'autopictureinpicture',
8477
- 'autoplay',
8478
- 'background',
8479
- 'bgcolor',
8480
- 'border',
8481
- 'capture',
8482
- 'cellpadding',
8483
- 'cellspacing',
8484
- 'checked',
8485
- 'cite',
8486
- 'class',
8487
- 'clear',
8488
- 'color',
8489
- 'cols',
8490
- 'colspan',
8491
- 'controls',
8492
- 'controlslist',
8493
- 'coords',
8494
- 'crossorigin',
8495
- 'datetime',
8496
- 'decoding',
8497
- 'default',
8498
- 'dir',
8499
- 'disabled',
8500
- 'disablepictureinpicture',
8501
- 'disableremoteplayback',
8502
- 'download',
8503
- 'draggable',
8504
- 'enctype',
8505
- 'enterkeyhint',
8506
- 'face',
8507
- 'for',
8508
- 'headers',
8509
- 'height',
8510
- 'hidden',
8511
- 'high',
8512
- 'href',
8513
- 'hreflang',
8514
- 'id',
8515
- 'inputmode',
8516
- 'integrity',
8517
- 'ismap',
8518
- 'kind',
8519
- 'label',
8520
- 'lang',
8521
- 'list',
8522
- 'loading',
8523
- 'loop',
8524
- 'low',
8525
- 'max',
8526
- 'maxlength',
8527
- 'media',
8528
- 'method',
8529
- 'min',
8530
- 'minlength',
8531
- 'multiple',
8532
- 'muted',
8533
- 'name',
8534
- 'nonce',
8535
- 'noshade',
8536
- 'novalidate',
8537
- 'nowrap',
8538
- 'open',
8539
- 'optimum',
8540
- 'pattern',
8541
- 'placeholder',
8542
- 'playsinline',
8543
- 'poster',
8544
- 'preload',
8545
- 'pubdate',
8546
- 'radiogroup',
8547
- 'readonly',
8548
- 'rel',
8549
- 'required',
8550
- 'rev',
8551
- 'reversed',
8552
- 'role',
8553
- 'rows',
8554
- 'rowspan',
8555
- 'spellcheck',
8556
- 'scope',
8557
- 'selected',
8558
- 'shape',
8559
- 'size',
8560
- 'sizes',
8561
- 'span',
8562
- 'srclang',
8563
- 'start',
8564
- 'src',
8565
- 'srcset',
8566
- 'step',
8567
- 'style',
8568
- 'summary',
8569
- 'tabindex',
8570
- 'title',
8571
- 'translate',
8572
- 'type',
8573
- 'usemap',
8574
- 'valign',
8575
- 'value',
8576
- 'width',
8577
- 'xmlns',
8578
- 'slot'
8579
- ]);
8580
- const svg = freeze([
8581
- 'accent-height',
8582
- 'accumulate',
8583
- 'additive',
8584
- 'alignment-baseline',
8585
- 'ascent',
8586
- 'attributename',
8587
- 'attributetype',
8588
- 'azimuth',
8589
- 'basefrequency',
8590
- 'baseline-shift',
8591
- 'begin',
8592
- 'bias',
8593
- 'by',
8594
- 'class',
8595
- 'clip',
8596
- 'clippathunits',
8597
- 'clip-path',
8598
- 'clip-rule',
8599
- 'color',
8600
- 'color-interpolation',
8601
- 'color-interpolation-filters',
8602
- 'color-profile',
8603
- 'color-rendering',
8604
- 'cx',
8605
- 'cy',
8606
- 'd',
8607
- 'dx',
8608
- 'dy',
8609
- 'diffuseconstant',
8610
- 'direction',
8611
- 'display',
8612
- 'divisor',
8613
- 'dur',
8614
- 'edgemode',
8615
- 'elevation',
8616
- 'end',
8617
- 'fill',
8618
- 'fill-opacity',
8619
- 'fill-rule',
8620
- 'filter',
8621
- 'filterunits',
8622
- 'flood-color',
8623
- 'flood-opacity',
8624
- 'font-family',
8625
- 'font-size',
8626
- 'font-size-adjust',
8627
- 'font-stretch',
8628
- 'font-style',
8629
- 'font-variant',
8630
- 'font-weight',
8631
- 'fx',
8632
- 'fy',
8633
- 'g1',
8634
- 'g2',
8635
- 'glyph-name',
8636
- 'glyphref',
8637
- 'gradientunits',
8638
- 'gradienttransform',
8639
- 'height',
8640
- 'href',
8641
- 'id',
8642
- 'image-rendering',
8643
- 'in',
8644
- 'in2',
8645
- 'k',
8646
- 'k1',
8647
- 'k2',
8648
- 'k3',
8649
- 'k4',
8650
- 'kerning',
8651
- 'keypoints',
8652
- 'keysplines',
8653
- 'keytimes',
8654
- 'lang',
8655
- 'lengthadjust',
8656
- 'letter-spacing',
8657
- 'kernelmatrix',
8658
- 'kernelunitlength',
8659
- 'lighting-color',
8660
- 'local',
8661
- 'marker-end',
8662
- 'marker-mid',
8663
- 'marker-start',
8664
- 'markerheight',
8665
- 'markerunits',
8666
- 'markerwidth',
8667
- 'maskcontentunits',
8668
- 'maskunits',
8669
- 'max',
8670
- 'mask',
8671
- 'media',
8672
- 'method',
8673
- 'mode',
8674
- 'min',
8675
- 'name',
8676
- 'numoctaves',
8677
- 'offset',
8678
- 'operator',
8679
- 'opacity',
8680
- 'order',
8681
- 'orient',
8682
- 'orientation',
8683
- 'origin',
8684
- 'overflow',
8685
- 'paint-order',
8686
- 'path',
8687
- 'pathlength',
8688
- 'patterncontentunits',
8689
- 'patterntransform',
8690
- 'patternunits',
8691
- 'points',
8692
- 'preservealpha',
8693
- 'preserveaspectratio',
8694
- 'primitiveunits',
8695
- 'r',
8696
- 'rx',
8697
- 'ry',
8698
- 'radius',
8699
- 'refx',
8700
- 'refy',
8701
- 'repeatcount',
8702
- 'repeatdur',
8703
- 'restart',
8704
- 'result',
8705
- 'rotate',
8706
- 'scale',
8707
- 'seed',
8708
- 'shape-rendering',
8709
- 'specularconstant',
8710
- 'specularexponent',
8711
- 'spreadmethod',
8712
- 'startoffset',
8713
- 'stddeviation',
8714
- 'stitchtiles',
8715
- 'stop-color',
8716
- 'stop-opacity',
8717
- 'stroke-dasharray',
8718
- 'stroke-dashoffset',
8719
- 'stroke-linecap',
8720
- 'stroke-linejoin',
8721
- 'stroke-miterlimit',
8722
- 'stroke-opacity',
8723
- 'stroke',
8724
- 'stroke-width',
8725
- 'style',
8726
- 'surfacescale',
8727
- 'systemlanguage',
8728
- 'tabindex',
8729
- 'targetx',
8730
- 'targety',
8731
- 'transform',
8732
- 'transform-origin',
8733
- 'text-anchor',
8734
- 'text-decoration',
8735
- 'text-rendering',
8736
- 'textlength',
8737
- 'type',
8738
- 'u1',
8739
- 'u2',
8740
- 'unicode',
8741
- 'values',
8742
- 'viewbox',
8743
- 'visibility',
8744
- 'version',
8745
- 'vert-adv-y',
8746
- 'vert-origin-x',
8747
- 'vert-origin-y',
8748
- 'width',
8749
- 'word-spacing',
8750
- 'wrap',
8751
- 'writing-mode',
8752
- 'xchannelselector',
8753
- 'ychannelselector',
8754
- 'x',
8755
- 'x1',
8756
- 'x2',
8757
- 'xmlns',
8758
- 'y',
8759
- 'y1',
8760
- 'y2',
8761
- 'z',
8762
- 'zoomandpan'
8763
- ]);
8764
- const mathMl = freeze([
8765
- 'accent',
8766
- 'accentunder',
8767
- 'align',
8768
- 'bevelled',
8769
- 'close',
8770
- 'columnsalign',
8771
- 'columnlines',
8772
- 'columnspan',
8773
- 'denomalign',
8774
- 'depth',
8775
- 'dir',
8776
- 'display',
8777
- 'displaystyle',
8778
- 'encoding',
8779
- 'fence',
8780
- 'frame',
8781
- 'height',
8782
- 'href',
8783
- 'id',
8784
- 'largeop',
8785
- 'length',
8786
- 'linethickness',
8787
- 'lspace',
8788
- 'lquote',
8789
- 'mathbackground',
8790
- 'mathcolor',
8791
- 'mathsize',
8792
- 'mathvariant',
8793
- 'maxsize',
8794
- 'minsize',
8795
- 'movablelimits',
8796
- 'notation',
8797
- 'numalign',
8798
- 'open',
8799
- 'rowalign',
8800
- 'rowlines',
8801
- 'rowspacing',
8802
- 'rowspan',
8803
- 'rspace',
8804
- 'rquote',
8805
- 'scriptlevel',
8806
- 'scriptminsize',
8807
- 'scriptsizemultiplier',
8808
- 'selection',
8809
- 'separator',
8810
- 'separators',
8811
- 'stretchy',
8812
- 'subscriptshift',
8813
- 'supscriptshift',
8814
- 'symmetric',
8815
- 'voffset',
8816
- 'width',
8817
- 'xmlns'
8818
- ]);
8819
- const xml = freeze([
8820
- 'xlink:href',
8821
- 'xml:id',
8822
- 'xlink:title',
8823
- 'xml:space',
8824
- 'xmlns:xlink'
8825
- ]);
8826
- const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm);
8827
- const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
8828
- const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
8829
- const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/);
8830
- const ARIA_ATTR = seal(/^aria-[\-\w]+$/);
8831
- const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i);
8417
+
8418
+ const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns']);
8419
+ const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
8420
+ const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', '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']);
8421
+ const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
8422
+
8423
+ const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
8424
+ const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
8425
+ const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
8426
+ const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
8427
+ const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
8428
+ const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
8429
+ );
8832
8430
  const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
8833
- const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g);
8431
+ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
8432
+ );
8834
8433
  const DOCTYPE_NAME = seal(/^html$/i);
8835
- var EXPRESSIONS = Object.freeze({
8836
- __proto__: null,
8837
- MUSTACHE_EXPR: MUSTACHE_EXPR,
8838
- ERB_EXPR: ERB_EXPR,
8839
- TMPLIT_EXPR: TMPLIT_EXPR,
8840
- DATA_ATTR: DATA_ATTR,
8841
- ARIA_ATTR: ARIA_ATTR,
8842
- IS_ALLOWED_URI: IS_ALLOWED_URI,
8843
- IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
8844
- ATTR_WHITESPACE: ATTR_WHITESPACE,
8845
- DOCTYPE_NAME: DOCTYPE_NAME
8846
- });
8847
- const getGlobal = () => typeof window === 'undefined' ? null : window;
8434
+ const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
8435
+
8436
+ /* eslint-disable @typescript-eslint/indent */
8437
+ // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
8438
+ const NODE_TYPE = {
8439
+ element: 1,
8440
+ text: 3,
8441
+ // Deprecated
8442
+ progressingInstruction: 7,
8443
+ comment: 8,
8444
+ document: 9};
8445
+ const getGlobal = function getGlobal() {
8446
+ return typeof window === 'undefined' ? null : window;
8447
+ };
8448
+ /**
8449
+ * Creates a no-op policy for internal use only.
8450
+ * Don't export this function outside this module!
8451
+ * @param trustedTypes The policy factory.
8452
+ * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
8453
+ * @return The policy created (or null, if Trusted Types
8454
+ * are not supported or creating the policy failed).
8455
+ */
8848
8456
  const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
8849
8457
  if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
8850
8458
  return null;
8851
8459
  }
8460
+ // Allow the callers to control the unique policy name
8461
+ // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
8462
+ // Policy creation with duplicate names throws in Trusted Types.
8852
8463
  let suffix = null;
8853
8464
  const ATTR_NAME = 'data-tt-policy-suffix';
8854
8465
  if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
@@ -8865,28 +8476,63 @@
8865
8476
  }
8866
8477
  });
8867
8478
  } catch (_) {
8479
+ // Policy creation failed (most likely another DOMPurify script has
8480
+ // already run). Skip creating the policy, as this will only cause errors
8481
+ // if TT are enforced.
8868
8482
  console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
8869
8483
  return null;
8870
8484
  }
8871
8485
  };
8486
+ const _createHooksMap = function _createHooksMap() {
8487
+ return {
8488
+ afterSanitizeAttributes: [],
8489
+ afterSanitizeElements: [],
8490
+ afterSanitizeShadowDOM: [],
8491
+ beforeSanitizeAttributes: [],
8492
+ beforeSanitizeElements: [],
8493
+ beforeSanitizeShadowDOM: [],
8494
+ uponSanitizeAttribute: [],
8495
+ uponSanitizeElement: [],
8496
+ uponSanitizeShadowNode: []
8497
+ };
8498
+ };
8872
8499
  function createDOMPurify() {
8873
8500
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
8874
8501
  const DOMPurify = root => createDOMPurify(root);
8875
- DOMPurify.version = '3.0.5';
8502
+ DOMPurify.version = '3.4.5';
8876
8503
  DOMPurify.removed = [];
8877
- if (!window || !window.document || window.document.nodeType !== 9) {
8504
+ if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
8505
+ // Not running in a browser, provide a factory function
8506
+ // so that you can pass your own Window
8878
8507
  DOMPurify.isSupported = false;
8879
8508
  return DOMPurify;
8880
8509
  }
8881
- const originalDocument = window.document;
8510
+ let document = window.document;
8511
+ const originalDocument = document;
8882
8512
  const currentScript = originalDocument.currentScript;
8883
- let {document} = window;
8884
- const {DocumentFragment, HTMLTemplateElement, Node, Element, NodeFilter, NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap, HTMLFormElement, DOMParser, trustedTypes} = window;
8513
+ const DocumentFragment = window.DocumentFragment,
8514
+ HTMLTemplateElement = window.HTMLTemplateElement,
8515
+ Node = window.Node,
8516
+ Element = window.Element,
8517
+ NodeFilter = window.NodeFilter,
8518
+ _window$NamedNodeMap = window.NamedNodeMap,
8519
+ NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
8520
+ HTMLFormElement = window.HTMLFormElement,
8521
+ DOMParser = window.DOMParser,
8522
+ trustedTypes = window.trustedTypes;
8885
8523
  const ElementPrototype = Element.prototype;
8886
8524
  const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
8525
+ const remove = lookupGetter(ElementPrototype, 'remove');
8887
8526
  const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
8888
8527
  const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
8889
8528
  const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
8529
+ const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
8530
+ // As per issue #47, the web-components registry is inherited by a
8531
+ // new document created via createHTMLDocument. As per the spec
8532
+ // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
8533
+ // a new empty registry is used when creating a template contents owner
8534
+ // document, so we use that as our parent document to ensure nothing
8535
+ // is inherited.
8890
8536
  if (typeof HTMLTemplateElement === 'function') {
8891
8537
  const template = document.createElement('template');
8892
8538
  if (template.content && template.content.ownerDocument) {
@@ -8895,28 +8541,43 @@
8895
8541
  }
8896
8542
  let trustedTypesPolicy;
8897
8543
  let emptyHTML = '';
8898
- const {implementation, createNodeIterator, createDocumentFragment, getElementsByTagName} = document;
8899
- const {importNode} = originalDocument;
8900
- let hooks = {};
8544
+ const _document = document,
8545
+ implementation = _document.implementation,
8546
+ createNodeIterator = _document.createNodeIterator,
8547
+ createDocumentFragment = _document.createDocumentFragment,
8548
+ getElementsByTagName = _document.getElementsByTagName;
8549
+ const importNode = originalDocument.importNode;
8550
+ let hooks = _createHooksMap();
8551
+ /**
8552
+ * Expose whether this browser supports running the full DOMPurify.
8553
+ */
8901
8554
  DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
8902
- const {MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR, DATA_ATTR, ARIA_ATTR, IS_SCRIPT_OR_DATA, ATTR_WHITESPACE} = EXPRESSIONS;
8903
- let {IS_ALLOWED_URI: IS_ALLOWED_URI$1} = EXPRESSIONS;
8555
+ const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
8556
+ ERB_EXPR$1 = ERB_EXPR,
8557
+ TMPLIT_EXPR$1 = TMPLIT_EXPR,
8558
+ DATA_ATTR$1 = DATA_ATTR,
8559
+ ARIA_ATTR$1 = ARIA_ATTR,
8560
+ IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
8561
+ ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
8562
+ CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
8563
+ let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
8564
+ /**
8565
+ * We consider the elements and attributes below to be safe. Ideally
8566
+ * don't add any new ones but feel free to remove unwanted ones.
8567
+ */
8568
+ /* allowed element names */
8904
8569
  let ALLOWED_TAGS = null;
8905
- const DEFAULT_ALLOWED_TAGS = addToSet({}, [
8906
- ...html$1,
8907
- ...svg$1,
8908
- ...svgFilters,
8909
- ...mathMl$1,
8910
- ...text$1
8911
- ]);
8570
+ const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text$1]);
8571
+ /* Allowed attribute names */
8912
8572
  let ALLOWED_ATTR = null;
8913
- const DEFAULT_ALLOWED_ATTR = addToSet({}, [
8914
- ...html,
8915
- ...svg,
8916
- ...mathMl,
8917
- ...xml
8918
- ]);
8919
- let CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
8573
+ const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
8574
+ /*
8575
+ * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
8576
+ * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
8577
+ * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
8578
+ * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
8579
+ */
8580
+ let CUSTOM_ELEMENT_HANDLING = Object.seal(create$1(null, {
8920
8581
  tagNameCheck: {
8921
8582
  writable: true,
8922
8583
  configurable: false,
@@ -8936,146 +8597,185 @@
8936
8597
  value: false
8937
8598
  }
8938
8599
  }));
8600
+ /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
8939
8601
  let FORBID_TAGS = null;
8602
+ /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
8940
8603
  let FORBID_ATTR = null;
8604
+ /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */
8605
+ const EXTRA_ELEMENT_HANDLING = Object.seal(create$1(null, {
8606
+ tagCheck: {
8607
+ writable: true,
8608
+ configurable: false,
8609
+ enumerable: true,
8610
+ value: null
8611
+ },
8612
+ attributeCheck: {
8613
+ writable: true,
8614
+ configurable: false,
8615
+ enumerable: true,
8616
+ value: null
8617
+ }
8618
+ }));
8619
+ /* Decide if ARIA attributes are okay */
8941
8620
  let ALLOW_ARIA_ATTR = true;
8621
+ /* Decide if custom data attributes are okay */
8942
8622
  let ALLOW_DATA_ATTR = true;
8623
+ /* Decide if unknown protocols are okay */
8943
8624
  let ALLOW_UNKNOWN_PROTOCOLS = false;
8625
+ /* Decide if self-closing tags in attributes are allowed.
8626
+ * Usually removed due to a mXSS issue in jQuery 3.0 */
8944
8627
  let ALLOW_SELF_CLOSE_IN_ATTR = true;
8628
+ /* Output should be safe for common template engines.
8629
+ * This means, DOMPurify removes data attributes, mustaches and ERB
8630
+ */
8945
8631
  let SAFE_FOR_TEMPLATES = false;
8632
+ /* Output should be safe even for XML used within HTML and alike.
8633
+ * This means, DOMPurify removes comments when containing risky content.
8634
+ */
8635
+ let SAFE_FOR_XML = true;
8636
+ /* Decide if document with <html>... should be returned */
8946
8637
  let WHOLE_DOCUMENT = false;
8638
+ /* Track whether config is already set on this instance of DOMPurify. */
8947
8639
  let SET_CONFIG = false;
8640
+ /* Decide if all elements (e.g. style, script) must be children of
8641
+ * document.body. By default, browsers might move them to document.head */
8948
8642
  let FORCE_BODY = false;
8643
+ /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
8644
+ * string (or a TrustedHTML object if Trusted Types are supported).
8645
+ * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
8646
+ */
8949
8647
  let RETURN_DOM = false;
8648
+ /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
8649
+ * string (or a TrustedHTML object if Trusted Types are supported) */
8950
8650
  let RETURN_DOM_FRAGMENT = false;
8651
+ /* Try to return a Trusted Type object instead of a string, return a string in
8652
+ * case Trusted Types are not supported */
8951
8653
  let RETURN_TRUSTED_TYPE = false;
8654
+ /* Output should be free from DOM clobbering attacks?
8655
+ * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
8656
+ */
8952
8657
  let SANITIZE_DOM = true;
8658
+ /* Achieve full DOM Clobbering protection by isolating the namespace of named
8659
+ * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
8660
+ *
8661
+ * HTML/DOM spec rules that enable DOM Clobbering:
8662
+ * - Named Access on Window (§7.3.3)
8663
+ * - DOM Tree Accessors (§3.1.5)
8664
+ * - Form Element Parent-Child Relations (§4.10.3)
8665
+ * - Iframe srcdoc / Nested WindowProxies (§4.8.5)
8666
+ * - HTMLCollection (§4.2.10.2)
8667
+ *
8668
+ * Namespace isolation is implemented by prefixing `id` and `name` attributes
8669
+ * with a constant string, i.e., `user-content-`
8670
+ */
8953
8671
  let SANITIZE_NAMED_PROPS = false;
8954
8672
  const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
8673
+ /* Keep element content when removing element? */
8955
8674
  let KEEP_CONTENT = true;
8675
+ /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
8676
+ * of importing it into a new Document and returning a sanitized copy */
8956
8677
  let IN_PLACE = false;
8678
+ /* Allow usage of profiles like html, svg and mathMl */
8957
8679
  let USE_PROFILES = {};
8680
+ /* Tags to ignore content of when KEEP_CONTENT is true */
8958
8681
  let FORBID_CONTENTS = null;
8959
- const DEFAULT_FORBID_CONTENTS = addToSet({}, [
8960
- 'annotation-xml',
8961
- 'audio',
8962
- 'colgroup',
8963
- 'desc',
8964
- 'foreignobject',
8965
- 'head',
8966
- 'iframe',
8967
- 'math',
8968
- 'mi',
8969
- 'mn',
8970
- 'mo',
8971
- 'ms',
8972
- 'mtext',
8973
- 'noembed',
8974
- 'noframes',
8975
- 'noscript',
8976
- 'plaintext',
8977
- 'script',
8978
- 'style',
8979
- 'svg',
8980
- 'template',
8981
- 'thead',
8982
- 'title',
8983
- 'video',
8984
- 'xmp'
8985
- ]);
8682
+ 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']);
8683
+ /* Tags that are safe for data: URIs */
8986
8684
  let DATA_URI_TAGS = null;
8987
- const DEFAULT_DATA_URI_TAGS = addToSet({}, [
8988
- 'audio',
8989
- 'video',
8990
- 'img',
8991
- 'source',
8992
- 'image',
8993
- 'track'
8994
- ]);
8685
+ const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
8686
+ /* Attributes safe for values like "javascript:" */
8995
8687
  let URI_SAFE_ATTRIBUTES = null;
8996
- const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [
8997
- 'alt',
8998
- 'class',
8999
- 'for',
9000
- 'id',
9001
- 'label',
9002
- 'name',
9003
- 'pattern',
9004
- 'placeholder',
9005
- 'role',
9006
- 'summary',
9007
- 'title',
9008
- 'value',
9009
- 'style',
9010
- 'xmlns'
9011
- ]);
8688
+ const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
9012
8689
  const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
9013
8690
  const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
9014
8691
  const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
8692
+ /* Document namespace */
9015
8693
  let NAMESPACE = HTML_NAMESPACE;
9016
8694
  let IS_EMPTY_INPUT = false;
8695
+ /* Allowed XHTML+XML namespaces */
9017
8696
  let ALLOWED_NAMESPACES = null;
9018
- const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [
9019
- MATHML_NAMESPACE,
9020
- SVG_NAMESPACE,
9021
- HTML_NAMESPACE
9022
- ], stringToString);
9023
- let PARSER_MEDIA_TYPE;
9024
- const SUPPORTED_PARSER_MEDIA_TYPES = [
9025
- 'application/xhtml+xml',
9026
- 'text/html'
9027
- ];
8697
+ const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
8698
+ let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
8699
+ let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
8700
+ // Certain elements are allowed in both SVG and HTML
8701
+ // namespace. We need to specify them explicitly
8702
+ // so that they don't get erroneously deleted from
8703
+ // HTML namespace.
8704
+ const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
8705
+ /* Parsing of strict XHTML documents */
8706
+ let PARSER_MEDIA_TYPE = null;
8707
+ const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
9028
8708
  const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
9029
- let transformCaseFunc;
8709
+ let transformCaseFunc = null;
8710
+ /* Keep a reference to config to pass to hooks */
9030
8711
  let CONFIG = null;
8712
+ /* Ideally, do not touch anything below this line */
8713
+ /* ______________________________________________ */
9031
8714
  const formElement = document.createElement('form');
9032
8715
  const isRegexOrFunction = function isRegexOrFunction(testValue) {
9033
8716
  return testValue instanceof RegExp || testValue instanceof Function;
9034
8717
  };
9035
- const _parseConfig = function _parseConfig(cfg) {
8718
+ /**
8719
+ * _parseConfig
8720
+ *
8721
+ * @param cfg optional config literal
8722
+ */
8723
+ // eslint-disable-next-line complexity
8724
+ const _parseConfig = function _parseConfig() {
8725
+ let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
9036
8726
  if (CONFIG && CONFIG === cfg) {
9037
8727
  return;
9038
8728
  }
8729
+ /* Shield configuration object from tampering */
9039
8730
  if (!cfg || typeof cfg !== 'object') {
9040
8731
  cfg = {};
9041
8732
  }
8733
+ /* Shield configuration object from prototype pollution */
9042
8734
  cfg = clone(cfg);
9043
- PARSER_MEDIA_TYPE = SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE;
8735
+ PARSER_MEDIA_TYPE =
8736
+ // eslint-disable-next-line unicorn/prefer-includes
8737
+ SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
8738
+ // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
9044
8739
  transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
9045
- ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
9046
- ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
9047
- ALLOWED_NAMESPACES = 'ALLOWED_NAMESPACES' in cfg ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
9048
- URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
9049
- DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
9050
- FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
9051
- FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
9052
- FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
9053
- USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
9054
- ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false;
9055
- ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false;
9056
- ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false;
9057
- ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false;
9058
- SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false;
9059
- WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false;
9060
- RETURN_DOM = cfg.RETURN_DOM || false;
9061
- RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false;
9062
- RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false;
9063
- FORCE_BODY = cfg.FORCE_BODY || false;
9064
- SANITIZE_DOM = cfg.SANITIZE_DOM !== false;
9065
- SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false;
9066
- KEEP_CONTENT = cfg.KEEP_CONTENT !== false;
9067
- IN_PLACE = cfg.IN_PLACE || false;
9068
- IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
9069
- NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
9070
- CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
9071
- if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
9072
- CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
9073
- }
9074
- if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
9075
- CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
9076
- }
9077
- if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
9078
- CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
8740
+ /* Set configuration parameters */
8741
+ ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') && arrayIsArray(cfg.ALLOWED_TAGS) ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
8742
+ ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') && arrayIsArray(cfg.ALLOWED_ATTR) ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
8743
+ ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') && arrayIsArray(cfg.ALLOWED_NAMESPACES) ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
8744
+ URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR) ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
8745
+ DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') && arrayIsArray(cfg.ADD_DATA_URI_TAGS) ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
8746
+ FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS) ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
8747
+ FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') && arrayIsArray(cfg.FORBID_TAGS) ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
8748
+ FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') && arrayIsArray(cfg.FORBID_ATTR) ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
8749
+ USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
8750
+ ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
8751
+ ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
8752
+ ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
8753
+ ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
8754
+ SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
8755
+ SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
8756
+ WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
8757
+ RETURN_DOM = cfg.RETURN_DOM || false; // Default false
8758
+ RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
8759
+ RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
8760
+ FORCE_BODY = cfg.FORCE_BODY || false; // Default false
8761
+ SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
8762
+ SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
8763
+ KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
8764
+ IN_PLACE = cfg.IN_PLACE || false; // Default false
8765
+ IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
8766
+ NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
8767
+ MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); // Default built-in map
8768
+ HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, ['annotation-xml']); // Default built-in map
8769
+ const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create$1(null);
8770
+ CUSTOM_ELEMENT_HANDLING = create$1(null);
8771
+ if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
8772
+ CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; // Default undefined
8773
+ }
8774
+ if (objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && isRegexOrFunction(customElementHandling.attributeNameCheck)) {
8775
+ CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; // Default undefined
8776
+ }
8777
+ if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
8778
+ CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
9079
8779
  }
9080
8780
  if (SAFE_FOR_TEMPLATES) {
9081
8781
  ALLOW_DATA_ATTR = false;
@@ -9083,9 +8783,10 @@
9083
8783
  if (RETURN_DOM_FRAGMENT) {
9084
8784
  RETURN_DOM = true;
9085
8785
  }
8786
+ /* Parse profile info */
9086
8787
  if (USE_PROFILES) {
9087
- ALLOWED_TAGS = addToSet({}, [...text$1]);
9088
- ALLOWED_ATTR = [];
8788
+ ALLOWED_TAGS = addToSet({}, text$1);
8789
+ ALLOWED_ATTR = create$1(null);
9089
8790
  if (USE_PROFILES.html === true) {
9090
8791
  addToSet(ALLOWED_TAGS, html$1);
9091
8792
  addToSet(ALLOWED_ATTR, html);
@@ -9106,37 +8807,55 @@
9106
8807
  addToSet(ALLOWED_ATTR, xml);
9107
8808
  }
9108
8809
  }
9109
- if (cfg.ADD_TAGS) {
9110
- if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
9111
- ALLOWED_TAGS = clone(ALLOWED_TAGS);
8810
+ /* Always reset function-based ADD_TAGS / ADD_ATTR checks to prevent
8811
+ * leaking across calls when switching from function to array config */
8812
+ EXTRA_ELEMENT_HANDLING.tagCheck = null;
8813
+ EXTRA_ELEMENT_HANDLING.attributeCheck = null;
8814
+ /* Merge configuration parameters */
8815
+ if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {
8816
+ if (typeof cfg.ADD_TAGS === 'function') {
8817
+ EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
8818
+ } else if (arrayIsArray(cfg.ADD_TAGS)) {
8819
+ if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
8820
+ ALLOWED_TAGS = clone(ALLOWED_TAGS);
8821
+ }
8822
+ addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
9112
8823
  }
9113
- addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
9114
8824
  }
9115
- if (cfg.ADD_ATTR) {
9116
- if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
9117
- ALLOWED_ATTR = clone(ALLOWED_ATTR);
8825
+ if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {
8826
+ if (typeof cfg.ADD_ATTR === 'function') {
8827
+ EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
8828
+ } else if (arrayIsArray(cfg.ADD_ATTR)) {
8829
+ if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
8830
+ ALLOWED_ATTR = clone(ALLOWED_ATTR);
8831
+ }
8832
+ addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
9118
8833
  }
9119
- addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
9120
8834
  }
9121
- if (cfg.ADD_URI_SAFE_ATTR) {
8835
+ if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {
9122
8836
  addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
9123
8837
  }
9124
- if (cfg.FORBID_CONTENTS) {
8838
+ if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {
9125
8839
  if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
9126
8840
  FORBID_CONTENTS = clone(FORBID_CONTENTS);
9127
8841
  }
9128
8842
  addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
9129
8843
  }
8844
+ if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
8845
+ if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
8846
+ FORBID_CONTENTS = clone(FORBID_CONTENTS);
8847
+ }
8848
+ addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
8849
+ }
8850
+ /* Add #text in case KEEP_CONTENT is set to true */
9130
8851
  if (KEEP_CONTENT) {
9131
8852
  ALLOWED_TAGS['#text'] = true;
9132
8853
  }
8854
+ /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
9133
8855
  if (WHOLE_DOCUMENT) {
9134
- addToSet(ALLOWED_TAGS, [
9135
- 'html',
9136
- 'head',
9137
- 'body'
9138
- ]);
8856
+ addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
9139
8857
  }
8858
+ /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
9140
8859
  if (ALLOWED_TAGS.table) {
9141
8860
  addToSet(ALLOWED_TAGS, ['tbody']);
9142
8861
  delete FORBID_TAGS.tbody;
@@ -9148,48 +8867,42 @@
9148
8867
  if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
9149
8868
  throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
9150
8869
  }
8870
+ // Overwrite existing TrustedTypes policy.
9151
8871
  trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
8872
+ // Sign local variables required by `sanitize`.
9152
8873
  emptyHTML = trustedTypesPolicy.createHTML('');
9153
8874
  } else {
8875
+ // Uninitialized policy, attempt to initialize the internal dompurify policy.
9154
8876
  if (trustedTypesPolicy === undefined) {
9155
8877
  trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
9156
8878
  }
8879
+ // If creating the internal policy succeeded sign internal variables.
9157
8880
  if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
9158
8881
  emptyHTML = trustedTypesPolicy.createHTML('');
9159
8882
  }
9160
8883
  }
8884
+ // Prevent further manipulation of configuration.
8885
+ // Not available in IE8, Safari 5, etc.
9161
8886
  if (freeze) {
9162
8887
  freeze(cfg);
9163
8888
  }
9164
8889
  CONFIG = cfg;
9165
8890
  };
9166
- const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, [
9167
- 'mi',
9168
- 'mo',
9169
- 'mn',
9170
- 'ms',
9171
- 'mtext'
9172
- ]);
9173
- const HTML_INTEGRATION_POINTS = addToSet({}, [
9174
- 'foreignobject',
9175
- 'desc',
9176
- 'title',
9177
- 'annotation-xml'
9178
- ]);
9179
- const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [
9180
- 'title',
9181
- 'style',
9182
- 'font',
9183
- 'a',
9184
- 'script'
9185
- ]);
9186
- const ALL_SVG_TAGS = addToSet({}, svg$1);
9187
- addToSet(ALL_SVG_TAGS, svgFilters);
9188
- addToSet(ALL_SVG_TAGS, svgDisallowed);
9189
- const ALL_MATHML_TAGS = addToSet({}, mathMl$1);
9190
- addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
8891
+ /* Keep track of all possible SVG and MathML tags
8892
+ * so that we can perform the namespace checks
8893
+ * correctly. */
8894
+ const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
8895
+ const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
8896
+ /**
8897
+ * @param element a DOM element whose namespace is being checked
8898
+ * @returns Return false if the element has a
8899
+ * namespace that a spec-compliant parser would never
8900
+ * return. Return true otherwise.
8901
+ */
9191
8902
  const _checkValidNamespace = function _checkValidNamespace(element) {
9192
8903
  let parent = getParentNode(element);
8904
+ // In JSDOM, if we're inside shadow DOM, then parentNode
8905
+ // can be null. We just simulate parent in this case.
9193
8906
  if (!parent || !parent.tagName) {
9194
8907
  parent = {
9195
8908
  namespaceURI: NAMESPACE,
@@ -9202,401 +8915,740 @@
9202
8915
  return false;
9203
8916
  }
9204
8917
  if (element.namespaceURI === SVG_NAMESPACE) {
8918
+ // The only way to switch from HTML namespace to SVG
8919
+ // is via <svg>. If it happens via any other tag, then
8920
+ // it should be killed.
9205
8921
  if (parent.namespaceURI === HTML_NAMESPACE) {
9206
8922
  return tagName === 'svg';
9207
8923
  }
8924
+ // The only way to switch from MathML to SVG is via`
8925
+ // svg if parent is either <annotation-xml> or MathML
8926
+ // text integration points.
9208
8927
  if (parent.namespaceURI === MATHML_NAMESPACE) {
9209
8928
  return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
9210
8929
  }
8930
+ // We only allow elements that are defined in SVG
8931
+ // spec. All others are disallowed in SVG namespace.
9211
8932
  return Boolean(ALL_SVG_TAGS[tagName]);
9212
8933
  }
9213
8934
  if (element.namespaceURI === MATHML_NAMESPACE) {
8935
+ // The only way to switch from HTML namespace to MathML
8936
+ // is via <math>. If it happens via any other tag, then
8937
+ // it should be killed.
9214
8938
  if (parent.namespaceURI === HTML_NAMESPACE) {
9215
8939
  return tagName === 'math';
9216
8940
  }
8941
+ // The only way to switch from SVG to MathML is via
8942
+ // <math> and HTML integration points
9217
8943
  if (parent.namespaceURI === SVG_NAMESPACE) {
9218
8944
  return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
9219
8945
  }
8946
+ // We only allow elements that are defined in MathML
8947
+ // spec. All others are disallowed in MathML namespace.
9220
8948
  return Boolean(ALL_MATHML_TAGS[tagName]);
9221
8949
  }
9222
8950
  if (element.namespaceURI === HTML_NAMESPACE) {
8951
+ // The only way to switch from SVG to HTML is via
8952
+ // HTML integration points, and from MathML to HTML
8953
+ // is via MathML text integration points
9223
8954
  if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
9224
8955
  return false;
9225
8956
  }
9226
8957
  if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
9227
8958
  return false;
9228
8959
  }
8960
+ // We disallow tags that are specific for MathML
8961
+ // or SVG and should never appear in HTML namespace
9229
8962
  return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
9230
8963
  }
8964
+ // For XHTML and XML documents that support custom namespaces
9231
8965
  if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
9232
8966
  return true;
9233
8967
  }
8968
+ // The code should never reach this place (this means
8969
+ // that the element somehow got namespace that is not
8970
+ // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
8971
+ // Return false just in case.
9234
8972
  return false;
9235
8973
  };
8974
+ /**
8975
+ * _forceRemove
8976
+ *
8977
+ * @param node a DOM node
8978
+ */
9236
8979
  const _forceRemove = function _forceRemove(node) {
9237
- arrayPush(DOMPurify.removed, { element: node });
8980
+ arrayPush(DOMPurify.removed, {
8981
+ element: node
8982
+ });
9238
8983
  try {
9239
- node.parentNode.removeChild(node);
8984
+ // eslint-disable-next-line unicorn/prefer-dom-node-remove
8985
+ getParentNode(node).removeChild(node);
9240
8986
  } catch (_) {
9241
- node.remove();
8987
+ remove(node);
9242
8988
  }
9243
8989
  };
9244
- const _removeAttribute = function _removeAttribute(name, node) {
8990
+ /**
8991
+ * _removeAttribute
8992
+ *
8993
+ * @param name an Attribute name
8994
+ * @param element a DOM node
8995
+ */
8996
+ const _removeAttribute = function _removeAttribute(name, element) {
9245
8997
  try {
9246
8998
  arrayPush(DOMPurify.removed, {
9247
- attribute: node.getAttributeNode(name),
9248
- from: node
8999
+ attribute: element.getAttributeNode(name),
9000
+ from: element
9249
9001
  });
9250
9002
  } catch (_) {
9251
9003
  arrayPush(DOMPurify.removed, {
9252
9004
  attribute: null,
9253
- from: node
9005
+ from: element
9254
9006
  });
9255
9007
  }
9256
- node.removeAttribute(name);
9257
- if (name === 'is' && !ALLOWED_ATTR[name]) {
9008
+ element.removeAttribute(name);
9009
+ // We void attribute values for unremovable "is" attributes
9010
+ if (name === 'is') {
9258
9011
  if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
9259
9012
  try {
9260
- _forceRemove(node);
9261
- } catch (_) {
9262
- }
9013
+ _forceRemove(element);
9014
+ } catch (_) {}
9263
9015
  } else {
9264
9016
  try {
9265
- node.setAttribute(name, '');
9266
- } catch (_) {
9267
- }
9017
+ element.setAttribute(name, '');
9018
+ } catch (_) {}
9268
9019
  }
9269
9020
  }
9270
9021
  };
9022
+ /**
9023
+ * _initDocument
9024
+ *
9025
+ * @param dirty - a string of dirty markup
9026
+ * @return a DOM, filled with the dirty markup
9027
+ */
9271
9028
  const _initDocument = function _initDocument(dirty) {
9272
- let doc;
9273
- let leadingWhitespace;
9029
+ /* Create a HTML document */
9030
+ let doc = null;
9031
+ let leadingWhitespace = null;
9274
9032
  if (FORCE_BODY) {
9275
9033
  dirty = '<remove></remove>' + dirty;
9276
9034
  } else {
9035
+ /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
9277
9036
  const matches = stringMatch(dirty, /^[\r\n\t ]+/);
9278
9037
  leadingWhitespace = matches && matches[0];
9279
9038
  }
9280
9039
  if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
9040
+ // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
9281
9041
  dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
9282
9042
  }
9283
9043
  const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
9044
+ /*
9045
+ * Use the DOMParser API by default, fallback later if needs be
9046
+ * DOMParser not work for svg when has multiple root element.
9047
+ */
9284
9048
  if (NAMESPACE === HTML_NAMESPACE) {
9285
9049
  try {
9286
9050
  doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
9287
- } catch (_) {
9288
- }
9051
+ } catch (_) {}
9289
9052
  }
9053
+ /* Use createHTMLDocument in case DOMParser is not available */
9290
9054
  if (!doc || !doc.documentElement) {
9291
9055
  doc = implementation.createDocument(NAMESPACE, 'template', null);
9292
9056
  try {
9293
9057
  doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
9294
9058
  } catch (_) {
9059
+ // Syntax error if dirtyPayload is invalid xml
9295
9060
  }
9296
9061
  }
9297
9062
  const body = doc.body || doc.documentElement;
9298
9063
  if (dirty && leadingWhitespace) {
9299
9064
  body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
9300
9065
  }
9066
+ /* Work on whole document or just its body */
9301
9067
  if (NAMESPACE === HTML_NAMESPACE) {
9302
9068
  return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
9303
9069
  }
9304
9070
  return WHOLE_DOCUMENT ? doc.documentElement : body;
9305
9071
  };
9306
- const _createIterator = function _createIterator(root) {
9307
- return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
9308
- };
9309
- const _isClobbered = function _isClobbered(elm) {
9310
- 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');
9311
- };
9312
- const _isNode = function _isNode(object) {
9313
- return typeof Node === 'object' ? object instanceof Node : object && typeof object === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string';
9314
- };
9315
- const _executeHook = function _executeHook(entryPoint, currentNode, data) {
9316
- if (!hooks[entryPoint]) {
9317
- return;
9072
+ /**
9073
+ * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
9074
+ *
9075
+ * @param root The root element or node to start traversing on.
9076
+ * @return The created NodeIterator
9077
+ */
9078
+ const _createNodeIterator = function _createNodeIterator(root) {
9079
+ return createNodeIterator.call(root.ownerDocument || root, root,
9080
+ // eslint-disable-next-line no-bitwise
9081
+ NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
9082
+ };
9083
+ /**
9084
+ * Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
9085
+ * character data of an element subtree. Used as the final safety net for
9086
+ * SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
9087
+ * which only form after text-node normalization (e.g. fragments split across
9088
+ * stripped elements) cannot survive into a template-evaluating framework.
9089
+ *
9090
+ * Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
9091
+ * in place rather than round-tripping through innerHTML. This preserves
9092
+ * descendant node references (important for IN_PLACE callers), avoids a
9093
+ * serialize/reparse cycle, and reads literal character data — which means
9094
+ * `<%...%>` in text content matches the ERB regex against its real bytes
9095
+ * instead of the HTML-entity-escaped form innerHTML would produce.
9096
+ *
9097
+ * Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
9098
+ * attributes is performed during the per-node `_sanitizeAttributes` pass.
9099
+ *
9100
+ * @param node The root element whose character data should be scrubbed.
9101
+ */
9102
+ const _scrubTemplateExpressions = function _scrubTemplateExpressions(node) {
9103
+ node.normalize();
9104
+ const walker = createNodeIterator.call(node.ownerDocument || node, node,
9105
+ // eslint-disable-next-line no-bitwise
9106
+ NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
9107
+ let currentNode = walker.nextNode();
9108
+ while (currentNode) {
9109
+ let data = currentNode.data;
9110
+ arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
9111
+ data = stringReplace(data, expr, ' ');
9112
+ });
9113
+ currentNode.data = data;
9114
+ currentNode = walker.nextNode();
9115
+ }
9116
+ };
9117
+ /**
9118
+ * _isClobbered
9119
+ *
9120
+ * @param element element to check for clobbering attacks
9121
+ * @return true if clobbered, false if safe
9122
+ */
9123
+ const _isClobbered = function _isClobbered(element) {
9124
+ 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');
9125
+ };
9126
+ /**
9127
+ * Checks whether the given object is a DOM node, including nodes that
9128
+ * originate from a different window/realm (e.g. an iframe's
9129
+ * contentDocument). The previous `value instanceof Node` check was
9130
+ * realm-bound: nodes from a different window failed it, causing
9131
+ * sanitize() to silently stringify them and reset IN_PLACE to false,
9132
+ * returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
9133
+ *
9134
+ * Implementation: call the cached `nodeType` getter from Node.prototype
9135
+ * directly on the value. This bypasses any clobbered instance property
9136
+ * (e.g. a child element named "nodeType") and works across realms
9137
+ * because the WebIDL `nodeType` getter reads an internal slot that
9138
+ * every real Node has, regardless of which window minted it.
9139
+ *
9140
+ * @param value object to check whether it's a DOM node
9141
+ * @return true if value is a DOM node from any realm
9142
+ */
9143
+ const _isNode = function _isNode(value) {
9144
+ if (!getNodeType || typeof value !== 'object' || value === null) {
9145
+ return false;
9318
9146
  }
9319
- arrayForEach(hooks[entryPoint], hook => {
9147
+ try {
9148
+ return typeof getNodeType(value) === 'number';
9149
+ } catch (_) {
9150
+ return false;
9151
+ }
9152
+ };
9153
+ function _executeHooks(hooks, currentNode, data) {
9154
+ arrayForEach(hooks, hook => {
9320
9155
  hook.call(DOMPurify, currentNode, data, CONFIG);
9321
9156
  });
9322
- };
9157
+ }
9158
+ /**
9159
+ * _sanitizeElements
9160
+ *
9161
+ * @protect nodeName
9162
+ * @protect textContent
9163
+ * @protect removeChild
9164
+ * @param currentNode to check for permission to exist
9165
+ * @return true if node was killed, false if left alive
9166
+ */
9323
9167
  const _sanitizeElements = function _sanitizeElements(currentNode) {
9324
- let content;
9325
- _executeHook('beforeSanitizeElements', currentNode, null);
9168
+ let content = null;
9169
+ /* Execute a hook if present */
9170
+ _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
9171
+ /* Check if element is clobbered or can clobber */
9326
9172
  if (_isClobbered(currentNode)) {
9327
9173
  _forceRemove(currentNode);
9328
9174
  return true;
9329
9175
  }
9176
+ /* Now let's check the element's type and name */
9330
9177
  const tagName = transformCaseFunc(currentNode.nodeName);
9331
- _executeHook('uponSanitizeElement', currentNode, {
9178
+ /* Execute a hook if present */
9179
+ _executeHooks(hooks.uponSanitizeElement, currentNode, {
9332
9180
  tagName,
9333
9181
  allowedTags: ALLOWED_TAGS
9334
9182
  });
9335
- if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
9183
+ /* Detect mXSS attempts abusing namespace confusion */
9184
+ if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
9185
+ _forceRemove(currentNode);
9186
+ return true;
9187
+ }
9188
+ /* Remove risky CSS construction leading to mXSS */
9189
+ if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
9190
+ _forceRemove(currentNode);
9191
+ return true;
9192
+ }
9193
+ /* Remove any occurrence of processing instructions */
9194
+ if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
9336
9195
  _forceRemove(currentNode);
9337
9196
  return true;
9338
9197
  }
9339
- if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
9340
- if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {
9341
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName))
9198
+ /* Remove any kind of possibly harmful comments */
9199
+ if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
9200
+ _forceRemove(currentNode);
9201
+ return true;
9202
+ }
9203
+ /* Remove element if anything forbids its presence */
9204
+ if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
9205
+ /* Check if we have a custom element to handle */
9206
+ if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
9207
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
9342
9208
  return false;
9343
- if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName))
9209
+ }
9210
+ if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
9344
9211
  return false;
9212
+ }
9345
9213
  }
9214
+ /* Keep content except for bad-listed elements */
9346
9215
  if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
9347
9216
  const parentNode = getParentNode(currentNode) || currentNode.parentNode;
9348
9217
  const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
9349
9218
  if (childNodes && parentNode) {
9350
9219
  const childCount = childNodes.length;
9351
9220
  for (let i = childCount - 1; i >= 0; --i) {
9352
- parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
9221
+ const childClone = cloneNode(childNodes[i], true);
9222
+ parentNode.insertBefore(childClone, getNextSibling(currentNode));
9353
9223
  }
9354
9224
  }
9355
9225
  }
9356
9226
  _forceRemove(currentNode);
9357
9227
  return true;
9358
9228
  }
9229
+ /* Check whether element has a valid namespace */
9359
9230
  if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
9360
9231
  _forceRemove(currentNode);
9361
9232
  return true;
9362
9233
  }
9234
+ /* Make sure that older browsers don't get fallback-tag mXSS */
9363
9235
  if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
9364
9236
  _forceRemove(currentNode);
9365
9237
  return true;
9366
9238
  }
9367
- if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
9239
+ /* Sanitize element content to be template-safe */
9240
+ if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
9241
+ /* Get the element's text content */
9368
9242
  content = currentNode.textContent;
9369
- content = stringReplace(content, MUSTACHE_EXPR, ' ');
9370
- content = stringReplace(content, ERB_EXPR, ' ');
9371
- content = stringReplace(content, TMPLIT_EXPR, ' ');
9243
+ arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
9244
+ content = stringReplace(content, expr, ' ');
9245
+ });
9372
9246
  if (currentNode.textContent !== content) {
9373
- arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });
9247
+ arrayPush(DOMPurify.removed, {
9248
+ element: currentNode.cloneNode()
9249
+ });
9374
9250
  currentNode.textContent = content;
9375
9251
  }
9376
9252
  }
9377
- _executeHook('afterSanitizeElements', currentNode, null);
9253
+ /* Execute a hook if present */
9254
+ _executeHooks(hooks.afterSanitizeElements, currentNode, null);
9378
9255
  return false;
9379
9256
  };
9257
+ /**
9258
+ * _isValidAttribute
9259
+ *
9260
+ * @param lcTag Lowercase tag name of containing element.
9261
+ * @param lcName Lowercase attribute name.
9262
+ * @param value Attribute value.
9263
+ * @return Returns true if `value` is valid, otherwise false.
9264
+ */
9265
+ // eslint-disable-next-line complexity
9380
9266
  const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
9267
+ /* FORBID_ATTR must always win, even if ADD_ATTR predicate would allow it */
9268
+ if (FORBID_ATTR[lcName]) {
9269
+ return false;
9270
+ }
9271
+ /* Make sure attribute cannot clobber */
9381
9272
  if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
9382
9273
  return false;
9383
9274
  }
9384
- if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName));
9385
- else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName));
9386
- else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
9387
- if (_basicCustomElementTest(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) || 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)));
9388
- else {
9275
+ const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
9276
+ /* Allow valid data-* attributes: At least one character after "-"
9277
+ (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
9278
+ XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
9279
+ We don't need to check the value; it's always URI safe. */
9280
+ if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted || FORBID_ATTR[lcName]) {
9281
+ if (
9282
+ // First condition does a very basic check if a) it's basically a valid custom element tagname AND
9283
+ // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
9284
+ // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
9285
+ _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, lcTag)) ||
9286
+ // Alternative, second condition checks if it's an `is`-attribute, AND
9287
+ // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
9288
+ 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 {
9389
9289
  return false;
9390
9290
  }
9391
- } else if (URI_SAFE_ATTRIBUTES[lcName]);
9392
- else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, '')));
9393
- else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]);
9394
- else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, '')));
9395
- else if (value) {
9291
+ /* Check value is safe. First, is attr inert? If so, is safe */
9292
+ } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; 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$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (value) {
9396
9293
  return false;
9397
9294
  } else ;
9398
9295
  return true;
9399
9296
  };
9400
- const _basicCustomElementTest = function _basicCustomElementTest(tagName) {
9401
- return tagName.indexOf('-') > 0;
9402
- };
9297
+ /* Names the HTML spec reserves from valid-custom-element-name; these must
9298
+ * never be treated as basic custom elements even when a permissive
9299
+ * CUSTOM_ELEMENT_HANDLING.tagNameCheck is configured. */
9300
+ const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, ['annotation-xml', 'color-profile', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'missing-glyph']);
9301
+ /**
9302
+ * _isBasicCustomElement
9303
+ * checks if at least one dash is included in tagName, and it's not the first char
9304
+ * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
9305
+ *
9306
+ * @param tagName name of the tag of the node to sanitize
9307
+ * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
9308
+ */
9309
+ const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
9310
+ return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
9311
+ };
9312
+ /**
9313
+ * _sanitizeAttributes
9314
+ *
9315
+ * @protect attributes
9316
+ * @protect nodeName
9317
+ * @protect removeAttribute
9318
+ * @protect setAttribute
9319
+ *
9320
+ * @param currentNode to sanitize
9321
+ */
9403
9322
  const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
9404
- let attr;
9405
- let value;
9406
- let lcName;
9407
- let l;
9408
- _executeHook('beforeSanitizeAttributes', currentNode, null);
9409
- const {attributes} = currentNode;
9410
- if (!attributes) {
9323
+ /* Execute a hook if present */
9324
+ _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
9325
+ const attributes = currentNode.attributes;
9326
+ /* Check if we have attributes; if not we might have a text node */
9327
+ if (!attributes || _isClobbered(currentNode)) {
9411
9328
  return;
9412
9329
  }
9413
9330
  const hookEvent = {
9414
9331
  attrName: '',
9415
9332
  attrValue: '',
9416
9333
  keepAttr: true,
9417
- allowedAttributes: ALLOWED_ATTR
9334
+ allowedAttributes: ALLOWED_ATTR,
9335
+ forceKeepAttr: undefined
9418
9336
  };
9419
- l = attributes.length;
9337
+ let l = attributes.length;
9338
+ /* Go backwards over all attributes; safely remove bad ones */
9420
9339
  while (l--) {
9421
- attr = attributes[l];
9422
- const {name, namespaceURI} = attr;
9423
- value = name === 'value' ? attr.value : stringTrim(attr.value);
9424
- const initValue = value;
9425
- lcName = transformCaseFunc(name);
9340
+ const attr = attributes[l];
9341
+ const name = attr.name,
9342
+ namespaceURI = attr.namespaceURI,
9343
+ attrValue = attr.value;
9344
+ const lcName = transformCaseFunc(name);
9345
+ const initValue = attrValue;
9346
+ let value = name === 'value' ? initValue : stringTrim(initValue);
9347
+ /* Execute a hook if present */
9426
9348
  hookEvent.attrName = lcName;
9427
9349
  hookEvent.attrValue = value;
9428
9350
  hookEvent.keepAttr = true;
9429
- hookEvent.forceKeepAttr = undefined;
9430
- _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
9351
+ hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
9352
+ _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
9431
9353
  value = hookEvent.attrValue;
9354
+ /* Full DOM Clobbering protection via namespace isolation,
9355
+ * Prefix id and name attributes with `user-content-`
9356
+ */
9357
+ if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name') && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
9358
+ // Remove the attribute with this value
9359
+ _removeAttribute(name, currentNode);
9360
+ // Prefix the value and later re-create the attribute with the sanitized value
9361
+ value = SANITIZE_NAMED_PROPS_PREFIX + value;
9362
+ }
9363
+ // Else: already prefixed, leave the attribute alone — the prefix is
9364
+ // itself the clobbering protection, and re-applying it is incorrect.
9365
+ /* Work around a security issue with comments inside attributes */
9366
+ if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
9367
+ _removeAttribute(name, currentNode);
9368
+ continue;
9369
+ }
9370
+ /* Make sure we cannot easily use animated hrefs, even if animations are allowed */
9371
+ if (lcName === 'attributename' && stringMatch(value, 'href')) {
9372
+ _removeAttribute(name, currentNode);
9373
+ continue;
9374
+ }
9375
+ /* Did the hooks approve of the attribute? */
9432
9376
  if (hookEvent.forceKeepAttr) {
9433
9377
  continue;
9434
9378
  }
9379
+ /* Did the hooks approve of the attribute? */
9435
9380
  if (!hookEvent.keepAttr) {
9436
9381
  _removeAttribute(name, currentNode);
9437
9382
  continue;
9438
9383
  }
9384
+ /* Work around a security issue in jQuery 3.0 */
9439
9385
  if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
9440
9386
  _removeAttribute(name, currentNode);
9441
9387
  continue;
9442
9388
  }
9389
+ /* Sanitize attribute content to be template-safe */
9443
9390
  if (SAFE_FOR_TEMPLATES) {
9444
- value = stringReplace(value, MUSTACHE_EXPR, ' ');
9445
- value = stringReplace(value, ERB_EXPR, ' ');
9446
- value = stringReplace(value, TMPLIT_EXPR, ' ');
9391
+ arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
9392
+ value = stringReplace(value, expr, ' ');
9393
+ });
9447
9394
  }
9395
+ /* Is `value` valid for this attribute? */
9448
9396
  const lcTag = transformCaseFunc(currentNode.nodeName);
9449
9397
  if (!_isValidAttribute(lcTag, lcName, value)) {
9450
9398
  _removeAttribute(name, currentNode);
9451
9399
  continue;
9452
9400
  }
9453
- if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
9454
- _removeAttribute(name, currentNode);
9455
- value = SANITIZE_NAMED_PROPS_PREFIX + value;
9456
- }
9401
+ /* Handle attributes that require Trusted Types */
9457
9402
  if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
9458
- if (namespaceURI);
9459
- else {
9403
+ if (namespaceURI) ; else {
9460
9404
  switch (trustedTypes.getAttributeType(lcTag, lcName)) {
9461
- case 'TrustedHTML': {
9462
- value = trustedTypesPolicy.createHTML(value);
9463
- break;
9464
- }
9465
- case 'TrustedScriptURL': {
9466
- value = trustedTypesPolicy.createScriptURL(value);
9467
- break;
9468
- }
9405
+ case 'TrustedHTML':
9406
+ {
9407
+ value = trustedTypesPolicy.createHTML(value);
9408
+ break;
9409
+ }
9410
+ case 'TrustedScriptURL':
9411
+ {
9412
+ value = trustedTypesPolicy.createScriptURL(value);
9413
+ break;
9414
+ }
9469
9415
  }
9470
9416
  }
9471
9417
  }
9418
+ /* Handle invalid data-* attribute set by try-catching it */
9472
9419
  if (value !== initValue) {
9473
9420
  try {
9474
9421
  if (namespaceURI) {
9475
9422
  currentNode.setAttributeNS(namespaceURI, name, value);
9476
9423
  } else {
9424
+ /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
9477
9425
  currentNode.setAttribute(name, value);
9478
9426
  }
9427
+ if (_isClobbered(currentNode)) {
9428
+ _forceRemove(currentNode);
9429
+ } else {
9430
+ arrayPop(DOMPurify.removed);
9431
+ }
9479
9432
  } catch (_) {
9480
9433
  _removeAttribute(name, currentNode);
9481
9434
  }
9482
9435
  }
9483
9436
  }
9484
- _executeHook('afterSanitizeAttributes', currentNode, null);
9485
- };
9486
- const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
9487
- let shadowNode;
9488
- const shadowIterator = _createIterator(fragment);
9489
- _executeHook('beforeSanitizeShadowDOM', fragment, null);
9437
+ /* Execute a hook if present */
9438
+ _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
9439
+ };
9440
+ /**
9441
+ * _sanitizeShadowDOM
9442
+ *
9443
+ * @param fragment to iterate over recursively
9444
+ */
9445
+ const _sanitizeShadowDOM2 = function _sanitizeShadowDOM(fragment) {
9446
+ let shadowNode = null;
9447
+ const shadowIterator = _createNodeIterator(fragment);
9448
+ /* Execute a hook if present */
9449
+ _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
9490
9450
  while (shadowNode = shadowIterator.nextNode()) {
9491
- _executeHook('uponSanitizeShadowNode', shadowNode, null);
9492
- if (_sanitizeElements(shadowNode)) {
9493
- continue;
9494
- }
9451
+ /* Execute a hook if present */
9452
+ _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
9453
+ /* Sanitize tags and elements */
9454
+ _sanitizeElements(shadowNode);
9455
+ /* Check attributes next */
9456
+ _sanitizeAttributes(shadowNode);
9457
+ /* Deep shadow DOM detected */
9495
9458
  if (shadowNode.content instanceof DocumentFragment) {
9496
- _sanitizeShadowDOM(shadowNode.content);
9459
+ _sanitizeShadowDOM2(shadowNode.content);
9497
9460
  }
9498
- _sanitizeAttributes(shadowNode);
9499
9461
  }
9500
- _executeHook('afterSanitizeShadowDOM', fragment, null);
9462
+ /* Execute a hook if present */
9463
+ _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
9464
+ };
9465
+ /**
9466
+ * _sanitizeAttachedShadowRoots
9467
+ *
9468
+ * Walks `root` and feeds every attached shadow root we encounter into
9469
+ * the existing _sanitizeShadowDOM pipeline. The default node iterator
9470
+ * does not descend into shadow trees, so nodes inside an attached
9471
+ * shadow root would otherwise be skipped entirely.
9472
+ *
9473
+ * Two real input paths put attached shadow roots in front of us:
9474
+ * 1. IN_PLACE on a DOM node that already has shadow roots attached.
9475
+ * 2. DOM-node input where importNode(dirty, true) deep-clones the
9476
+ * shadow root because it was created with `clonable: true`.
9477
+ *
9478
+ * This pass runs once, up front, so the main iteration loop (and the
9479
+ * existing _sanitizeShadowDOM template-content recursion) stay
9480
+ * untouched — string-input paths are not affected.
9481
+ *
9482
+ * @param root the subtree root to walk for attached shadow roots
9483
+ */
9484
+ const _sanitizeAttachedShadowRoots2 = function _sanitizeAttachedShadowRoots(root) {
9485
+ if (root.nodeType === NODE_TYPE.element && root.shadowRoot instanceof DocumentFragment) {
9486
+ const sr = root.shadowRoot;
9487
+ // Recurse first so that nested shadow roots are reached even if
9488
+ // _sanitizeShadowDOM removes hosts at this level.
9489
+ _sanitizeAttachedShadowRoots2(sr);
9490
+ _sanitizeShadowDOM2(sr);
9491
+ }
9492
+ // Snapshot children before recursing. Sanitization of one subtree
9493
+ // (e.g. via an uponSanitizeShadowNode hook) may detach siblings,
9494
+ // and naive nextSibling traversal would silently skip the rest of
9495
+ // the list once a node is detached.
9496
+ const childNodes = root.childNodes;
9497
+ if (!childNodes) {
9498
+ return;
9499
+ }
9500
+ const snapshot = [];
9501
+ arrayForEach(childNodes, child => {
9502
+ arrayPush(snapshot, child);
9503
+ });
9504
+ for (const child of snapshot) {
9505
+ _sanitizeAttachedShadowRoots2(child);
9506
+ }
9501
9507
  };
9508
+ // eslint-disable-next-line complexity
9502
9509
  DOMPurify.sanitize = function (dirty) {
9503
9510
  let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
9504
- let body;
9505
- let importedNode;
9506
- let currentNode;
9507
- let returnNode;
9511
+ let body = null;
9512
+ let importedNode = null;
9513
+ let currentNode = null;
9514
+ let returnNode = null;
9515
+ /* Make sure we have a string to sanitize.
9516
+ DO NOT return early, as this will return the wrong type if
9517
+ the user has requested a DOM object rather than a string */
9508
9518
  IS_EMPTY_INPUT = !dirty;
9509
9519
  if (IS_EMPTY_INPUT) {
9510
9520
  dirty = '<!-->';
9511
9521
  }
9522
+ /* Stringify, in case dirty is an object */
9512
9523
  if (typeof dirty !== 'string' && !_isNode(dirty)) {
9513
- if (typeof dirty.toString === 'function') {
9514
- dirty = dirty.toString();
9515
- if (typeof dirty !== 'string') {
9516
- throw typeErrorCreate('dirty is not a string, aborting');
9517
- }
9518
- } else {
9519
- throw typeErrorCreate('toString is not a function');
9524
+ dirty = stringifyValue(dirty);
9525
+ if (typeof dirty !== 'string') {
9526
+ throw typeErrorCreate('dirty is not a string, aborting');
9520
9527
  }
9521
9528
  }
9529
+ /* Return dirty HTML if DOMPurify cannot run */
9522
9530
  if (!DOMPurify.isSupported) {
9523
9531
  return dirty;
9524
9532
  }
9533
+ /* Assign config vars */
9525
9534
  if (!SET_CONFIG) {
9526
9535
  _parseConfig(cfg);
9527
9536
  }
9537
+ /* Clean up removed elements */
9528
9538
  DOMPurify.removed = [];
9539
+ /* Check if dirty is correctly typed for IN_PLACE */
9529
9540
  if (typeof dirty === 'string') {
9530
9541
  IN_PLACE = false;
9531
9542
  }
9532
9543
  if (IN_PLACE) {
9533
- if (dirty.nodeName) {
9534
- const tagName = transformCaseFunc(dirty.nodeName);
9544
+ /* Do some early pre-sanitization to avoid unsafe root nodes */
9545
+ const nn = dirty.nodeName;
9546
+ if (typeof nn === 'string') {
9547
+ const tagName = transformCaseFunc(nn);
9535
9548
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
9536
9549
  throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
9537
9550
  }
9538
9551
  }
9539
- } else if (dirty instanceof Node) {
9552
+ /* Sanitize attached shadow roots before the main iterator runs.
9553
+ The iterator does not descend into shadow trees. */
9554
+ _sanitizeAttachedShadowRoots2(dirty);
9555
+ } else if (_isNode(dirty)) {
9556
+ /* If dirty is a DOM element, append to an empty document to avoid
9557
+ elements being stripped by the parser */
9540
9558
  body = _initDocument('<!---->');
9541
9559
  importedNode = body.ownerDocument.importNode(dirty, true);
9542
- if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
9560
+ if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
9561
+ /* Node is already a body, use as is */
9543
9562
  body = importedNode;
9544
9563
  } else if (importedNode.nodeName === 'HTML') {
9545
9564
  body = importedNode;
9546
9565
  } else {
9566
+ // eslint-disable-next-line unicorn/prefer-dom-node-append
9547
9567
  body.appendChild(importedNode);
9548
9568
  }
9569
+ /* Clonable shadow roots are deep-cloned by importNode(); sanitize
9570
+ them before the main iterator runs, since the iterator does not
9571
+ descend into shadow trees. */
9572
+ _sanitizeAttachedShadowRoots2(importedNode);
9549
9573
  } else {
9550
- if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) {
9574
+ /* Exit directly if we have nothing to do */
9575
+ if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
9576
+ // eslint-disable-next-line unicorn/prefer-includes
9577
+ dirty.indexOf('<') === -1) {
9551
9578
  return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
9552
9579
  }
9580
+ /* Initialize the document to work on */
9553
9581
  body = _initDocument(dirty);
9582
+ /* Check we have a DOM node from the data */
9554
9583
  if (!body) {
9555
9584
  return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
9556
9585
  }
9557
9586
  }
9587
+ /* Remove first element node (ours) if FORCE_BODY is set */
9558
9588
  if (body && FORCE_BODY) {
9559
9589
  _forceRemove(body.firstChild);
9560
9590
  }
9561
- const nodeIterator = _createIterator(IN_PLACE ? dirty : body);
9591
+ /* Get node iterator */
9592
+ const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
9593
+ /* Now start iterating over the created document */
9562
9594
  while (currentNode = nodeIterator.nextNode()) {
9563
- if (_sanitizeElements(currentNode)) {
9564
- continue;
9565
- }
9595
+ /* Sanitize tags and elements */
9596
+ _sanitizeElements(currentNode);
9597
+ /* Check attributes next */
9598
+ _sanitizeAttributes(currentNode);
9599
+ /* Shadow DOM detected, sanitize it */
9566
9600
  if (currentNode.content instanceof DocumentFragment) {
9567
- _sanitizeShadowDOM(currentNode.content);
9601
+ _sanitizeShadowDOM2(currentNode.content);
9568
9602
  }
9569
- _sanitizeAttributes(currentNode);
9570
9603
  }
9604
+ /* If we sanitized `dirty` in-place, return it. */
9571
9605
  if (IN_PLACE) {
9606
+ if (SAFE_FOR_TEMPLATES) {
9607
+ _scrubTemplateExpressions(dirty);
9608
+ }
9572
9609
  return dirty;
9573
9610
  }
9611
+ /* Return sanitized string or DOM */
9574
9612
  if (RETURN_DOM) {
9613
+ if (SAFE_FOR_TEMPLATES) {
9614
+ _scrubTemplateExpressions(body);
9615
+ }
9575
9616
  if (RETURN_DOM_FRAGMENT) {
9576
9617
  returnNode = createDocumentFragment.call(body.ownerDocument);
9577
9618
  while (body.firstChild) {
9619
+ // eslint-disable-next-line unicorn/prefer-dom-node-append
9578
9620
  returnNode.appendChild(body.firstChild);
9579
9621
  }
9580
9622
  } else {
9581
9623
  returnNode = body;
9582
9624
  }
9583
9625
  if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
9626
+ /*
9627
+ AdoptNode() is not used because internal state is not reset
9628
+ (e.g. the past names map of a HTMLFormElement), this is safe
9629
+ in theory but we would rather not risk another attack vector.
9630
+ The state that is cloned by importNode() is explicitly defined
9631
+ by the specs.
9632
+ */
9584
9633
  returnNode = importNode.call(originalDocument, returnNode, true);
9585
9634
  }
9586
9635
  return returnNode;
9587
9636
  }
9588
9637
  let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
9638
+ /* Serialize doctype if allowed */
9589
9639
  if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
9590
9640
  serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
9591
9641
  }
9642
+ /* Sanitize final string template-safe */
9592
9643
  if (SAFE_FOR_TEMPLATES) {
9593
- serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR, ' ');
9594
- serializedHTML = stringReplace(serializedHTML, ERB_EXPR, ' ');
9595
- serializedHTML = stringReplace(serializedHTML, TMPLIT_EXPR, ' ');
9644
+ arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
9645
+ serializedHTML = stringReplace(serializedHTML, expr, ' ');
9646
+ });
9596
9647
  }
9597
9648
  return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
9598
9649
  };
9599
- DOMPurify.setConfig = function (cfg) {
9650
+ DOMPurify.setConfig = function () {
9651
+ let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
9600
9652
  _parseConfig(cfg);
9601
9653
  SET_CONFIG = true;
9602
9654
  };
@@ -9605,6 +9657,7 @@
9605
9657
  SET_CONFIG = false;
9606
9658
  };
9607
9659
  DOMPurify.isValidAttribute = function (tag, attr, value) {
9660
+ /* Initialize shared config vars if necessary. */
9608
9661
  if (!CONFIG) {
9609
9662
  _parseConfig({});
9610
9663
  }
@@ -9616,21 +9669,20 @@
9616
9669
  if (typeof hookFunction !== 'function') {
9617
9670
  return;
9618
9671
  }
9619
- hooks[entryPoint] = hooks[entryPoint] || [];
9620
9672
  arrayPush(hooks[entryPoint], hookFunction);
9621
9673
  };
9622
- DOMPurify.removeHook = function (entryPoint) {
9623
- if (hooks[entryPoint]) {
9624
- return arrayPop(hooks[entryPoint]);
9674
+ DOMPurify.removeHook = function (entryPoint, hookFunction) {
9675
+ if (hookFunction !== undefined) {
9676
+ const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
9677
+ return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
9625
9678
  }
9679
+ return arrayPop(hooks[entryPoint]);
9626
9680
  };
9627
9681
  DOMPurify.removeHooks = function (entryPoint) {
9628
- if (hooks[entryPoint]) {
9629
- hooks[entryPoint] = [];
9630
- }
9682
+ hooks[entryPoint] = [];
9631
9683
  };
9632
9684
  DOMPurify.removeAllHooks = function () {
9633
- hooks = {};
9685
+ hooks = _createHooksMap();
9634
9686
  };
9635
9687
  return DOMPurify;
9636
9688
  }