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/README.md
CHANGED
|
@@ -6,14 +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
|
-
|
|
16
|
-
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 v25 running DOMPurify on [jsdom](https://github.com/jsdom/jsdom). Older Node versions are known to work as well, but hey... no guarantees.
|
|
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.
|
|
17
16
|
|
|
18
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.
|
|
19
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) {
|
|
@@ -1097,11 +1116,12 @@ function createDOMPurify() {
|
|
|
1097
1116
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
1098
1117
|
return false;
|
|
1099
1118
|
}
|
|
1119
|
+
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
1100
1120
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
1101
1121
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1102
1122
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1103
1123
|
We don't need to check the value; it's always URI safe. */
|
|
1104
|
-
if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (
|
|
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]) {
|
|
1105
1125
|
if (
|
|
1106
1126
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1107
1127
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -1113,7 +1133,7 @@ function createDOMPurify() {
|
|
|
1113
1133
|
return false;
|
|
1114
1134
|
}
|
|
1115
1135
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
1116
|
-
} 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) {
|
|
1117
1137
|
return false;
|
|
1118
1138
|
} else ;
|
|
1119
1139
|
return true;
|
|
@@ -1131,7 +1151,7 @@ function createDOMPurify() {
|
|
|
1131
1151
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
1132
1152
|
*/
|
|
1133
1153
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1134
|
-
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT, tagName);
|
|
1154
|
+
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
1135
1155
|
};
|
|
1136
1156
|
/**
|
|
1137
1157
|
* _sanitizeAttributes
|
|
@@ -1146,9 +1166,7 @@ function createDOMPurify() {
|
|
|
1146
1166
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1147
1167
|
/* Execute a hook if present */
|
|
1148
1168
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
1149
|
-
const
|
|
1150
|
-
attributes
|
|
1151
|
-
} = currentNode;
|
|
1169
|
+
const attributes = currentNode.attributes;
|
|
1152
1170
|
/* Check if we have attributes; if not we might have a text node */
|
|
1153
1171
|
if (!attributes || _isClobbered(currentNode)) {
|
|
1154
1172
|
return;
|
|
@@ -1164,11 +1182,9 @@ function createDOMPurify() {
|
|
|
1164
1182
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1165
1183
|
while (l--) {
|
|
1166
1184
|
const attr = attributes[l];
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
value: attrValue
|
|
1171
|
-
} = attr;
|
|
1185
|
+
const name = attr.name,
|
|
1186
|
+
namespaceURI = attr.namespaceURI,
|
|
1187
|
+
attrValue = attr.value;
|
|
1172
1188
|
const lcName = transformCaseFunc(name);
|
|
1173
1189
|
const initValue = attrValue;
|
|
1174
1190
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -1216,7 +1232,7 @@ function createDOMPurify() {
|
|
|
1216
1232
|
}
|
|
1217
1233
|
/* Sanitize attribute content to be template-safe */
|
|
1218
1234
|
if (SAFE_FOR_TEMPLATES) {
|
|
1219
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1235
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1220
1236
|
value = stringReplace(value, expr, ' ');
|
|
1221
1237
|
});
|
|
1222
1238
|
}
|
|
@@ -1290,6 +1306,49 @@ function createDOMPurify() {
|
|
|
1290
1306
|
/* Execute a hook if present */
|
|
1291
1307
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
1292
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
|
+
};
|
|
1293
1352
|
// eslint-disable-next-line complexity
|
|
1294
1353
|
DOMPurify.sanitize = function (dirty) {
|
|
1295
1354
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -1334,6 +1393,9 @@ function createDOMPurify() {
|
|
|
1334
1393
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
1335
1394
|
}
|
|
1336
1395
|
}
|
|
1396
|
+
/* Sanitize attached shadow roots before the main iterator runs.
|
|
1397
|
+
The iterator does not descend into shadow trees. */
|
|
1398
|
+
_sanitizeAttachedShadowRoots2(dirty);
|
|
1337
1399
|
} else if (dirty instanceof Node) {
|
|
1338
1400
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
1339
1401
|
elements being stripped by the parser */
|
|
@@ -1348,6 +1410,10 @@ function createDOMPurify() {
|
|
|
1348
1410
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
1349
1411
|
body.appendChild(importedNode);
|
|
1350
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);
|
|
1351
1417
|
} else {
|
|
1352
1418
|
/* Exit directly if we have nothing to do */
|
|
1353
1419
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
@@ -1388,7 +1454,7 @@ function createDOMPurify() {
|
|
|
1388
1454
|
if (SAFE_FOR_TEMPLATES) {
|
|
1389
1455
|
body.normalize();
|
|
1390
1456
|
let html = body.innerHTML;
|
|
1391
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1457
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1392
1458
|
html = stringReplace(html, expr, ' ');
|
|
1393
1459
|
});
|
|
1394
1460
|
body.innerHTML = html;
|
|
@@ -1421,7 +1487,7 @@ function createDOMPurify() {
|
|
|
1421
1487
|
}
|
|
1422
1488
|
/* Sanitize final string template-safe */
|
|
1423
1489
|
if (SAFE_FOR_TEMPLATES) {
|
|
1424
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1490
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1425
1491
|
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
1426
1492
|
});
|
|
1427
1493
|
}
|