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/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# DOMPurify
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/dompurify) [](https://github.com/cure53/DOMPurify/blob/main/LICENSE)
|
|
3
|
+
[](https://www.npmjs.com/package/dompurify) [](https://github.com/cure53/DOMPurify/blob/main/LICENSE) [](https://www.npmjs.com/package/dompurify) [](https://github.com/cure53/DOMPurify/network/dependents) 
|
|
4
4
|
|
|
5
|
-
](https://github.com/cure53/DOMPurify/actions/workflows/build-and-test.yml) [](https://www.bestpractices.dev/projects/12162) [](https://scorecard.dev/viewer/?uri=github.com/cure53/DOMPurify) [](https://badge.socket.dev/npm/package/dompurify/latest) [](https://cloudback.it)
|
|
6
6
|
|
|
7
7
|
DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
|
|
8
8
|
|
|
9
|
-
It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version **v3.4.
|
|
9
|
+
It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version **v3.4.4**.
|
|
10
10
|
|
|
11
11
|
DOMPurify runs as JavaScript and works in all modern browsers (Safari (10+), Opera (15+), Edge, Firefox and Chrome - as well as almost anything else using Blink, Gecko or WebKit). It doesn't break on MSIE or other legacy browsers. It simply does nothing.
|
|
12
12
|
|
|
13
13
|
**Note that [DOMPurify v2.5.9](https://github.com/cure53/DOMPurify/releases/tag/2.5.9) is the latest version supporting MSIE. For important security updates compatible with MSIE, please use the [2.x branch](https://github.com/cure53/DOMPurify/tree/2.x).**
|
|
14
14
|
|
|
15
|
-
Our automated tests cover 9 browser/OS combinations (Chromium, Firefox, and WebKit across Ubuntu, macOS, and Windows) on every push, plus Node.js v20, v22, v24, and
|
|
15
|
+
Our automated tests cover 9 browser/OS combinations (Chromium, Firefox, and WebKit across Ubuntu, macOS, and Windows) on every push, plus Node.js v20, v22, v24, v25 and v26 running DOMPurify on [jsdom](https://github.com/jsdom/jsdom). Older Node versions are known to work as well, but hey... no guarantees.
|
|
16
16
|
|
|
17
17
|
DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not. For more details please also read about our [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model). Please, read it. Like, really.
|
|
18
18
|
|
|
@@ -463,7 +463,7 @@ DOMPurify.addHook(
|
|
|
463
463
|
|
|
464
464
|
## Continuous Integration
|
|
465
465
|
|
|
466
|
-
We are currently using GitHub Actions in combination with
|
|
466
|
+
We are currently using GitHub Actions in combination with Playwright. This gives us the possibility to confirm for each and every commit that all is going according to plan in relevant modern browsers. Check out the build logs here: https://github.com/cure53/DOMPurify/actions
|
|
467
467
|
|
|
468
468
|
You can further run local tests by executing `npm run test`.
|
|
469
469
|
|
package/dist/purify.cjs.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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
3
|
import { TrustedTypePolicy, TrustedTypesWindow, TrustedHTML } from 'trusted-types/lib/index.js';
|
|
4
4
|
|
package/dist/purify.cjs.js
CHANGED
|
@@ -1,23 +1,64 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.4.
|
|
1
|
+
/*! @license DOMPurify 3.4.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
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
5
|
+
function _arrayLikeToArray(r, a) {
|
|
6
|
+
(null == a || a > r.length) && (a = r.length);
|
|
7
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
8
|
+
return n;
|
|
9
|
+
}
|
|
10
|
+
function _arrayWithHoles(r) {
|
|
11
|
+
if (Array.isArray(r)) return r;
|
|
12
|
+
}
|
|
13
|
+
function _iterableToArrayLimit(r, l) {
|
|
14
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
15
|
+
if (null != t) {
|
|
16
|
+
var e,
|
|
17
|
+
n,
|
|
18
|
+
i,
|
|
19
|
+
u,
|
|
20
|
+
a = [],
|
|
21
|
+
f = true,
|
|
22
|
+
o = false;
|
|
23
|
+
try {
|
|
24
|
+
if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
25
|
+
} catch (r) {
|
|
26
|
+
o = true, n = r;
|
|
27
|
+
} finally {
|
|
28
|
+
try {
|
|
29
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
30
|
+
} finally {
|
|
31
|
+
if (o) throw n;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return a;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function _nonIterableRest() {
|
|
38
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
39
|
+
}
|
|
40
|
+
function _slicedToArray(r, e) {
|
|
41
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
42
|
+
}
|
|
43
|
+
function _unsupportedIterableToArray(r, a) {
|
|
44
|
+
if (r) {
|
|
45
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
46
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
47
|
+
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const entries = Object.entries,
|
|
52
|
+
setPrototypeOf = Object.setPrototypeOf,
|
|
53
|
+
isFrozen = Object.isFrozen,
|
|
54
|
+
getPrototypeOf = Object.getPrototypeOf,
|
|
55
|
+
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
56
|
+
let freeze = Object.freeze,
|
|
57
|
+
seal = Object.seal,
|
|
58
|
+
create = Object.create; // eslint-disable-line import/no-mutable-exports
|
|
59
|
+
let _ref = typeof Reflect !== 'undefined' && Reflect,
|
|
60
|
+
apply = _ref.apply,
|
|
61
|
+
construct = _ref.construct;
|
|
21
62
|
if (!freeze) {
|
|
22
63
|
freeze = function freeze(x) {
|
|
23
64
|
return x;
|
|
@@ -154,7 +195,10 @@ function cleanArray(array) {
|
|
|
154
195
|
*/
|
|
155
196
|
function clone(object) {
|
|
156
197
|
const newObject = create(null);
|
|
157
|
-
for (const
|
|
198
|
+
for (const _ref2 of entries(object)) {
|
|
199
|
+
var _ref3 = _slicedToArray(_ref2, 2);
|
|
200
|
+
const property = _ref3[0];
|
|
201
|
+
const value = _ref3[1];
|
|
158
202
|
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
159
203
|
if (isPropertyExist) {
|
|
160
204
|
if (arrayIsArray(value)) {
|
|
@@ -254,7 +298,7 @@ function isRegex(value) {
|
|
|
254
298
|
}
|
|
255
299
|
}
|
|
256
300
|
|
|
257
|
-
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']);
|
|
301
|
+
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']);
|
|
258
302
|
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']);
|
|
259
303
|
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']);
|
|
260
304
|
// List of SVG elements that are disallowed by default.
|
|
@@ -268,15 +312,14 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
|
|
|
268
312
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
269
313
|
const text = freeze(['#text']);
|
|
270
314
|
|
|
271
|
-
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']);
|
|
315
|
+
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']);
|
|
272
316
|
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']);
|
|
273
317
|
const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
|
|
274
318
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
275
319
|
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
320
|
+
const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
|
|
321
|
+
const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
|
|
322
|
+
const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
|
|
280
323
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
281
324
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
282
325
|
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
|
@@ -287,20 +330,6 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
|
|
|
287
330
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
288
331
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
289
332
|
|
|
290
|
-
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
291
|
-
__proto__: null,
|
|
292
|
-
ARIA_ATTR: ARIA_ATTR,
|
|
293
|
-
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
294
|
-
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
295
|
-
DATA_ATTR: DATA_ATTR,
|
|
296
|
-
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
297
|
-
ERB_EXPR: ERB_EXPR,
|
|
298
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
299
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
300
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
301
|
-
TMPLIT_EXPR: TMPLIT_EXPR
|
|
302
|
-
});
|
|
303
|
-
|
|
304
333
|
/* eslint-disable @typescript-eslint/indent */
|
|
305
334
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
306
335
|
const NODE_TYPE = {
|
|
@@ -367,7 +396,7 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
367
396
|
function createDOMPurify() {
|
|
368
397
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
369
398
|
const DOMPurify = root => createDOMPurify(root);
|
|
370
|
-
DOMPurify.version = '3.4.
|
|
399
|
+
DOMPurify.version = '3.4.4';
|
|
371
400
|
DOMPurify.removed = [];
|
|
372
401
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
373
402
|
// Not running in a browser, provide a factory function
|
|
@@ -375,28 +404,26 @@ function createDOMPurify() {
|
|
|
375
404
|
DOMPurify.isSupported = false;
|
|
376
405
|
return DOMPurify;
|
|
377
406
|
}
|
|
378
|
-
let
|
|
379
|
-
document
|
|
380
|
-
} = window;
|
|
407
|
+
let document = window.document;
|
|
381
408
|
const originalDocument = document;
|
|
382
409
|
const currentScript = originalDocument.currentScript;
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
|
|
390
|
-
HTMLFormElement,
|
|
391
|
-
DOMParser,
|
|
392
|
-
trustedTypes
|
|
393
|
-
} = window;
|
|
410
|
+
const DocumentFragment = window.DocumentFragment,
|
|
411
|
+
HTMLTemplateElement = window.HTMLTemplateElement,
|
|
412
|
+
Node = window.Node,
|
|
413
|
+
Element = window.Element,
|
|
414
|
+
NodeFilter = window.NodeFilter,
|
|
415
|
+
_window$NamedNodeMap = window.NamedNodeMap,
|
|
416
|
+
NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
|
|
417
|
+
HTMLFormElement = window.HTMLFormElement,
|
|
418
|
+
DOMParser = window.DOMParser,
|
|
419
|
+
trustedTypes = window.trustedTypes;
|
|
394
420
|
const ElementPrototype = Element.prototype;
|
|
395
421
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
396
422
|
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
397
423
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
398
424
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
399
425
|
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
426
|
+
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
|
|
400
427
|
// As per issue #47, the web-components registry is inherited by a
|
|
401
428
|
// new document created via createHTMLDocument. As per the spec
|
|
402
429
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -411,33 +438,26 @@ function createDOMPurify() {
|
|
|
411
438
|
}
|
|
412
439
|
let trustedTypesPolicy;
|
|
413
440
|
let emptyHTML = '';
|
|
414
|
-
const
|
|
415
|
-
implementation,
|
|
416
|
-
createNodeIterator,
|
|
417
|
-
createDocumentFragment,
|
|
418
|
-
getElementsByTagName
|
|
419
|
-
|
|
420
|
-
const {
|
|
421
|
-
importNode
|
|
422
|
-
} = originalDocument;
|
|
441
|
+
const _document = document,
|
|
442
|
+
implementation = _document.implementation,
|
|
443
|
+
createNodeIterator = _document.createNodeIterator,
|
|
444
|
+
createDocumentFragment = _document.createDocumentFragment,
|
|
445
|
+
getElementsByTagName = _document.getElementsByTagName;
|
|
446
|
+
const importNode = originalDocument.importNode;
|
|
423
447
|
let hooks = _createHooksMap();
|
|
424
448
|
/**
|
|
425
449
|
* Expose whether this browser supports running the full DOMPurify.
|
|
426
450
|
*/
|
|
427
451
|
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
} = EXPRESSIONS;
|
|
438
|
-
let {
|
|
439
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
440
|
-
} = EXPRESSIONS;
|
|
452
|
+
const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
|
|
453
|
+
ERB_EXPR$1 = ERB_EXPR,
|
|
454
|
+
TMPLIT_EXPR$1 = TMPLIT_EXPR,
|
|
455
|
+
DATA_ATTR$1 = DATA_ATTR,
|
|
456
|
+
ARIA_ATTR$1 = ARIA_ATTR,
|
|
457
|
+
IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
|
|
458
|
+
ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
|
|
459
|
+
CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
|
|
460
|
+
let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
|
|
441
461
|
/**
|
|
442
462
|
* We consider the elements and attributes below to be safe. Ideally
|
|
443
463
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
@@ -957,6 +977,40 @@ function createDOMPurify() {
|
|
|
957
977
|
// eslint-disable-next-line no-bitwise
|
|
958
978
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
959
979
|
};
|
|
980
|
+
/**
|
|
981
|
+
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
982
|
+
* character data of an element subtree. Used as the final safety net for
|
|
983
|
+
* SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
|
|
984
|
+
* which only form after text-node normalization (e.g. fragments split across
|
|
985
|
+
* stripped elements) cannot survive into a template-evaluating framework.
|
|
986
|
+
*
|
|
987
|
+
* Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
|
|
988
|
+
* in place rather than round-tripping through innerHTML. This preserves
|
|
989
|
+
* descendant node references (important for IN_PLACE callers), avoids a
|
|
990
|
+
* serialize/reparse cycle, and reads literal character data — which means
|
|
991
|
+
* `<%...%>` in text content matches the ERB regex against its real bytes
|
|
992
|
+
* instead of the HTML-entity-escaped form innerHTML would produce.
|
|
993
|
+
*
|
|
994
|
+
* Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
|
|
995
|
+
* attributes is performed during the per-node `_sanitizeAttributes` pass.
|
|
996
|
+
*
|
|
997
|
+
* @param node The root element whose character data should be scrubbed.
|
|
998
|
+
*/
|
|
999
|
+
const _scrubTemplateExpressions = function _scrubTemplateExpressions(node) {
|
|
1000
|
+
node.normalize();
|
|
1001
|
+
const walker = createNodeIterator.call(node.ownerDocument || node, node,
|
|
1002
|
+
// eslint-disable-next-line no-bitwise
|
|
1003
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
1004
|
+
let currentNode = walker.nextNode();
|
|
1005
|
+
while (currentNode) {
|
|
1006
|
+
let data = currentNode.data;
|
|
1007
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1008
|
+
data = stringReplace(data, expr, ' ');
|
|
1009
|
+
});
|
|
1010
|
+
currentNode.data = data;
|
|
1011
|
+
currentNode = walker.nextNode();
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
960
1014
|
/**
|
|
961
1015
|
* _isClobbered
|
|
962
1016
|
*
|
|
@@ -967,13 +1021,31 @@ function createDOMPurify() {
|
|
|
967
1021
|
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');
|
|
968
1022
|
};
|
|
969
1023
|
/**
|
|
970
|
-
* Checks whether the given object is a DOM node
|
|
1024
|
+
* Checks whether the given object is a DOM node, including nodes that
|
|
1025
|
+
* originate from a different window/realm (e.g. an iframe's
|
|
1026
|
+
* contentDocument). The previous `value instanceof Node` check was
|
|
1027
|
+
* realm-bound: nodes from a different window failed it, causing
|
|
1028
|
+
* sanitize() to silently stringify them and reset IN_PLACE to false,
|
|
1029
|
+
* returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
|
|
1030
|
+
*
|
|
1031
|
+
* Implementation: call the cached `nodeType` getter from Node.prototype
|
|
1032
|
+
* directly on the value. This bypasses any clobbered instance property
|
|
1033
|
+
* (e.g. a child element named "nodeType") and works across realms
|
|
1034
|
+
* because the WebIDL `nodeType` getter reads an internal slot that
|
|
1035
|
+
* every real Node has, regardless of which window minted it.
|
|
971
1036
|
*
|
|
972
1037
|
* @param value object to check whether it's a DOM node
|
|
973
|
-
* @return true
|
|
1038
|
+
* @return true if value is a DOM node from any realm
|
|
974
1039
|
*/
|
|
975
1040
|
const _isNode = function _isNode(value) {
|
|
976
|
-
|
|
1041
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
try {
|
|
1045
|
+
return typeof getNodeType(value) === 'number';
|
|
1046
|
+
} catch (_) {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
977
1049
|
};
|
|
978
1050
|
function _executeHooks(hooks, currentNode, data) {
|
|
979
1051
|
arrayForEach(hooks, hook => {
|
|
@@ -1065,7 +1137,7 @@ function createDOMPurify() {
|
|
|
1065
1137
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1066
1138
|
/* Get the element's text content */
|
|
1067
1139
|
content = currentNode.textContent;
|
|
1068
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1140
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1069
1141
|
content = stringReplace(content, expr, ' ');
|
|
1070
1142
|
});
|
|
1071
1143
|
if (currentNode.textContent !== content) {
|
|
@@ -1102,7 +1174,7 @@ function createDOMPurify() {
|
|
|
1102
1174
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1103
1175
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1104
1176
|
We don't need to check the value; it's always URI safe. */
|
|
1105
|
-
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]) {
|
|
1177
|
+
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]) {
|
|
1106
1178
|
if (
|
|
1107
1179
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1108
1180
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -1114,7 +1186,7 @@ function createDOMPurify() {
|
|
|
1114
1186
|
return false;
|
|
1115
1187
|
}
|
|
1116
1188
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
1117
|
-
} 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) {
|
|
1189
|
+
} 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) {
|
|
1118
1190
|
return false;
|
|
1119
1191
|
} else ;
|
|
1120
1192
|
return true;
|
|
@@ -1132,7 +1204,7 @@ function createDOMPurify() {
|
|
|
1132
1204
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
1133
1205
|
*/
|
|
1134
1206
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1135
|
-
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT, tagName);
|
|
1207
|
+
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
1136
1208
|
};
|
|
1137
1209
|
/**
|
|
1138
1210
|
* _sanitizeAttributes
|
|
@@ -1147,9 +1219,7 @@ function createDOMPurify() {
|
|
|
1147
1219
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1148
1220
|
/* Execute a hook if present */
|
|
1149
1221
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
1150
|
-
const
|
|
1151
|
-
attributes
|
|
1152
|
-
} = currentNode;
|
|
1222
|
+
const attributes = currentNode.attributes;
|
|
1153
1223
|
/* Check if we have attributes; if not we might have a text node */
|
|
1154
1224
|
if (!attributes || _isClobbered(currentNode)) {
|
|
1155
1225
|
return;
|
|
@@ -1165,11 +1235,9 @@ function createDOMPurify() {
|
|
|
1165
1235
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1166
1236
|
while (l--) {
|
|
1167
1237
|
const attr = attributes[l];
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
value: attrValue
|
|
1172
|
-
} = attr;
|
|
1238
|
+
const name = attr.name,
|
|
1239
|
+
namespaceURI = attr.namespaceURI,
|
|
1240
|
+
attrValue = attr.value;
|
|
1173
1241
|
const lcName = transformCaseFunc(name);
|
|
1174
1242
|
const initValue = attrValue;
|
|
1175
1243
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -1217,7 +1285,7 @@ function createDOMPurify() {
|
|
|
1217
1285
|
}
|
|
1218
1286
|
/* Sanitize attribute content to be template-safe */
|
|
1219
1287
|
if (SAFE_FOR_TEMPLATES) {
|
|
1220
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1288
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1221
1289
|
value = stringReplace(value, expr, ' ');
|
|
1222
1290
|
});
|
|
1223
1291
|
}
|
|
@@ -1291,6 +1359,49 @@ function createDOMPurify() {
|
|
|
1291
1359
|
/* Execute a hook if present */
|
|
1292
1360
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
1293
1361
|
};
|
|
1362
|
+
/**
|
|
1363
|
+
* _sanitizeAttachedShadowRoots
|
|
1364
|
+
*
|
|
1365
|
+
* Walks `root` and feeds every attached shadow root we encounter into
|
|
1366
|
+
* the existing _sanitizeShadowDOM pipeline. The default node iterator
|
|
1367
|
+
* does not descend into shadow trees, so nodes inside an attached
|
|
1368
|
+
* shadow root would otherwise be skipped entirely.
|
|
1369
|
+
*
|
|
1370
|
+
* Two real input paths put attached shadow roots in front of us:
|
|
1371
|
+
* 1. IN_PLACE on a DOM node that already has shadow roots attached.
|
|
1372
|
+
* 2. DOM-node input where importNode(dirty, true) deep-clones the
|
|
1373
|
+
* shadow root because it was created with `clonable: true`.
|
|
1374
|
+
*
|
|
1375
|
+
* This pass runs once, up front, so the main iteration loop (and the
|
|
1376
|
+
* existing _sanitizeShadowDOM template-content recursion) stay
|
|
1377
|
+
* untouched — string-input paths are not affected.
|
|
1378
|
+
*
|
|
1379
|
+
* @param root the subtree root to walk for attached shadow roots
|
|
1380
|
+
*/
|
|
1381
|
+
const _sanitizeAttachedShadowRoots2 = function _sanitizeAttachedShadowRoots(root) {
|
|
1382
|
+
if (root.nodeType === NODE_TYPE.element && root.shadowRoot instanceof DocumentFragment) {
|
|
1383
|
+
const sr = root.shadowRoot;
|
|
1384
|
+
// Recurse first so that nested shadow roots are reached even if
|
|
1385
|
+
// _sanitizeShadowDOM removes hosts at this level.
|
|
1386
|
+
_sanitizeAttachedShadowRoots2(sr);
|
|
1387
|
+
_sanitizeShadowDOM2(sr);
|
|
1388
|
+
}
|
|
1389
|
+
// Snapshot children before recursing. Sanitization of one subtree
|
|
1390
|
+
// (e.g. via an uponSanitizeShadowNode hook) may detach siblings,
|
|
1391
|
+
// and naive nextSibling traversal would silently skip the rest of
|
|
1392
|
+
// the list once a node is detached.
|
|
1393
|
+
const childNodes = root.childNodes;
|
|
1394
|
+
if (!childNodes) {
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
const snapshot = [];
|
|
1398
|
+
arrayForEach(childNodes, child => {
|
|
1399
|
+
arrayPush(snapshot, child);
|
|
1400
|
+
});
|
|
1401
|
+
for (const child of snapshot) {
|
|
1402
|
+
_sanitizeAttachedShadowRoots2(child);
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1294
1405
|
// eslint-disable-next-line complexity
|
|
1295
1406
|
DOMPurify.sanitize = function (dirty) {
|
|
1296
1407
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -1335,7 +1446,10 @@ function createDOMPurify() {
|
|
|
1335
1446
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
1336
1447
|
}
|
|
1337
1448
|
}
|
|
1338
|
-
|
|
1449
|
+
/* Sanitize attached shadow roots before the main iterator runs.
|
|
1450
|
+
The iterator does not descend into shadow trees. */
|
|
1451
|
+
_sanitizeAttachedShadowRoots2(dirty);
|
|
1452
|
+
} else if (_isNode(dirty)) {
|
|
1339
1453
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
1340
1454
|
elements being stripped by the parser */
|
|
1341
1455
|
body = _initDocument('<!---->');
|
|
@@ -1349,6 +1463,10 @@ function createDOMPurify() {
|
|
|
1349
1463
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
1350
1464
|
body.appendChild(importedNode);
|
|
1351
1465
|
}
|
|
1466
|
+
/* Clonable shadow roots are deep-cloned by importNode(); sanitize
|
|
1467
|
+
them before the main iterator runs, since the iterator does not
|
|
1468
|
+
descend into shadow trees. */
|
|
1469
|
+
_sanitizeAttachedShadowRoots2(importedNode);
|
|
1352
1470
|
} else {
|
|
1353
1471
|
/* Exit directly if we have nothing to do */
|
|
1354
1472
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
@@ -1382,17 +1500,15 @@ function createDOMPurify() {
|
|
|
1382
1500
|
}
|
|
1383
1501
|
/* If we sanitized `dirty` in-place, return it. */
|
|
1384
1502
|
if (IN_PLACE) {
|
|
1503
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
1504
|
+
_scrubTemplateExpressions(dirty);
|
|
1505
|
+
}
|
|
1385
1506
|
return dirty;
|
|
1386
1507
|
}
|
|
1387
1508
|
/* Return sanitized string or DOM */
|
|
1388
1509
|
if (RETURN_DOM) {
|
|
1389
1510
|
if (SAFE_FOR_TEMPLATES) {
|
|
1390
|
-
body
|
|
1391
|
-
let html = body.innerHTML;
|
|
1392
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1393
|
-
html = stringReplace(html, expr, ' ');
|
|
1394
|
-
});
|
|
1395
|
-
body.innerHTML = html;
|
|
1511
|
+
_scrubTemplateExpressions(body);
|
|
1396
1512
|
}
|
|
1397
1513
|
if (RETURN_DOM_FRAGMENT) {
|
|
1398
1514
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
@@ -1422,7 +1538,7 @@ function createDOMPurify() {
|
|
|
1422
1538
|
}
|
|
1423
1539
|
/* Sanitize final string template-safe */
|
|
1424
1540
|
if (SAFE_FOR_TEMPLATES) {
|
|
1425
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1541
|
+
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1426
1542
|
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
1427
1543
|
});
|
|
1428
1544
|
}
|