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