dompurify 3.4.1 → 3.4.3

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,21 +1,62 @@
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 */
1
+ /*! @license DOMPurify 3.4.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.4.3/LICENSE */
2
2
 
3
- const {
4
- entries,
5
- setPrototypeOf,
6
- isFrozen,
7
- getPrototypeOf,
8
- getOwnPropertyDescriptor
9
- } = Object;
10
- let {
11
- freeze,
12
- seal,
13
- create
14
- } = Object; // eslint-disable-line import/no-mutable-exports
15
- let {
16
- apply,
17
- construct
18
- } = typeof Reflect !== 'undefined' && Reflect;
3
+ function _arrayLikeToArray(r, a) {
4
+ (null == a || a > r.length) && (a = r.length);
5
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
6
+ return n;
7
+ }
8
+ function _arrayWithHoles(r) {
9
+ if (Array.isArray(r)) return r;
10
+ }
11
+ function _iterableToArrayLimit(r, l) {
12
+ var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
13
+ if (null != t) {
14
+ var e,
15
+ n,
16
+ i,
17
+ u,
18
+ a = [],
19
+ f = true,
20
+ o = false;
21
+ try {
22
+ if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
23
+ } catch (r) {
24
+ o = true, n = r;
25
+ } finally {
26
+ try {
27
+ if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
28
+ } finally {
29
+ if (o) throw n;
30
+ }
31
+ }
32
+ return a;
33
+ }
34
+ }
35
+ function _nonIterableRest() {
36
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
37
+ }
38
+ function _slicedToArray(r, e) {
39
+ return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
40
+ }
41
+ function _unsupportedIterableToArray(r, a) {
42
+ if (r) {
43
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
44
+ var t = {}.toString.call(r).slice(8, -1);
45
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
46
+ }
47
+ }
48
+
49
+ const entries = Object.entries,
50
+ setPrototypeOf = Object.setPrototypeOf,
51
+ isFrozen = Object.isFrozen,
52
+ getPrototypeOf = Object.getPrototypeOf,
53
+ getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
54
+ let freeze = Object.freeze,
55
+ seal = Object.seal,
56
+ create = Object.create; // eslint-disable-line import/no-mutable-exports
57
+ let _ref = typeof Reflect !== 'undefined' && Reflect,
58
+ apply = _ref.apply,
59
+ construct = _ref.construct;
19
60
  if (!freeze) {
20
61
  freeze = function freeze(x) {
21
62
  return x;
@@ -152,7 +193,10 @@ function cleanArray(array) {
152
193
  */
153
194
  function clone(object) {
154
195
  const newObject = create(null);
155
- for (const [property, value] of entries(object)) {
196
+ for (const _ref2 of entries(object)) {
197
+ var _ref3 = _slicedToArray(_ref2, 2);
198
+ const property = _ref3[0];
199
+ const value = _ref3[1];
156
200
  const isPropertyExist = objectHasOwnProperty(object, property);
157
201
  if (isPropertyExist) {
158
202
  if (arrayIsArray(value)) {
@@ -271,10 +315,9 @@ const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseli
271
315
  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']);
272
316
  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
273
317
 
274
- // eslint-disable-next-line unicorn/better-regex
275
- const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
276
- const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
277
- const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
318
+ const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
319
+ const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
320
+ const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
278
321
  const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
279
322
  const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
280
323
  const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
@@ -285,20 +328,6 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
285
328
  const DOCTYPE_NAME = seal(/^html$/i);
286
329
  const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
287
330
 
288
- var EXPRESSIONS = /*#__PURE__*/Object.freeze({
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
300
- });
301
-
302
331
  /* eslint-disable @typescript-eslint/indent */
303
332
  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
304
333
  const NODE_TYPE = {
@@ -365,7 +394,7 @@ const _createHooksMap = function _createHooksMap() {
365
394
  function createDOMPurify() {
366
395
  let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
367
396
  const DOMPurify = root => createDOMPurify(root);
368
- DOMPurify.version = '3.4.1';
397
+ DOMPurify.version = '3.4.3';
369
398
  DOMPurify.removed = [];
370
399
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
371
400
  // Not running in a browser, provide a factory function
@@ -373,22 +402,19 @@ function createDOMPurify() {
373
402
  DOMPurify.isSupported = false;
374
403
  return DOMPurify;
375
404
  }
376
- let {
377
- document
378
- } = window;
405
+ let document = window.document;
379
406
  const originalDocument = document;
380
407
  const currentScript = originalDocument.currentScript;
381
- const {
382
- DocumentFragment,
383
- HTMLTemplateElement,
384
- Node,
385
- Element,
386
- NodeFilter,
387
- NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
388
- HTMLFormElement,
389
- DOMParser,
390
- trustedTypes
391
- } = window;
408
+ const DocumentFragment = window.DocumentFragment,
409
+ HTMLTemplateElement = window.HTMLTemplateElement,
410
+ Node = window.Node,
411
+ Element = window.Element,
412
+ NodeFilter = window.NodeFilter,
413
+ _window$NamedNodeMap = window.NamedNodeMap,
414
+ NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
415
+ HTMLFormElement = window.HTMLFormElement,
416
+ DOMParser = window.DOMParser,
417
+ trustedTypes = window.trustedTypes;
392
418
  const ElementPrototype = Element.prototype;
393
419
  const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
394
420
  const remove = lookupGetter(ElementPrototype, 'remove');
@@ -409,33 +435,26 @@ function createDOMPurify() {
409
435
  }
410
436
  let trustedTypesPolicy;
411
437
  let emptyHTML = '';
412
- const {
413
- implementation,
414
- createNodeIterator,
415
- createDocumentFragment,
416
- getElementsByTagName
417
- } = document;
418
- const {
419
- importNode
420
- } = originalDocument;
438
+ const _document = document,
439
+ implementation = _document.implementation,
440
+ createNodeIterator = _document.createNodeIterator,
441
+ createDocumentFragment = _document.createDocumentFragment,
442
+ getElementsByTagName = _document.getElementsByTagName;
443
+ const importNode = originalDocument.importNode;
421
444
  let hooks = _createHooksMap();
422
445
  /**
423
446
  * Expose whether this browser supports running the full DOMPurify.
424
447
  */
425
448
  DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
426
- const {
427
- MUSTACHE_EXPR,
428
- ERB_EXPR,
429
- TMPLIT_EXPR,
430
- DATA_ATTR,
431
- ARIA_ATTR,
432
- IS_SCRIPT_OR_DATA,
433
- ATTR_WHITESPACE,
434
- CUSTOM_ELEMENT
435
- } = EXPRESSIONS;
436
- let {
437
- IS_ALLOWED_URI: IS_ALLOWED_URI$1
438
- } = EXPRESSIONS;
449
+ const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
450
+ ERB_EXPR$1 = ERB_EXPR,
451
+ TMPLIT_EXPR$1 = TMPLIT_EXPR,
452
+ DATA_ATTR$1 = DATA_ATTR,
453
+ ARIA_ATTR$1 = ARIA_ATTR,
454
+ IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
455
+ ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
456
+ CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
457
+ let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
439
458
  /**
440
459
  * We consider the elements and attributes below to be safe. Ideally
441
460
  * don't add any new ones but feel free to remove unwanted ones.
@@ -1063,7 +1082,7 @@ function createDOMPurify() {
1063
1082
  if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
1064
1083
  /* Get the element's text content */
1065
1084
  content = currentNode.textContent;
1066
- arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1085
+ arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
1067
1086
  content = stringReplace(content, expr, ' ');
1068
1087
  });
1069
1088
  if (currentNode.textContent !== content) {
@@ -1095,11 +1114,12 @@ function createDOMPurify() {
1095
1114
  if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
1096
1115
  return false;
1097
1116
  }
1117
+ const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
1098
1118
  /* Allow valid data-* attributes: At least one character after "-"
1099
1119
  (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
1100
1120
  XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
1101
1121
  We don't need to check the value; it's always URI safe. */
1102
- if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
1122
+ if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted || FORBID_ATTR[lcName]) {
1103
1123
  if (
1104
1124
  // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1105
1125
  // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
@@ -1111,7 +1131,7 @@ function createDOMPurify() {
1111
1131
  return false;
1112
1132
  }
1113
1133
  /* Check value is safe. First, is attr inert? If so, is safe */
1114
- } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
1134
+ } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (value) {
1115
1135
  return false;
1116
1136
  } else ;
1117
1137
  return true;
@@ -1129,7 +1149,7 @@ function createDOMPurify() {
1129
1149
  * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
1130
1150
  */
1131
1151
  const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
1132
- return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT, tagName);
1152
+ return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
1133
1153
  };
1134
1154
  /**
1135
1155
  * _sanitizeAttributes
@@ -1144,9 +1164,7 @@ function createDOMPurify() {
1144
1164
  const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
1145
1165
  /* Execute a hook if present */
1146
1166
  _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
1147
- const {
1148
- attributes
1149
- } = currentNode;
1167
+ const attributes = currentNode.attributes;
1150
1168
  /* Check if we have attributes; if not we might have a text node */
1151
1169
  if (!attributes || _isClobbered(currentNode)) {
1152
1170
  return;
@@ -1162,11 +1180,9 @@ function createDOMPurify() {
1162
1180
  /* Go backwards over all attributes; safely remove bad ones */
1163
1181
  while (l--) {
1164
1182
  const attr = attributes[l];
1165
- const {
1166
- name,
1167
- namespaceURI,
1168
- value: attrValue
1169
- } = attr;
1183
+ const name = attr.name,
1184
+ namespaceURI = attr.namespaceURI,
1185
+ attrValue = attr.value;
1170
1186
  const lcName = transformCaseFunc(name);
1171
1187
  const initValue = attrValue;
1172
1188
  let value = name === 'value' ? initValue : stringTrim(initValue);
@@ -1214,7 +1230,7 @@ function createDOMPurify() {
1214
1230
  }
1215
1231
  /* Sanitize attribute content to be template-safe */
1216
1232
  if (SAFE_FOR_TEMPLATES) {
1217
- arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1233
+ arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
1218
1234
  value = stringReplace(value, expr, ' ');
1219
1235
  });
1220
1236
  }
@@ -1288,6 +1304,49 @@ function createDOMPurify() {
1288
1304
  /* Execute a hook if present */
1289
1305
  _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
1290
1306
  };
1307
+ /**
1308
+ * _sanitizeAttachedShadowRoots
1309
+ *
1310
+ * Walks `root` and feeds every attached shadow root we encounter into
1311
+ * the existing _sanitizeShadowDOM pipeline. The default node iterator
1312
+ * does not descend into shadow trees, so nodes inside an attached
1313
+ * shadow root would otherwise be skipped entirely.
1314
+ *
1315
+ * Two real input paths put attached shadow roots in front of us:
1316
+ * 1. IN_PLACE on a DOM node that already has shadow roots attached.
1317
+ * 2. DOM-node input where importNode(dirty, true) deep-clones the
1318
+ * shadow root because it was created with `clonable: true`.
1319
+ *
1320
+ * This pass runs once, up front, so the main iteration loop (and the
1321
+ * existing _sanitizeShadowDOM template-content recursion) stay
1322
+ * untouched — string-input paths are not affected.
1323
+ *
1324
+ * @param root the subtree root to walk for attached shadow roots
1325
+ */
1326
+ const _sanitizeAttachedShadowRoots2 = function _sanitizeAttachedShadowRoots(root) {
1327
+ if (root.nodeType === NODE_TYPE.element && root.shadowRoot instanceof DocumentFragment) {
1328
+ const sr = root.shadowRoot;
1329
+ // Recurse first so that nested shadow roots are reached even if
1330
+ // _sanitizeShadowDOM removes hosts at this level.
1331
+ _sanitizeAttachedShadowRoots2(sr);
1332
+ _sanitizeShadowDOM2(sr);
1333
+ }
1334
+ // Snapshot children before recursing. Sanitization of one subtree
1335
+ // (e.g. via an uponSanitizeShadowNode hook) may detach siblings,
1336
+ // and naive nextSibling traversal would silently skip the rest of
1337
+ // the list once a node is detached.
1338
+ const childNodes = root.childNodes;
1339
+ if (!childNodes) {
1340
+ return;
1341
+ }
1342
+ const snapshot = [];
1343
+ arrayForEach(childNodes, child => {
1344
+ arrayPush(snapshot, child);
1345
+ });
1346
+ for (const child of snapshot) {
1347
+ _sanitizeAttachedShadowRoots2(child);
1348
+ }
1349
+ };
1291
1350
  // eslint-disable-next-line complexity
1292
1351
  DOMPurify.sanitize = function (dirty) {
1293
1352
  let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
@@ -1332,6 +1391,9 @@ function createDOMPurify() {
1332
1391
  throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1333
1392
  }
1334
1393
  }
1394
+ /* Sanitize attached shadow roots before the main iterator runs.
1395
+ The iterator does not descend into shadow trees. */
1396
+ _sanitizeAttachedShadowRoots2(dirty);
1335
1397
  } else if (dirty instanceof Node) {
1336
1398
  /* If dirty is a DOM element, append to an empty document to avoid
1337
1399
  elements being stripped by the parser */
@@ -1346,6 +1408,10 @@ function createDOMPurify() {
1346
1408
  // eslint-disable-next-line unicorn/prefer-dom-node-append
1347
1409
  body.appendChild(importedNode);
1348
1410
  }
1411
+ /* Clonable shadow roots are deep-cloned by importNode(); sanitize
1412
+ them before the main iterator runs, since the iterator does not
1413
+ descend into shadow trees. */
1414
+ _sanitizeAttachedShadowRoots2(importedNode);
1349
1415
  } else {
1350
1416
  /* Exit directly if we have nothing to do */
1351
1417
  if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
@@ -1386,7 +1452,7 @@ function createDOMPurify() {
1386
1452
  if (SAFE_FOR_TEMPLATES) {
1387
1453
  body.normalize();
1388
1454
  let html = body.innerHTML;
1389
- arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1455
+ arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
1390
1456
  html = stringReplace(html, expr, ' ');
1391
1457
  });
1392
1458
  body.innerHTML = html;
@@ -1419,7 +1485,7 @@ function createDOMPurify() {
1419
1485
  }
1420
1486
  /* Sanitize final string template-safe */
1421
1487
  if (SAFE_FOR_TEMPLATES) {
1422
- arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1488
+ arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
1423
1489
  serializedHTML = stringReplace(serializedHTML, expr, ' ');
1424
1490
  });
1425
1491
  }