@vaadin/bundles 25.1.3 → 25.1.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 +135 -135
- package/vaadin-bundle.json +1807 -1807
- package/vaadin.js +1592 -1201
- package/vaadin.js.map +1 -1
- package/vendors-node_modules_vaadin_markdown_node_modules_dompurify_dist_purify_es_mjs.js +554 -164
- package/vendors-node_modules_vaadin_markdown_node_modules_dompurify_dist_purify_es_mjs.js.map +1 -1
|
@@ -10,24 +10,65 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
10
10
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
11
11
|
/* harmony export */ "default": () => (/* binding */ purify)
|
|
12
12
|
/* harmony export */ });
|
|
13
|
-
/*! @license DOMPurify 3.
|
|
13
|
+
/*! @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 */
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
function _arrayLikeToArray(r, a) {
|
|
16
|
+
(null == a || a > r.length) && (a = r.length);
|
|
17
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
18
|
+
return n;
|
|
19
|
+
}
|
|
20
|
+
function _arrayWithHoles(r) {
|
|
21
|
+
if (Array.isArray(r)) return r;
|
|
22
|
+
}
|
|
23
|
+
function _iterableToArrayLimit(r, l) {
|
|
24
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
25
|
+
if (null != t) {
|
|
26
|
+
var e,
|
|
27
|
+
n,
|
|
28
|
+
i,
|
|
29
|
+
u,
|
|
30
|
+
a = [],
|
|
31
|
+
f = true,
|
|
32
|
+
o = false;
|
|
33
|
+
try {
|
|
34
|
+
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);
|
|
35
|
+
} catch (r) {
|
|
36
|
+
o = true, n = r;
|
|
37
|
+
} finally {
|
|
38
|
+
try {
|
|
39
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
40
|
+
} finally {
|
|
41
|
+
if (o) throw n;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return a;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function _nonIterableRest() {
|
|
48
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
49
|
+
}
|
|
50
|
+
function _slicedToArray(r, e) {
|
|
51
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
52
|
+
}
|
|
53
|
+
function _unsupportedIterableToArray(r, a) {
|
|
54
|
+
if (r) {
|
|
55
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
56
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
57
|
+
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;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const entries = Object.entries,
|
|
62
|
+
setPrototypeOf = Object.setPrototypeOf,
|
|
63
|
+
isFrozen = Object.isFrozen,
|
|
64
|
+
getPrototypeOf = Object.getPrototypeOf,
|
|
65
|
+
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
66
|
+
let freeze = Object.freeze,
|
|
67
|
+
seal = Object.seal,
|
|
68
|
+
create = Object.create; // eslint-disable-line import/no-mutable-exports
|
|
69
|
+
let _ref = typeof Reflect !== 'undefined' && Reflect,
|
|
70
|
+
apply = _ref.apply,
|
|
71
|
+
construct = _ref.construct;
|
|
31
72
|
if (!freeze) {
|
|
32
73
|
freeze = function freeze(x) {
|
|
33
74
|
return x;
|
|
@@ -59,13 +100,19 @@ const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
|
59
100
|
const arrayPop = unapply(Array.prototype.pop);
|
|
60
101
|
const arrayPush = unapply(Array.prototype.push);
|
|
61
102
|
const arraySplice = unapply(Array.prototype.splice);
|
|
103
|
+
const arrayIsArray = Array.isArray;
|
|
62
104
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
63
105
|
const stringToString = unapply(String.prototype.toString);
|
|
64
106
|
const stringMatch = unapply(String.prototype.match);
|
|
65
107
|
const stringReplace = unapply(String.prototype.replace);
|
|
66
108
|
const stringIndexOf = unapply(String.prototype.indexOf);
|
|
67
109
|
const stringTrim = unapply(String.prototype.trim);
|
|
110
|
+
const numberToString = unapply(Number.prototype.toString);
|
|
111
|
+
const booleanToString = unapply(Boolean.prototype.toString);
|
|
112
|
+
const bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);
|
|
113
|
+
const symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);
|
|
68
114
|
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
115
|
+
const objectToString = unapply(Object.prototype.toString);
|
|
69
116
|
const regExpTest = unapply(RegExp.prototype.test);
|
|
70
117
|
const typeErrorCreate = unconstruct(TypeError);
|
|
71
118
|
/**
|
|
@@ -115,6 +162,9 @@ function addToSet(set, array) {
|
|
|
115
162
|
// Prevent prototype setters from intercepting set as a this value.
|
|
116
163
|
setPrototypeOf(set, null);
|
|
117
164
|
}
|
|
165
|
+
if (!arrayIsArray(array)) {
|
|
166
|
+
return set;
|
|
167
|
+
}
|
|
118
168
|
let l = array.length;
|
|
119
169
|
while (l--) {
|
|
120
170
|
let element = array[l];
|
|
@@ -155,10 +205,13 @@ function cleanArray(array) {
|
|
|
155
205
|
*/
|
|
156
206
|
function clone(object) {
|
|
157
207
|
const newObject = create(null);
|
|
158
|
-
for (const
|
|
208
|
+
for (const _ref2 of entries(object)) {
|
|
209
|
+
var _ref3 = _slicedToArray(_ref2, 2);
|
|
210
|
+
const property = _ref3[0];
|
|
211
|
+
const value = _ref3[1];
|
|
159
212
|
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
160
213
|
if (isPropertyExist) {
|
|
161
|
-
if (
|
|
214
|
+
if (arrayIsArray(value)) {
|
|
162
215
|
newObject[property] = cleanArray(value);
|
|
163
216
|
} else if (value && typeof value === 'object' && value.constructor === Object) {
|
|
164
217
|
newObject[property] = clone(value);
|
|
@@ -169,6 +222,58 @@ function clone(object) {
|
|
|
169
222
|
}
|
|
170
223
|
return newObject;
|
|
171
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Convert non-node values into strings without depending on direct property access.
|
|
227
|
+
*
|
|
228
|
+
* @param value - The value to stringify.
|
|
229
|
+
* @returns A string representation of the provided value.
|
|
230
|
+
*/
|
|
231
|
+
function stringifyValue(value) {
|
|
232
|
+
switch (typeof value) {
|
|
233
|
+
case 'string':
|
|
234
|
+
{
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
case 'number':
|
|
238
|
+
{
|
|
239
|
+
return numberToString(value);
|
|
240
|
+
}
|
|
241
|
+
case 'boolean':
|
|
242
|
+
{
|
|
243
|
+
return booleanToString(value);
|
|
244
|
+
}
|
|
245
|
+
case 'bigint':
|
|
246
|
+
{
|
|
247
|
+
return bigintToString ? bigintToString(value) : '0';
|
|
248
|
+
}
|
|
249
|
+
case 'symbol':
|
|
250
|
+
{
|
|
251
|
+
return symbolToString ? symbolToString(value) : 'Symbol()';
|
|
252
|
+
}
|
|
253
|
+
case 'undefined':
|
|
254
|
+
{
|
|
255
|
+
return objectToString(value);
|
|
256
|
+
}
|
|
257
|
+
case 'function':
|
|
258
|
+
case 'object':
|
|
259
|
+
{
|
|
260
|
+
if (value === null) {
|
|
261
|
+
return objectToString(value);
|
|
262
|
+
}
|
|
263
|
+
const valueAsRecord = value;
|
|
264
|
+
const valueToString = lookupGetter(valueAsRecord, 'toString');
|
|
265
|
+
if (typeof valueToString === 'function') {
|
|
266
|
+
const stringified = valueToString(valueAsRecord);
|
|
267
|
+
return typeof stringified === 'string' ? stringified : objectToString(stringified);
|
|
268
|
+
}
|
|
269
|
+
return objectToString(value);
|
|
270
|
+
}
|
|
271
|
+
default:
|
|
272
|
+
{
|
|
273
|
+
return objectToString(value);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
172
277
|
/**
|
|
173
278
|
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
174
279
|
*
|
|
@@ -194,6 +299,14 @@ function lookupGetter(object, prop) {
|
|
|
194
299
|
}
|
|
195
300
|
return fallbackValue;
|
|
196
301
|
}
|
|
302
|
+
function isRegex(value) {
|
|
303
|
+
try {
|
|
304
|
+
regExpTest(value, '');
|
|
305
|
+
return true;
|
|
306
|
+
} catch (_unused) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
197
310
|
|
|
198
311
|
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']);
|
|
199
312
|
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']);
|
|
@@ -209,15 +322,14 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
|
|
|
209
322
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
210
323
|
const text = freeze(['#text']);
|
|
211
324
|
|
|
212
|
-
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'
|
|
325
|
+
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']);
|
|
213
326
|
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']);
|
|
214
|
-
const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', '
|
|
327
|
+
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']);
|
|
215
328
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
216
329
|
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
330
|
+
const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
|
|
331
|
+
const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
|
|
332
|
+
const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
|
|
221
333
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
222
334
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
223
335
|
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
|
|
@@ -228,20 +340,6 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
|
|
|
228
340
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
229
341
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
230
342
|
|
|
231
|
-
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
232
|
-
__proto__: null,
|
|
233
|
-
ARIA_ATTR: ARIA_ATTR,
|
|
234
|
-
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
235
|
-
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
236
|
-
DATA_ATTR: DATA_ATTR,
|
|
237
|
-
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
238
|
-
ERB_EXPR: ERB_EXPR,
|
|
239
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
240
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
241
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
242
|
-
TMPLIT_EXPR: TMPLIT_EXPR
|
|
243
|
-
});
|
|
244
|
-
|
|
245
343
|
/* eslint-disable @typescript-eslint/indent */
|
|
246
344
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
247
345
|
const NODE_TYPE = {
|
|
@@ -317,7 +415,7 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
317
415
|
function createDOMPurify() {
|
|
318
416
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
319
417
|
const DOMPurify = root => createDOMPurify(root);
|
|
320
|
-
DOMPurify.version = '3.
|
|
418
|
+
DOMPurify.version = '3.4.8';
|
|
321
419
|
DOMPurify.removed = [];
|
|
322
420
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
323
421
|
// Not running in a browser, provide a factory function
|
|
@@ -325,28 +423,29 @@ function createDOMPurify() {
|
|
|
325
423
|
DOMPurify.isSupported = false;
|
|
326
424
|
return DOMPurify;
|
|
327
425
|
}
|
|
328
|
-
let
|
|
329
|
-
document
|
|
330
|
-
} = window;
|
|
426
|
+
let document = window.document;
|
|
331
427
|
const originalDocument = document;
|
|
332
428
|
const currentScript = originalDocument.currentScript;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
NamedNodeMap
|
|
340
|
-
HTMLFormElement
|
|
341
|
-
DOMParser,
|
|
342
|
-
trustedTypes
|
|
343
|
-
} = window;
|
|
429
|
+
window.DocumentFragment;
|
|
430
|
+
const HTMLTemplateElement = window.HTMLTemplateElement,
|
|
431
|
+
Node = window.Node,
|
|
432
|
+
Element = window.Element,
|
|
433
|
+
NodeFilter = window.NodeFilter,
|
|
434
|
+
_window$NamedNodeMap = window.NamedNodeMap;
|
|
435
|
+
_window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap;
|
|
436
|
+
window.HTMLFormElement;
|
|
437
|
+
const DOMParser = window.DOMParser,
|
|
438
|
+
trustedTypes = window.trustedTypes;
|
|
344
439
|
const ElementPrototype = Element.prototype;
|
|
345
440
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
346
441
|
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
347
442
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
348
443
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
349
444
|
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
445
|
+
const getShadowRoot = lookupGetter(ElementPrototype, 'shadowRoot');
|
|
446
|
+
const getAttributes = lookupGetter(ElementPrototype, 'attributes');
|
|
447
|
+
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
|
|
448
|
+
const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeName') : null;
|
|
350
449
|
// As per issue #47, the web-components registry is inherited by a
|
|
351
450
|
// new document created via createHTMLDocument. As per the spec
|
|
352
451
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -361,33 +460,43 @@ function createDOMPurify() {
|
|
|
361
460
|
}
|
|
362
461
|
let trustedTypesPolicy;
|
|
363
462
|
let emptyHTML = '';
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const {
|
|
371
|
-
|
|
372
|
-
|
|
463
|
+
// Tracks whether we are already inside a call to the configured Trusted Types
|
|
464
|
+
// policy's `createHTML`. If the supplied `TRUSTED_TYPES_POLICY.createHTML`
|
|
465
|
+
// itself calls `DOMPurify.sanitize` (the cause of #1422), `sanitize` would
|
|
466
|
+
// re-enter the policy and recurse until the stack overflows. We detect that
|
|
467
|
+
// re-entry and throw a clear, actionable error instead.
|
|
468
|
+
let IN_POLICY_CREATE_HTML = 0;
|
|
469
|
+
const _createTrustedHTML = function _createTrustedHTML(html) {
|
|
470
|
+
if (IN_POLICY_CREATE_HTML > 0) {
|
|
471
|
+
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.');
|
|
472
|
+
}
|
|
473
|
+
IN_POLICY_CREATE_HTML++;
|
|
474
|
+
try {
|
|
475
|
+
return trustedTypesPolicy.createHTML(html);
|
|
476
|
+
} finally {
|
|
477
|
+
IN_POLICY_CREATE_HTML--;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
const _document = document,
|
|
481
|
+
implementation = _document.implementation,
|
|
482
|
+
createNodeIterator = _document.createNodeIterator,
|
|
483
|
+
createDocumentFragment = _document.createDocumentFragment,
|
|
484
|
+
getElementsByTagName = _document.getElementsByTagName;
|
|
485
|
+
const importNode = originalDocument.importNode;
|
|
373
486
|
let hooks = _createHooksMap();
|
|
374
487
|
/**
|
|
375
488
|
* Expose whether this browser supports running the full DOMPurify.
|
|
376
489
|
*/
|
|
377
490
|
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
} = EXPRESSIONS;
|
|
388
|
-
let {
|
|
389
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
390
|
-
} = EXPRESSIONS;
|
|
491
|
+
const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
|
|
492
|
+
ERB_EXPR$1 = ERB_EXPR,
|
|
493
|
+
TMPLIT_EXPR$1 = TMPLIT_EXPR,
|
|
494
|
+
DATA_ATTR$1 = DATA_ATTR,
|
|
495
|
+
ARIA_ATTR$1 = ARIA_ATTR,
|
|
496
|
+
IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
|
|
497
|
+
ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
|
|
498
|
+
CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
|
|
499
|
+
let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
|
|
391
500
|
/**
|
|
392
501
|
* We consider the elements and attributes below to be safe. Ideally
|
|
393
502
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
@@ -565,15 +674,15 @@ function createDOMPurify() {
|
|
|
565
674
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
566
675
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
567
676
|
/* Set configuration parameters */
|
|
568
|
-
ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
|
|
569
|
-
ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
|
|
570
|
-
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
|
|
571
|
-
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;
|
|
572
|
-
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;
|
|
573
|
-
FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
|
|
574
|
-
FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
|
|
575
|
-
FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
|
|
576
|
-
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
|
|
677
|
+
ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') && arrayIsArray(cfg.ALLOWED_TAGS) ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
|
|
678
|
+
ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') && arrayIsArray(cfg.ALLOWED_ATTR) ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
|
|
679
|
+
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') && arrayIsArray(cfg.ALLOWED_NAMESPACES) ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
|
|
680
|
+
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;
|
|
681
|
+
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;
|
|
682
|
+
FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS) ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
|
|
683
|
+
FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') && arrayIsArray(cfg.FORBID_TAGS) ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
|
|
684
|
+
FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') && arrayIsArray(cfg.FORBID_ATTR) ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
|
|
685
|
+
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
|
|
577
686
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
578
687
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
579
688
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
|
|
@@ -589,19 +698,20 @@ function createDOMPurify() {
|
|
|
589
698
|
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
|
|
590
699
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
|
|
591
700
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
592
|
-
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP
|
|
593
|
-
NAMESPACE = cfg.NAMESPACE
|
|
594
|
-
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS
|
|
595
|
-
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
701
|
+
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
|
|
702
|
+
NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
|
|
703
|
+
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
|
|
704
|
+
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
|
|
705
|
+
const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
|
|
706
|
+
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
707
|
+
if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
|
|
708
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; // Default undefined
|
|
599
709
|
}
|
|
600
|
-
if (
|
|
601
|
-
CUSTOM_ELEMENT_HANDLING.attributeNameCheck =
|
|
710
|
+
if (objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && isRegexOrFunction(customElementHandling.attributeNameCheck)) {
|
|
711
|
+
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; // Default undefined
|
|
602
712
|
}
|
|
603
|
-
if (
|
|
604
|
-
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements =
|
|
713
|
+
if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
|
|
714
|
+
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
|
|
605
715
|
}
|
|
606
716
|
if (SAFE_FOR_TEMPLATES) {
|
|
607
717
|
ALLOW_DATA_ATTR = false;
|
|
@@ -633,44 +743,41 @@ function createDOMPurify() {
|
|
|
633
743
|
addToSet(ALLOWED_ATTR, xml);
|
|
634
744
|
}
|
|
635
745
|
}
|
|
636
|
-
/*
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (!objectHasOwnProperty(cfg, 'ADD_ATTR')) {
|
|
641
|
-
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
|
|
642
|
-
}
|
|
746
|
+
/* Always reset function-based ADD_TAGS / ADD_ATTR checks to prevent
|
|
747
|
+
* leaking across calls when switching from function to array config */
|
|
748
|
+
EXTRA_ELEMENT_HANDLING.tagCheck = null;
|
|
749
|
+
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
|
|
643
750
|
/* Merge configuration parameters */
|
|
644
|
-
if (cfg
|
|
751
|
+
if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {
|
|
645
752
|
if (typeof cfg.ADD_TAGS === 'function') {
|
|
646
753
|
EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
|
|
647
|
-
} else {
|
|
754
|
+
} else if (arrayIsArray(cfg.ADD_TAGS)) {
|
|
648
755
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
649
756
|
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
650
757
|
}
|
|
651
758
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
652
759
|
}
|
|
653
760
|
}
|
|
654
|
-
if (cfg
|
|
761
|
+
if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {
|
|
655
762
|
if (typeof cfg.ADD_ATTR === 'function') {
|
|
656
763
|
EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
|
|
657
|
-
} else {
|
|
764
|
+
} else if (arrayIsArray(cfg.ADD_ATTR)) {
|
|
658
765
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
659
766
|
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
660
767
|
}
|
|
661
768
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
662
769
|
}
|
|
663
770
|
}
|
|
664
|
-
if (cfg.ADD_URI_SAFE_ATTR) {
|
|
771
|
+
if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {
|
|
665
772
|
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
666
773
|
}
|
|
667
|
-
if (cfg.FORBID_CONTENTS) {
|
|
774
|
+
if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {
|
|
668
775
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
669
776
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
670
777
|
}
|
|
671
778
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
672
779
|
}
|
|
673
|
-
if (cfg.ADD_FORBID_CONTENTS) {
|
|
780
|
+
if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
|
|
674
781
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
675
782
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
676
783
|
}
|
|
@@ -697,19 +804,47 @@ function createDOMPurify() {
|
|
|
697
804
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
698
805
|
}
|
|
699
806
|
// Overwrite existing TrustedTypes policy.
|
|
807
|
+
const previousTrustedTypesPolicy = trustedTypesPolicy;
|
|
700
808
|
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
701
|
-
// Sign local variables required by `sanitize`.
|
|
702
|
-
|
|
809
|
+
// Sign local variables required by `sanitize`. If the supplied policy's
|
|
810
|
+
// `createHTML` is circular (i.e. it calls `DOMPurify.sanitize`), this
|
|
811
|
+
// throws via the re-entrancy guard. Restore the previous policy first so
|
|
812
|
+
// the instance is not left in a poisoned state. See #1422.
|
|
813
|
+
try {
|
|
814
|
+
emptyHTML = _createTrustedHTML('');
|
|
815
|
+
} catch (error) {
|
|
816
|
+
trustedTypesPolicy = previousTrustedTypesPolicy;
|
|
817
|
+
throw error;
|
|
818
|
+
}
|
|
703
819
|
} else {
|
|
704
820
|
// Uninitialized policy, attempt to initialize the internal dompurify policy.
|
|
705
|
-
if (trustedTypesPolicy === undefined) {
|
|
821
|
+
if (trustedTypesPolicy === undefined && cfg.TRUSTED_TYPES_POLICY !== null) {
|
|
706
822
|
trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
707
823
|
}
|
|
708
824
|
// If creating the internal policy succeeded sign internal variables.
|
|
709
|
-
|
|
710
|
-
|
|
825
|
+
// Note: a falsy `trustedTypesPolicy` (null when policy creation failed or
|
|
826
|
+
// was skipped via `TRUSTED_TYPES_POLICY: null`, or undefined when no
|
|
827
|
+
// policy has been initialized yet) must be excluded here, otherwise we
|
|
828
|
+
// would call `.createHTML` on a non-policy and throw. See #1422.
|
|
829
|
+
if (trustedTypesPolicy && typeof emptyHTML === 'string') {
|
|
830
|
+
emptyHTML = _createTrustedHTML('');
|
|
711
831
|
}
|
|
712
832
|
}
|
|
833
|
+
/*
|
|
834
|
+
* Mirror the clone-before-mutate pattern already applied above for
|
|
835
|
+
* cfg.ADD_TAGS / cfg.ADD_ATTR: if any uponSanitize* hook is
|
|
836
|
+
* registered AND the set still points at the default constant,
|
|
837
|
+
* clone it. The hook then mutates the clone (in-call widening
|
|
838
|
+
* still works exactly as documented) and the next default-cfg
|
|
839
|
+
* call rebinds to the untouched original via the reassignment at
|
|
840
|
+
* the top of this function.
|
|
841
|
+
*/
|
|
842
|
+
if ((hooks.uponSanitizeElement.length > 0 || hooks.uponSanitizeAttribute.length > 0) && ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
843
|
+
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
844
|
+
}
|
|
845
|
+
if (hooks.uponSanitizeAttribute.length > 0 && ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
846
|
+
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
847
|
+
}
|
|
713
848
|
// Prevent further manipulation of configuration.
|
|
714
849
|
// Not available in IE8, Safari 5, etc.
|
|
715
850
|
if (freeze) {
|
|
@@ -869,7 +1004,7 @@ function createDOMPurify() {
|
|
|
869
1004
|
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
|
|
870
1005
|
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
|
|
871
1006
|
}
|
|
872
|
-
const dirtyPayload = trustedTypesPolicy ?
|
|
1007
|
+
const dirtyPayload = trustedTypesPolicy ? _createTrustedHTML(dirty) : dirty;
|
|
873
1008
|
/*
|
|
874
1009
|
* Use the DOMParser API by default, fallback later if needs be
|
|
875
1010
|
* DOMParser not work for svg when has multiple root element.
|
|
@@ -909,23 +1044,142 @@ function createDOMPurify() {
|
|
|
909
1044
|
// eslint-disable-next-line no-bitwise
|
|
910
1045
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
911
1046
|
};
|
|
1047
|
+
/**
|
|
1048
|
+
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
1049
|
+
* character data of an element subtree. Used as the final safety net for
|
|
1050
|
+
* SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
|
|
1051
|
+
* which only form after text-node normalization (e.g. fragments split across
|
|
1052
|
+
* stripped elements) cannot survive into a template-evaluating framework.
|
|
1053
|
+
*
|
|
1054
|
+
* Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
|
|
1055
|
+
* in place rather than round-tripping through innerHTML. This preserves
|
|
1056
|
+
* descendant node references (important for IN_PLACE callers), avoids a
|
|
1057
|
+
* serialize/reparse cycle, and reads literal character data — which means
|
|
1058
|
+
* `<%...%>` in text content matches the ERB regex against its real bytes
|
|
1059
|
+
* instead of the HTML-entity-escaped form innerHTML would produce.
|
|
1060
|
+
*
|
|
1061
|
+
* Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
|
|
1062
|
+
* attributes is performed during the per-node `_sanitizeAttributes` pass.
|
|
1063
|
+
*
|
|
1064
|
+
* @param node The root element whose character data should be scrubbed.
|
|
1065
|
+
*/
|
|
1066
|
+
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
1067
|
+
var _node$querySelectorAl, _node$querySelectorAl2;
|
|
1068
|
+
node.normalize();
|
|
1069
|
+
const walker = createNodeIterator.call(node.ownerDocument || node, node,
|
|
1070
|
+
// eslint-disable-next-line no-bitwise
|
|
1071
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
1072
|
+
let currentNode = walker.nextNode();
|
|
1073
|
+
while (currentNode) {
|
|
1074
|
+
let data = currentNode.data;
|
|
1075
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1076
|
+
data = stringReplace(data, expr, ' ');
|
|
1077
|
+
});
|
|
1078
|
+
currentNode.data = data;
|
|
1079
|
+
currentNode = walker.nextNode();
|
|
1080
|
+
}
|
|
1081
|
+
// NodeIterator does not descend into <template>.content per the DOM spec,
|
|
1082
|
+
// so we must explicitly recurse into each template's content fragment,
|
|
1083
|
+
// mirroring the approach used by _sanitizeShadowDOM.
|
|
1084
|
+
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 : [];
|
|
1085
|
+
arrayForEach(Array.from(templates), tmpl => {
|
|
1086
|
+
if (_isDocumentFragment(tmpl.content)) {
|
|
1087
|
+
_scrubTemplateExpressions2(tmpl.content);
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
};
|
|
912
1091
|
/**
|
|
913
1092
|
* _isClobbered
|
|
914
1093
|
*
|
|
1094
|
+
* Detect DOM-clobbering on HTMLFormElement nodes. Form is the only HTML
|
|
1095
|
+
* interface with [LegacyOverrideBuiltIns]; a descendant element with a
|
|
1096
|
+
* `name` attribute matching a prototype property shadows that property
|
|
1097
|
+
* on direct reads. We use this check at the IN_PLACE entry-point and
|
|
1098
|
+
* during attribute sanitization to refuse clobbered forms.
|
|
1099
|
+
*
|
|
915
1100
|
* @param element element to check for clobbering attacks
|
|
916
1101
|
* @return true if clobbered, false if safe
|
|
917
1102
|
*/
|
|
918
1103
|
const _isClobbered = function _isClobbered(element) {
|
|
919
|
-
|
|
1104
|
+
// Realm-independent tag-name probe. If we can't determine the tag
|
|
1105
|
+
// name at all, we can't reason about clobbering — return false
|
|
1106
|
+
// (the caller's other defences still apply).
|
|
1107
|
+
const realTagName = getNodeName ? getNodeName(element) : null;
|
|
1108
|
+
if (typeof realTagName !== 'string') {
|
|
1109
|
+
return false;
|
|
1110
|
+
}
|
|
1111
|
+
if (transformCaseFunc(realTagName) !== 'form') {
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
1114
|
+
return typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' ||
|
|
1115
|
+
// Realm-safe NamedNodeMap detection: equality against the cached
|
|
1116
|
+
// prototype getter. Clobbered .attributes (e.g. <input name="attributes">)
|
|
1117
|
+
// makes the direct read diverge from the cached read; a clean form
|
|
1118
|
+
// (same-realm OR foreign-realm) has both reads pointing at the same
|
|
1119
|
+
// canonical NamedNodeMap.
|
|
1120
|
+
element.attributes !== getAttributes(element) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function' ||
|
|
1121
|
+
// NodeType clobbering probe. Cached Node.prototype.nodeType getter
|
|
1122
|
+
// returns the integer 1 for any Element regardless of realm; direct
|
|
1123
|
+
// read on a clobbered form (e.g. <input name="nodeType">) returns
|
|
1124
|
+
// the named child element. Cheap addition — nodeType is read from
|
|
1125
|
+
// an internal slot, no serialization cost — and removes a residual
|
|
1126
|
+
// clobbering surface used by several mXSS / PI / comment branches
|
|
1127
|
+
// in _sanitizeElements that compare currentNode.nodeType directly.
|
|
1128
|
+
element.nodeType !== getNodeType(element) ||
|
|
1129
|
+
// HTMLFormElement has [LegacyOverrideBuiltIns]: a descendant named
|
|
1130
|
+
// "childNodes" shadows the prototype getter. Direct reads of
|
|
1131
|
+
// form.childNodes from a clobbered form return the named child
|
|
1132
|
+
// instead of the real NodeList, so any walk that reads it directly
|
|
1133
|
+
// skips the form's real children. Compare the direct read to the
|
|
1134
|
+
// cached Node.prototype getter — when the form's named-property
|
|
1135
|
+
// getter intercepts the read, the two values differ and we flag
|
|
1136
|
+
// the form. This catches every clobbering child type (input,
|
|
1137
|
+
// select, etc.) regardless of whether the named child happens to
|
|
1138
|
+
// carry a numeric .length, which a typeof-based probe would miss
|
|
1139
|
+
// (e.g. HTMLSelectElement.length is a defined unsigned-long).
|
|
1140
|
+
element.childNodes !== getChildNodes(element);
|
|
920
1141
|
};
|
|
921
1142
|
/**
|
|
922
|
-
* Checks whether the given
|
|
1143
|
+
* Checks whether the given value is a DocumentFragment from any realm.
|
|
1144
|
+
*
|
|
1145
|
+
* The realm-independent replacement reads `nodeType` through the cached
|
|
1146
|
+
* Node.prototype getter and compares to the DOCUMENT_FRAGMENT_NODE
|
|
1147
|
+
* constant (11). nodeType is a numeric value resolved from the node's
|
|
1148
|
+
* internal slot, identical across realms for the same kind of node.
|
|
1149
|
+
*
|
|
1150
|
+
* @param value object to check
|
|
1151
|
+
* @return true if value is a DocumentFragment-shaped node from any realm
|
|
1152
|
+
*/
|
|
1153
|
+
const _isDocumentFragment = function _isDocumentFragment(value) {
|
|
1154
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
1155
|
+
return false;
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
return getNodeType(value) === NODE_TYPE.documentFragment;
|
|
1159
|
+
} catch (_) {
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
/**
|
|
1164
|
+
* Checks whether the given object is a DOM node, including nodes that
|
|
1165
|
+
* originate from a different window/realm (e.g. an iframe's
|
|
1166
|
+
* contentDocument). The previous `value instanceof Node` check was
|
|
1167
|
+
* realm-bound: nodes from a different window failed it, causing
|
|
1168
|
+
* sanitize() to silently stringify them and reset IN_PLACE to false,
|
|
1169
|
+
* returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
|
|
923
1170
|
*
|
|
924
1171
|
* @param value object to check whether it's a DOM node
|
|
925
|
-
* @return true
|
|
1172
|
+
* @return true if value is a DOM node from any realm
|
|
926
1173
|
*/
|
|
927
1174
|
const _isNode = function _isNode(value) {
|
|
928
|
-
|
|
1175
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
1176
|
+
return false;
|
|
1177
|
+
}
|
|
1178
|
+
try {
|
|
1179
|
+
return typeof getNodeType(value) === 'number';
|
|
1180
|
+
} catch (_) {
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
929
1183
|
};
|
|
930
1184
|
function _executeHooks(hooks, currentNode, data) {
|
|
931
1185
|
arrayForEach(hooks, hook => {
|
|
@@ -951,7 +1205,7 @@ function createDOMPurify() {
|
|
|
951
1205
|
return true;
|
|
952
1206
|
}
|
|
953
1207
|
/* Now let's check the element's type and name */
|
|
954
|
-
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
1208
|
+
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
955
1209
|
/* Execute a hook if present */
|
|
956
1210
|
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
957
1211
|
tagName,
|
|
@@ -962,6 +1216,11 @@ function createDOMPurify() {
|
|
|
962
1216
|
_forceRemove(currentNode);
|
|
963
1217
|
return true;
|
|
964
1218
|
}
|
|
1219
|
+
/* Remove risky CSS construction leading to mXSS */
|
|
1220
|
+
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
|
|
1221
|
+
_forceRemove(currentNode);
|
|
1222
|
+
return true;
|
|
1223
|
+
}
|
|
965
1224
|
/* Remove any occurrence of processing instructions */
|
|
966
1225
|
if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
|
|
967
1226
|
_forceRemove(currentNode);
|
|
@@ -973,7 +1232,7 @@ function createDOMPurify() {
|
|
|
973
1232
|
return true;
|
|
974
1233
|
}
|
|
975
1234
|
/* Remove element if anything forbids its presence */
|
|
976
|
-
if (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) &&
|
|
1235
|
+
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
977
1236
|
/* Check if we have a custom element to handle */
|
|
978
1237
|
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
979
1238
|
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
|
|
@@ -983,15 +1242,21 @@ function createDOMPurify() {
|
|
|
983
1242
|
return false;
|
|
984
1243
|
}
|
|
985
1244
|
}
|
|
986
|
-
/* Keep content except for bad-listed elements
|
|
1245
|
+
/* Keep content except for bad-listed elements.
|
|
1246
|
+
Use the cached prototype getters exclusively — the previous code
|
|
1247
|
+
had `|| currentNode.parentNode` / `|| currentNode.childNodes`
|
|
1248
|
+
fallbacks, but the cached getters always return the canonical
|
|
1249
|
+
value (or null for a real parent-less node), so the fallback
|
|
1250
|
+
path was dead in safe cases and a clobbering surface in unsafe
|
|
1251
|
+
ones. Falsy cached results stay falsy; the `if (childNodes &&
|
|
1252
|
+
parentNode)` check already gates correctly. */
|
|
987
1253
|
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
988
|
-
const parentNode = getParentNode(currentNode)
|
|
989
|
-
const childNodes = getChildNodes(currentNode)
|
|
1254
|
+
const parentNode = getParentNode(currentNode);
|
|
1255
|
+
const childNodes = getChildNodes(currentNode);
|
|
990
1256
|
if (childNodes && parentNode) {
|
|
991
1257
|
const childCount = childNodes.length;
|
|
992
1258
|
for (let i = childCount - 1; i >= 0; --i) {
|
|
993
1259
|
const childClone = cloneNode(childNodes[i], true);
|
|
994
|
-
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
|
|
995
1260
|
parentNode.insertBefore(childClone, getNextSibling(currentNode));
|
|
996
1261
|
}
|
|
997
1262
|
}
|
|
@@ -999,8 +1264,14 @@ function createDOMPurify() {
|
|
|
999
1264
|
_forceRemove(currentNode);
|
|
1000
1265
|
return true;
|
|
1001
1266
|
}
|
|
1002
|
-
/* Check whether element has a valid namespace
|
|
1003
|
-
|
|
1267
|
+
/* Check whether element has a valid namespace.
|
|
1268
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
1269
|
+
nodeType getter rather than `instanceof Element`, which is realm-
|
|
1270
|
+
bound and short-circuits to false for any node minted in a different
|
|
1271
|
+
realm — letting a foreign-realm element with a forbidden namespace
|
|
1272
|
+
slip past the namespace check entirely. */
|
|
1273
|
+
const nt = getNodeType ? getNodeType(currentNode) : currentNode.nodeType;
|
|
1274
|
+
if (nt === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
|
|
1004
1275
|
_forceRemove(currentNode);
|
|
1005
1276
|
return true;
|
|
1006
1277
|
}
|
|
@@ -1013,7 +1284,7 @@ function createDOMPurify() {
|
|
|
1013
1284
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1014
1285
|
/* Get the element's text content */
|
|
1015
1286
|
content = currentNode.textContent;
|
|
1016
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1287
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1017
1288
|
content = stringReplace(content, expr, ' ');
|
|
1018
1289
|
});
|
|
1019
1290
|
if (currentNode.textContent !== content) {
|
|
@@ -1045,11 +1316,12 @@ function createDOMPurify() {
|
|
|
1045
1316
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
1046
1317
|
return false;
|
|
1047
1318
|
}
|
|
1319
|
+
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
1048
1320
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
1049
1321
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1050
1322
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1051
1323
|
We don't need to check the value; it's always URI safe. */
|
|
1052
|
-
if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (
|
|
1324
|
+
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]) {
|
|
1053
1325
|
if (
|
|
1054
1326
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1055
1327
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -1061,11 +1333,15 @@ function createDOMPurify() {
|
|
|
1061
1333
|
return false;
|
|
1062
1334
|
}
|
|
1063
1335
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
1064
|
-
} 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) {
|
|
1336
|
+
} 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) {
|
|
1065
1337
|
return false;
|
|
1066
1338
|
} else ;
|
|
1067
1339
|
return true;
|
|
1068
1340
|
};
|
|
1341
|
+
/* Names the HTML spec reserves from valid-custom-element-name; these must
|
|
1342
|
+
* never be treated as basic custom elements even when a permissive
|
|
1343
|
+
* CUSTOM_ELEMENT_HANDLING.tagNameCheck is configured. */
|
|
1344
|
+
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']);
|
|
1069
1345
|
/**
|
|
1070
1346
|
* _isBasicCustomElement
|
|
1071
1347
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
@@ -1075,7 +1351,7 @@ function createDOMPurify() {
|
|
|
1075
1351
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
1076
1352
|
*/
|
|
1077
1353
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1078
|
-
return tagName
|
|
1354
|
+
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
1079
1355
|
};
|
|
1080
1356
|
/**
|
|
1081
1357
|
* _sanitizeAttributes
|
|
@@ -1090,9 +1366,7 @@ function createDOMPurify() {
|
|
|
1090
1366
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1091
1367
|
/* Execute a hook if present */
|
|
1092
1368
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
1093
|
-
const
|
|
1094
|
-
attributes
|
|
1095
|
-
} = currentNode;
|
|
1369
|
+
const attributes = currentNode.attributes;
|
|
1096
1370
|
/* Check if we have attributes; if not we might have a text node */
|
|
1097
1371
|
if (!attributes || _isClobbered(currentNode)) {
|
|
1098
1372
|
return;
|
|
@@ -1108,11 +1382,9 @@ function createDOMPurify() {
|
|
|
1108
1382
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1109
1383
|
while (l--) {
|
|
1110
1384
|
const attr = attributes[l];
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
value: attrValue
|
|
1115
|
-
} = attr;
|
|
1385
|
+
const name = attr.name,
|
|
1386
|
+
namespaceURI = attr.namespaceURI,
|
|
1387
|
+
attrValue = attr.value;
|
|
1116
1388
|
const lcName = transformCaseFunc(name);
|
|
1117
1389
|
const initValue = attrValue;
|
|
1118
1390
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -1126,12 +1398,14 @@ function createDOMPurify() {
|
|
|
1126
1398
|
/* Full DOM Clobbering protection via namespace isolation,
|
|
1127
1399
|
* Prefix id and name attributes with `user-content-`
|
|
1128
1400
|
*/
|
|
1129
|
-
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
1401
|
+
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name') && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
|
|
1130
1402
|
// Remove the attribute with this value
|
|
1131
1403
|
_removeAttribute(name, currentNode);
|
|
1132
1404
|
// Prefix the value and later re-create the attribute with the sanitized value
|
|
1133
1405
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
1134
1406
|
}
|
|
1407
|
+
// Else: already prefixed, leave the attribute alone — the prefix is
|
|
1408
|
+
// itself the clobbering protection, and re-applying it is incorrect.
|
|
1135
1409
|
/* Work around a security issue with comments inside attributes */
|
|
1136
1410
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
1137
1411
|
_removeAttribute(name, currentNode);
|
|
@@ -1158,7 +1432,7 @@ function createDOMPurify() {
|
|
|
1158
1432
|
}
|
|
1159
1433
|
/* Sanitize attribute content to be template-safe */
|
|
1160
1434
|
if (SAFE_FOR_TEMPLATES) {
|
|
1161
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1435
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1162
1436
|
value = stringReplace(value, expr, ' ');
|
|
1163
1437
|
});
|
|
1164
1438
|
}
|
|
@@ -1174,7 +1448,7 @@ function createDOMPurify() {
|
|
|
1174
1448
|
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
1175
1449
|
case 'TrustedHTML':
|
|
1176
1450
|
{
|
|
1177
|
-
value =
|
|
1451
|
+
value = _createTrustedHTML(value);
|
|
1178
1452
|
break;
|
|
1179
1453
|
}
|
|
1180
1454
|
case 'TrustedScriptURL':
|
|
@@ -1212,7 +1486,7 @@ function createDOMPurify() {
|
|
|
1212
1486
|
*
|
|
1213
1487
|
* @param fragment to iterate over recursively
|
|
1214
1488
|
*/
|
|
1215
|
-
const
|
|
1489
|
+
const _sanitizeShadowDOM2 = function _sanitizeShadowDOM(fragment) {
|
|
1216
1490
|
let shadowNode = null;
|
|
1217
1491
|
const shadowIterator = _createNodeIterator(fragment);
|
|
1218
1492
|
/* Execute a hook if present */
|
|
@@ -1224,14 +1498,98 @@ function createDOMPurify() {
|
|
|
1224
1498
|
_sanitizeElements(shadowNode);
|
|
1225
1499
|
/* Check attributes next */
|
|
1226
1500
|
_sanitizeAttributes(shadowNode);
|
|
1227
|
-
/* Deep shadow DOM detected
|
|
1228
|
-
|
|
1229
|
-
|
|
1501
|
+
/* Deep shadow DOM detected.
|
|
1502
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use nodeType against the
|
|
1503
|
+
DOCUMENT_FRAGMENT_NODE constant rather than instanceof, so we
|
|
1504
|
+
recurse into <template>.content from foreign realms too. */
|
|
1505
|
+
if (_isDocumentFragment(shadowNode.content)) {
|
|
1506
|
+
_sanitizeShadowDOM2(shadowNode.content);
|
|
1507
|
+
}
|
|
1508
|
+
/* An element iterated here may itself host an attached
|
|
1509
|
+
shadow root. The default NodeIterator does not enter shadow
|
|
1510
|
+
trees, so a shadow root nested inside template.content was
|
|
1511
|
+
previously reached by no walk at all (the pre-pass at
|
|
1512
|
+
_sanitizeAttachedShadowRoots descends via childNodes, which
|
|
1513
|
+
doesn't enter template.content; the template-content recursion
|
|
1514
|
+
above iterates the content but never inspected shadowRoot).
|
|
1515
|
+
Walk it explicitly. The nodeType guard avoids reading
|
|
1516
|
+
shadowRoot off text / comment / CDATA / PI nodes that the
|
|
1517
|
+
iterator also surfaces. */
|
|
1518
|
+
const shadowNodeType = getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType;
|
|
1519
|
+
if (shadowNodeType === NODE_TYPE.element) {
|
|
1520
|
+
const innerSr = getShadowRoot ? getShadowRoot(shadowNode) : shadowNode.shadowRoot;
|
|
1521
|
+
if (_isDocumentFragment(innerSr)) {
|
|
1522
|
+
_sanitizeAttachedShadowRoots2(innerSr);
|
|
1523
|
+
_sanitizeShadowDOM2(innerSr);
|
|
1524
|
+
}
|
|
1230
1525
|
}
|
|
1231
1526
|
}
|
|
1232
1527
|
/* Execute a hook if present */
|
|
1233
1528
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
1234
1529
|
};
|
|
1530
|
+
/**
|
|
1531
|
+
* _sanitizeAttachedShadowRoots
|
|
1532
|
+
*
|
|
1533
|
+
* Walks `root` and feeds every attached shadow root we encounter into
|
|
1534
|
+
* the existing _sanitizeShadowDOM pipeline. The default node iterator
|
|
1535
|
+
* does not descend into shadow trees, so nodes inside an attached
|
|
1536
|
+
* shadow root would otherwise be skipped entirely.
|
|
1537
|
+
*
|
|
1538
|
+
* Two real input paths put attached shadow roots in front of us:
|
|
1539
|
+
* 1. IN_PLACE on a DOM node that already has shadow roots attached.
|
|
1540
|
+
* 2. DOM-node input where importNode(dirty, true) deep-clones the
|
|
1541
|
+
* shadow root because it was created with `clonable: true`.
|
|
1542
|
+
*
|
|
1543
|
+
* This pass runs once, up front, so the main iteration loop (and the
|
|
1544
|
+
* existing _sanitizeShadowDOM template-content recursion) stay
|
|
1545
|
+
* untouched — string-input paths are not affected.
|
|
1546
|
+
*
|
|
1547
|
+
* @param root the subtree root to walk for attached shadow roots
|
|
1548
|
+
*/
|
|
1549
|
+
const _sanitizeAttachedShadowRoots2 = function _sanitizeAttachedShadowRoots(root) {
|
|
1550
|
+
const nodeType = getNodeType ? getNodeType(root) : root.nodeType;
|
|
1551
|
+
if (nodeType === NODE_TYPE.element) {
|
|
1552
|
+
const sr = getShadowRoot ? getShadowRoot(root) : root.shadowRoot;
|
|
1553
|
+
// Realm-safe check (GHSA-hpcv-96wg-7vj8): use nodeType-based
|
|
1554
|
+
// detection rather than `instanceof DocumentFragment`, which is
|
|
1555
|
+
// realm-bound and silently skipped shadow roots whose host element
|
|
1556
|
+
// belonged to a foreign realm (e.g. iframe.contentDocument
|
|
1557
|
+
// attachShadow). A foreign-realm ShadowRoot extends the foreign
|
|
1558
|
+
// realm's DocumentFragment, not ours, so the old instanceof check
|
|
1559
|
+
// returned false and the shadow subtree was never walked.
|
|
1560
|
+
if (_isDocumentFragment(sr)) {
|
|
1561
|
+
// Recurse first so that nested shadow roots are reached even if
|
|
1562
|
+
// _sanitizeShadowDOM removes hosts at this level.
|
|
1563
|
+
_sanitizeAttachedShadowRoots2(sr);
|
|
1564
|
+
_sanitizeShadowDOM2(sr);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
// Snapshot children before recursing. Sanitization of one subtree
|
|
1568
|
+
// (e.g. via an uponSanitizeShadowNode hook) may detach siblings,
|
|
1569
|
+
// and naive nextSibling traversal would silently skip the rest of
|
|
1570
|
+
// the list once a node is detached.
|
|
1571
|
+
const childNodes = getChildNodes ? getChildNodes(root) : root.childNodes;
|
|
1572
|
+
if (!childNodes) {
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
const snapshot = [];
|
|
1576
|
+
arrayForEach(childNodes, child => {
|
|
1577
|
+
arrayPush(snapshot, child);
|
|
1578
|
+
});
|
|
1579
|
+
for (const child of snapshot) {
|
|
1580
|
+
_sanitizeAttachedShadowRoots2(child);
|
|
1581
|
+
}
|
|
1582
|
+
/* When the root is a <template>, also descend into root.content */
|
|
1583
|
+
if (nodeType === NODE_TYPE.element) {
|
|
1584
|
+
const rootName = getNodeName ? getNodeName(root) : null;
|
|
1585
|
+
if (typeof rootName === 'string' && transformCaseFunc(rootName) === 'template') {
|
|
1586
|
+
const content = root.content;
|
|
1587
|
+
if (_isDocumentFragment(content)) {
|
|
1588
|
+
_sanitizeAttachedShadowRoots2(content);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1235
1593
|
// eslint-disable-next-line complexity
|
|
1236
1594
|
DOMPurify.sanitize = function (dirty) {
|
|
1237
1595
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -1248,13 +1606,9 @@ function createDOMPurify() {
|
|
|
1248
1606
|
}
|
|
1249
1607
|
/* Stringify, in case dirty is an object */
|
|
1250
1608
|
if (typeof dirty !== 'string' && !_isNode(dirty)) {
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
throw typeErrorCreate('dirty is not a string, aborting');
|
|
1255
|
-
}
|
|
1256
|
-
} else {
|
|
1257
|
-
throw typeErrorCreate('toString is not a function');
|
|
1609
|
+
dirty = stringifyValue(dirty);
|
|
1610
|
+
if (typeof dirty !== 'string') {
|
|
1611
|
+
throw typeErrorCreate('dirty is not a string, aborting');
|
|
1258
1612
|
}
|
|
1259
1613
|
}
|
|
1260
1614
|
/* Return dirty HTML if DOMPurify cannot run */
|
|
@@ -1272,14 +1626,35 @@ function createDOMPurify() {
|
|
|
1272
1626
|
IN_PLACE = false;
|
|
1273
1627
|
}
|
|
1274
1628
|
if (IN_PLACE) {
|
|
1275
|
-
/* Do some early pre-sanitization to avoid unsafe root nodes
|
|
1276
|
-
|
|
1277
|
-
|
|
1629
|
+
/* Do some early pre-sanitization to avoid unsafe root nodes.
|
|
1630
|
+
Read nodeName through the cached prototype getter — a clobbering
|
|
1631
|
+
child named "nodeName" on the form root would otherwise shadow
|
|
1632
|
+
the property and let this check skip the root-allowlist
|
|
1633
|
+
validation entirely. */
|
|
1634
|
+
const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
|
|
1635
|
+
if (typeof nn === 'string') {
|
|
1636
|
+
const tagName = transformCaseFunc(nn);
|
|
1278
1637
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
1279
1638
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
1280
1639
|
}
|
|
1281
1640
|
}
|
|
1282
|
-
|
|
1641
|
+
/* Pre-flight the root through _isClobbered. The iterator-driven
|
|
1642
|
+
removal path can not detach a parent-less root: _forceRemove
|
|
1643
|
+
falls through to Element.prototype.remove(), which per spec
|
|
1644
|
+
is a no-op on a node with no parent. A clobbered root would
|
|
1645
|
+
then survive the main loop with its attributes uninspected,
|
|
1646
|
+
because _sanitizeAttributes early-returns on _isClobbered. The
|
|
1647
|
+
result would be an attacker-controlled form, complete with any
|
|
1648
|
+
event-handler attributes the caller passed in, handed back to
|
|
1649
|
+
the application unsanitized. Refuse to sanitize such a root
|
|
1650
|
+
the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
|
|
1651
|
+
if (_isClobbered(dirty)) {
|
|
1652
|
+
throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
|
|
1653
|
+
}
|
|
1654
|
+
/* Sanitize attached shadow roots before the main iterator runs.
|
|
1655
|
+
The iterator does not descend into shadow trees. */
|
|
1656
|
+
_sanitizeAttachedShadowRoots2(dirty);
|
|
1657
|
+
} else if (_isNode(dirty)) {
|
|
1283
1658
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
1284
1659
|
elements being stripped by the parser */
|
|
1285
1660
|
body = _initDocument('<!---->');
|
|
@@ -1293,12 +1668,18 @@ function createDOMPurify() {
|
|
|
1293
1668
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
1294
1669
|
body.appendChild(importedNode);
|
|
1295
1670
|
}
|
|
1671
|
+
/* Clonable shadow roots are deep-cloned by importNode(); sanitize
|
|
1672
|
+
them before the main iterator runs, since the iterator does not
|
|
1673
|
+
descend into shadow trees. The walk routes every read through a
|
|
1674
|
+
cached prototype getter so clobbering descendants on a form root
|
|
1675
|
+
cannot hide a shadow host from this pass. */
|
|
1676
|
+
_sanitizeAttachedShadowRoots2(importedNode);
|
|
1296
1677
|
} else {
|
|
1297
1678
|
/* Exit directly if we have nothing to do */
|
|
1298
1679
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
1299
1680
|
// eslint-disable-next-line unicorn/prefer-includes
|
|
1300
1681
|
dirty.indexOf('<') === -1) {
|
|
1301
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
1682
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
|
|
1302
1683
|
}
|
|
1303
1684
|
/* Initialize the document to work on */
|
|
1304
1685
|
body = _initDocument(dirty);
|
|
@@ -1319,17 +1700,26 @@ function createDOMPurify() {
|
|
|
1319
1700
|
_sanitizeElements(currentNode);
|
|
1320
1701
|
/* Check attributes next */
|
|
1321
1702
|
_sanitizeAttributes(currentNode);
|
|
1322
|
-
/* Shadow DOM detected, sanitize it
|
|
1323
|
-
|
|
1324
|
-
|
|
1703
|
+
/* Shadow DOM detected, sanitize it.
|
|
1704
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
|
|
1705
|
+
instead of instanceof, so foreign-realm <template>.content is
|
|
1706
|
+
walked correctly. */
|
|
1707
|
+
if (_isDocumentFragment(currentNode.content)) {
|
|
1708
|
+
_sanitizeShadowDOM2(currentNode.content);
|
|
1325
1709
|
}
|
|
1326
1710
|
}
|
|
1327
1711
|
/* If we sanitized `dirty` in-place, return it. */
|
|
1328
1712
|
if (IN_PLACE) {
|
|
1713
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
1714
|
+
_scrubTemplateExpressions2(dirty);
|
|
1715
|
+
}
|
|
1329
1716
|
return dirty;
|
|
1330
1717
|
}
|
|
1331
1718
|
/* Return sanitized string or DOM */
|
|
1332
1719
|
if (RETURN_DOM) {
|
|
1720
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
1721
|
+
_scrubTemplateExpressions2(body);
|
|
1722
|
+
}
|
|
1333
1723
|
if (RETURN_DOM_FRAGMENT) {
|
|
1334
1724
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
1335
1725
|
while (body.firstChild) {
|
|
@@ -1358,11 +1748,11 @@ function createDOMPurify() {
|
|
|
1358
1748
|
}
|
|
1359
1749
|
/* Sanitize final string template-safe */
|
|
1360
1750
|
if (SAFE_FOR_TEMPLATES) {
|
|
1361
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1751
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1362
1752
|
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
1363
1753
|
});
|
|
1364
1754
|
}
|
|
1365
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
1755
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
|
|
1366
1756
|
};
|
|
1367
1757
|
DOMPurify.setConfig = function () {
|
|
1368
1758
|
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|