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.
- package/README.md +2 -3
- package/dist/purify.cjs.d.ts +1 -1
- package/dist/purify.cjs.js +154 -88
- package/dist/purify.cjs.js.map +1 -1
- package/dist/purify.es.d.mts +1 -1
- package/dist/purify.es.mjs +154 -88
- package/dist/purify.es.mjs.map +1 -1
- package/dist/purify.js +1454 -1388
- 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 +10 -9
- package/src/attrs.ts +376 -0
- package/src/config.ts +259 -0
- package/src/license_header +1 -0
- package/src/purify.ts +2243 -0
- package/src/regexp.ts +16 -0
- package/src/tags.ts +285 -0
- package/src/utils.ts +338 -0
package/dist/purify.es.mjs
CHANGED
|
@@ -1,21 +1,62 @@
|
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
275
|
-
const
|
|
276
|
-
const
|
|
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.
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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 (
|
|
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
|
-
|
|
1167
|
-
|
|
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
|
}
|