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.
package/dist/purify.js CHANGED
@@ -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
  (function (global, factory) {
4
4
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
@@ -202,7 +202,7 @@
202
202
  const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
203
203
  const text = freeze(['#text']);
204
204
 
205
- 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']);
205
+ 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']);
206
206
  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']);
207
207
  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']);
208
208
  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
@@ -288,7 +288,7 @@
288
288
  * Version label, exposed for easier checks
289
289
  * if DOMPurify is up to date or not
290
290
  */
291
- DOMPurify.version = '3.0.11';
291
+ DOMPurify.version = '3.1.1';
292
292
 
293
293
  /**
294
294
  * Array of elements that DOMPurify removed during sanitation.
@@ -430,6 +430,11 @@
430
430
  */
431
431
  let SAFE_FOR_TEMPLATES = false;
432
432
 
433
+ /* Output should be safe even for XML used within HTML and alike.
434
+ * This means, DOMPurify removes comments when containing risky content.
435
+ */
436
+ let SAFE_FOR_XML = true;
437
+
433
438
  /* Decide if document with <html>... should be returned */
434
439
  let WHOLE_DOCUMENT = false;
435
440
 
@@ -516,6 +521,9 @@
516
521
  /* Keep a reference to config to pass to hooks */
517
522
  let CONFIG = null;
518
523
 
524
+ /* Specify the maximum element nesting depth to prevent mXSS */
525
+ const MAX_NESTING_DEPTH = 255;
526
+
519
527
  /* Ideally, do not touch anything below this line */
520
528
  /* ______________________________________________ */
521
529
 
@@ -577,6 +585,7 @@
577
585
  ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
578
586
  ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
579
587
  SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
588
+ SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
580
589
  WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
581
590
  RETURN_DOM = cfg.RETURN_DOM || false; // Default false
582
591
  RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
@@ -925,7 +934,11 @@
925
934
  * @return {Boolean} true if clobbered, false if safe
926
935
  */
927
936
  const _isClobbered = function _isClobbered(elm) {
928
- 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');
937
+ return elm instanceof HTMLFormElement && (
938
+ // eslint-disable-next-line unicorn/no-typeof-undefined
939
+ typeof elm.__depth !== 'undefined' && typeof elm.__depth !== 'number' ||
940
+ // eslint-disable-next-line unicorn/no-typeof-undefined
941
+ 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');
929
942
  };
930
943
 
931
944
  /**
@@ -998,6 +1011,12 @@
998
1011
  return true;
999
1012
  }
1000
1013
 
1014
+ /* Remove any kind of possibly harmful comments */
1015
+ if (SAFE_FOR_XML && currentNode.nodeType === 8 && regExpTest(/<[/\w]/g, currentNode.data)) {
1016
+ _forceRemove(currentNode);
1017
+ return true;
1018
+ }
1019
+
1001
1020
  /* Remove element if anything forbids its presence */
1002
1021
  if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1003
1022
  /* Check if we have a custom element to handle */
@@ -1017,7 +1036,9 @@
1017
1036
  if (childNodes && parentNode) {
1018
1037
  const childCount = childNodes.length;
1019
1038
  for (let i = childCount - 1; i >= 0; --i) {
1020
- parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
1039
+ const childClone = cloneNode(childNodes[i], true);
1040
+ childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
1041
+ parentNode.insertBefore(childClone, getNextSibling(currentNode));
1021
1042
  }
1022
1043
  }
1023
1044
  }
@@ -1250,8 +1271,27 @@
1250
1271
  continue;
1251
1272
  }
1252
1273
 
1274
+ /* Set the nesting depth of an element */
1275
+ if (shadowNode.nodeType === 1) {
1276
+ if (shadowNode.parentNode && shadowNode.parentNode.__depth) {
1277
+ /*
1278
+ We want the depth of the node in the original tree, which can
1279
+ change when it's removed from its parent.
1280
+ */
1281
+ shadowNode.__depth = (shadowNode.__removalCount || 0) + shadowNode.parentNode.__depth + 1;
1282
+ } else {
1283
+ shadowNode.__depth = 1;
1284
+ }
1285
+ }
1286
+
1287
+ /* Remove an element if nested too deeply to avoid mXSS */
1288
+ if (shadowNode.__depth >= MAX_NESTING_DEPTH) {
1289
+ _forceRemove(shadowNode);
1290
+ }
1291
+
1253
1292
  /* Deep shadow DOM detected */
1254
1293
  if (shadowNode.content instanceof DocumentFragment) {
1294
+ shadowNode.content.__depth = shadowNode.__depth;
1255
1295
  _sanitizeShadowDOM(shadowNode.content);
1256
1296
  }
1257
1297
 
@@ -1368,8 +1408,27 @@
1368
1408
  continue;
1369
1409
  }
1370
1410
 
1411
+ /* Set the nesting depth of an element */
1412
+ if (currentNode.nodeType === 1) {
1413
+ if (currentNode.parentNode && currentNode.parentNode.__depth) {
1414
+ /*
1415
+ We want the depth of the node in the original tree, which can
1416
+ change when it's removed from its parent.
1417
+ */
1418
+ currentNode.__depth = (currentNode.__removalCount || 0) + currentNode.parentNode.__depth + 1;
1419
+ } else {
1420
+ currentNode.__depth = 1;
1421
+ }
1422
+ }
1423
+
1424
+ /* Remove an element if nested too deeply to avoid mXSS */
1425
+ if (currentNode.__depth >= MAX_NESTING_DEPTH) {
1426
+ _forceRemove(currentNode);
1427
+ }
1428
+
1371
1429
  /* Shadow DOM detected, sanitize it */
1372
1430
  if (currentNode.content instanceof DocumentFragment) {
1431
+ currentNode.content.__depth = currentNode.__depth;
1373
1432
  _sanitizeShadowDOM(currentNode.content);
1374
1433
  }
1375
1434