dompurify 3.1.6 → 3.2.0

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