dompurify 3.4.2 → 3.4.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/README.md +5 -5
- package/dist/purify.cjs.d.ts +1 -1
- package/dist/purify.cjs.js +215 -99
- package/dist/purify.cjs.js.map +1 -1
- package/dist/purify.es.d.mts +1 -1
- package/dist/purify.es.mjs +215 -99
- package/dist/purify.es.mjs.map +1 -1
- package/dist/purify.js +1504 -1388
- package/dist/purify.js.map +1 -1
- package/dist/purify.min.js +2 -2
- package/dist/purify.min.js.map +1 -1
- package/package.json +8 -8
- package/src/attrs.ts +2 -0
- package/src/purify.ts +132 -10
- package/src/regexp.ts +3 -4
- package/src/tags.ts +1 -0
package/dist/purify.es.mjs
CHANGED
|
@@ -1,21 +1,62 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.4.
|
|
1
|
+
/*! @license DOMPurify 3.4.4 | (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.4/LICENSE */
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
function _arrayLikeToArray(r, a) {
|
|
4
|
+
(null == a || a > r.length) && (a = r.length);
|
|
5
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
6
|
+
return n;
|
|
7
|
+
}
|
|
8
|
+
function _arrayWithHoles(r) {
|
|
9
|
+
if (Array.isArray(r)) return r;
|
|
10
|
+
}
|
|
11
|
+
function _iterableToArrayLimit(r, l) {
|
|
12
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
13
|
+
if (null != t) {
|
|
14
|
+
var e,
|
|
15
|
+
n,
|
|
16
|
+
i,
|
|
17
|
+
u,
|
|
18
|
+
a = [],
|
|
19
|
+
f = true,
|
|
20
|
+
o = false;
|
|
21
|
+
try {
|
|
22
|
+
if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
23
|
+
} catch (r) {
|
|
24
|
+
o = true, n = r;
|
|
25
|
+
} finally {
|
|
26
|
+
try {
|
|
27
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
28
|
+
} finally {
|
|
29
|
+
if (o) throw n;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return a;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function _nonIterableRest() {
|
|
36
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
37
|
+
}
|
|
38
|
+
function _slicedToArray(r, e) {
|
|
39
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
40
|
+
}
|
|
41
|
+
function _unsupportedIterableToArray(r, a) {
|
|
42
|
+
if (r) {
|
|
43
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
44
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
45
|
+
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const entries = Object.entries,
|
|
50
|
+
setPrototypeOf = Object.setPrototypeOf,
|
|
51
|
+
isFrozen = Object.isFrozen,
|
|
52
|
+
getPrototypeOf = Object.getPrototypeOf,
|
|
53
|
+
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
54
|
+
let freeze = Object.freeze,
|
|
55
|
+
seal = Object.seal,
|
|
56
|
+
create = Object.create; // eslint-disable-line import/no-mutable-exports
|
|
57
|
+
let _ref = typeof Reflect !== 'undefined' && Reflect,
|
|
58
|
+
apply = _ref.apply,
|
|
59
|
+
construct = _ref.construct;
|
|
19
60
|
if (!freeze) {
|
|
20
61
|
freeze = function freeze(x) {
|
|
21
62
|
return x;
|
|
@@ -152,7 +193,10 @@ function cleanArray(array) {
|
|
|
152
193
|
*/
|
|
153
194
|
function clone(object) {
|
|
154
195
|
const newObject = create(null);
|
|
155
|
-
for (const
|
|
196
|
+
for (const _ref2 of entries(object)) {
|
|
197
|
+
var _ref3 = _slicedToArray(_ref2, 2);
|
|
198
|
+
const property = _ref3[0];
|
|
199
|
+
const value = _ref3[1];
|
|
156
200
|
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
157
201
|
if (isPropertyExist) {
|
|
158
202
|
if (arrayIsArray(value)) {
|
|
@@ -252,7 +296,7 @@ function isRegex(value) {
|
|
|
252
296
|
}
|
|
253
297
|
}
|
|
254
298
|
|
|
255
|
-
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']);
|
|
299
|
+
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', 'selectedcontent', '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']);
|
|
256
300
|
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']);
|
|
257
301
|
const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
|
|
258
302
|
// List of SVG elements that are disallowed by default.
|
|
@@ -266,15 +310,14 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
|
|
|
266
310
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
267
311
|
const text = freeze(['#text']);
|
|
268
312
|
|
|
269
|
-
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']);
|
|
313
|
+
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']);
|
|
270
314
|
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']);
|
|
271
315
|
const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
|
|
272
316
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
273
317
|
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
const
|
|
277
|
-
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
318
|
+
const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
|
|
319
|
+
const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
|
|
320
|
+
const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
|
|
278
321
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
279
322
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
280
323
|
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
|
@@ -285,20 +328,6 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
|
|
|
285
328
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
286
329
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
287
330
|
|
|
288
|
-
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
289
|
-
__proto__: null,
|
|
290
|
-
ARIA_ATTR: ARIA_ATTR,
|
|
291
|
-
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
292
|
-
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
293
|
-
DATA_ATTR: DATA_ATTR,
|
|
294
|
-
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
295
|
-
ERB_EXPR: ERB_EXPR,
|
|
296
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
297
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
298
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
299
|
-
TMPLIT_EXPR: TMPLIT_EXPR
|
|
300
|
-
});
|
|
301
|
-
|
|
302
331
|
/* eslint-disable @typescript-eslint/indent */
|
|
303
332
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
304
333
|
const NODE_TYPE = {
|
|
@@ -365,7 +394,7 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
365
394
|
function createDOMPurify() {
|
|
366
395
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
367
396
|
const DOMPurify = root => createDOMPurify(root);
|
|
368
|
-
DOMPurify.version = '3.4.
|
|
397
|
+
DOMPurify.version = '3.4.4';
|
|
369
398
|
DOMPurify.removed = [];
|
|
370
399
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
371
400
|
// Not running in a browser, provide a factory function
|
|
@@ -373,28 +402,26 @@ function createDOMPurify() {
|
|
|
373
402
|
DOMPurify.isSupported = false;
|
|
374
403
|
return DOMPurify;
|
|
375
404
|
}
|
|
376
|
-
let
|
|
377
|
-
document
|
|
378
|
-
} = window;
|
|
405
|
+
let document = window.document;
|
|
379
406
|
const originalDocument = document;
|
|
380
407
|
const currentScript = originalDocument.currentScript;
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
|
|
388
|
-
HTMLFormElement,
|
|
389
|
-
DOMParser,
|
|
390
|
-
trustedTypes
|
|
391
|
-
} = window;
|
|
408
|
+
const DocumentFragment = window.DocumentFragment,
|
|
409
|
+
HTMLTemplateElement = window.HTMLTemplateElement,
|
|
410
|
+
Node = window.Node,
|
|
411
|
+
Element = window.Element,
|
|
412
|
+
NodeFilter = window.NodeFilter,
|
|
413
|
+
_window$NamedNodeMap = window.NamedNodeMap,
|
|
414
|
+
NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
|
|
415
|
+
HTMLFormElement = window.HTMLFormElement,
|
|
416
|
+
DOMParser = window.DOMParser,
|
|
417
|
+
trustedTypes = window.trustedTypes;
|
|
392
418
|
const ElementPrototype = Element.prototype;
|
|
393
419
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
394
420
|
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
395
421
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
396
422
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
397
423
|
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
424
|
+
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
|
|
398
425
|
// As per issue #47, the web-components registry is inherited by a
|
|
399
426
|
// new document created via createHTMLDocument. As per the spec
|
|
400
427
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -409,33 +436,26 @@ function createDOMPurify() {
|
|
|
409
436
|
}
|
|
410
437
|
let trustedTypesPolicy;
|
|
411
438
|
let emptyHTML = '';
|
|
412
|
-
const
|
|
413
|
-
implementation,
|
|
414
|
-
createNodeIterator,
|
|
415
|
-
createDocumentFragment,
|
|
416
|
-
getElementsByTagName
|
|
417
|
-
|
|
418
|
-
const {
|
|
419
|
-
importNode
|
|
420
|
-
} = originalDocument;
|
|
439
|
+
const _document = document,
|
|
440
|
+
implementation = _document.implementation,
|
|
441
|
+
createNodeIterator = _document.createNodeIterator,
|
|
442
|
+
createDocumentFragment = _document.createDocumentFragment,
|
|
443
|
+
getElementsByTagName = _document.getElementsByTagName;
|
|
444
|
+
const importNode = originalDocument.importNode;
|
|
421
445
|
let hooks = _createHooksMap();
|
|
422
446
|
/**
|
|
423
447
|
* Expose whether this browser supports running the full DOMPurify.
|
|
424
448
|
*/
|
|
425
449
|
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
} = EXPRESSIONS;
|
|
436
|
-
let {
|
|
437
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
438
|
-
} = EXPRESSIONS;
|
|
450
|
+
const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
|
|
451
|
+
ERB_EXPR$1 = ERB_EXPR,
|
|
452
|
+
TMPLIT_EXPR$1 = TMPLIT_EXPR,
|
|
453
|
+
DATA_ATTR$1 = DATA_ATTR,
|
|
454
|
+
ARIA_ATTR$1 = ARIA_ATTR,
|
|
455
|
+
IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
|
|
456
|
+
ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
|
|
457
|
+
CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
|
|
458
|
+
let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
|
|
439
459
|
/**
|
|
440
460
|
* We consider the elements and attributes below to be safe. Ideally
|
|
441
461
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
@@ -955,6 +975,40 @@ function createDOMPurify() {
|
|
|
955
975
|
// eslint-disable-next-line no-bitwise
|
|
956
976
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
957
977
|
};
|
|
978
|
+
/**
|
|
979
|
+
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
980
|
+
* character data of an element subtree. Used as the final safety net for
|
|
981
|
+
* SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
|
|
982
|
+
* which only form after text-node normalization (e.g. fragments split across
|
|
983
|
+
* stripped elements) cannot survive into a template-evaluating framework.
|
|
984
|
+
*
|
|
985
|
+
* Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
|
|
986
|
+
* in place rather than round-tripping through innerHTML. This preserves
|
|
987
|
+
* descendant node references (important for IN_PLACE callers), avoids a
|
|
988
|
+
* serialize/reparse cycle, and reads literal character data — which means
|
|
989
|
+
* `<%...%>` in text content matches the ERB regex against its real bytes
|
|
990
|
+
* instead of the HTML-entity-escaped form innerHTML would produce.
|
|
991
|
+
*
|
|
992
|
+
* Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
|
|
993
|
+
* attributes is performed during the per-node `_sanitizeAttributes` pass.
|
|
994
|
+
*
|
|
995
|
+
* @param node The root element whose character data should be scrubbed.
|
|
996
|
+
*/
|
|
997
|
+
const _scrubTemplateExpressions = function _scrubTemplateExpressions(node) {
|
|
998
|
+
node.normalize();
|
|
999
|
+
const walker = createNodeIterator.call(node.ownerDocument || node, node,
|
|
1000
|
+
// eslint-disable-next-line no-bitwise
|
|
1001
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
1002
|
+
let currentNode = walker.nextNode();
|
|
1003
|
+
while (currentNode) {
|
|
1004
|
+
let data = currentNode.data;
|
|
1005
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1006
|
+
data = stringReplace(data, expr, ' ');
|
|
1007
|
+
});
|
|
1008
|
+
currentNode.data = data;
|
|
1009
|
+
currentNode = walker.nextNode();
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
958
1012
|
/**
|
|
959
1013
|
* _isClobbered
|
|
960
1014
|
*
|
|
@@ -965,13 +1019,31 @@ function createDOMPurify() {
|
|
|
965
1019
|
return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
|
|
966
1020
|
};
|
|
967
1021
|
/**
|
|
968
|
-
* Checks whether the given object is a DOM node
|
|
1022
|
+
* Checks whether the given object is a DOM node, including nodes that
|
|
1023
|
+
* originate from a different window/realm (e.g. an iframe's
|
|
1024
|
+
* contentDocument). The previous `value instanceof Node` check was
|
|
1025
|
+
* realm-bound: nodes from a different window failed it, causing
|
|
1026
|
+
* sanitize() to silently stringify them and reset IN_PLACE to false,
|
|
1027
|
+
* returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
|
|
1028
|
+
*
|
|
1029
|
+
* Implementation: call the cached `nodeType` getter from Node.prototype
|
|
1030
|
+
* directly on the value. This bypasses any clobbered instance property
|
|
1031
|
+
* (e.g. a child element named "nodeType") and works across realms
|
|
1032
|
+
* because the WebIDL `nodeType` getter reads an internal slot that
|
|
1033
|
+
* every real Node has, regardless of which window minted it.
|
|
969
1034
|
*
|
|
970
1035
|
* @param value object to check whether it's a DOM node
|
|
971
|
-
* @return true
|
|
1036
|
+
* @return true if value is a DOM node from any realm
|
|
972
1037
|
*/
|
|
973
1038
|
const _isNode = function _isNode(value) {
|
|
974
|
-
|
|
1039
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
try {
|
|
1043
|
+
return typeof getNodeType(value) === 'number';
|
|
1044
|
+
} catch (_) {
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
975
1047
|
};
|
|
976
1048
|
function _executeHooks(hooks, currentNode, data) {
|
|
977
1049
|
arrayForEach(hooks, hook => {
|
|
@@ -1063,7 +1135,7 @@ function createDOMPurify() {
|
|
|
1063
1135
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1064
1136
|
/* Get the element's text content */
|
|
1065
1137
|
content = currentNode.textContent;
|
|
1066
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1138
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1067
1139
|
content = stringReplace(content, expr, ' ');
|
|
1068
1140
|
});
|
|
1069
1141
|
if (currentNode.textContent !== content) {
|
|
@@ -1100,7 +1172,7 @@ function createDOMPurify() {
|
|
|
1100
1172
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1101
1173
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1102
1174
|
We don't need to check the value; it's always URI safe. */
|
|
1103
|
-
if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!nameIsPermitted || FORBID_ATTR[lcName]) {
|
|
1175
|
+
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]) {
|
|
1104
1176
|
if (
|
|
1105
1177
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1106
1178
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -1112,7 +1184,7 @@ function createDOMPurify() {
|
|
|
1112
1184
|
return false;
|
|
1113
1185
|
}
|
|
1114
1186
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
1115
|
-
} 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) {
|
|
1187
|
+
} 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) {
|
|
1116
1188
|
return false;
|
|
1117
1189
|
} else ;
|
|
1118
1190
|
return true;
|
|
@@ -1130,7 +1202,7 @@ function createDOMPurify() {
|
|
|
1130
1202
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
1131
1203
|
*/
|
|
1132
1204
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1133
|
-
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT, tagName);
|
|
1205
|
+
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
1134
1206
|
};
|
|
1135
1207
|
/**
|
|
1136
1208
|
* _sanitizeAttributes
|
|
@@ -1145,9 +1217,7 @@ function createDOMPurify() {
|
|
|
1145
1217
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1146
1218
|
/* Execute a hook if present */
|
|
1147
1219
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
1148
|
-
const
|
|
1149
|
-
attributes
|
|
1150
|
-
} = currentNode;
|
|
1220
|
+
const attributes = currentNode.attributes;
|
|
1151
1221
|
/* Check if we have attributes; if not we might have a text node */
|
|
1152
1222
|
if (!attributes || _isClobbered(currentNode)) {
|
|
1153
1223
|
return;
|
|
@@ -1163,11 +1233,9 @@ function createDOMPurify() {
|
|
|
1163
1233
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1164
1234
|
while (l--) {
|
|
1165
1235
|
const attr = attributes[l];
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
value: attrValue
|
|
1170
|
-
} = attr;
|
|
1236
|
+
const name = attr.name,
|
|
1237
|
+
namespaceURI = attr.namespaceURI,
|
|
1238
|
+
attrValue = attr.value;
|
|
1171
1239
|
const lcName = transformCaseFunc(name);
|
|
1172
1240
|
const initValue = attrValue;
|
|
1173
1241
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -1215,7 +1283,7 @@ function createDOMPurify() {
|
|
|
1215
1283
|
}
|
|
1216
1284
|
/* Sanitize attribute content to be template-safe */
|
|
1217
1285
|
if (SAFE_FOR_TEMPLATES) {
|
|
1218
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1286
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1219
1287
|
value = stringReplace(value, expr, ' ');
|
|
1220
1288
|
});
|
|
1221
1289
|
}
|
|
@@ -1289,6 +1357,49 @@ function createDOMPurify() {
|
|
|
1289
1357
|
/* Execute a hook if present */
|
|
1290
1358
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
1291
1359
|
};
|
|
1360
|
+
/**
|
|
1361
|
+
* _sanitizeAttachedShadowRoots
|
|
1362
|
+
*
|
|
1363
|
+
* Walks `root` and feeds every attached shadow root we encounter into
|
|
1364
|
+
* the existing _sanitizeShadowDOM pipeline. The default node iterator
|
|
1365
|
+
* does not descend into shadow trees, so nodes inside an attached
|
|
1366
|
+
* shadow root would otherwise be skipped entirely.
|
|
1367
|
+
*
|
|
1368
|
+
* Two real input paths put attached shadow roots in front of us:
|
|
1369
|
+
* 1. IN_PLACE on a DOM node that already has shadow roots attached.
|
|
1370
|
+
* 2. DOM-node input where importNode(dirty, true) deep-clones the
|
|
1371
|
+
* shadow root because it was created with `clonable: true`.
|
|
1372
|
+
*
|
|
1373
|
+
* This pass runs once, up front, so the main iteration loop (and the
|
|
1374
|
+
* existing _sanitizeShadowDOM template-content recursion) stay
|
|
1375
|
+
* untouched — string-input paths are not affected.
|
|
1376
|
+
*
|
|
1377
|
+
* @param root the subtree root to walk for attached shadow roots
|
|
1378
|
+
*/
|
|
1379
|
+
const _sanitizeAttachedShadowRoots2 = function _sanitizeAttachedShadowRoots(root) {
|
|
1380
|
+
if (root.nodeType === NODE_TYPE.element && root.shadowRoot instanceof DocumentFragment) {
|
|
1381
|
+
const sr = root.shadowRoot;
|
|
1382
|
+
// Recurse first so that nested shadow roots are reached even if
|
|
1383
|
+
// _sanitizeShadowDOM removes hosts at this level.
|
|
1384
|
+
_sanitizeAttachedShadowRoots2(sr);
|
|
1385
|
+
_sanitizeShadowDOM2(sr);
|
|
1386
|
+
}
|
|
1387
|
+
// Snapshot children before recursing. Sanitization of one subtree
|
|
1388
|
+
// (e.g. via an uponSanitizeShadowNode hook) may detach siblings,
|
|
1389
|
+
// and naive nextSibling traversal would silently skip the rest of
|
|
1390
|
+
// the list once a node is detached.
|
|
1391
|
+
const childNodes = root.childNodes;
|
|
1392
|
+
if (!childNodes) {
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const snapshot = [];
|
|
1396
|
+
arrayForEach(childNodes, child => {
|
|
1397
|
+
arrayPush(snapshot, child);
|
|
1398
|
+
});
|
|
1399
|
+
for (const child of snapshot) {
|
|
1400
|
+
_sanitizeAttachedShadowRoots2(child);
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1292
1403
|
// eslint-disable-next-line complexity
|
|
1293
1404
|
DOMPurify.sanitize = function (dirty) {
|
|
1294
1405
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -1333,7 +1444,10 @@ function createDOMPurify() {
|
|
|
1333
1444
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
1334
1445
|
}
|
|
1335
1446
|
}
|
|
1336
|
-
|
|
1447
|
+
/* Sanitize attached shadow roots before the main iterator runs.
|
|
1448
|
+
The iterator does not descend into shadow trees. */
|
|
1449
|
+
_sanitizeAttachedShadowRoots2(dirty);
|
|
1450
|
+
} else if (_isNode(dirty)) {
|
|
1337
1451
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
1338
1452
|
elements being stripped by the parser */
|
|
1339
1453
|
body = _initDocument('<!---->');
|
|
@@ -1347,6 +1461,10 @@ function createDOMPurify() {
|
|
|
1347
1461
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
1348
1462
|
body.appendChild(importedNode);
|
|
1349
1463
|
}
|
|
1464
|
+
/* Clonable shadow roots are deep-cloned by importNode(); sanitize
|
|
1465
|
+
them before the main iterator runs, since the iterator does not
|
|
1466
|
+
descend into shadow trees. */
|
|
1467
|
+
_sanitizeAttachedShadowRoots2(importedNode);
|
|
1350
1468
|
} else {
|
|
1351
1469
|
/* Exit directly if we have nothing to do */
|
|
1352
1470
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
@@ -1380,17 +1498,15 @@ function createDOMPurify() {
|
|
|
1380
1498
|
}
|
|
1381
1499
|
/* If we sanitized `dirty` in-place, return it. */
|
|
1382
1500
|
if (IN_PLACE) {
|
|
1501
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
1502
|
+
_scrubTemplateExpressions(dirty);
|
|
1503
|
+
}
|
|
1383
1504
|
return dirty;
|
|
1384
1505
|
}
|
|
1385
1506
|
/* Return sanitized string or DOM */
|
|
1386
1507
|
if (RETURN_DOM) {
|
|
1387
1508
|
if (SAFE_FOR_TEMPLATES) {
|
|
1388
|
-
body
|
|
1389
|
-
let html = body.innerHTML;
|
|
1390
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1391
|
-
html = stringReplace(html, expr, ' ');
|
|
1392
|
-
});
|
|
1393
|
-
body.innerHTML = html;
|
|
1509
|
+
_scrubTemplateExpressions(body);
|
|
1394
1510
|
}
|
|
1395
1511
|
if (RETURN_DOM_FRAGMENT) {
|
|
1396
1512
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
@@ -1420,7 +1536,7 @@ function createDOMPurify() {
|
|
|
1420
1536
|
}
|
|
1421
1537
|
/* Sanitize final string template-safe */
|
|
1422
1538
|
if (SAFE_FOR_TEMPLATES) {
|
|
1423
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1539
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1424
1540
|
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
1425
1541
|
});
|
|
1426
1542
|
}
|