dompurify 3.0.11 → 3.1.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.0.11 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.0.11/LICENSE */
1
+ /*! @license DOMPurify 3.1.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.1.1/LICENSE */
2
2
 
3
3
  const {
4
4
  entries,
@@ -196,7 +196,7 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
196
196
  const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
197
197
  const text = freeze(['#text']);
198
198
 
199
- 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', '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', 'xmlns', 'slot']);
199
+ 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', '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
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']);
201
201
  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
202
  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
@@ -282,7 +282,7 @@ function createDOMPurify() {
282
282
  * Version label, exposed for easier checks
283
283
  * if DOMPurify is up to date or not
284
284
  */
285
- DOMPurify.version = '3.0.11';
285
+ DOMPurify.version = '3.1.1';
286
286
 
287
287
  /**
288
288
  * Array of elements that DOMPurify removed during sanitation.
@@ -424,6 +424,11 @@ function createDOMPurify() {
424
424
  */
425
425
  let SAFE_FOR_TEMPLATES = false;
426
426
 
427
+ /* Output should be safe even for XML used within HTML and alike.
428
+ * This means, DOMPurify removes comments when containing risky content.
429
+ */
430
+ let SAFE_FOR_XML = true;
431
+
427
432
  /* Decide if document with <html>... should be returned */
428
433
  let WHOLE_DOCUMENT = false;
429
434
 
@@ -510,6 +515,9 @@ function createDOMPurify() {
510
515
  /* Keep a reference to config to pass to hooks */
511
516
  let CONFIG = null;
512
517
 
518
+ /* Specify the maximum element nesting depth to prevent mXSS */
519
+ const MAX_NESTING_DEPTH = 255;
520
+
513
521
  /* Ideally, do not touch anything below this line */
514
522
  /* ______________________________________________ */
515
523
 
@@ -571,6 +579,7 @@ function createDOMPurify() {
571
579
  ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
572
580
  ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
573
581
  SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
582
+ SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
574
583
  WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
575
584
  RETURN_DOM = cfg.RETURN_DOM || false; // Default false
576
585
  RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
@@ -919,7 +928,11 @@ function createDOMPurify() {
919
928
  * @return {Boolean} true if clobbered, false if safe
920
929
  */
921
930
  const _isClobbered = function _isClobbered(elm) {
922
- 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');
931
+ return elm instanceof HTMLFormElement && (
932
+ // eslint-disable-next-line unicorn/no-typeof-undefined
933
+ typeof elm.__depth !== 'undefined' && typeof elm.__depth !== 'number' ||
934
+ // eslint-disable-next-line unicorn/no-typeof-undefined
935
+ typeof elm.__removalCount !== 'undefined' && typeof elm.__removalCount !== 'number' || 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');
923
936
  };
924
937
 
925
938
  /**
@@ -992,6 +1005,12 @@ function createDOMPurify() {
992
1005
  return true;
993
1006
  }
994
1007
 
1008
+ /* Remove any kind of possibly harmful comments */
1009
+ if (SAFE_FOR_XML && currentNode.nodeType === 8 && regExpTest(/<[/\w]/g, currentNode.data)) {
1010
+ _forceRemove(currentNode);
1011
+ return true;
1012
+ }
1013
+
995
1014
  /* Remove element if anything forbids its presence */
996
1015
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
997
1016
  /* Check if we have a custom element to handle */
@@ -1011,7 +1030,9 @@ function createDOMPurify() {
1011
1030
  if (childNodes && parentNode) {
1012
1031
  const childCount = childNodes.length;
1013
1032
  for (let i = childCount - 1; i >= 0; --i) {
1014
- parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
1033
+ const childClone = cloneNode(childNodes[i], true);
1034
+ childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
1035
+ parentNode.insertBefore(childClone, getNextSibling(currentNode));
1015
1036
  }
1016
1037
  }
1017
1038
  }
@@ -1244,8 +1265,27 @@ function createDOMPurify() {
1244
1265
  continue;
1245
1266
  }
1246
1267
 
1268
+ /* Set the nesting depth of an element */
1269
+ if (shadowNode.nodeType === 1) {
1270
+ if (shadowNode.parentNode && shadowNode.parentNode.__depth) {
1271
+ /*
1272
+ We want the depth of the node in the original tree, which can
1273
+ change when it's removed from its parent.
1274
+ */
1275
+ shadowNode.__depth = (shadowNode.__removalCount || 0) + shadowNode.parentNode.__depth + 1;
1276
+ } else {
1277
+ shadowNode.__depth = 1;
1278
+ }
1279
+ }
1280
+
1281
+ /* Remove an element if nested too deeply to avoid mXSS */
1282
+ if (shadowNode.__depth >= MAX_NESTING_DEPTH) {
1283
+ _forceRemove(shadowNode);
1284
+ }
1285
+
1247
1286
  /* Deep shadow DOM detected */
1248
1287
  if (shadowNode.content instanceof DocumentFragment) {
1288
+ shadowNode.content.__depth = shadowNode.__depth;
1249
1289
  _sanitizeShadowDOM(shadowNode.content);
1250
1290
  }
1251
1291
 
@@ -1362,8 +1402,27 @@ function createDOMPurify() {
1362
1402
  continue;
1363
1403
  }
1364
1404
 
1405
+ /* Set the nesting depth of an element */
1406
+ if (currentNode.nodeType === 1) {
1407
+ if (currentNode.parentNode && currentNode.parentNode.__depth) {
1408
+ /*
1409
+ We want the depth of the node in the original tree, which can
1410
+ change when it's removed from its parent.
1411
+ */
1412
+ currentNode.__depth = (currentNode.__removalCount || 0) + currentNode.parentNode.__depth + 1;
1413
+ } else {
1414
+ currentNode.__depth = 1;
1415
+ }
1416
+ }
1417
+
1418
+ /* Remove an element if nested too deeply to avoid mXSS */
1419
+ if (currentNode.__depth >= MAX_NESTING_DEPTH) {
1420
+ _forceRemove(currentNode);
1421
+ }
1422
+
1365
1423
  /* Shadow DOM detected, sanitize it */
1366
1424
  if (currentNode.content instanceof DocumentFragment) {
1425
+ currentNode.content.__depth = currentNode.__depth;
1367
1426
  _sanitizeShadowDOM(currentNode.content);
1368
1427
  }
1369
1428