dompurify 3.1.2 → 3.1.4

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.1.2 | (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.2/LICENSE */
1
+ /*! @license DOMPurify 3.1.4 | (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.4/LICENSE */
2
2
 
3
3
  (function (global, factory) {
4
4
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
@@ -54,6 +54,10 @@
54
54
  const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
55
55
  const regExpTest = unapply(RegExp.prototype.test);
56
56
  const typeErrorCreate = unconstruct(TypeError);
57
+ function numberIsNaN(x) {
58
+ // eslint-disable-next-line unicorn/prefer-number-properties
59
+ return typeof x === 'number' && isNaN(x);
60
+ }
57
61
 
58
62
  /**
59
63
  * Creates a new function that calls the given function with a specified thisArg and arguments.
@@ -202,7 +206,7 @@
202
206
  const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
203
207
  const text = freeze(['#text']);
204
208
 
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']);
209
+ 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']);
206
210
  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
211
  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
212
  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
@@ -237,6 +241,24 @@
237
241
  CUSTOM_ELEMENT: CUSTOM_ELEMENT
238
242
  });
239
243
 
244
+ // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
245
+ const NODE_TYPE = {
246
+ element: 1,
247
+ attribute: 2,
248
+ text: 3,
249
+ cdataSection: 4,
250
+ entityReference: 5,
251
+ // Deprecated
252
+ entityNode: 6,
253
+ // Deprecated
254
+ progressingInstruction: 7,
255
+ comment: 8,
256
+ document: 9,
257
+ documentType: 10,
258
+ documentFragment: 11,
259
+ notation: 12 // Deprecated
260
+ };
261
+
240
262
  const getGlobal = function getGlobal() {
241
263
  return typeof window === 'undefined' ? null : window;
242
264
  };
@@ -288,14 +310,14 @@
288
310
  * Version label, exposed for easier checks
289
311
  * if DOMPurify is up to date or not
290
312
  */
291
- DOMPurify.version = '3.1.2';
313
+ DOMPurify.version = '3.1.4';
292
314
 
293
315
  /**
294
316
  * Array of elements that DOMPurify removed during sanitation.
295
317
  * Empty if nothing was removed.
296
318
  */
297
319
  DOMPurify.removed = [];
298
- if (!window || !window.document || window.document.nodeType !== 9) {
320
+ if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {
299
321
  // Not running in a browser, provide a factory function
300
322
  // so that you can pass your own Window
301
323
  DOMPurify.isSupported = false;
@@ -1006,13 +1028,13 @@
1006
1028
  }
1007
1029
 
1008
1030
  /* Remove any ocurrence of processing instructions */
1009
- if (currentNode.nodeType === 7) {
1031
+ if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
1010
1032
  _forceRemove(currentNode);
1011
1033
  return true;
1012
1034
  }
1013
1035
 
1014
1036
  /* Remove any kind of possibly harmful comments */
1015
- if (SAFE_FOR_XML && currentNode.nodeType === 8 && regExpTest(/<[/\w]/g, currentNode.data)) {
1037
+ if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
1016
1038
  _forceRemove(currentNode);
1017
1039
  return true;
1018
1040
  }
@@ -1059,7 +1081,7 @@
1059
1081
  }
1060
1082
 
1061
1083
  /* Sanitize element content to be template-safe */
1062
- if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
1084
+ if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
1063
1085
  /* Get the element's text content */
1064
1086
  content = currentNode.textContent;
1065
1087
  arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
@@ -1089,7 +1111,7 @@
1089
1111
  // eslint-disable-next-line complexity
1090
1112
  const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
1091
1113
  /* Make sure attribute cannot clobber */
1092
- if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
1114
+ if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement || value === '__depth' || value === '__removalCount')) {
1093
1115
  return false;
1094
1116
  }
1095
1117
 
@@ -1193,6 +1215,12 @@
1193
1215
  continue;
1194
1216
  }
1195
1217
 
1218
+ /* Work around a security issue with comments inside attributes */
1219
+ if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
1220
+ _removeAttribute(name, currentNode);
1221
+ continue;
1222
+ }
1223
+
1196
1224
  /* Sanitize attribute content to be template-safe */
1197
1225
  if (SAFE_FOR_TEMPLATES) {
1198
1226
  arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
@@ -1243,7 +1271,11 @@
1243
1271
  /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1244
1272
  currentNode.setAttribute(name, value);
1245
1273
  }
1246
- arrayPop(DOMPurify.removed);
1274
+ if (_isClobbered(currentNode)) {
1275
+ _forceRemove(currentNode);
1276
+ } else {
1277
+ arrayPop(DOMPurify.removed);
1278
+ }
1247
1279
  } catch (_) {}
1248
1280
  }
1249
1281
 
@@ -1273,7 +1305,7 @@
1273
1305
  const parentNode = getParentNode(shadowNode);
1274
1306
 
1275
1307
  /* Set the nesting depth of an element */
1276
- if (shadowNode.nodeType === 1) {
1308
+ if (shadowNode.nodeType === NODE_TYPE.element) {
1277
1309
  if (parentNode && parentNode.__depth) {
1278
1310
  /*
1279
1311
  We want the depth of the node in the original tree, which can
@@ -1285,8 +1317,11 @@
1285
1317
  }
1286
1318
  }
1287
1319
 
1288
- /* Remove an element if nested too deeply to avoid mXSS */
1289
- if (shadowNode.__depth >= MAX_NESTING_DEPTH) {
1320
+ /*
1321
+ * Remove an element if nested too deeply to avoid mXSS
1322
+ * or if the __depth might have been tampered with
1323
+ */
1324
+ if (shadowNode.__depth >= MAX_NESTING_DEPTH || shadowNode.__depth < 0 || numberIsNaN(shadowNode.__depth)) {
1290
1325
  _forceRemove(shadowNode);
1291
1326
  }
1292
1327
 
@@ -1368,7 +1403,7 @@
1368
1403
  elements being stripped by the parser */
1369
1404
  body = _initDocument('<!---->');
1370
1405
  importedNode = body.ownerDocument.importNode(dirty, true);
1371
- if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
1406
+ if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
1372
1407
  /* Node is already a body, use as is */
1373
1408
  body = importedNode;
1374
1409
  } else if (importedNode.nodeName === 'HTML') {
@@ -1411,7 +1446,7 @@
1411
1446
  const parentNode = getParentNode(currentNode);
1412
1447
 
1413
1448
  /* Set the nesting depth of an element */
1414
- if (currentNode.nodeType === 1) {
1449
+ if (currentNode.nodeType === NODE_TYPE.element) {
1415
1450
  if (parentNode && parentNode.__depth) {
1416
1451
  /*
1417
1452
  We want the depth of the node in the original tree, which can
@@ -1423,8 +1458,11 @@
1423
1458
  }
1424
1459
  }
1425
1460
 
1426
- /* Remove an element if nested too deeply to avoid mXSS */
1427
- if (currentNode.__depth >= MAX_NESTING_DEPTH) {
1461
+ /*
1462
+ * Remove an element if nested too deeply to avoid mXSS
1463
+ * or if the __depth might have been tampered with
1464
+ */
1465
+ if (currentNode.__depth >= MAX_NESTING_DEPTH || currentNode.__depth < 0 || numberIsNaN(currentNode.__depth)) {
1428
1466
  _forceRemove(currentNode);
1429
1467
  }
1430
1468