dompurify 3.4.2 → 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.
- package/README.md +2 -2
- package/dist/purify.cjs.d.ts +1 -1
- package/dist/purify.cjs.js +153 -88
- package/dist/purify.cjs.js.map +1 -1
- package/dist/purify.es.d.mts +1 -1
- package/dist/purify.es.mjs +153 -88
- package/dist/purify.es.mjs.map +1 -1
- package/dist/purify.js +1454 -1389
- package/dist/purify.js.map +1 -1
- package/dist/purify.min.js +2 -2
- package/dist/purify.min.js.map +1 -1
- package/package.json +8 -8
- package/src/purify.ts +59 -0
- package/src/regexp.ts +3 -4
package/README.md
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
|
|
8
8
|
|
|
9
|
-
It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version **v3.4.
|
|
9
|
+
It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version **v3.4.3**.
|
|
10
10
|
|
|
11
11
|
DOMPurify runs as JavaScript and works in all modern browsers (Safari (10+), Opera (15+), Edge, Firefox and Chrome - as well as almost anything else using Blink, Gecko or WebKit). It doesn't break on MSIE or other legacy browsers. It simply does nothing.
|
|
12
12
|
|
|
13
13
|
**Note that [DOMPurify v2.5.9](https://github.com/cure53/DOMPurify/releases/tag/2.5.9) is the latest version supporting MSIE. For important security updates compatible with MSIE, please use the [2.x branch](https://github.com/cure53/DOMPurify/tree/2.x).**
|
|
14
14
|
|
|
15
|
-
Our automated tests cover 9 browser/OS combinations (Chromium, Firefox, and WebKit across Ubuntu, macOS, and Windows) on every push, plus Node.js v20, v22, v24, and
|
|
15
|
+
Our automated tests cover 9 browser/OS combinations (Chromium, Firefox, and WebKit across Ubuntu, macOS, and Windows) on every push, plus Node.js v20, v22, v24, v25 and v26 running DOMPurify on [jsdom](https://github.com/jsdom/jsdom). Older Node versions are known to work as well, but hey... no guarantees.
|
|
16
16
|
|
|
17
17
|
DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not. For more details please also read about our [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model). Please, read it. Like, really.
|
|
18
18
|
|
package/dist/purify.cjs.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.4.
|
|
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
3
|
import { TrustedTypePolicy, TrustedTypesWindow, TrustedHTML } from 'trusted-types/lib/index.js';
|
|
4
4
|
|
package/dist/purify.cjs.js
CHANGED
|
@@ -1,23 +1,64 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.4.
|
|
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
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
5
|
+
function _arrayLikeToArray(r, a) {
|
|
6
|
+
(null == a || a > r.length) && (a = r.length);
|
|
7
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
8
|
+
return n;
|
|
9
|
+
}
|
|
10
|
+
function _arrayWithHoles(r) {
|
|
11
|
+
if (Array.isArray(r)) return r;
|
|
12
|
+
}
|
|
13
|
+
function _iterableToArrayLimit(r, l) {
|
|
14
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
15
|
+
if (null != t) {
|
|
16
|
+
var e,
|
|
17
|
+
n,
|
|
18
|
+
i,
|
|
19
|
+
u,
|
|
20
|
+
a = [],
|
|
21
|
+
f = true,
|
|
22
|
+
o = false;
|
|
23
|
+
try {
|
|
24
|
+
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);
|
|
25
|
+
} catch (r) {
|
|
26
|
+
o = true, n = r;
|
|
27
|
+
} finally {
|
|
28
|
+
try {
|
|
29
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
30
|
+
} finally {
|
|
31
|
+
if (o) throw n;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return a;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function _nonIterableRest() {
|
|
38
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
39
|
+
}
|
|
40
|
+
function _slicedToArray(r, e) {
|
|
41
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
42
|
+
}
|
|
43
|
+
function _unsupportedIterableToArray(r, a) {
|
|
44
|
+
if (r) {
|
|
45
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
46
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
47
|
+
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;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const entries = Object.entries,
|
|
52
|
+
setPrototypeOf = Object.setPrototypeOf,
|
|
53
|
+
isFrozen = Object.isFrozen,
|
|
54
|
+
getPrototypeOf = Object.getPrototypeOf,
|
|
55
|
+
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
56
|
+
let freeze = Object.freeze,
|
|
57
|
+
seal = Object.seal,
|
|
58
|
+
create = Object.create; // eslint-disable-line import/no-mutable-exports
|
|
59
|
+
let _ref = typeof Reflect !== 'undefined' && Reflect,
|
|
60
|
+
apply = _ref.apply,
|
|
61
|
+
construct = _ref.construct;
|
|
21
62
|
if (!freeze) {
|
|
22
63
|
freeze = function freeze(x) {
|
|
23
64
|
return x;
|
|
@@ -154,7 +195,10 @@ function cleanArray(array) {
|
|
|
154
195
|
*/
|
|
155
196
|
function clone(object) {
|
|
156
197
|
const newObject = create(null);
|
|
157
|
-
for (const
|
|
198
|
+
for (const _ref2 of entries(object)) {
|
|
199
|
+
var _ref3 = _slicedToArray(_ref2, 2);
|
|
200
|
+
const property = _ref3[0];
|
|
201
|
+
const value = _ref3[1];
|
|
158
202
|
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
159
203
|
if (isPropertyExist) {
|
|
160
204
|
if (arrayIsArray(value)) {
|
|
@@ -273,10 +317,9 @@ const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseli
|
|
|
273
317
|
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']);
|
|
274
318
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
275
319
|
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
320
|
+
const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
|
|
321
|
+
const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
|
|
322
|
+
const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
|
|
280
323
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
281
324
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
282
325
|
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
|
|
@@ -287,20 +330,6 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
|
|
|
287
330
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
288
331
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
289
332
|
|
|
290
|
-
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
291
|
-
__proto__: null,
|
|
292
|
-
ARIA_ATTR: ARIA_ATTR,
|
|
293
|
-
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
294
|
-
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
295
|
-
DATA_ATTR: DATA_ATTR,
|
|
296
|
-
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
297
|
-
ERB_EXPR: ERB_EXPR,
|
|
298
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
299
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
300
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
301
|
-
TMPLIT_EXPR: TMPLIT_EXPR
|
|
302
|
-
});
|
|
303
|
-
|
|
304
333
|
/* eslint-disable @typescript-eslint/indent */
|
|
305
334
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
306
335
|
const NODE_TYPE = {
|
|
@@ -367,7 +396,7 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
367
396
|
function createDOMPurify() {
|
|
368
397
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
369
398
|
const DOMPurify = root => createDOMPurify(root);
|
|
370
|
-
DOMPurify.version = '3.4.
|
|
399
|
+
DOMPurify.version = '3.4.3';
|
|
371
400
|
DOMPurify.removed = [];
|
|
372
401
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
373
402
|
// Not running in a browser, provide a factory function
|
|
@@ -375,22 +404,19 @@ function createDOMPurify() {
|
|
|
375
404
|
DOMPurify.isSupported = false;
|
|
376
405
|
return DOMPurify;
|
|
377
406
|
}
|
|
378
|
-
let
|
|
379
|
-
document
|
|
380
|
-
} = window;
|
|
407
|
+
let document = window.document;
|
|
381
408
|
const originalDocument = document;
|
|
382
409
|
const currentScript = originalDocument.currentScript;
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
|
|
390
|
-
HTMLFormElement,
|
|
391
|
-
DOMParser,
|
|
392
|
-
trustedTypes
|
|
393
|
-
} = window;
|
|
410
|
+
const DocumentFragment = window.DocumentFragment,
|
|
411
|
+
HTMLTemplateElement = window.HTMLTemplateElement,
|
|
412
|
+
Node = window.Node,
|
|
413
|
+
Element = window.Element,
|
|
414
|
+
NodeFilter = window.NodeFilter,
|
|
415
|
+
_window$NamedNodeMap = window.NamedNodeMap,
|
|
416
|
+
NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
|
|
417
|
+
HTMLFormElement = window.HTMLFormElement,
|
|
418
|
+
DOMParser = window.DOMParser,
|
|
419
|
+
trustedTypes = window.trustedTypes;
|
|
394
420
|
const ElementPrototype = Element.prototype;
|
|
395
421
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
396
422
|
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
@@ -411,33 +437,26 @@ function createDOMPurify() {
|
|
|
411
437
|
}
|
|
412
438
|
let trustedTypesPolicy;
|
|
413
439
|
let emptyHTML = '';
|
|
414
|
-
const
|
|
415
|
-
implementation,
|
|
416
|
-
createNodeIterator,
|
|
417
|
-
createDocumentFragment,
|
|
418
|
-
getElementsByTagName
|
|
419
|
-
|
|
420
|
-
const {
|
|
421
|
-
importNode
|
|
422
|
-
} = originalDocument;
|
|
440
|
+
const _document = document,
|
|
441
|
+
implementation = _document.implementation,
|
|
442
|
+
createNodeIterator = _document.createNodeIterator,
|
|
443
|
+
createDocumentFragment = _document.createDocumentFragment,
|
|
444
|
+
getElementsByTagName = _document.getElementsByTagName;
|
|
445
|
+
const importNode = originalDocument.importNode;
|
|
423
446
|
let hooks = _createHooksMap();
|
|
424
447
|
/**
|
|
425
448
|
* Expose whether this browser supports running the full DOMPurify.
|
|
426
449
|
*/
|
|
427
450
|
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
} = EXPRESSIONS;
|
|
438
|
-
let {
|
|
439
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
440
|
-
} = EXPRESSIONS;
|
|
451
|
+
const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
|
|
452
|
+
ERB_EXPR$1 = ERB_EXPR,
|
|
453
|
+
TMPLIT_EXPR$1 = TMPLIT_EXPR,
|
|
454
|
+
DATA_ATTR$1 = DATA_ATTR,
|
|
455
|
+
ARIA_ATTR$1 = ARIA_ATTR,
|
|
456
|
+
IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
|
|
457
|
+
ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
|
|
458
|
+
CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
|
|
459
|
+
let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
|
|
441
460
|
/**
|
|
442
461
|
* We consider the elements and attributes below to be safe. Ideally
|
|
443
462
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
@@ -1065,7 +1084,7 @@ function createDOMPurify() {
|
|
|
1065
1084
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1066
1085
|
/* Get the element's text content */
|
|
1067
1086
|
content = currentNode.textContent;
|
|
1068
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1087
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1069
1088
|
content = stringReplace(content, expr, ' ');
|
|
1070
1089
|
});
|
|
1071
1090
|
if (currentNode.textContent !== content) {
|
|
@@ -1102,7 +1121,7 @@ function createDOMPurify() {
|
|
|
1102
1121
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1103
1122
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1104
1123
|
We don't need to check the value; it's always URI safe. */
|
|
1105
|
-
if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!nameIsPermitted || FORBID_ATTR[lcName]) {
|
|
1124
|
+
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]) {
|
|
1106
1125
|
if (
|
|
1107
1126
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1108
1127
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -1114,7 +1133,7 @@ function createDOMPurify() {
|
|
|
1114
1133
|
return false;
|
|
1115
1134
|
}
|
|
1116
1135
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
1117
|
-
} 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) {
|
|
1136
|
+
} 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) {
|
|
1118
1137
|
return false;
|
|
1119
1138
|
} else ;
|
|
1120
1139
|
return true;
|
|
@@ -1132,7 +1151,7 @@ function createDOMPurify() {
|
|
|
1132
1151
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
1133
1152
|
*/
|
|
1134
1153
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1135
|
-
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT, tagName);
|
|
1154
|
+
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
1136
1155
|
};
|
|
1137
1156
|
/**
|
|
1138
1157
|
* _sanitizeAttributes
|
|
@@ -1147,9 +1166,7 @@ function createDOMPurify() {
|
|
|
1147
1166
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1148
1167
|
/* Execute a hook if present */
|
|
1149
1168
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
1150
|
-
const
|
|
1151
|
-
attributes
|
|
1152
|
-
} = currentNode;
|
|
1169
|
+
const attributes = currentNode.attributes;
|
|
1153
1170
|
/* Check if we have attributes; if not we might have a text node */
|
|
1154
1171
|
if (!attributes || _isClobbered(currentNode)) {
|
|
1155
1172
|
return;
|
|
@@ -1165,11 +1182,9 @@ function createDOMPurify() {
|
|
|
1165
1182
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1166
1183
|
while (l--) {
|
|
1167
1184
|
const attr = attributes[l];
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
value: attrValue
|
|
1172
|
-
} = attr;
|
|
1185
|
+
const name = attr.name,
|
|
1186
|
+
namespaceURI = attr.namespaceURI,
|
|
1187
|
+
attrValue = attr.value;
|
|
1173
1188
|
const lcName = transformCaseFunc(name);
|
|
1174
1189
|
const initValue = attrValue;
|
|
1175
1190
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -1217,7 +1232,7 @@ function createDOMPurify() {
|
|
|
1217
1232
|
}
|
|
1218
1233
|
/* Sanitize attribute content to be template-safe */
|
|
1219
1234
|
if (SAFE_FOR_TEMPLATES) {
|
|
1220
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1235
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1221
1236
|
value = stringReplace(value, expr, ' ');
|
|
1222
1237
|
});
|
|
1223
1238
|
}
|
|
@@ -1291,6 +1306,49 @@ function createDOMPurify() {
|
|
|
1291
1306
|
/* Execute a hook if present */
|
|
1292
1307
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
1293
1308
|
};
|
|
1309
|
+
/**
|
|
1310
|
+
* _sanitizeAttachedShadowRoots
|
|
1311
|
+
*
|
|
1312
|
+
* Walks `root` and feeds every attached shadow root we encounter into
|
|
1313
|
+
* the existing _sanitizeShadowDOM pipeline. The default node iterator
|
|
1314
|
+
* does not descend into shadow trees, so nodes inside an attached
|
|
1315
|
+
* shadow root would otherwise be skipped entirely.
|
|
1316
|
+
*
|
|
1317
|
+
* Two real input paths put attached shadow roots in front of us:
|
|
1318
|
+
* 1. IN_PLACE on a DOM node that already has shadow roots attached.
|
|
1319
|
+
* 2. DOM-node input where importNode(dirty, true) deep-clones the
|
|
1320
|
+
* shadow root because it was created with `clonable: true`.
|
|
1321
|
+
*
|
|
1322
|
+
* This pass runs once, up front, so the main iteration loop (and the
|
|
1323
|
+
* existing _sanitizeShadowDOM template-content recursion) stay
|
|
1324
|
+
* untouched — string-input paths are not affected.
|
|
1325
|
+
*
|
|
1326
|
+
* @param root the subtree root to walk for attached shadow roots
|
|
1327
|
+
*/
|
|
1328
|
+
const _sanitizeAttachedShadowRoots2 = function _sanitizeAttachedShadowRoots(root) {
|
|
1329
|
+
if (root.nodeType === NODE_TYPE.element && root.shadowRoot instanceof DocumentFragment) {
|
|
1330
|
+
const sr = root.shadowRoot;
|
|
1331
|
+
// Recurse first so that nested shadow roots are reached even if
|
|
1332
|
+
// _sanitizeShadowDOM removes hosts at this level.
|
|
1333
|
+
_sanitizeAttachedShadowRoots2(sr);
|
|
1334
|
+
_sanitizeShadowDOM2(sr);
|
|
1335
|
+
}
|
|
1336
|
+
// Snapshot children before recursing. Sanitization of one subtree
|
|
1337
|
+
// (e.g. via an uponSanitizeShadowNode hook) may detach siblings,
|
|
1338
|
+
// and naive nextSibling traversal would silently skip the rest of
|
|
1339
|
+
// the list once a node is detached.
|
|
1340
|
+
const childNodes = root.childNodes;
|
|
1341
|
+
if (!childNodes) {
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
const snapshot = [];
|
|
1345
|
+
arrayForEach(childNodes, child => {
|
|
1346
|
+
arrayPush(snapshot, child);
|
|
1347
|
+
});
|
|
1348
|
+
for (const child of snapshot) {
|
|
1349
|
+
_sanitizeAttachedShadowRoots2(child);
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1294
1352
|
// eslint-disable-next-line complexity
|
|
1295
1353
|
DOMPurify.sanitize = function (dirty) {
|
|
1296
1354
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -1335,6 +1393,9 @@ function createDOMPurify() {
|
|
|
1335
1393
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
1336
1394
|
}
|
|
1337
1395
|
}
|
|
1396
|
+
/* Sanitize attached shadow roots before the main iterator runs.
|
|
1397
|
+
The iterator does not descend into shadow trees. */
|
|
1398
|
+
_sanitizeAttachedShadowRoots2(dirty);
|
|
1338
1399
|
} else if (dirty instanceof Node) {
|
|
1339
1400
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
1340
1401
|
elements being stripped by the parser */
|
|
@@ -1349,6 +1410,10 @@ function createDOMPurify() {
|
|
|
1349
1410
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
1350
1411
|
body.appendChild(importedNode);
|
|
1351
1412
|
}
|
|
1413
|
+
/* Clonable shadow roots are deep-cloned by importNode(); sanitize
|
|
1414
|
+
them before the main iterator runs, since the iterator does not
|
|
1415
|
+
descend into shadow trees. */
|
|
1416
|
+
_sanitizeAttachedShadowRoots2(importedNode);
|
|
1352
1417
|
} else {
|
|
1353
1418
|
/* Exit directly if we have nothing to do */
|
|
1354
1419
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
@@ -1389,7 +1454,7 @@ function createDOMPurify() {
|
|
|
1389
1454
|
if (SAFE_FOR_TEMPLATES) {
|
|
1390
1455
|
body.normalize();
|
|
1391
1456
|
let html = body.innerHTML;
|
|
1392
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1457
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1393
1458
|
html = stringReplace(html, expr, ' ');
|
|
1394
1459
|
});
|
|
1395
1460
|
body.innerHTML = html;
|
|
@@ -1422,7 +1487,7 @@ function createDOMPurify() {
|
|
|
1422
1487
|
}
|
|
1423
1488
|
/* Sanitize final string template-safe */
|
|
1424
1489
|
if (SAFE_FOR_TEMPLATES) {
|
|
1425
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1490
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1426
1491
|
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
1427
1492
|
});
|
|
1428
1493
|
}
|