dompurify 3.3.3 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- /*! @license DOMPurify 3.3.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.3/LICENSE */
1
+ /*! @license DOMPurify 3.4.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.1/LICENSE */
2
2
 
3
3
  'use strict';
4
4
 
@@ -49,13 +49,19 @@ const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
49
49
  const arrayPop = unapply(Array.prototype.pop);
50
50
  const arrayPush = unapply(Array.prototype.push);
51
51
  const arraySplice = unapply(Array.prototype.splice);
52
+ const arrayIsArray = Array.isArray;
52
53
  const stringToLowerCase = unapply(String.prototype.toLowerCase);
53
54
  const stringToString = unapply(String.prototype.toString);
54
55
  const stringMatch = unapply(String.prototype.match);
55
56
  const stringReplace = unapply(String.prototype.replace);
56
57
  const stringIndexOf = unapply(String.prototype.indexOf);
57
58
  const stringTrim = unapply(String.prototype.trim);
59
+ const numberToString = unapply(Number.prototype.toString);
60
+ const booleanToString = unapply(Boolean.prototype.toString);
61
+ const bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);
62
+ const symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);
58
63
  const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
64
+ const objectToString = unapply(Object.prototype.toString);
59
65
  const regExpTest = unapply(RegExp.prototype.test);
60
66
  const typeErrorCreate = unconstruct(TypeError);
61
67
  /**
@@ -105,6 +111,9 @@ function addToSet(set, array) {
105
111
  // Prevent prototype setters from intercepting set as a this value.
106
112
  setPrototypeOf(set, null);
107
113
  }
114
+ if (!arrayIsArray(array)) {
115
+ return set;
116
+ }
108
117
  let l = array.length;
109
118
  while (l--) {
110
119
  let element = array[l];
@@ -148,7 +157,7 @@ function clone(object) {
148
157
  for (const [property, value] of entries(object)) {
149
158
  const isPropertyExist = objectHasOwnProperty(object, property);
150
159
  if (isPropertyExist) {
151
- if (Array.isArray(value)) {
160
+ if (arrayIsArray(value)) {
152
161
  newObject[property] = cleanArray(value);
153
162
  } else if (value && typeof value === 'object' && value.constructor === Object) {
154
163
  newObject[property] = clone(value);
@@ -159,6 +168,58 @@ function clone(object) {
159
168
  }
160
169
  return newObject;
161
170
  }
171
+ /**
172
+ * Convert non-node values into strings without depending on direct property access.
173
+ *
174
+ * @param value - The value to stringify.
175
+ * @returns A string representation of the provided value.
176
+ */
177
+ function stringifyValue(value) {
178
+ switch (typeof value) {
179
+ case 'string':
180
+ {
181
+ return value;
182
+ }
183
+ case 'number':
184
+ {
185
+ return numberToString(value);
186
+ }
187
+ case 'boolean':
188
+ {
189
+ return booleanToString(value);
190
+ }
191
+ case 'bigint':
192
+ {
193
+ return bigintToString ? bigintToString(value) : '0';
194
+ }
195
+ case 'symbol':
196
+ {
197
+ return symbolToString ? symbolToString(value) : 'Symbol()';
198
+ }
199
+ case 'undefined':
200
+ {
201
+ return objectToString(value);
202
+ }
203
+ case 'function':
204
+ case 'object':
205
+ {
206
+ if (value === null) {
207
+ return objectToString(value);
208
+ }
209
+ const valueAsRecord = value;
210
+ const valueToString = lookupGetter(valueAsRecord, 'toString');
211
+ if (typeof valueToString === 'function') {
212
+ const stringified = valueToString(valueAsRecord);
213
+ return typeof stringified === 'string' ? stringified : objectToString(stringified);
214
+ }
215
+ return objectToString(value);
216
+ }
217
+ default:
218
+ {
219
+ return objectToString(value);
220
+ }
221
+ }
222
+ }
162
223
  /**
163
224
  * This method automatically checks if the prop is function or getter and behaves accordingly.
164
225
  *
@@ -184,6 +245,14 @@ function lookupGetter(object, prop) {
184
245
  }
185
246
  return fallbackValue;
186
247
  }
248
+ function isRegex(value) {
249
+ try {
250
+ regExpTest(value, '');
251
+ return true;
252
+ } catch (_unused) {
253
+ return false;
254
+ }
255
+ }
187
256
 
188
257
  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']);
189
258
  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']);
@@ -199,9 +268,9 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
199
268
  const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
200
269
  const text = freeze(['#text']);
201
270
 
202
- 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', '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', 'slot']);
271
+ 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', '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']);
203
272
  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']);
204
- 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']);
273
+ 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']);
205
274
  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
206
275
 
207
276
  // eslint-disable-next-line unicorn/better-regex
@@ -219,37 +288,28 @@ const DOCTYPE_NAME = seal(/^html$/i);
219
288
  const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
220
289
 
221
290
  var EXPRESSIONS = /*#__PURE__*/Object.freeze({
222
- __proto__: null,
223
- ARIA_ATTR: ARIA_ATTR,
224
- ATTR_WHITESPACE: ATTR_WHITESPACE,
225
- CUSTOM_ELEMENT: CUSTOM_ELEMENT,
226
- DATA_ATTR: DATA_ATTR,
227
- DOCTYPE_NAME: DOCTYPE_NAME,
228
- ERB_EXPR: ERB_EXPR,
229
- IS_ALLOWED_URI: IS_ALLOWED_URI,
230
- IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
231
- MUSTACHE_EXPR: MUSTACHE_EXPR,
232
- TMPLIT_EXPR: TMPLIT_EXPR
291
+ __proto__: null,
292
+ ARIA_ATTR: ARIA_ATTR,
293
+ ATTR_WHITESPACE: ATTR_WHITESPACE,
294
+ CUSTOM_ELEMENT: CUSTOM_ELEMENT,
295
+ DATA_ATTR: DATA_ATTR,
296
+ DOCTYPE_NAME: DOCTYPE_NAME,
297
+ ERB_EXPR: ERB_EXPR,
298
+ IS_ALLOWED_URI: IS_ALLOWED_URI,
299
+ IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
300
+ MUSTACHE_EXPR: MUSTACHE_EXPR,
301
+ TMPLIT_EXPR: TMPLIT_EXPR
233
302
  });
234
303
 
235
304
  /* eslint-disable @typescript-eslint/indent */
236
305
  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
237
306
  const NODE_TYPE = {
238
307
  element: 1,
239
- attribute: 2,
240
308
  text: 3,
241
- cdataSection: 4,
242
- entityReference: 5,
243
- // Deprecated
244
- entityNode: 6,
245
309
  // Deprecated
246
310
  progressingInstruction: 7,
247
311
  comment: 8,
248
- document: 9,
249
- documentType: 10,
250
- documentFragment: 11,
251
- notation: 12 // Deprecated
252
- };
312
+ document: 9};
253
313
  const getGlobal = function getGlobal() {
254
314
  return typeof window === 'undefined' ? null : window;
255
315
  };
@@ -307,7 +367,7 @@ const _createHooksMap = function _createHooksMap() {
307
367
  function createDOMPurify() {
308
368
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
309
369
  const DOMPurify = root => createDOMPurify(root);
310
- DOMPurify.version = '3.3.3';
370
+ DOMPurify.version = '3.4.1';
311
371
  DOMPurify.removed = [];
312
372
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
313
373
  // Not running in a browser, provide a factory function
@@ -555,15 +615,15 @@ function createDOMPurify() {
555
615
  // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
556
616
  transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
557
617
  /* Set configuration parameters */
558
- ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
559
- ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
560
- ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
561
- 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;
562
- 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;
563
- FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
564
- FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
565
- FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
566
- USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
618
+ ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') && arrayIsArray(cfg.ALLOWED_TAGS) ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
619
+ ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') && arrayIsArray(cfg.ALLOWED_ATTR) ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
620
+ ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') && arrayIsArray(cfg.ALLOWED_NAMESPACES) ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
621
+ 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;
622
+ 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;
623
+ FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS) ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
624
+ FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') && arrayIsArray(cfg.FORBID_TAGS) ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
625
+ FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') && arrayIsArray(cfg.FORBID_ATTR) ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
626
+ USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
567
627
  ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
568
628
  ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
569
629
  ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
@@ -579,19 +639,20 @@ function createDOMPurify() {
579
639
  SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
580
640
  KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
581
641
  IN_PLACE = cfg.IN_PLACE || false; // Default false
582
- IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
583
- NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
584
- MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
585
- HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
586
- CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
587
- if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
588
- CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
642
+ IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
643
+ NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
644
+ 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
645
+ 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
646
+ const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
647
+ CUSTOM_ELEMENT_HANDLING = create(null);
648
+ if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
649
+ CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; // Default undefined
589
650
  }
590
- if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
591
- CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
651
+ if (objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && isRegexOrFunction(customElementHandling.attributeNameCheck)) {
652
+ CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; // Default undefined
592
653
  }
593
- if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
594
- CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
654
+ if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
655
+ CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
595
656
  }
596
657
  if (SAFE_FOR_TEMPLATES) {
597
658
  ALLOW_DATA_ATTR = false;
@@ -623,44 +684,41 @@ function createDOMPurify() {
623
684
  addToSet(ALLOWED_ATTR, xml);
624
685
  }
625
686
  }
626
- /* Prevent function-based ADD_ATTR / ADD_TAGS from leaking across calls */
627
- if (!objectHasOwnProperty(cfg, 'ADD_TAGS')) {
628
- EXTRA_ELEMENT_HANDLING.tagCheck = null;
629
- }
630
- if (!objectHasOwnProperty(cfg, 'ADD_ATTR')) {
631
- EXTRA_ELEMENT_HANDLING.attributeCheck = null;
632
- }
687
+ /* Always reset function-based ADD_TAGS / ADD_ATTR checks to prevent
688
+ * leaking across calls when switching from function to array config */
689
+ EXTRA_ELEMENT_HANDLING.tagCheck = null;
690
+ EXTRA_ELEMENT_HANDLING.attributeCheck = null;
633
691
  /* Merge configuration parameters */
634
- if (cfg.ADD_TAGS) {
692
+ if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {
635
693
  if (typeof cfg.ADD_TAGS === 'function') {
636
694
  EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
637
- } else {
695
+ } else if (arrayIsArray(cfg.ADD_TAGS)) {
638
696
  if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
639
697
  ALLOWED_TAGS = clone(ALLOWED_TAGS);
640
698
  }
641
699
  addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
642
700
  }
643
701
  }
644
- if (cfg.ADD_ATTR) {
702
+ if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {
645
703
  if (typeof cfg.ADD_ATTR === 'function') {
646
704
  EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
647
- } else {
705
+ } else if (arrayIsArray(cfg.ADD_ATTR)) {
648
706
  if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
649
707
  ALLOWED_ATTR = clone(ALLOWED_ATTR);
650
708
  }
651
709
  addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
652
710
  }
653
711
  }
654
- if (cfg.ADD_URI_SAFE_ATTR) {
712
+ if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {
655
713
  addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
656
714
  }
657
- if (cfg.FORBID_CONTENTS) {
715
+ if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {
658
716
  if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
659
717
  FORBID_CONTENTS = clone(FORBID_CONTENTS);
660
718
  }
661
719
  addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
662
720
  }
663
- if (cfg.ADD_FORBID_CONTENTS) {
721
+ if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
664
722
  if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
665
723
  FORBID_CONTENTS = clone(FORBID_CONTENTS);
666
724
  }
@@ -952,6 +1010,11 @@ function createDOMPurify() {
952
1010
  _forceRemove(currentNode);
953
1011
  return true;
954
1012
  }
1013
+ /* Remove risky CSS construction leading to mXSS */
1014
+ if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
1015
+ _forceRemove(currentNode);
1016
+ return true;
1017
+ }
955
1018
  /* Remove any occurrence of processing instructions */
956
1019
  if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
957
1020
  _forceRemove(currentNode);
@@ -963,7 +1026,7 @@ function createDOMPurify() {
963
1026
  return true;
964
1027
  }
965
1028
  /* Remove element if anything forbids its presence */
966
- if (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName])) {
1029
+ if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
967
1030
  /* Check if we have a custom element to handle */
968
1031
  if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
969
1032
  if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
@@ -981,7 +1044,6 @@ function createDOMPurify() {
981
1044
  const childCount = childNodes.length;
982
1045
  for (let i = childCount - 1; i >= 0; --i) {
983
1046
  const childClone = cloneNode(childNodes[i], true);
984
- childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
985
1047
  parentNode.insertBefore(childClone, getNextSibling(currentNode));
986
1048
  }
987
1049
  }
@@ -1056,6 +1118,10 @@ function createDOMPurify() {
1056
1118
  } else ;
1057
1119
  return true;
1058
1120
  };
1121
+ /* Names the HTML spec reserves from valid-custom-element-name; these must
1122
+ * never be treated as basic custom elements even when a permissive
1123
+ * CUSTOM_ELEMENT_HANDLING.tagNameCheck is configured. */
1124
+ 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']);
1059
1125
  /**
1060
1126
  * _isBasicCustomElement
1061
1127
  * checks if at least one dash is included in tagName, and it's not the first char
@@ -1065,7 +1131,7 @@ function createDOMPurify() {
1065
1131
  * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
1066
1132
  */
1067
1133
  const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
1068
- return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
1134
+ return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT, tagName);
1069
1135
  };
1070
1136
  /**
1071
1137
  * _sanitizeAttributes
@@ -1116,12 +1182,14 @@ function createDOMPurify() {
1116
1182
  /* Full DOM Clobbering protection via namespace isolation,
1117
1183
  * Prefix id and name attributes with `user-content-`
1118
1184
  */
1119
- if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
1185
+ if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name') && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
1120
1186
  // Remove the attribute with this value
1121
1187
  _removeAttribute(name, currentNode);
1122
1188
  // Prefix the value and later re-create the attribute with the sanitized value
1123
1189
  value = SANITIZE_NAMED_PROPS_PREFIX + value;
1124
1190
  }
1191
+ // Else: already prefixed, leave the attribute alone — the prefix is
1192
+ // itself the clobbering protection, and re-applying it is incorrect.
1125
1193
  /* Work around a security issue with comments inside attributes */
1126
1194
  if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
1127
1195
  _removeAttribute(name, currentNode);
@@ -1202,7 +1270,7 @@ function createDOMPurify() {
1202
1270
  *
1203
1271
  * @param fragment to iterate over recursively
1204
1272
  */
1205
- const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
1273
+ const _sanitizeShadowDOM2 = function _sanitizeShadowDOM(fragment) {
1206
1274
  let shadowNode = null;
1207
1275
  const shadowIterator = _createNodeIterator(fragment);
1208
1276
  /* Execute a hook if present */
@@ -1216,7 +1284,7 @@ function createDOMPurify() {
1216
1284
  _sanitizeAttributes(shadowNode);
1217
1285
  /* Deep shadow DOM detected */
1218
1286
  if (shadowNode.content instanceof DocumentFragment) {
1219
- _sanitizeShadowDOM(shadowNode.content);
1287
+ _sanitizeShadowDOM2(shadowNode.content);
1220
1288
  }
1221
1289
  }
1222
1290
  /* Execute a hook if present */
@@ -1238,13 +1306,9 @@ function createDOMPurify() {
1238
1306
  }
1239
1307
  /* Stringify, in case dirty is an object */
1240
1308
  if (typeof dirty !== 'string' && !_isNode(dirty)) {
1241
- if (typeof dirty.toString === 'function') {
1242
- dirty = dirty.toString();
1243
- if (typeof dirty !== 'string') {
1244
- throw typeErrorCreate('dirty is not a string, aborting');
1245
- }
1246
- } else {
1247
- throw typeErrorCreate('toString is not a function');
1309
+ dirty = stringifyValue(dirty);
1310
+ if (typeof dirty !== 'string') {
1311
+ throw typeErrorCreate('dirty is not a string, aborting');
1248
1312
  }
1249
1313
  }
1250
1314
  /* Return dirty HTML if DOMPurify cannot run */
@@ -1263,8 +1327,9 @@ function createDOMPurify() {
1263
1327
  }
1264
1328
  if (IN_PLACE) {
1265
1329
  /* Do some early pre-sanitization to avoid unsafe root nodes */
1266
- if (dirty.nodeName) {
1267
- const tagName = transformCaseFunc(dirty.nodeName);
1330
+ const nn = dirty.nodeName;
1331
+ if (typeof nn === 'string') {
1332
+ const tagName = transformCaseFunc(nn);
1268
1333
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1269
1334
  throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1270
1335
  }
@@ -1311,7 +1376,7 @@ function createDOMPurify() {
1311
1376
  _sanitizeAttributes(currentNode);
1312
1377
  /* Shadow DOM detected, sanitize it */
1313
1378
  if (currentNode.content instanceof DocumentFragment) {
1314
- _sanitizeShadowDOM(currentNode.content);
1379
+ _sanitizeShadowDOM2(currentNode.content);
1315
1380
  }
1316
1381
  }
1317
1382
  /* If we sanitized `dirty` in-place, return it. */
@@ -1320,6 +1385,14 @@ function createDOMPurify() {
1320
1385
  }
1321
1386
  /* Return sanitized string or DOM */
1322
1387
  if (RETURN_DOM) {
1388
+ if (SAFE_FOR_TEMPLATES) {
1389
+ body.normalize();
1390
+ let html = body.innerHTML;
1391
+ arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1392
+ html = stringReplace(html, expr, ' ');
1393
+ });
1394
+ body.innerHTML = html;
1395
+ }
1323
1396
  if (RETURN_DOM_FRAGMENT) {
1324
1397
  returnNode = createDocumentFragment.call(body.ownerDocument);
1325
1398
  while (body.firstChild) {