@vaadin/bundles 24.10.3 → 24.10.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/package.json +142 -142
- package/vaadin-bundle.json +1567 -1567
- package/vaadin.js +1557 -1151
- package/vaadin.js.map +1 -1
- package/vendors-node_modules_dompurify_dist_purify_es_mjs.js +548 -159
- package/vendors-node_modules_dompurify_dist_purify_es_mjs.js.map +1 -1
- package/vendors-node_modules_dompurify_dist_purify_js.js +548 -159
- package/vendors-node_modules_dompurify_dist_purify_js.js.map +1 -1
|
@@ -6,29 +6,70 @@
|
|
|
6
6
|
\***********************************************/
|
|
7
7
|
/***/ (function(module) {
|
|
8
8
|
|
|
9
|
-
/*! @license DOMPurify 3.4.
|
|
9
|
+
/*! @license DOMPurify 3.4.8 | (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.8/LICENSE */
|
|
10
10
|
|
|
11
11
|
(function (global, factory) {
|
|
12
12
|
true ? module.exports = factory() :
|
|
13
13
|
0;
|
|
14
14
|
})(this, (function () { 'use strict';
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
16
|
+
function _arrayLikeToArray(r, a) {
|
|
17
|
+
(null == a || a > r.length) && (a = r.length);
|
|
18
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
19
|
+
return n;
|
|
20
|
+
}
|
|
21
|
+
function _arrayWithHoles(r) {
|
|
22
|
+
if (Array.isArray(r)) return r;
|
|
23
|
+
}
|
|
24
|
+
function _iterableToArrayLimit(r, l) {
|
|
25
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
26
|
+
if (null != t) {
|
|
27
|
+
var e,
|
|
28
|
+
n,
|
|
29
|
+
i,
|
|
30
|
+
u,
|
|
31
|
+
a = [],
|
|
32
|
+
f = true,
|
|
33
|
+
o = false;
|
|
34
|
+
try {
|
|
35
|
+
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);
|
|
36
|
+
} catch (r) {
|
|
37
|
+
o = true, n = r;
|
|
38
|
+
} finally {
|
|
39
|
+
try {
|
|
40
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
41
|
+
} finally {
|
|
42
|
+
if (o) throw n;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return a;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function _nonIterableRest() {
|
|
49
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
50
|
+
}
|
|
51
|
+
function _slicedToArray(r, e) {
|
|
52
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
53
|
+
}
|
|
54
|
+
function _unsupportedIterableToArray(r, a) {
|
|
55
|
+
if (r) {
|
|
56
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
57
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
58
|
+
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;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const entries = Object.entries,
|
|
63
|
+
setPrototypeOf = Object.setPrototypeOf,
|
|
64
|
+
isFrozen = Object.isFrozen,
|
|
65
|
+
getPrototypeOf = Object.getPrototypeOf,
|
|
66
|
+
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
67
|
+
let freeze = Object.freeze,
|
|
68
|
+
seal = Object.seal,
|
|
69
|
+
create = Object.create; // eslint-disable-line import/no-mutable-exports
|
|
70
|
+
let _ref = typeof Reflect !== 'undefined' && Reflect,
|
|
71
|
+
apply = _ref.apply,
|
|
72
|
+
construct = _ref.construct;
|
|
32
73
|
if (!freeze) {
|
|
33
74
|
freeze = function freeze(x) {
|
|
34
75
|
return x;
|
|
@@ -60,13 +101,19 @@
|
|
|
60
101
|
const arrayPop = unapply(Array.prototype.pop);
|
|
61
102
|
const arrayPush = unapply(Array.prototype.push);
|
|
62
103
|
const arraySplice = unapply(Array.prototype.splice);
|
|
104
|
+
const arrayIsArray = Array.isArray;
|
|
63
105
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
64
106
|
const stringToString = unapply(String.prototype.toString);
|
|
65
107
|
const stringMatch = unapply(String.prototype.match);
|
|
66
108
|
const stringReplace = unapply(String.prototype.replace);
|
|
67
109
|
const stringIndexOf = unapply(String.prototype.indexOf);
|
|
68
110
|
const stringTrim = unapply(String.prototype.trim);
|
|
111
|
+
const numberToString = unapply(Number.prototype.toString);
|
|
112
|
+
const booleanToString = unapply(Boolean.prototype.toString);
|
|
113
|
+
const bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);
|
|
114
|
+
const symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);
|
|
69
115
|
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
116
|
+
const objectToString = unapply(Object.prototype.toString);
|
|
70
117
|
const regExpTest = unapply(RegExp.prototype.test);
|
|
71
118
|
const typeErrorCreate = unconstruct(TypeError);
|
|
72
119
|
/**
|
|
@@ -116,6 +163,9 @@
|
|
|
116
163
|
// Prevent prototype setters from intercepting set as a this value.
|
|
117
164
|
setPrototypeOf(set, null);
|
|
118
165
|
}
|
|
166
|
+
if (!arrayIsArray(array)) {
|
|
167
|
+
return set;
|
|
168
|
+
}
|
|
119
169
|
let l = array.length;
|
|
120
170
|
while (l--) {
|
|
121
171
|
let element = array[l];
|
|
@@ -156,10 +206,13 @@
|
|
|
156
206
|
*/
|
|
157
207
|
function clone(object) {
|
|
158
208
|
const newObject = create(null);
|
|
159
|
-
for (const
|
|
209
|
+
for (const _ref2 of entries(object)) {
|
|
210
|
+
var _ref3 = _slicedToArray(_ref2, 2);
|
|
211
|
+
const property = _ref3[0];
|
|
212
|
+
const value = _ref3[1];
|
|
160
213
|
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
161
214
|
if (isPropertyExist) {
|
|
162
|
-
if (
|
|
215
|
+
if (arrayIsArray(value)) {
|
|
163
216
|
newObject[property] = cleanArray(value);
|
|
164
217
|
} else if (value && typeof value === 'object' && value.constructor === Object) {
|
|
165
218
|
newObject[property] = clone(value);
|
|
@@ -170,6 +223,58 @@
|
|
|
170
223
|
}
|
|
171
224
|
return newObject;
|
|
172
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Convert non-node values into strings without depending on direct property access.
|
|
228
|
+
*
|
|
229
|
+
* @param value - The value to stringify.
|
|
230
|
+
* @returns A string representation of the provided value.
|
|
231
|
+
*/
|
|
232
|
+
function stringifyValue(value) {
|
|
233
|
+
switch (typeof value) {
|
|
234
|
+
case 'string':
|
|
235
|
+
{
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
case 'number':
|
|
239
|
+
{
|
|
240
|
+
return numberToString(value);
|
|
241
|
+
}
|
|
242
|
+
case 'boolean':
|
|
243
|
+
{
|
|
244
|
+
return booleanToString(value);
|
|
245
|
+
}
|
|
246
|
+
case 'bigint':
|
|
247
|
+
{
|
|
248
|
+
return bigintToString ? bigintToString(value) : '0';
|
|
249
|
+
}
|
|
250
|
+
case 'symbol':
|
|
251
|
+
{
|
|
252
|
+
return symbolToString ? symbolToString(value) : 'Symbol()';
|
|
253
|
+
}
|
|
254
|
+
case 'undefined':
|
|
255
|
+
{
|
|
256
|
+
return objectToString(value);
|
|
257
|
+
}
|
|
258
|
+
case 'function':
|
|
259
|
+
case 'object':
|
|
260
|
+
{
|
|
261
|
+
if (value === null) {
|
|
262
|
+
return objectToString(value);
|
|
263
|
+
}
|
|
264
|
+
const valueAsRecord = value;
|
|
265
|
+
const valueToString = lookupGetter(valueAsRecord, 'toString');
|
|
266
|
+
if (typeof valueToString === 'function') {
|
|
267
|
+
const stringified = valueToString(valueAsRecord);
|
|
268
|
+
return typeof stringified === 'string' ? stringified : objectToString(stringified);
|
|
269
|
+
}
|
|
270
|
+
return objectToString(value);
|
|
271
|
+
}
|
|
272
|
+
default:
|
|
273
|
+
{
|
|
274
|
+
return objectToString(value);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
173
278
|
/**
|
|
174
279
|
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
175
280
|
*
|
|
@@ -195,6 +300,14 @@
|
|
|
195
300
|
}
|
|
196
301
|
return fallbackValue;
|
|
197
302
|
}
|
|
303
|
+
function isRegex(value) {
|
|
304
|
+
try {
|
|
305
|
+
regExpTest(value, '');
|
|
306
|
+
return true;
|
|
307
|
+
} catch (_unused) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
198
311
|
|
|
199
312
|
const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
|
|
200
313
|
const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
|
|
@@ -210,15 +323,14 @@
|
|
|
210
323
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
211
324
|
const text = freeze(['#text']);
|
|
212
325
|
|
|
213
|
-
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', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', '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', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns'
|
|
326
|
+
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', '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', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns']);
|
|
214
327
|
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', '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', 'exponent', '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', 'intercept', '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', 'mask-type', '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', 'slope', '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', 'tablevalues', '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']);
|
|
215
328
|
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']);
|
|
216
329
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
217
330
|
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
const
|
|
221
|
-
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
331
|
+
const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
|
|
332
|
+
const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
|
|
333
|
+
const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
|
|
222
334
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
223
335
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
224
336
|
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
|
|
@@ -229,29 +341,24 @@
|
|
|
229
341
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
230
342
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
231
343
|
|
|
232
|
-
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
233
|
-
__proto__: null,
|
|
234
|
-
ARIA_ATTR: ARIA_ATTR,
|
|
235
|
-
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
236
|
-
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
237
|
-
DATA_ATTR: DATA_ATTR,
|
|
238
|
-
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
239
|
-
ERB_EXPR: ERB_EXPR,
|
|
240
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
241
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
242
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
243
|
-
TMPLIT_EXPR: TMPLIT_EXPR
|
|
244
|
-
});
|
|
245
|
-
|
|
246
344
|
/* eslint-disable @typescript-eslint/indent */
|
|
247
345
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
248
346
|
const NODE_TYPE = {
|
|
249
347
|
element: 1,
|
|
348
|
+
attribute: 2,
|
|
250
349
|
text: 3,
|
|
350
|
+
cdataSection: 4,
|
|
351
|
+
entityReference: 5,
|
|
352
|
+
// Deprecated
|
|
353
|
+
entityNode: 6,
|
|
251
354
|
// Deprecated
|
|
252
355
|
progressingInstruction: 7,
|
|
253
356
|
comment: 8,
|
|
254
|
-
document: 9
|
|
357
|
+
document: 9,
|
|
358
|
+
documentType: 10,
|
|
359
|
+
documentFragment: 11,
|
|
360
|
+
notation: 12 // Deprecated
|
|
361
|
+
};
|
|
255
362
|
const getGlobal = function getGlobal() {
|
|
256
363
|
return typeof window === 'undefined' ? null : window;
|
|
257
364
|
};
|
|
@@ -309,7 +416,7 @@
|
|
|
309
416
|
function createDOMPurify() {
|
|
310
417
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
311
418
|
const DOMPurify = root => createDOMPurify(root);
|
|
312
|
-
DOMPurify.version = '3.4.
|
|
419
|
+
DOMPurify.version = '3.4.8';
|
|
313
420
|
DOMPurify.removed = [];
|
|
314
421
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
315
422
|
// Not running in a browser, provide a factory function
|
|
@@ -317,28 +424,29 @@
|
|
|
317
424
|
DOMPurify.isSupported = false;
|
|
318
425
|
return DOMPurify;
|
|
319
426
|
}
|
|
320
|
-
let
|
|
321
|
-
document
|
|
322
|
-
} = window;
|
|
427
|
+
let document = window.document;
|
|
323
428
|
const originalDocument = document;
|
|
324
429
|
const currentScript = originalDocument.currentScript;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
NamedNodeMap
|
|
332
|
-
HTMLFormElement
|
|
333
|
-
DOMParser,
|
|
334
|
-
trustedTypes
|
|
335
|
-
} = window;
|
|
430
|
+
window.DocumentFragment;
|
|
431
|
+
const HTMLTemplateElement = window.HTMLTemplateElement,
|
|
432
|
+
Node = window.Node,
|
|
433
|
+
Element = window.Element,
|
|
434
|
+
NodeFilter = window.NodeFilter,
|
|
435
|
+
_window$NamedNodeMap = window.NamedNodeMap;
|
|
436
|
+
_window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap;
|
|
437
|
+
window.HTMLFormElement;
|
|
438
|
+
const DOMParser = window.DOMParser,
|
|
439
|
+
trustedTypes = window.trustedTypes;
|
|
336
440
|
const ElementPrototype = Element.prototype;
|
|
337
441
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
338
442
|
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
339
443
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
340
444
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
341
445
|
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
446
|
+
const getShadowRoot = lookupGetter(ElementPrototype, 'shadowRoot');
|
|
447
|
+
const getAttributes = lookupGetter(ElementPrototype, 'attributes');
|
|
448
|
+
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
|
|
449
|
+
const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeName') : null;
|
|
342
450
|
// As per issue #47, the web-components registry is inherited by a
|
|
343
451
|
// new document created via createHTMLDocument. As per the spec
|
|
344
452
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -353,33 +461,43 @@
|
|
|
353
461
|
}
|
|
354
462
|
let trustedTypesPolicy;
|
|
355
463
|
let emptyHTML = '';
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const {
|
|
363
|
-
|
|
364
|
-
|
|
464
|
+
// Tracks whether we are already inside a call to the configured Trusted Types
|
|
465
|
+
// policy's `createHTML`. If the supplied `TRUSTED_TYPES_POLICY.createHTML`
|
|
466
|
+
// itself calls `DOMPurify.sanitize` (the cause of #1422), `sanitize` would
|
|
467
|
+
// re-enter the policy and recurse until the stack overflows. We detect that
|
|
468
|
+
// re-entry and throw a clear, actionable error instead.
|
|
469
|
+
let IN_POLICY_CREATE_HTML = 0;
|
|
470
|
+
const _createTrustedHTML = function _createTrustedHTML(html) {
|
|
471
|
+
if (IN_POLICY_CREATE_HTML > 0) {
|
|
472
|
+
throw typeErrorCreate('The configured TRUSTED_TYPES_POLICY.createHTML must not call ' + 'DOMPurify.sanitize, as that causes infinite recursion. Do not pass ' + 'a policy whose createHTML wraps DOMPurify as TRUSTED_TYPES_POLICY; ' + 'see the "DOMPurify and Trusted Types" section of the README.');
|
|
473
|
+
}
|
|
474
|
+
IN_POLICY_CREATE_HTML++;
|
|
475
|
+
try {
|
|
476
|
+
return trustedTypesPolicy.createHTML(html);
|
|
477
|
+
} finally {
|
|
478
|
+
IN_POLICY_CREATE_HTML--;
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
const _document = document,
|
|
482
|
+
implementation = _document.implementation,
|
|
483
|
+
createNodeIterator = _document.createNodeIterator,
|
|
484
|
+
createDocumentFragment = _document.createDocumentFragment,
|
|
485
|
+
getElementsByTagName = _document.getElementsByTagName;
|
|
486
|
+
const importNode = originalDocument.importNode;
|
|
365
487
|
let hooks = _createHooksMap();
|
|
366
488
|
/**
|
|
367
489
|
* Expose whether this browser supports running the full DOMPurify.
|
|
368
490
|
*/
|
|
369
491
|
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
} = EXPRESSIONS;
|
|
380
|
-
let {
|
|
381
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
382
|
-
} = EXPRESSIONS;
|
|
492
|
+
const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
|
|
493
|
+
ERB_EXPR$1 = ERB_EXPR,
|
|
494
|
+
TMPLIT_EXPR$1 = TMPLIT_EXPR,
|
|
495
|
+
DATA_ATTR$1 = DATA_ATTR,
|
|
496
|
+
ARIA_ATTR$1 = ARIA_ATTR,
|
|
497
|
+
IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
|
|
498
|
+
ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
|
|
499
|
+
CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
|
|
500
|
+
let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
|
|
383
501
|
/**
|
|
384
502
|
* We consider the elements and attributes below to be safe. Ideally
|
|
385
503
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
@@ -557,15 +675,15 @@
|
|
|
557
675
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
558
676
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
559
677
|
/* Set configuration parameters */
|
|
560
|
-
ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
|
|
561
|
-
ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
|
|
562
|
-
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
|
|
563
|
-
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
|
|
564
|
-
DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
|
|
565
|
-
FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
|
|
566
|
-
FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
|
|
567
|
-
FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
|
|
568
|
-
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
|
|
678
|
+
ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') && arrayIsArray(cfg.ALLOWED_TAGS) ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
|
|
679
|
+
ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') && arrayIsArray(cfg.ALLOWED_ATTR) ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
|
|
680
|
+
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') && arrayIsArray(cfg.ALLOWED_NAMESPACES) ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
|
|
681
|
+
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR) ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
|
|
682
|
+
DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') && arrayIsArray(cfg.ADD_DATA_URI_TAGS) ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
|
|
683
|
+
FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS) ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
|
|
684
|
+
FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') && arrayIsArray(cfg.FORBID_TAGS) ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
|
|
685
|
+
FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') && arrayIsArray(cfg.FORBID_ATTR) ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
|
|
686
|
+
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
|
|
569
687
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
570
688
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
571
689
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
|
|
@@ -581,19 +699,20 @@
|
|
|
581
699
|
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
|
|
582
700
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
|
|
583
701
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
584
|
-
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP
|
|
585
|
-
NAMESPACE = cfg.NAMESPACE
|
|
586
|
-
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS
|
|
587
|
-
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
702
|
+
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
|
|
703
|
+
NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
|
|
704
|
+
MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); // Default built-in map
|
|
705
|
+
HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, ['annotation-xml']); // Default built-in map
|
|
706
|
+
const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
|
|
707
|
+
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
708
|
+
if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
|
|
709
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; // Default undefined
|
|
591
710
|
}
|
|
592
|
-
if (
|
|
593
|
-
CUSTOM_ELEMENT_HANDLING.attributeNameCheck =
|
|
711
|
+
if (objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && isRegexOrFunction(customElementHandling.attributeNameCheck)) {
|
|
712
|
+
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; // Default undefined
|
|
594
713
|
}
|
|
595
|
-
if (
|
|
596
|
-
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements =
|
|
714
|
+
if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
|
|
715
|
+
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
|
|
597
716
|
}
|
|
598
717
|
if (SAFE_FOR_TEMPLATES) {
|
|
599
718
|
ALLOW_DATA_ATTR = false;
|
|
@@ -630,36 +749,36 @@
|
|
|
630
749
|
EXTRA_ELEMENT_HANDLING.tagCheck = null;
|
|
631
750
|
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
|
|
632
751
|
/* Merge configuration parameters */
|
|
633
|
-
if (cfg
|
|
752
|
+
if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {
|
|
634
753
|
if (typeof cfg.ADD_TAGS === 'function') {
|
|
635
754
|
EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
|
|
636
|
-
} else {
|
|
755
|
+
} else if (arrayIsArray(cfg.ADD_TAGS)) {
|
|
637
756
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
638
757
|
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
639
758
|
}
|
|
640
759
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
641
760
|
}
|
|
642
761
|
}
|
|
643
|
-
if (cfg
|
|
762
|
+
if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {
|
|
644
763
|
if (typeof cfg.ADD_ATTR === 'function') {
|
|
645
764
|
EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
|
|
646
|
-
} else {
|
|
765
|
+
} else if (arrayIsArray(cfg.ADD_ATTR)) {
|
|
647
766
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
648
767
|
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
649
768
|
}
|
|
650
769
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
651
770
|
}
|
|
652
771
|
}
|
|
653
|
-
if (cfg.ADD_URI_SAFE_ATTR) {
|
|
772
|
+
if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {
|
|
654
773
|
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
655
774
|
}
|
|
656
|
-
if (cfg.FORBID_CONTENTS) {
|
|
775
|
+
if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {
|
|
657
776
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
658
777
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
659
778
|
}
|
|
660
779
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
661
780
|
}
|
|
662
|
-
if (cfg.ADD_FORBID_CONTENTS) {
|
|
781
|
+
if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
|
|
663
782
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
664
783
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
665
784
|
}
|
|
@@ -686,19 +805,47 @@
|
|
|
686
805
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
687
806
|
}
|
|
688
807
|
// Overwrite existing TrustedTypes policy.
|
|
808
|
+
const previousTrustedTypesPolicy = trustedTypesPolicy;
|
|
689
809
|
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
690
|
-
// Sign local variables required by `sanitize`.
|
|
691
|
-
|
|
810
|
+
// Sign local variables required by `sanitize`. If the supplied policy's
|
|
811
|
+
// `createHTML` is circular (i.e. it calls `DOMPurify.sanitize`), this
|
|
812
|
+
// throws via the re-entrancy guard. Restore the previous policy first so
|
|
813
|
+
// the instance is not left in a poisoned state. See #1422.
|
|
814
|
+
try {
|
|
815
|
+
emptyHTML = _createTrustedHTML('');
|
|
816
|
+
} catch (error) {
|
|
817
|
+
trustedTypesPolicy = previousTrustedTypesPolicy;
|
|
818
|
+
throw error;
|
|
819
|
+
}
|
|
692
820
|
} else {
|
|
693
821
|
// Uninitialized policy, attempt to initialize the internal dompurify policy.
|
|
694
|
-
if (trustedTypesPolicy === undefined) {
|
|
822
|
+
if (trustedTypesPolicy === undefined && cfg.TRUSTED_TYPES_POLICY !== null) {
|
|
695
823
|
trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
696
824
|
}
|
|
697
825
|
// If creating the internal policy succeeded sign internal variables.
|
|
698
|
-
|
|
699
|
-
|
|
826
|
+
// Note: a falsy `trustedTypesPolicy` (null when policy creation failed or
|
|
827
|
+
// was skipped via `TRUSTED_TYPES_POLICY: null`, or undefined when no
|
|
828
|
+
// policy has been initialized yet) must be excluded here, otherwise we
|
|
829
|
+
// would call `.createHTML` on a non-policy and throw. See #1422.
|
|
830
|
+
if (trustedTypesPolicy && typeof emptyHTML === 'string') {
|
|
831
|
+
emptyHTML = _createTrustedHTML('');
|
|
700
832
|
}
|
|
701
833
|
}
|
|
834
|
+
/*
|
|
835
|
+
* Mirror the clone-before-mutate pattern already applied above for
|
|
836
|
+
* cfg.ADD_TAGS / cfg.ADD_ATTR: if any uponSanitize* hook is
|
|
837
|
+
* registered AND the set still points at the default constant,
|
|
838
|
+
* clone it. The hook then mutates the clone (in-call widening
|
|
839
|
+
* still works exactly as documented) and the next default-cfg
|
|
840
|
+
* call rebinds to the untouched original via the reassignment at
|
|
841
|
+
* the top of this function.
|
|
842
|
+
*/
|
|
843
|
+
if ((hooks.uponSanitizeElement.length > 0 || hooks.uponSanitizeAttribute.length > 0) && ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
844
|
+
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
845
|
+
}
|
|
846
|
+
if (hooks.uponSanitizeAttribute.length > 0 && ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
847
|
+
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
848
|
+
}
|
|
702
849
|
// Prevent further manipulation of configuration.
|
|
703
850
|
// Not available in IE8, Safari 5, etc.
|
|
704
851
|
if (freeze) {
|
|
@@ -858,7 +1005,7 @@
|
|
|
858
1005
|
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
|
|
859
1006
|
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
|
|
860
1007
|
}
|
|
861
|
-
const dirtyPayload = trustedTypesPolicy ?
|
|
1008
|
+
const dirtyPayload = trustedTypesPolicy ? _createTrustedHTML(dirty) : dirty;
|
|
862
1009
|
/*
|
|
863
1010
|
* Use the DOMParser API by default, fallback later if needs be
|
|
864
1011
|
* DOMParser not work for svg when has multiple root element.
|
|
@@ -898,23 +1045,142 @@
|
|
|
898
1045
|
// eslint-disable-next-line no-bitwise
|
|
899
1046
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
900
1047
|
};
|
|
1048
|
+
/**
|
|
1049
|
+
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
1050
|
+
* character data of an element subtree. Used as the final safety net for
|
|
1051
|
+
* SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
|
|
1052
|
+
* which only form after text-node normalization (e.g. fragments split across
|
|
1053
|
+
* stripped elements) cannot survive into a template-evaluating framework.
|
|
1054
|
+
*
|
|
1055
|
+
* Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
|
|
1056
|
+
* in place rather than round-tripping through innerHTML. This preserves
|
|
1057
|
+
* descendant node references (important for IN_PLACE callers), avoids a
|
|
1058
|
+
* serialize/reparse cycle, and reads literal character data — which means
|
|
1059
|
+
* `<%...%>` in text content matches the ERB regex against its real bytes
|
|
1060
|
+
* instead of the HTML-entity-escaped form innerHTML would produce.
|
|
1061
|
+
*
|
|
1062
|
+
* Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
|
|
1063
|
+
* attributes is performed during the per-node `_sanitizeAttributes` pass.
|
|
1064
|
+
*
|
|
1065
|
+
* @param node The root element whose character data should be scrubbed.
|
|
1066
|
+
*/
|
|
1067
|
+
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
1068
|
+
var _node$querySelectorAl, _node$querySelectorAl2;
|
|
1069
|
+
node.normalize();
|
|
1070
|
+
const walker = createNodeIterator.call(node.ownerDocument || node, node,
|
|
1071
|
+
// eslint-disable-next-line no-bitwise
|
|
1072
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
1073
|
+
let currentNode = walker.nextNode();
|
|
1074
|
+
while (currentNode) {
|
|
1075
|
+
let data = currentNode.data;
|
|
1076
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1077
|
+
data = stringReplace(data, expr, ' ');
|
|
1078
|
+
});
|
|
1079
|
+
currentNode.data = data;
|
|
1080
|
+
currentNode = walker.nextNode();
|
|
1081
|
+
}
|
|
1082
|
+
// NodeIterator does not descend into <template>.content per the DOM spec,
|
|
1083
|
+
// so we must explicitly recurse into each template's content fragment,
|
|
1084
|
+
// mirroring the approach used by _sanitizeShadowDOM.
|
|
1085
|
+
const templates = (_node$querySelectorAl = (_node$querySelectorAl2 = node.querySelectorAll) === null || _node$querySelectorAl2 === void 0 ? void 0 : _node$querySelectorAl2.call(node, 'template')) !== null && _node$querySelectorAl !== void 0 ? _node$querySelectorAl : [];
|
|
1086
|
+
arrayForEach(Array.from(templates), tmpl => {
|
|
1087
|
+
if (_isDocumentFragment(tmpl.content)) {
|
|
1088
|
+
_scrubTemplateExpressions2(tmpl.content);
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
};
|
|
901
1092
|
/**
|
|
902
1093
|
* _isClobbered
|
|
903
1094
|
*
|
|
1095
|
+
* Detect DOM-clobbering on HTMLFormElement nodes. Form is the only HTML
|
|
1096
|
+
* interface with [LegacyOverrideBuiltIns]; a descendant element with a
|
|
1097
|
+
* `name` attribute matching a prototype property shadows that property
|
|
1098
|
+
* on direct reads. We use this check at the IN_PLACE entry-point and
|
|
1099
|
+
* during attribute sanitization to refuse clobbered forms.
|
|
1100
|
+
*
|
|
904
1101
|
* @param element element to check for clobbering attacks
|
|
905
1102
|
* @return true if clobbered, false if safe
|
|
906
1103
|
*/
|
|
907
1104
|
const _isClobbered = function _isClobbered(element) {
|
|
908
|
-
|
|
1105
|
+
// Realm-independent tag-name probe. If we can't determine the tag
|
|
1106
|
+
// name at all, we can't reason about clobbering — return false
|
|
1107
|
+
// (the caller's other defences still apply).
|
|
1108
|
+
const realTagName = getNodeName ? getNodeName(element) : null;
|
|
1109
|
+
if (typeof realTagName !== 'string') {
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
if (transformCaseFunc(realTagName) !== 'form') {
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
return typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' ||
|
|
1116
|
+
// Realm-safe NamedNodeMap detection: equality against the cached
|
|
1117
|
+
// prototype getter. Clobbered .attributes (e.g. <input name="attributes">)
|
|
1118
|
+
// makes the direct read diverge from the cached read; a clean form
|
|
1119
|
+
// (same-realm OR foreign-realm) has both reads pointing at the same
|
|
1120
|
+
// canonical NamedNodeMap.
|
|
1121
|
+
element.attributes !== getAttributes(element) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function' ||
|
|
1122
|
+
// NodeType clobbering probe. Cached Node.prototype.nodeType getter
|
|
1123
|
+
// returns the integer 1 for any Element regardless of realm; direct
|
|
1124
|
+
// read on a clobbered form (e.g. <input name="nodeType">) returns
|
|
1125
|
+
// the named child element. Cheap addition — nodeType is read from
|
|
1126
|
+
// an internal slot, no serialization cost — and removes a residual
|
|
1127
|
+
// clobbering surface used by several mXSS / PI / comment branches
|
|
1128
|
+
// in _sanitizeElements that compare currentNode.nodeType directly.
|
|
1129
|
+
element.nodeType !== getNodeType(element) ||
|
|
1130
|
+
// HTMLFormElement has [LegacyOverrideBuiltIns]: a descendant named
|
|
1131
|
+
// "childNodes" shadows the prototype getter. Direct reads of
|
|
1132
|
+
// form.childNodes from a clobbered form return the named child
|
|
1133
|
+
// instead of the real NodeList, so any walk that reads it directly
|
|
1134
|
+
// skips the form's real children. Compare the direct read to the
|
|
1135
|
+
// cached Node.prototype getter — when the form's named-property
|
|
1136
|
+
// getter intercepts the read, the two values differ and we flag
|
|
1137
|
+
// the form. This catches every clobbering child type (input,
|
|
1138
|
+
// select, etc.) regardless of whether the named child happens to
|
|
1139
|
+
// carry a numeric .length, which a typeof-based probe would miss
|
|
1140
|
+
// (e.g. HTMLSelectElement.length is a defined unsigned-long).
|
|
1141
|
+
element.childNodes !== getChildNodes(element);
|
|
1142
|
+
};
|
|
1143
|
+
/**
|
|
1144
|
+
* Checks whether the given value is a DocumentFragment from any realm.
|
|
1145
|
+
*
|
|
1146
|
+
* The realm-independent replacement reads `nodeType` through the cached
|
|
1147
|
+
* Node.prototype getter and compares to the DOCUMENT_FRAGMENT_NODE
|
|
1148
|
+
* constant (11). nodeType is a numeric value resolved from the node's
|
|
1149
|
+
* internal slot, identical across realms for the same kind of node.
|
|
1150
|
+
*
|
|
1151
|
+
* @param value object to check
|
|
1152
|
+
* @return true if value is a DocumentFragment-shaped node from any realm
|
|
1153
|
+
*/
|
|
1154
|
+
const _isDocumentFragment = function _isDocumentFragment(value) {
|
|
1155
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
try {
|
|
1159
|
+
return getNodeType(value) === NODE_TYPE.documentFragment;
|
|
1160
|
+
} catch (_) {
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
909
1163
|
};
|
|
910
1164
|
/**
|
|
911
|
-
* Checks whether the given object is a DOM node
|
|
1165
|
+
* Checks whether the given object is a DOM node, including nodes that
|
|
1166
|
+
* originate from a different window/realm (e.g. an iframe's
|
|
1167
|
+
* contentDocument). The previous `value instanceof Node` check was
|
|
1168
|
+
* realm-bound: nodes from a different window failed it, causing
|
|
1169
|
+
* sanitize() to silently stringify them and reset IN_PLACE to false,
|
|
1170
|
+
* returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
|
|
912
1171
|
*
|
|
913
1172
|
* @param value object to check whether it's a DOM node
|
|
914
|
-
* @return true
|
|
1173
|
+
* @return true if value is a DOM node from any realm
|
|
915
1174
|
*/
|
|
916
1175
|
const _isNode = function _isNode(value) {
|
|
917
|
-
|
|
1176
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
try {
|
|
1180
|
+
return typeof getNodeType(value) === 'number';
|
|
1181
|
+
} catch (_) {
|
|
1182
|
+
return false;
|
|
1183
|
+
}
|
|
918
1184
|
};
|
|
919
1185
|
function _executeHooks(hooks, currentNode, data) {
|
|
920
1186
|
arrayForEach(hooks, hook => {
|
|
@@ -940,7 +1206,7 @@
|
|
|
940
1206
|
return true;
|
|
941
1207
|
}
|
|
942
1208
|
/* Now let's check the element's type and name */
|
|
943
|
-
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
1209
|
+
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
944
1210
|
/* Execute a hook if present */
|
|
945
1211
|
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
946
1212
|
tagName,
|
|
@@ -977,15 +1243,21 @@
|
|
|
977
1243
|
return false;
|
|
978
1244
|
}
|
|
979
1245
|
}
|
|
980
|
-
/* Keep content except for bad-listed elements
|
|
1246
|
+
/* Keep content except for bad-listed elements.
|
|
1247
|
+
Use the cached prototype getters exclusively — the previous code
|
|
1248
|
+
had `|| currentNode.parentNode` / `|| currentNode.childNodes`
|
|
1249
|
+
fallbacks, but the cached getters always return the canonical
|
|
1250
|
+
value (or null for a real parent-less node), so the fallback
|
|
1251
|
+
path was dead in safe cases and a clobbering surface in unsafe
|
|
1252
|
+
ones. Falsy cached results stay falsy; the `if (childNodes &&
|
|
1253
|
+
parentNode)` check already gates correctly. */
|
|
981
1254
|
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
982
|
-
const parentNode = getParentNode(currentNode)
|
|
983
|
-
const childNodes = getChildNodes(currentNode)
|
|
1255
|
+
const parentNode = getParentNode(currentNode);
|
|
1256
|
+
const childNodes = getChildNodes(currentNode);
|
|
984
1257
|
if (childNodes && parentNode) {
|
|
985
1258
|
const childCount = childNodes.length;
|
|
986
1259
|
for (let i = childCount - 1; i >= 0; --i) {
|
|
987
1260
|
const childClone = cloneNode(childNodes[i], true);
|
|
988
|
-
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
|
|
989
1261
|
parentNode.insertBefore(childClone, getNextSibling(currentNode));
|
|
990
1262
|
}
|
|
991
1263
|
}
|
|
@@ -993,8 +1265,14 @@
|
|
|
993
1265
|
_forceRemove(currentNode);
|
|
994
1266
|
return true;
|
|
995
1267
|
}
|
|
996
|
-
/* Check whether element has a valid namespace
|
|
997
|
-
|
|
1268
|
+
/* Check whether element has a valid namespace.
|
|
1269
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
1270
|
+
nodeType getter rather than `instanceof Element`, which is realm-
|
|
1271
|
+
bound and short-circuits to false for any node minted in a different
|
|
1272
|
+
realm — letting a foreign-realm element with a forbidden namespace
|
|
1273
|
+
slip past the namespace check entirely. */
|
|
1274
|
+
const nt = getNodeType ? getNodeType(currentNode) : currentNode.nodeType;
|
|
1275
|
+
if (nt === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
|
|
998
1276
|
_forceRemove(currentNode);
|
|
999
1277
|
return true;
|
|
1000
1278
|
}
|
|
@@ -1007,7 +1285,7 @@
|
|
|
1007
1285
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1008
1286
|
/* Get the element's text content */
|
|
1009
1287
|
content = currentNode.textContent;
|
|
1010
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1288
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1011
1289
|
content = stringReplace(content, expr, ' ');
|
|
1012
1290
|
});
|
|
1013
1291
|
if (currentNode.textContent !== content) {
|
|
@@ -1039,11 +1317,12 @@
|
|
|
1039
1317
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
1040
1318
|
return false;
|
|
1041
1319
|
}
|
|
1320
|
+
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
1042
1321
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
1043
1322
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1044
1323
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1045
1324
|
We don't need to check the value; it's always URI safe. */
|
|
1046
|
-
if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (
|
|
1325
|
+
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]) {
|
|
1047
1326
|
if (
|
|
1048
1327
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1049
1328
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -1055,11 +1334,15 @@
|
|
|
1055
1334
|
return false;
|
|
1056
1335
|
}
|
|
1057
1336
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
1058
|
-
} 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) {
|
|
1337
|
+
} 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) {
|
|
1059
1338
|
return false;
|
|
1060
1339
|
} else ;
|
|
1061
1340
|
return true;
|
|
1062
1341
|
};
|
|
1342
|
+
/* Names the HTML spec reserves from valid-custom-element-name; these must
|
|
1343
|
+
* never be treated as basic custom elements even when a permissive
|
|
1344
|
+
* CUSTOM_ELEMENT_HANDLING.tagNameCheck is configured. */
|
|
1345
|
+
const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, ['annotation-xml', 'color-profile', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'missing-glyph']);
|
|
1063
1346
|
/**
|
|
1064
1347
|
* _isBasicCustomElement
|
|
1065
1348
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
@@ -1069,7 +1352,7 @@
|
|
|
1069
1352
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
1070
1353
|
*/
|
|
1071
1354
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1072
|
-
return tagName
|
|
1355
|
+
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
1073
1356
|
};
|
|
1074
1357
|
/**
|
|
1075
1358
|
* _sanitizeAttributes
|
|
@@ -1084,9 +1367,7 @@
|
|
|
1084
1367
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1085
1368
|
/* Execute a hook if present */
|
|
1086
1369
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
1087
|
-
const
|
|
1088
|
-
attributes
|
|
1089
|
-
} = currentNode;
|
|
1370
|
+
const attributes = currentNode.attributes;
|
|
1090
1371
|
/* Check if we have attributes; if not we might have a text node */
|
|
1091
1372
|
if (!attributes || _isClobbered(currentNode)) {
|
|
1092
1373
|
return;
|
|
@@ -1102,11 +1383,9 @@
|
|
|
1102
1383
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1103
1384
|
while (l--) {
|
|
1104
1385
|
const attr = attributes[l];
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
value: attrValue
|
|
1109
|
-
} = attr;
|
|
1386
|
+
const name = attr.name,
|
|
1387
|
+
namespaceURI = attr.namespaceURI,
|
|
1388
|
+
attrValue = attr.value;
|
|
1110
1389
|
const lcName = transformCaseFunc(name);
|
|
1111
1390
|
const initValue = attrValue;
|
|
1112
1391
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -1120,12 +1399,14 @@
|
|
|
1120
1399
|
/* Full DOM Clobbering protection via namespace isolation,
|
|
1121
1400
|
* Prefix id and name attributes with `user-content-`
|
|
1122
1401
|
*/
|
|
1123
|
-
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
1402
|
+
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name') && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
|
|
1124
1403
|
// Remove the attribute with this value
|
|
1125
1404
|
_removeAttribute(name, currentNode);
|
|
1126
1405
|
// Prefix the value and later re-create the attribute with the sanitized value
|
|
1127
1406
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
1128
1407
|
}
|
|
1408
|
+
// Else: already prefixed, leave the attribute alone — the prefix is
|
|
1409
|
+
// itself the clobbering protection, and re-applying it is incorrect.
|
|
1129
1410
|
/* Work around a security issue with comments inside attributes */
|
|
1130
1411
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
1131
1412
|
_removeAttribute(name, currentNode);
|
|
@@ -1152,7 +1433,7 @@
|
|
|
1152
1433
|
}
|
|
1153
1434
|
/* Sanitize attribute content to be template-safe */
|
|
1154
1435
|
if (SAFE_FOR_TEMPLATES) {
|
|
1155
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1436
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1156
1437
|
value = stringReplace(value, expr, ' ');
|
|
1157
1438
|
});
|
|
1158
1439
|
}
|
|
@@ -1168,7 +1449,7 @@
|
|
|
1168
1449
|
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
1169
1450
|
case 'TrustedHTML':
|
|
1170
1451
|
{
|
|
1171
|
-
value =
|
|
1452
|
+
value = _createTrustedHTML(value);
|
|
1172
1453
|
break;
|
|
1173
1454
|
}
|
|
1174
1455
|
case 'TrustedScriptURL':
|
|
@@ -1218,14 +1499,98 @@
|
|
|
1218
1499
|
_sanitizeElements(shadowNode);
|
|
1219
1500
|
/* Check attributes next */
|
|
1220
1501
|
_sanitizeAttributes(shadowNode);
|
|
1221
|
-
/* Deep shadow DOM detected
|
|
1222
|
-
|
|
1502
|
+
/* Deep shadow DOM detected.
|
|
1503
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use nodeType against the
|
|
1504
|
+
DOCUMENT_FRAGMENT_NODE constant rather than instanceof, so we
|
|
1505
|
+
recurse into <template>.content from foreign realms too. */
|
|
1506
|
+
if (_isDocumentFragment(shadowNode.content)) {
|
|
1223
1507
|
_sanitizeShadowDOM2(shadowNode.content);
|
|
1224
1508
|
}
|
|
1509
|
+
/* An element iterated here may itself host an attached
|
|
1510
|
+
shadow root. The default NodeIterator does not enter shadow
|
|
1511
|
+
trees, so a shadow root nested inside template.content was
|
|
1512
|
+
previously reached by no walk at all (the pre-pass at
|
|
1513
|
+
_sanitizeAttachedShadowRoots descends via childNodes, which
|
|
1514
|
+
doesn't enter template.content; the template-content recursion
|
|
1515
|
+
above iterates the content but never inspected shadowRoot).
|
|
1516
|
+
Walk it explicitly. The nodeType guard avoids reading
|
|
1517
|
+
shadowRoot off text / comment / CDATA / PI nodes that the
|
|
1518
|
+
iterator also surfaces. */
|
|
1519
|
+
const shadowNodeType = getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType;
|
|
1520
|
+
if (shadowNodeType === NODE_TYPE.element) {
|
|
1521
|
+
const innerSr = getShadowRoot ? getShadowRoot(shadowNode) : shadowNode.shadowRoot;
|
|
1522
|
+
if (_isDocumentFragment(innerSr)) {
|
|
1523
|
+
_sanitizeAttachedShadowRoots2(innerSr);
|
|
1524
|
+
_sanitizeShadowDOM2(innerSr);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1225
1527
|
}
|
|
1226
1528
|
/* Execute a hook if present */
|
|
1227
1529
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
1228
1530
|
};
|
|
1531
|
+
/**
|
|
1532
|
+
* _sanitizeAttachedShadowRoots
|
|
1533
|
+
*
|
|
1534
|
+
* Walks `root` and feeds every attached shadow root we encounter into
|
|
1535
|
+
* the existing _sanitizeShadowDOM pipeline. The default node iterator
|
|
1536
|
+
* does not descend into shadow trees, so nodes inside an attached
|
|
1537
|
+
* shadow root would otherwise be skipped entirely.
|
|
1538
|
+
*
|
|
1539
|
+
* Two real input paths put attached shadow roots in front of us:
|
|
1540
|
+
* 1. IN_PLACE on a DOM node that already has shadow roots attached.
|
|
1541
|
+
* 2. DOM-node input where importNode(dirty, true) deep-clones the
|
|
1542
|
+
* shadow root because it was created with `clonable: true`.
|
|
1543
|
+
*
|
|
1544
|
+
* This pass runs once, up front, so the main iteration loop (and the
|
|
1545
|
+
* existing _sanitizeShadowDOM template-content recursion) stay
|
|
1546
|
+
* untouched — string-input paths are not affected.
|
|
1547
|
+
*
|
|
1548
|
+
* @param root the subtree root to walk for attached shadow roots
|
|
1549
|
+
*/
|
|
1550
|
+
const _sanitizeAttachedShadowRoots2 = function _sanitizeAttachedShadowRoots(root) {
|
|
1551
|
+
const nodeType = getNodeType ? getNodeType(root) : root.nodeType;
|
|
1552
|
+
if (nodeType === NODE_TYPE.element) {
|
|
1553
|
+
const sr = getShadowRoot ? getShadowRoot(root) : root.shadowRoot;
|
|
1554
|
+
// Realm-safe check (GHSA-hpcv-96wg-7vj8): use nodeType-based
|
|
1555
|
+
// detection rather than `instanceof DocumentFragment`, which is
|
|
1556
|
+
// realm-bound and silently skipped shadow roots whose host element
|
|
1557
|
+
// belonged to a foreign realm (e.g. iframe.contentDocument
|
|
1558
|
+
// attachShadow). A foreign-realm ShadowRoot extends the foreign
|
|
1559
|
+
// realm's DocumentFragment, not ours, so the old instanceof check
|
|
1560
|
+
// returned false and the shadow subtree was never walked.
|
|
1561
|
+
if (_isDocumentFragment(sr)) {
|
|
1562
|
+
// Recurse first so that nested shadow roots are reached even if
|
|
1563
|
+
// _sanitizeShadowDOM removes hosts at this level.
|
|
1564
|
+
_sanitizeAttachedShadowRoots2(sr);
|
|
1565
|
+
_sanitizeShadowDOM2(sr);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
// Snapshot children before recursing. Sanitization of one subtree
|
|
1569
|
+
// (e.g. via an uponSanitizeShadowNode hook) may detach siblings,
|
|
1570
|
+
// and naive nextSibling traversal would silently skip the rest of
|
|
1571
|
+
// the list once a node is detached.
|
|
1572
|
+
const childNodes = getChildNodes ? getChildNodes(root) : root.childNodes;
|
|
1573
|
+
if (!childNodes) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
const snapshot = [];
|
|
1577
|
+
arrayForEach(childNodes, child => {
|
|
1578
|
+
arrayPush(snapshot, child);
|
|
1579
|
+
});
|
|
1580
|
+
for (const child of snapshot) {
|
|
1581
|
+
_sanitizeAttachedShadowRoots2(child);
|
|
1582
|
+
}
|
|
1583
|
+
/* When the root is a <template>, also descend into root.content */
|
|
1584
|
+
if (nodeType === NODE_TYPE.element) {
|
|
1585
|
+
const rootName = getNodeName ? getNodeName(root) : null;
|
|
1586
|
+
if (typeof rootName === 'string' && transformCaseFunc(rootName) === 'template') {
|
|
1587
|
+
const content = root.content;
|
|
1588
|
+
if (_isDocumentFragment(content)) {
|
|
1589
|
+
_sanitizeAttachedShadowRoots2(content);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1229
1594
|
// eslint-disable-next-line complexity
|
|
1230
1595
|
DOMPurify.sanitize = function (dirty) {
|
|
1231
1596
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -1242,13 +1607,9 @@
|
|
|
1242
1607
|
}
|
|
1243
1608
|
/* Stringify, in case dirty is an object */
|
|
1244
1609
|
if (typeof dirty !== 'string' && !_isNode(dirty)) {
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
throw typeErrorCreate('dirty is not a string, aborting');
|
|
1249
|
-
}
|
|
1250
|
-
} else {
|
|
1251
|
-
throw typeErrorCreate('toString is not a function');
|
|
1610
|
+
dirty = stringifyValue(dirty);
|
|
1611
|
+
if (typeof dirty !== 'string') {
|
|
1612
|
+
throw typeErrorCreate('dirty is not a string, aborting');
|
|
1252
1613
|
}
|
|
1253
1614
|
}
|
|
1254
1615
|
/* Return dirty HTML if DOMPurify cannot run */
|
|
@@ -1266,14 +1627,35 @@
|
|
|
1266
1627
|
IN_PLACE = false;
|
|
1267
1628
|
}
|
|
1268
1629
|
if (IN_PLACE) {
|
|
1269
|
-
/* Do some early pre-sanitization to avoid unsafe root nodes
|
|
1270
|
-
|
|
1271
|
-
|
|
1630
|
+
/* Do some early pre-sanitization to avoid unsafe root nodes.
|
|
1631
|
+
Read nodeName through the cached prototype getter — a clobbering
|
|
1632
|
+
child named "nodeName" on the form root would otherwise shadow
|
|
1633
|
+
the property and let this check skip the root-allowlist
|
|
1634
|
+
validation entirely. */
|
|
1635
|
+
const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
|
|
1636
|
+
if (typeof nn === 'string') {
|
|
1637
|
+
const tagName = transformCaseFunc(nn);
|
|
1272
1638
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
1273
1639
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
1274
1640
|
}
|
|
1275
1641
|
}
|
|
1276
|
-
|
|
1642
|
+
/* Pre-flight the root through _isClobbered. The iterator-driven
|
|
1643
|
+
removal path can not detach a parent-less root: _forceRemove
|
|
1644
|
+
falls through to Element.prototype.remove(), which per spec
|
|
1645
|
+
is a no-op on a node with no parent. A clobbered root would
|
|
1646
|
+
then survive the main loop with its attributes uninspected,
|
|
1647
|
+
because _sanitizeAttributes early-returns on _isClobbered. The
|
|
1648
|
+
result would be an attacker-controlled form, complete with any
|
|
1649
|
+
event-handler attributes the caller passed in, handed back to
|
|
1650
|
+
the application unsanitized. Refuse to sanitize such a root
|
|
1651
|
+
the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
|
|
1652
|
+
if (_isClobbered(dirty)) {
|
|
1653
|
+
throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
|
|
1654
|
+
}
|
|
1655
|
+
/* Sanitize attached shadow roots before the main iterator runs.
|
|
1656
|
+
The iterator does not descend into shadow trees. */
|
|
1657
|
+
_sanitizeAttachedShadowRoots2(dirty);
|
|
1658
|
+
} else if (_isNode(dirty)) {
|
|
1277
1659
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
1278
1660
|
elements being stripped by the parser */
|
|
1279
1661
|
body = _initDocument('<!---->');
|
|
@@ -1287,12 +1669,18 @@
|
|
|
1287
1669
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
1288
1670
|
body.appendChild(importedNode);
|
|
1289
1671
|
}
|
|
1672
|
+
/* Clonable shadow roots are deep-cloned by importNode(); sanitize
|
|
1673
|
+
them before the main iterator runs, since the iterator does not
|
|
1674
|
+
descend into shadow trees. The walk routes every read through a
|
|
1675
|
+
cached prototype getter so clobbering descendants on a form root
|
|
1676
|
+
cannot hide a shadow host from this pass. */
|
|
1677
|
+
_sanitizeAttachedShadowRoots2(importedNode);
|
|
1290
1678
|
} else {
|
|
1291
1679
|
/* Exit directly if we have nothing to do */
|
|
1292
1680
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
1293
1681
|
// eslint-disable-next-line unicorn/prefer-includes
|
|
1294
1682
|
dirty.indexOf('<') === -1) {
|
|
1295
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
1683
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
|
|
1296
1684
|
}
|
|
1297
1685
|
/* Initialize the document to work on */
|
|
1298
1686
|
body = _initDocument(dirty);
|
|
@@ -1313,24 +1701,25 @@
|
|
|
1313
1701
|
_sanitizeElements(currentNode);
|
|
1314
1702
|
/* Check attributes next */
|
|
1315
1703
|
_sanitizeAttributes(currentNode);
|
|
1316
|
-
/* Shadow DOM detected, sanitize it
|
|
1317
|
-
|
|
1704
|
+
/* Shadow DOM detected, sanitize it.
|
|
1705
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
|
|
1706
|
+
instead of instanceof, so foreign-realm <template>.content is
|
|
1707
|
+
walked correctly. */
|
|
1708
|
+
if (_isDocumentFragment(currentNode.content)) {
|
|
1318
1709
|
_sanitizeShadowDOM2(currentNode.content);
|
|
1319
1710
|
}
|
|
1320
1711
|
}
|
|
1321
1712
|
/* If we sanitized `dirty` in-place, return it. */
|
|
1322
1713
|
if (IN_PLACE) {
|
|
1714
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
1715
|
+
_scrubTemplateExpressions2(dirty);
|
|
1716
|
+
}
|
|
1323
1717
|
return dirty;
|
|
1324
1718
|
}
|
|
1325
1719
|
/* Return sanitized string or DOM */
|
|
1326
1720
|
if (RETURN_DOM) {
|
|
1327
1721
|
if (SAFE_FOR_TEMPLATES) {
|
|
1328
|
-
body
|
|
1329
|
-
let html = body.innerHTML;
|
|
1330
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1331
|
-
html = stringReplace(html, expr, ' ');
|
|
1332
|
-
});
|
|
1333
|
-
body.innerHTML = html;
|
|
1722
|
+
_scrubTemplateExpressions2(body);
|
|
1334
1723
|
}
|
|
1335
1724
|
if (RETURN_DOM_FRAGMENT) {
|
|
1336
1725
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
@@ -1360,11 +1749,11 @@
|
|
|
1360
1749
|
}
|
|
1361
1750
|
/* Sanitize final string template-safe */
|
|
1362
1751
|
if (SAFE_FOR_TEMPLATES) {
|
|
1363
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1752
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1364
1753
|
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
1365
1754
|
});
|
|
1366
1755
|
}
|
|
1367
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
1756
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
|
|
1368
1757
|
};
|
|
1369
1758
|
DOMPurify.setConfig = function () {
|
|
1370
1759
|
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|