@deepseek-ai/dsh-client-ui-sidebar-documentpreview 0.2.0-rc.2 → 0.2.1-alpha.2
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.i18n.yaml +4 -4
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/lib/client.excel.js +2 -2
- package/lib/client.frontmatter-fields.js +5768 -0
- package/lib/client.js +494 -96
- package/lib/types/client/markdown/MarkdownBody.d.ts +2 -0
- package/lib/types/client/markdown/frontmatter-fields.d.ts +20 -0
- package/lib/types/client/markdown/frontmatter.d.ts +13 -0
- package/package.json +27 -26
package/lib/client.js
CHANGED
|
@@ -1506,6 +1506,23 @@ window.__ModuleLoader__.load({
|
|
|
1506
1506
|
*/
|
|
1507
1507
|
const documentTabInfoFactory = (_standard, useTabInfo) => useTabInfo;
|
|
1508
1508
|
//#endregion
|
|
1509
|
+
//#region lib/types/client/markdown/frontmatter.js
|
|
1510
|
+
/** Leading YAML frontmatter split from a Markdown document before Markdown parsing. */
|
|
1511
|
+
const FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n(?:([\s\S]*?)\r?\n)??(?:---|\.\.\.)[ \t]*(?:\r?\n|$)/u;
|
|
1512
|
+
/**
|
|
1513
|
+
* Split a leading YAML frontmatter block from Markdown text without parsing its YAML.
|
|
1514
|
+
* @param text - Markdown source; an unterminated block is not frontmatter.
|
|
1515
|
+
* @returns frontmatter source and the remaining body, or `undefined` when the text has none.
|
|
1516
|
+
*/
|
|
1517
|
+
function splitFrontmatter(text) {
|
|
1518
|
+
const match = FRONTMATTER.exec(text);
|
|
1519
|
+
if (match === null) return void 0;
|
|
1520
|
+
return {
|
|
1521
|
+
source: match[1] ?? "",
|
|
1522
|
+
body: text.slice(match[0].length)
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
//#endregion
|
|
1509
1526
|
//#region lib/types/client/markdown/path-images.js
|
|
1510
1527
|
/** Local Markdown image destinations served by the authenticated file route. */
|
|
1511
1528
|
/**
|
|
@@ -1533,7 +1550,7 @@ window.__ModuleLoader__.load({
|
|
|
1533
1550
|
}
|
|
1534
1551
|
//#endregion
|
|
1535
1552
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-documentpreview/src/client/markdown/MarkdownBody.module.css.mjs
|
|
1536
|
-
const css$7 = "._0RKuNG_document{min-width:0;font-family:var(--dsw-font,inherit);white-space:normal;padding:10px 12px}";
|
|
1553
|
+
const css$7 = "._0RKuNG_document{min-width:0;font-family:var(--dsw-font,inherit);white-space:normal;padding:10px 12px}._0RKuNG_frontmatter{border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);font:var(--dsw-font-markdown-small);color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0 0 16px;padding:10px 12px}._0RKuNG_frontmatter:empty{display:none}._0RKuNG_frontmatterSource{font-family:var(--ds-font-family-code);white-space:inherit;margin:0}";
|
|
1537
1554
|
const tagId$7 = "@deepseek-ai/dsh-client-ui-sidebar-documentpreview/MarkdownBody.module.css";
|
|
1538
1555
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$7) + "]") === null) {
|
|
1539
1556
|
const tag = document.createElement("style");
|
|
@@ -1542,12 +1559,23 @@ window.__ModuleLoader__.load({
|
|
|
1542
1559
|
tag.textContent = css$7;
|
|
1543
1560
|
document.head.appendChild(tag);
|
|
1544
1561
|
}
|
|
1545
|
-
var MarkdownBody_module_css_default = {
|
|
1562
|
+
var MarkdownBody_module_css_default = {
|
|
1563
|
+
"document": "_0RKuNG_document",
|
|
1564
|
+
"frontmatter": "_0RKuNG_frontmatter",
|
|
1565
|
+
"frontmatterSource": "_0RKuNG_frontmatterSource"
|
|
1566
|
+
};
|
|
1546
1567
|
//#endregion
|
|
1547
1568
|
//#region lib/types/client/markdown/MarkdownBody.js
|
|
1548
1569
|
/** One retained Markdown renderer over the document owner's accumulated text. */
|
|
1570
|
+
/** Show the verbatim frontmatter when the field-list chunk cannot load. */
|
|
1571
|
+
function FrontmatterFallback({ fallback }) {
|
|
1572
|
+
return fallback;
|
|
1573
|
+
}
|
|
1574
|
+
const FrontmatterFields = (0, react.lazy)(() => require.async("./client.frontmatter-fields.js").then((module) => ({ default: module.FrontmatterFields }), () => ({ default: FrontmatterFallback })));
|
|
1549
1575
|
/**
|
|
1550
1576
|
* Render one accumulated document; EOF completes the primitive's full parse.
|
|
1577
|
+
* A leading YAML frontmatter block renders as a field list instead of Markdown; its
|
|
1578
|
+
* verbatim source stands in while the field-list chunk loads or when it fails.
|
|
1551
1579
|
* @param props - owner-loaded contents and localized primitive labels.
|
|
1552
1580
|
* @returns Markdown content, or nothing for a non-text delivery.
|
|
1553
1581
|
*/
|
|
@@ -1579,15 +1607,39 @@ window.__ModuleLoader__.load({
|
|
|
1579
1607
|
wrapLabel,
|
|
1580
1608
|
unwrapLabel
|
|
1581
1609
|
]);
|
|
1610
|
+
const text = content.kind === "text" ? content.text : "";
|
|
1611
|
+
const split = (0, react.useMemo)(() => splitFrontmatter(text), [text]);
|
|
1582
1612
|
if (content.kind !== "text") return null;
|
|
1583
|
-
return (0, react_jsx_runtime.
|
|
1613
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1584
1614
|
className: MarkdownBody_module_css_default.document,
|
|
1585
1615
|
"data-document-markdown": true,
|
|
1586
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
|
|
1587
|
-
text:
|
|
1616
|
+
children: [split === void 0 ? null : (0, react_jsx_runtime.jsx)(FrontmatterBox, { source: split.source }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
|
|
1617
|
+
text: split?.body ?? text,
|
|
1588
1618
|
streaming: !content.eof,
|
|
1589
1619
|
labels,
|
|
1590
1620
|
pathImages
|
|
1621
|
+
})]
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Frame one frontmatter block and choose its field list or verbatim source.
|
|
1626
|
+
* @param props - YAML between the frontmatter delimiters.
|
|
1627
|
+
* @returns the metadata box; it collapses when the document has no content.
|
|
1628
|
+
*/
|
|
1629
|
+
function FrontmatterBox({ source }) {
|
|
1630
|
+
const verbatim = (0, react_jsx_runtime.jsx)("pre", {
|
|
1631
|
+
className: MarkdownBody_module_css_default.frontmatterSource,
|
|
1632
|
+
children: source
|
|
1633
|
+
});
|
|
1634
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
1635
|
+
className: MarkdownBody_module_css_default.frontmatter,
|
|
1636
|
+
"data-document-frontmatter": true,
|
|
1637
|
+
children: (0, react_jsx_runtime.jsx)(react.Suspense, {
|
|
1638
|
+
fallback: verbatim,
|
|
1639
|
+
children: (0, react_jsx_runtime.jsx)(FrontmatterFields, {
|
|
1640
|
+
source,
|
|
1641
|
+
fallback: verbatim
|
|
1642
|
+
})
|
|
1591
1643
|
})
|
|
1592
1644
|
});
|
|
1593
1645
|
}
|
|
@@ -1704,8 +1756,11 @@ document.open();document.write(html);document.close();
|
|
|
1704
1756
|
})()<\/script>`;
|
|
1705
1757
|
}
|
|
1706
1758
|
//#endregion
|
|
1707
|
-
//#region ../../../node_modules/.pnpm/dompurify@3.4.
|
|
1708
|
-
/*! @license DOMPurify 3.4.
|
|
1759
|
+
//#region ../../../node_modules/.pnpm/dompurify@3.4.16/node_modules/dompurify/dist/purify.es.mjs
|
|
1760
|
+
/*! @license DOMPurify 3.4.16 | (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.16/LICENSE */
|
|
1761
|
+
function _OverloadYield(e, d) {
|
|
1762
|
+
this.v = e, this.k = d;
|
|
1763
|
+
}
|
|
1709
1764
|
function _arrayLikeToArray(r, a) {
|
|
1710
1765
|
(null == a || a > r.length) && (a = r.length);
|
|
1711
1766
|
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
@@ -1717,12 +1772,14 @@ document.open();document.write(html);document.close();
|
|
|
1717
1772
|
function _iterableToArrayLimit(r, l) {
|
|
1718
1773
|
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
1719
1774
|
if (null != t) {
|
|
1720
|
-
var e, n, i, u, a = [], f =
|
|
1775
|
+
var e, n, i, u, a = [], f = !0, o = !1;
|
|
1721
1776
|
try {
|
|
1722
|
-
if (i = (t = t.call(r)).next, 0 === l)
|
|
1723
|
-
|
|
1777
|
+
if (i = (t = t.call(r)).next, 0 === l) {
|
|
1778
|
+
if (Object(t) !== t) return;
|
|
1779
|
+
f = !1;
|
|
1780
|
+
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
1724
1781
|
} catch (r) {
|
|
1725
|
-
o =
|
|
1782
|
+
o = !0, n = r;
|
|
1726
1783
|
} finally {
|
|
1727
1784
|
try {
|
|
1728
1785
|
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
@@ -1736,6 +1793,7 @@ document.open();document.write(html);document.close();
|
|
|
1736
1793
|
function _nonIterableRest() {
|
|
1737
1794
|
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
1738
1795
|
}
|
|
1796
|
+
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
|
|
1739
1797
|
function _slicedToArray(r, e) {
|
|
1740
1798
|
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
1741
1799
|
}
|
|
@@ -1746,9 +1804,64 @@ document.open();document.write(html);document.close();
|
|
|
1746
1804
|
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;
|
|
1747
1805
|
}
|
|
1748
1806
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1807
|
+
function AsyncGenerator(e) {
|
|
1808
|
+
var t, n;
|
|
1809
|
+
function resume(t, n) {
|
|
1810
|
+
try {
|
|
1811
|
+
var r = e[t](n), o = r.value, u = o instanceof _OverloadYield;
|
|
1812
|
+
Promise.resolve(u ? o.v : o).then(function(n) {
|
|
1813
|
+
if (u) {
|
|
1814
|
+
var i = "return" === t && o.k ? t : "next";
|
|
1815
|
+
if (!o.k || n.done) return resume(i, n);
|
|
1816
|
+
n = e[i](n).value;
|
|
1817
|
+
}
|
|
1818
|
+
settle(!!r.done, n);
|
|
1819
|
+
}, function(e) {
|
|
1820
|
+
resume("throw", e);
|
|
1821
|
+
});
|
|
1822
|
+
} catch (e) {
|
|
1823
|
+
settle(2, e);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
function settle(e, r) {
|
|
1827
|
+
2 === e ? t.reject(r) : t.resolve({
|
|
1828
|
+
value: r,
|
|
1829
|
+
done: e
|
|
1830
|
+
}), (t = t.next) ? resume(t.key, t.arg) : n = null;
|
|
1831
|
+
}
|
|
1832
|
+
this._invoke = function(e, r) {
|
|
1833
|
+
return new Promise(function(o, u) {
|
|
1834
|
+
var i = {
|
|
1835
|
+
key: e,
|
|
1836
|
+
arg: r,
|
|
1837
|
+
resolve: o,
|
|
1838
|
+
reject: u,
|
|
1839
|
+
next: null
|
|
1840
|
+
};
|
|
1841
|
+
n ? n = n.next = i : (t = n = i, resume(e, r));
|
|
1842
|
+
});
|
|
1843
|
+
}, "function" != typeof e.return && (this.return = void 0);
|
|
1844
|
+
}
|
|
1845
|
+
AsyncGenerator.prototype["function" == typeof Symbol && Symbol.asyncIterator || "@@asyncIterator"] = function() {
|
|
1846
|
+
return this;
|
|
1847
|
+
}, AsyncGenerator.prototype.next = function(e) {
|
|
1848
|
+
return this._invoke("next", e);
|
|
1849
|
+
}, AsyncGenerator.prototype.throw = function(e) {
|
|
1850
|
+
return this._invoke("throw", e);
|
|
1851
|
+
}, AsyncGenerator.prototype.return = function(e) {
|
|
1852
|
+
return this._invoke("return", e);
|
|
1853
|
+
};
|
|
1854
|
+
const entries = Object.entries;
|
|
1855
|
+
const setPrototypeOf = Object.setPrototypeOf;
|
|
1856
|
+
const isFrozen = Object.isFrozen;
|
|
1857
|
+
const getPrototypeOf = Object.getPrototypeOf;
|
|
1858
|
+
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
1859
|
+
let freeze = Object.freeze;
|
|
1860
|
+
let seal = Object.seal;
|
|
1861
|
+
let create = Object.create;
|
|
1862
|
+
let _ref = typeof Reflect !== "undefined" && Reflect;
|
|
1863
|
+
let apply$7 = _ref.apply;
|
|
1864
|
+
let construct = _ref.construct;
|
|
1752
1865
|
if (!freeze) freeze = function freeze(x) {
|
|
1753
1866
|
return x;
|
|
1754
1867
|
};
|
|
@@ -1764,9 +1877,11 @@ document.open();document.write(html);document.close();
|
|
|
1764
1877
|
return new Func(...args);
|
|
1765
1878
|
};
|
|
1766
1879
|
const arrayForEach = unapply(Array.prototype.forEach);
|
|
1880
|
+
Array.prototype.indexOf;
|
|
1767
1881
|
const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
1768
1882
|
const arrayPop = unapply(Array.prototype.pop);
|
|
1769
1883
|
const arrayPush = unapply(Array.prototype.push);
|
|
1884
|
+
Array.prototype.slice;
|
|
1770
1885
|
const arraySplice = unapply(Array.prototype.splice);
|
|
1771
1886
|
const arrayIsArray = Array.isArray;
|
|
1772
1887
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
@@ -2344,6 +2459,7 @@ document.open();document.write(html);document.close();
|
|
|
2344
2459
|
"direction",
|
|
2345
2460
|
"display",
|
|
2346
2461
|
"divisor",
|
|
2462
|
+
"dominant-baseline",
|
|
2347
2463
|
"dur",
|
|
2348
2464
|
"edgemode",
|
|
2349
2465
|
"elevation",
|
|
@@ -2425,6 +2541,7 @@ document.open();document.write(html);document.close();
|
|
|
2425
2541
|
"patterncontentunits",
|
|
2426
2542
|
"patterntransform",
|
|
2427
2543
|
"patternunits",
|
|
2544
|
+
"pointer-events",
|
|
2428
2545
|
"points",
|
|
2429
2546
|
"preservealpha",
|
|
2430
2547
|
"preserveaspectratio",
|
|
@@ -2471,6 +2588,7 @@ document.open();document.write(html);document.close();
|
|
|
2471
2588
|
"transform-origin",
|
|
2472
2589
|
"text-anchor",
|
|
2473
2590
|
"text-decoration",
|
|
2591
|
+
"text-orientation",
|
|
2474
2592
|
"text-rendering",
|
|
2475
2593
|
"textlength",
|
|
2476
2594
|
"type",
|
|
@@ -2478,6 +2596,7 @@ document.open();document.write(html);document.close();
|
|
|
2478
2596
|
"u2",
|
|
2479
2597
|
"unicode",
|
|
2480
2598
|
"values",
|
|
2599
|
+
"vector-effect",
|
|
2481
2600
|
"viewbox",
|
|
2482
2601
|
"visibility",
|
|
2483
2602
|
"version",
|
|
@@ -2591,6 +2710,24 @@ document.open();document.write(html);document.close();
|
|
|
2591
2710
|
documentFragment: 11,
|
|
2592
2711
|
notation: 12
|
|
2593
2712
|
};
|
|
2713
|
+
const LITERAL_TEXT_ELEMENT_NAMES = [
|
|
2714
|
+
"style",
|
|
2715
|
+
"script",
|
|
2716
|
+
"xmp",
|
|
2717
|
+
"iframe",
|
|
2718
|
+
"noembed",
|
|
2719
|
+
"noframes",
|
|
2720
|
+
"plaintext",
|
|
2721
|
+
"noscript"
|
|
2722
|
+
];
|
|
2723
|
+
const LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, LITERAL_TEXT_ELEMENT_NAMES));
|
|
2724
|
+
const LITERAL_TEXT_CLOSE = function() {
|
|
2725
|
+
const map = {};
|
|
2726
|
+
arrayForEach(LITERAL_TEXT_ELEMENT_NAMES, (name) => {
|
|
2727
|
+
map[name] = seal(new RegExp("</" + name + "(?=[\\t\\n\\f\\r />])", "i"));
|
|
2728
|
+
});
|
|
2729
|
+
return freeze(map);
|
|
2730
|
+
}();
|
|
2594
2731
|
const getGlobal = function getGlobal() {
|
|
2595
2732
|
return typeof window === "undefined" ? null : window;
|
|
2596
2733
|
};
|
|
@@ -2650,10 +2787,25 @@ document.open();document.write(html);document.close();
|
|
|
2650
2787
|
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
|
|
2651
2788
|
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone$1(options.base) : {}, cfg[key], options.transform) : fallback;
|
|
2652
2789
|
};
|
|
2790
|
+
/**
|
|
2791
|
+
* Resolve an object-valued configuration option: a prototype-free clone
|
|
2792
|
+
* of cfg[key] when it is an own, truthy object property, else a fresh
|
|
2793
|
+
* fallback built by makeFallback (fresh on every parse, so a previous
|
|
2794
|
+
* parse can never leak state into the next one).
|
|
2795
|
+
*
|
|
2796
|
+
* @param cfg the cloned, prototype-free configuration object
|
|
2797
|
+
* @param key the configuration property to read
|
|
2798
|
+
* @param makeFallback builds the fallback value when the option is absent
|
|
2799
|
+
* @returns the resolved object
|
|
2800
|
+
*/
|
|
2801
|
+
const _resolveObjectOption = function _resolveObjectOption(cfg, key, makeFallback) {
|
|
2802
|
+
const value = objectHasOwnProperty(cfg, key) ? cfg[key] : void 0;
|
|
2803
|
+
return value && typeof value === "object" ? clone$1(value) : makeFallback();
|
|
2804
|
+
};
|
|
2653
2805
|
function createDOMPurify() {
|
|
2654
2806
|
let window = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();
|
|
2655
2807
|
const DOMPurify = (root) => createDOMPurify(root);
|
|
2656
|
-
DOMPurify.version = "3.4.
|
|
2808
|
+
DOMPurify.version = "3.4.16";
|
|
2657
2809
|
DOMPurify.removed = [];
|
|
2658
2810
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
2659
2811
|
DOMPurify.isSupported = false;
|
|
@@ -2670,6 +2822,7 @@ document.open();document.write(html);document.close();
|
|
|
2670
2822
|
const ElementPrototype = Element.prototype;
|
|
2671
2823
|
const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
|
|
2672
2824
|
const remove = lookupGetter(ElementPrototype, "remove");
|
|
2825
|
+
const removeAttributeNode = lookupGetter(ElementPrototype, "removeAttributeNode");
|
|
2673
2826
|
const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
|
|
2674
2827
|
const getChildNodes = lookupGetter(ElementPrototype, "childNodes");
|
|
2675
2828
|
const getParentNode = lookupGetter(ElementPrototype, "parentNode");
|
|
@@ -2677,6 +2830,13 @@ document.open();document.write(html);document.close();
|
|
|
2677
2830
|
const getAttributes = lookupGetter(ElementPrototype, "attributes");
|
|
2678
2831
|
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeType") : null;
|
|
2679
2832
|
const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeName") : null;
|
|
2833
|
+
const getOwnerDocument = Node && Node.prototype ? lookupGetter(Node.prototype, "ownerDocument") : null;
|
|
2834
|
+
const _readNodeType = function _readNodeType(node) {
|
|
2835
|
+
return getNodeType ? getNodeType(node) : node.nodeType;
|
|
2836
|
+
};
|
|
2837
|
+
const _readNodeName = function _readNodeName(node) {
|
|
2838
|
+
return getNodeName ? getNodeName(node) : node.nodeName;
|
|
2839
|
+
};
|
|
2680
2840
|
if (typeof HTMLTemplateElement === "function") {
|
|
2681
2841
|
const template = document.createElement("template");
|
|
2682
2842
|
if (template.content && template.content.ownerDocument) document = template.content.ownerDocument;
|
|
@@ -2934,9 +3094,9 @@ document.open();document.write(html);document.close();
|
|
|
2934
3094
|
IN_PLACE = cfg.IN_PLACE || false;
|
|
2935
3095
|
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI;
|
|
2936
3096
|
NAMESPACE = typeof cfg.NAMESPACE === "string" ? cfg.NAMESPACE : HTML_NAMESPACE;
|
|
2937
|
-
MATHML_TEXT_INTEGRATION_POINTS =
|
|
2938
|
-
HTML_INTEGRATION_POINTS =
|
|
2939
|
-
const customElementHandling =
|
|
3097
|
+
MATHML_TEXT_INTEGRATION_POINTS = _resolveObjectOption(cfg, "MATHML_TEXT_INTEGRATION_POINTS", () => addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS));
|
|
3098
|
+
HTML_INTEGRATION_POINTS = _resolveObjectOption(cfg, "HTML_INTEGRATION_POINTS", () => addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS));
|
|
3099
|
+
const customElementHandling = _resolveObjectOption(cfg, "CUSTOM_ELEMENT_HANDLING", () => create(null));
|
|
2940
3100
|
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
2941
3101
|
if (objectHasOwnProperty(customElementHandling, "tagNameCheck") && isRegexOrFunction(customElementHandling.tagNameCheck)) CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck;
|
|
2942
3102
|
if (objectHasOwnProperty(customElementHandling, "attributeNameCheck") && isRegexOrFunction(customElementHandling.attributeNameCheck)) CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck;
|
|
@@ -2983,11 +3143,6 @@ document.open();document.write(html);document.close();
|
|
|
2983
3143
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
2984
3144
|
}
|
|
2985
3145
|
}
|
|
2986
|
-
if (objectHasOwnProperty(cfg, "ADD_URI_SAFE_ATTR") && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
2987
|
-
if (objectHasOwnProperty(cfg, "FORBID_CONTENTS") && arrayIsArray(cfg.FORBID_CONTENTS)) {
|
|
2988
|
-
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone$1(FORBID_CONTENTS);
|
|
2989
|
-
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
2990
|
-
}
|
|
2991
3146
|
if (objectHasOwnProperty(cfg, "ADD_FORBID_CONTENTS") && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
|
|
2992
3147
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone$1(FORBID_CONTENTS);
|
|
2993
3148
|
addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
|
|
@@ -3104,6 +3259,32 @@ document.open();document.write(html);document.close();
|
|
|
3104
3259
|
}
|
|
3105
3260
|
};
|
|
3106
3261
|
/**
|
|
3262
|
+
* _stripAttributeNode
|
|
3263
|
+
*
|
|
3264
|
+
* Remove a single Attr node case/namespace-exactly on an attribute-teardown
|
|
3265
|
+
* path. Name-based removeAttribute() ASCII-lowercases its lookup key for an
|
|
3266
|
+
* HTML element in an HTML document and so silently misses a case-preserved
|
|
3267
|
+
* handler (e.g. `ONERROR` off an XML/XHTML import) - the same defect
|
|
3268
|
+
* _removeAttribute() was fixed for, which a name-based call would reintroduce
|
|
3269
|
+
* on these IN_PLACE teardown paths. Unlike _removeAttribute this does not
|
|
3270
|
+
* record into DOMPurify.removed: the neutralize passes intentionally do not
|
|
3271
|
+
* book-keep. A clobbered/detached node falls back to best-effort name-based
|
|
3272
|
+
* removal.
|
|
3273
|
+
*
|
|
3274
|
+
* @param element the element to strip the attribute from
|
|
3275
|
+
* @param attribute the Attr node to remove
|
|
3276
|
+
* @param name the attribute's name, for the fallback path
|
|
3277
|
+
*/
|
|
3278
|
+
const _stripAttributeNode = function _stripAttributeNode(element, attribute, name) {
|
|
3279
|
+
try {
|
|
3280
|
+
removeAttributeNode(element, attribute);
|
|
3281
|
+
} catch (_) {
|
|
3282
|
+
try {
|
|
3283
|
+
element.removeAttribute(name);
|
|
3284
|
+
} catch (_) {}
|
|
3285
|
+
}
|
|
3286
|
+
};
|
|
3287
|
+
/**
|
|
3107
3288
|
* _neutralizeRoot
|
|
3108
3289
|
*
|
|
3109
3290
|
* Fail-closed teardown of an in-place root after the sanitize walk aborts
|
|
@@ -3120,6 +3301,7 @@ document.open();document.write(html);document.close();
|
|
|
3120
3301
|
* @param root the in-place root to empty
|
|
3121
3302
|
*/
|
|
3122
3303
|
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
3304
|
+
_neutralizeSubtree(root);
|
|
3123
3305
|
const childNodes = getChildNodes(root);
|
|
3124
3306
|
if (childNodes) {
|
|
3125
3307
|
const snapshot = [];
|
|
@@ -3136,30 +3318,45 @@ document.open();document.write(html);document.close();
|
|
|
3136
3318
|
if (attributes) for (let i = attributes.length - 1; i >= 0; --i) {
|
|
3137
3319
|
const attribute = attributes[i];
|
|
3138
3320
|
const name = attribute && attribute.name;
|
|
3139
|
-
if (typeof name === "string")
|
|
3140
|
-
root.removeAttribute(name);
|
|
3141
|
-
} catch (_) {}
|
|
3321
|
+
if (typeof name === "string") _stripAttributeNode(root, attribute, name);
|
|
3142
3322
|
}
|
|
3143
3323
|
};
|
|
3144
3324
|
/**
|
|
3145
3325
|
* _removeAttribute
|
|
3146
3326
|
*
|
|
3327
|
+
* Name-based getAttributeNode()/removeAttribute() ASCII-lowercase their
|
|
3328
|
+
* lookup key for HTML elements in an HTML document, so they silently miss an
|
|
3329
|
+
* attribute whose stored qualified name still contains uppercase ASCII
|
|
3330
|
+
* letters. That happens when the node came from a case-preserving source
|
|
3331
|
+
* (an XML/XHTML document imported via importNode(), or createAttributeNS()),
|
|
3332
|
+
* where e.g. `ONERROR` survives the walk: the policy check lowercases to
|
|
3333
|
+
* `onerror` and rejects it, but `removeAttribute('ONERROR')` looks up
|
|
3334
|
+
* `onerror` and finds nothing. Remove the exact Attr node instead, which is
|
|
3335
|
+
* case- and namespace-exact, and fall back to name-based removal only when
|
|
3336
|
+
* the caller could not supply the node.
|
|
3337
|
+
*
|
|
3147
3338
|
* @param name an Attribute name
|
|
3148
3339
|
* @param element a DOM node
|
|
3340
|
+
* @param attr the exact Attr node to remove, when the caller has it
|
|
3149
3341
|
*/
|
|
3150
|
-
const _removeAttribute = function _removeAttribute(name, element) {
|
|
3342
|
+
const _removeAttribute = function _removeAttribute(name, element, attr) {
|
|
3343
|
+
if (!attr) try {
|
|
3344
|
+
attr = element.getAttributeNode(name);
|
|
3345
|
+
} catch (_) {
|
|
3346
|
+
attr = null;
|
|
3347
|
+
}
|
|
3348
|
+
arrayPush(DOMPurify.removed, {
|
|
3349
|
+
attribute: attr || null,
|
|
3350
|
+
from: element
|
|
3351
|
+
});
|
|
3151
3352
|
try {
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
from: element
|
|
3155
|
-
});
|
|
3353
|
+
if (attr) removeAttributeNode(element, attr);
|
|
3354
|
+
else element.removeAttribute(name);
|
|
3156
3355
|
} catch (_) {
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
});
|
|
3356
|
+
try {
|
|
3357
|
+
element.removeAttribute(name);
|
|
3358
|
+
} catch (_) {}
|
|
3161
3359
|
}
|
|
3162
|
-
element.removeAttribute(name);
|
|
3163
3360
|
if (name === "is") if (RETURN_DOM || RETURN_DOM_FRAGMENT) try {
|
|
3164
3361
|
_forceRemove(element);
|
|
3165
3362
|
} catch (_) {}
|
|
@@ -3184,9 +3381,7 @@ document.open();document.write(html);document.close();
|
|
|
3184
3381
|
const attribute = attributes[i];
|
|
3185
3382
|
const name = attribute && attribute.name;
|
|
3186
3383
|
if (typeof name !== "string" || ALLOWED_ATTR[transformCaseFunc(name)]) continue;
|
|
3187
|
-
|
|
3188
|
-
element.removeAttribute(name);
|
|
3189
|
-
} catch (_) {}
|
|
3384
|
+
_stripAttributeNode(element, attribute, name);
|
|
3190
3385
|
}
|
|
3191
3386
|
};
|
|
3192
3387
|
/**
|
|
@@ -3215,7 +3410,82 @@ document.open();document.write(html);document.close();
|
|
|
3215
3410
|
const stack = [root];
|
|
3216
3411
|
while (stack.length > 0) {
|
|
3217
3412
|
const node = stack.pop();
|
|
3218
|
-
if ((
|
|
3413
|
+
if (_readNodeType(node) === NODE_TYPE.element) _stripDisallowedAttributes(node);
|
|
3414
|
+
const childNodes = getChildNodes(node);
|
|
3415
|
+
if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
|
|
3416
|
+
}
|
|
3417
|
+
};
|
|
3418
|
+
/**
|
|
3419
|
+
* _neutralizePatchLinkage
|
|
3420
|
+
*
|
|
3421
|
+
* IN_PLACE entry pre-pass (declarative-partial-updates / streaming
|
|
3422
|
+
* hardening, https://github.com/WICG/declarative-partial-updates).
|
|
3423
|
+
*
|
|
3424
|
+
* The main walk strips patch linkage (`for`/`patchsrc`) and removes range
|
|
3425
|
+
* markers (PIs / markup comments) node-by-node, in document order, AS it
|
|
3426
|
+
* reaches each node. On a live in-place root that leaves a window: from the
|
|
3427
|
+
* moment the root is connected until the walk arrives at a given node, that
|
|
3428
|
+
* node's linkage is live. A patch applied on connection/stream can fire as
|
|
3429
|
+
* a microtask during the walk and inject or teleport an unsanitized DOM
|
|
3430
|
+
* range into a region the iterator has already passed and will not revisit,
|
|
3431
|
+
* so the post-return "tree is sanitized" contract is violated. Sweep the
|
|
3432
|
+
* whole tree once up front and sever every linkage before the walk begins,
|
|
3433
|
+
* closing that window.
|
|
3434
|
+
*
|
|
3435
|
+
* This CANNOT undo a patch that already fired before sanitize ran — that is
|
|
3436
|
+
* the irreducible "do not IN_PLACE a live-connected attacker tree" caveat —
|
|
3437
|
+
* but it closes everything from sanitize-start onward. Gated on SAFE_FOR_XML
|
|
3438
|
+
* to group with the rest of the declarative-partial-updates handling and
|
|
3439
|
+
* stay overridable, consistent with the codebase.
|
|
3440
|
+
*
|
|
3441
|
+
* Clobber-safe traversal (cached childNodes getter); per-node try/catch so a
|
|
3442
|
+
* clobbered root cannot defeat the sweep of its non-clobbered descendants.
|
|
3443
|
+
*
|
|
3444
|
+
* NOTE (pending real-Chrome confirmation, see test/declarative-patch-probe
|
|
3445
|
+
* .html Q1): this mirrors the existing policy of keeping `for` on
|
|
3446
|
+
* <label>/<output>. If the shipping feature can drive a patch through a
|
|
3447
|
+
* surviving `for`-on-label/output + `id` pair, this pre-pass and the
|
|
3448
|
+
* attribute check at _isBasicCustomElement's caller must additionally drop
|
|
3449
|
+
* that pair on the IN_PLACE path. Left as-is until the taxonomy is verified.
|
|
3450
|
+
*
|
|
3451
|
+
* @param root the in-place root to sweep
|
|
3452
|
+
*/
|
|
3453
|
+
/**
|
|
3454
|
+
* Central policy for declarative-partial-updates patch-linkage attributes,
|
|
3455
|
+
* shared by the _neutralizePatchLinkage pre-pass and _isValidAttribute so
|
|
3456
|
+
* the two sites cannot drift: `patchsrc` always links, `for` links
|
|
3457
|
+
* everywhere except on <label>/<output>, and the whole policy is gated on
|
|
3458
|
+
* SAFE_FOR_XML (see the rationale block in _isValidAttribute).
|
|
3459
|
+
*
|
|
3460
|
+
* @param lcName the transformCaseFunc'd attribute name
|
|
3461
|
+
* @param lcTag the transformCaseFunc'd tag name of the carrying element
|
|
3462
|
+
* @return true if the attribute is patch linkage and must be dropped
|
|
3463
|
+
*/
|
|
3464
|
+
const _isPatchLinkageAttribute = function _isPatchLinkageAttribute(lcName, lcTag) {
|
|
3465
|
+
if (!SAFE_FOR_XML) return false;
|
|
3466
|
+
if (lcName === "patchsrc") return true;
|
|
3467
|
+
return lcName === "for" && lcTag !== "label" && lcTag !== "output";
|
|
3468
|
+
};
|
|
3469
|
+
const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
|
|
3470
|
+
if (!SAFE_FOR_XML) return;
|
|
3471
|
+
const stack = [root];
|
|
3472
|
+
while (stack.length > 0) {
|
|
3473
|
+
const node = stack.pop();
|
|
3474
|
+
const nodeType = _readNodeType(node);
|
|
3475
|
+
if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
|
|
3476
|
+
try {
|
|
3477
|
+
remove(node);
|
|
3478
|
+
} catch (_) {}
|
|
3479
|
+
continue;
|
|
3480
|
+
}
|
|
3481
|
+
if (nodeType === NODE_TYPE.element) {
|
|
3482
|
+
const element = node;
|
|
3483
|
+
const lcTag = transformCaseFunc(_readNodeName(node));
|
|
3484
|
+
try {
|
|
3485
|
+
if (element.hasAttribute && element.hasAttribute("patchsrc")) element.removeAttribute("patchsrc");
|
|
3486
|
+
if (element.hasAttribute && element.hasAttribute("for") && _isPatchLinkageAttribute("for", lcTag)) element.removeAttribute("for");
|
|
3487
|
+
} catch (_) {}
|
|
3488
|
+
}
|
|
3219
3489
|
const childNodes = getChildNodes(node);
|
|
3220
3490
|
if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
|
|
3221
3491
|
}
|
|
@@ -3257,7 +3527,8 @@ document.open();document.write(html);document.close();
|
|
|
3257
3527
|
* @return The created NodeIterator
|
|
3258
3528
|
*/
|
|
3259
3529
|
const _createNodeIterator = function _createNodeIterator(root) {
|
|
3260
|
-
|
|
3530
|
+
const doc = getOwnerDocument ? getOwnerDocument(root) : root.ownerDocument;
|
|
3531
|
+
return createNodeIterator.call(doc || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
3261
3532
|
};
|
|
3262
3533
|
/**
|
|
3263
3534
|
* Replace template expression syntax (mustache, ERB, template
|
|
@@ -3295,7 +3566,8 @@ document.open();document.write(html);document.close();
|
|
|
3295
3566
|
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
3296
3567
|
var _node$querySelectorAl;
|
|
3297
3568
|
node.normalize();
|
|
3298
|
-
const
|
|
3569
|
+
const doc = getOwnerDocument ? getOwnerDocument(node) : node.ownerDocument;
|
|
3570
|
+
const walker = createNodeIterator.call(doc || node, node, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
3299
3571
|
let currentNode = walker.nextNode();
|
|
3300
3572
|
while (currentNode) {
|
|
3301
3573
|
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
@@ -3322,7 +3594,7 @@ document.open();document.write(html);document.close();
|
|
|
3322
3594
|
const realTagName = getNodeName ? getNodeName(element) : null;
|
|
3323
3595
|
if (typeof realTagName !== "string") return false;
|
|
3324
3596
|
if (transformCaseFunc(realTagName) !== "form") return false;
|
|
3325
|
-
return typeof element.nodeName !== "string" || typeof element.textContent !== "string" || typeof element.removeChild !== "function" || element.attributes !== getAttributes(element) || typeof element.removeAttribute !== "function" || typeof element.setAttribute !== "function" || typeof element.namespaceURI !== "string" || typeof element.insertBefore !== "function" || typeof element.hasChildNodes !== "function" || element.nodeType !== getNodeType(element) || element.childNodes !== getChildNodes(element);
|
|
3597
|
+
return typeof element.nodeName !== "string" || typeof element.textContent !== "string" || typeof element.removeChild !== "function" || element.attributes !== getAttributes(element) || typeof element.removeAttribute !== "function" || typeof element.removeAttributeNode !== "function" || typeof element.getAttributeNode !== "function" || typeof element.setAttribute !== "function" || typeof element.namespaceURI !== "string" || typeof element.insertBefore !== "function" || typeof element.hasChildNodes !== "function" || element.nodeType !== getNodeType(element) || element.childNodes !== getChildNodes(element);
|
|
3326
3598
|
};
|
|
3327
3599
|
/**
|
|
3328
3600
|
* Checks whether the given value is a DocumentFragment from any realm.
|
|
@@ -3380,34 +3652,57 @@ document.open();document.write(html);document.close();
|
|
|
3380
3652
|
*/
|
|
3381
3653
|
const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
|
|
3382
3654
|
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) return true;
|
|
3383
|
-
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === "
|
|
3655
|
+
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && LITERAL_TEXT_ELEMENTS[tagName] && (_isNode(currentNode.firstElementChild) || typeof currentNode.textContent === "string" && regExpTest(LITERAL_TEXT_CLOSE[tagName], currentNode.textContent))) return true;
|
|
3384
3656
|
if (currentNode.nodeType === NODE_TYPE.processingInstruction) return true;
|
|
3385
3657
|
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) return true;
|
|
3386
3658
|
return false;
|
|
3387
3659
|
};
|
|
3388
3660
|
/**
|
|
3661
|
+
* Evaluate a CUSTOM_ELEMENT_HANDLING check (a RegExp or a predicate
|
|
3662
|
+
* function, per the validation in _parseConfig) against a name.
|
|
3663
|
+
* Additional arguments are forwarded to predicate functions - the
|
|
3664
|
+
* attributeNameCheck predicate receives the tag name as its second
|
|
3665
|
+
* argument. A null/absent check never matches.
|
|
3666
|
+
*
|
|
3667
|
+
* @param check the configured tagNameCheck / attributeNameCheck value
|
|
3668
|
+
* @param name the name to test
|
|
3669
|
+
* @param args extra arguments forwarded to a predicate function
|
|
3670
|
+
* @return true if the check matches the name
|
|
3671
|
+
*/
|
|
3672
|
+
const _matchesNameCheck = function _matchesNameCheck(check, name) {
|
|
3673
|
+
if (check instanceof RegExp) return regExpTest(check, name);
|
|
3674
|
+
if (check instanceof Function) {
|
|
3675
|
+
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) args[_key - 2] = arguments[_key];
|
|
3676
|
+
return Boolean(check(name, ...args));
|
|
3677
|
+
}
|
|
3678
|
+
return false;
|
|
3679
|
+
};
|
|
3680
|
+
/**
|
|
3389
3681
|
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
3390
3682
|
* allowed custom elements (false return exits _sanitizeElements
|
|
3391
|
-
* early - namespace
|
|
3392
|
-
*
|
|
3393
|
-
*
|
|
3683
|
+
* early - the namespace and fallback-tag removal checks are
|
|
3684
|
+
* intentionally skipped for kept custom elements), else hoist
|
|
3685
|
+
* content per KEEP_CONTENT and remove.
|
|
3686
|
+
*
|
|
3687
|
+
* A kept custom element is the ONLY case in which this function
|
|
3688
|
+
* returns false, so the caller uses that return value to run the
|
|
3689
|
+
* afterSanitizeElements hook on the kept element and keep the
|
|
3690
|
+
* element-hook lifecycle consistent with normal allowlisted
|
|
3691
|
+
* elements (GHSA-c2j3-45gr-mqc4).
|
|
3394
3692
|
*
|
|
3395
3693
|
* @param currentNode the disallowed node
|
|
3396
3694
|
* @param tagName the node's transformCaseFunc'd tag name
|
|
3397
3695
|
* @return true if the node was removed, false if kept
|
|
3398
3696
|
*/
|
|
3399
|
-
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
|
|
3400
|
-
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName))
|
|
3401
|
-
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
|
|
3402
|
-
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
|
|
3403
|
-
}
|
|
3697
|
+
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName, root) {
|
|
3698
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
|
|
3404
3699
|
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
3405
3700
|
const parentNode = getParentNode(currentNode);
|
|
3406
3701
|
const childNodes = getChildNodes(currentNode);
|
|
3407
3702
|
if (childNodes && parentNode) {
|
|
3408
3703
|
const childCount = childNodes.length;
|
|
3409
3704
|
for (let i = childCount - 1; i >= 0; --i) {
|
|
3410
|
-
const hoisted =
|
|
3705
|
+
const hoisted = currentNode === root ? cloneNode(childNodes[i], true) : childNodes[i];
|
|
3411
3706
|
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
3412
3707
|
}
|
|
3413
3708
|
}
|
|
@@ -3416,6 +3711,60 @@ document.open();document.write(html);document.close();
|
|
|
3416
3711
|
return true;
|
|
3417
3712
|
};
|
|
3418
3713
|
/**
|
|
3714
|
+
* Fork a hook-mutable allowlist off its shared binding the first time a
|
|
3715
|
+
* (possibly lazily-installed) uponSanitize* hook is about to see it, so the
|
|
3716
|
+
* hook cannot widen the per-instance default or the setConfig binding by
|
|
3717
|
+
* reference and leak past the call. Returns the set unchanged once it is
|
|
3718
|
+
* already call-local, so repeated calls across elements are idempotent.
|
|
3719
|
+
*
|
|
3720
|
+
* @param hookList the uponSanitize* hook array for this event
|
|
3721
|
+
* @param set the current ALLOWED_TAGS / ALLOWED_ATTR binding
|
|
3722
|
+
* @param defaultSet the per-instance DEFAULT_ALLOWED_* constant
|
|
3723
|
+
* @param setConfigSet the captured setConfig() binding, or null
|
|
3724
|
+
* @return a call-local clone if a hook is present and set is still shared,
|
|
3725
|
+
* else set unchanged
|
|
3726
|
+
*/
|
|
3727
|
+
const _forkSharedAllowlist = function _forkSharedAllowlist(hookList, set, defaultSet, setConfigSet) {
|
|
3728
|
+
if (hookList.length === 0) return set;
|
|
3729
|
+
return set === defaultSet || set === setConfigSet ? clone$1(set) : set;
|
|
3730
|
+
};
|
|
3731
|
+
/**
|
|
3732
|
+
* Shared guard for a node that a hook has detached from the walk tree,
|
|
3733
|
+
* used after each element-hook site in _sanitizeElements. Detaching is a
|
|
3734
|
+
* long-standing user pattern (issue #469; draw.io-style foreignObject
|
|
3735
|
+
* filtering). Per the cached, unclobberable parentNode getter the node is
|
|
3736
|
+
* genuinely out of the tree, so it can reach neither the serialized
|
|
3737
|
+
* output nor an IN_PLACE live tree; treat it as removed and stop
|
|
3738
|
+
* processing it. Without this guard, the unsafe-node / namespace checks
|
|
3739
|
+
* would call _forceRemove on a parentless node and hit the REPORT-3
|
|
3740
|
+
* fail-closed throw — which exists for nodes DOMPurify wants gone but
|
|
3741
|
+
* *cannot* detach (clobbered / parentless roots), the opposite of a node
|
|
3742
|
+
* that is already safely gone. The walk root is exempt: a detached
|
|
3743
|
+
* IN_PLACE root is legitimate input and must still be fully sanitized,
|
|
3744
|
+
* and a kill-decision on it must keep hitting the REPORT-3 throw.
|
|
3745
|
+
*
|
|
3746
|
+
* Nodes detached by hooks stay the hook's responsibility for placement:
|
|
3747
|
+
* they are not recorded in DOMPurify.removed, so the post-walk IN_PLACE
|
|
3748
|
+
* pass (which iterates DOMPurify.removed) does not reach them. But a
|
|
3749
|
+
* hook-detached subtree can still hold a queued resource-event handler -
|
|
3750
|
+
* e.g. an <img onload> that began loading when the caller built the live
|
|
3751
|
+
* tree - which fires in page scope after sanitize returns even though the
|
|
3752
|
+
* handler never reached the returned tree. That is the audit-5 F1 hazard,
|
|
3753
|
+
* and the documented node.remove() hook pattern walks straight into it.
|
|
3754
|
+
* So on the IN_PLACE path we neutralize the detached subtree inline,
|
|
3755
|
+
* stripping its non-allow-listed attributes before returning, exactly as
|
|
3756
|
+
* the post-walk pass does for _forceRemove'd subtrees.
|
|
3757
|
+
*
|
|
3758
|
+
* @param currentNode the node a hook may have detached
|
|
3759
|
+
* @param root the current walk root
|
|
3760
|
+
* @return true if the node is detached and now handled, false otherwise
|
|
3761
|
+
*/
|
|
3762
|
+
const _handleHookDetachedNode = function _handleHookDetachedNode(currentNode, root) {
|
|
3763
|
+
if (currentNode === root || getParentNode(currentNode) !== null) return false;
|
|
3764
|
+
if (IN_PLACE) _neutralizeSubtree(currentNode);
|
|
3765
|
+
return true;
|
|
3766
|
+
};
|
|
3767
|
+
/**
|
|
3419
3768
|
* _sanitizeElements
|
|
3420
3769
|
*
|
|
3421
3770
|
* @protect nodeName
|
|
@@ -3424,23 +3773,33 @@ document.open();document.write(html);document.close();
|
|
|
3424
3773
|
* @param currentNode to check for permission to exist
|
|
3425
3774
|
* @return true if node was killed, false if left alive
|
|
3426
3775
|
*/
|
|
3427
|
-
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
3776
|
+
const _sanitizeElements = function _sanitizeElements(currentNode, root) {
|
|
3428
3777
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
3778
|
+
if (_handleHookDetachedNode(currentNode, root)) return true;
|
|
3429
3779
|
if (_isClobbered(currentNode)) {
|
|
3430
3780
|
_forceRemove(currentNode);
|
|
3431
3781
|
return true;
|
|
3432
3782
|
}
|
|
3433
|
-
const tagName = transformCaseFunc(
|
|
3783
|
+
const tagName = transformCaseFunc(_readNodeName(currentNode));
|
|
3784
|
+
ALLOWED_TAGS = _forkSharedAllowlist(hooks.uponSanitizeElement, ALLOWED_TAGS, DEFAULT_ALLOWED_TAGS, SET_CONFIG_ALLOWED_TAGS);
|
|
3434
3785
|
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
3435
3786
|
tagName,
|
|
3436
3787
|
allowedTags: ALLOWED_TAGS
|
|
3437
3788
|
});
|
|
3789
|
+
if (_handleHookDetachedNode(currentNode, root)) return true;
|
|
3438
3790
|
if (_isUnsafeNode(currentNode, tagName)) {
|
|
3439
3791
|
_forceRemove(currentNode);
|
|
3440
3792
|
return true;
|
|
3441
3793
|
}
|
|
3442
|
-
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName])
|
|
3443
|
-
|
|
3794
|
+
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
3795
|
+
const removed = _sanitizeDisallowedNode(currentNode, tagName, root);
|
|
3796
|
+
if (removed === false) {
|
|
3797
|
+
_executeHooks(hooks.afterSanitizeElements, currentNode, null);
|
|
3798
|
+
if (_handleHookDetachedNode(currentNode, root)) return true;
|
|
3799
|
+
}
|
|
3800
|
+
return removed;
|
|
3801
|
+
}
|
|
3802
|
+
if (_readNodeType(currentNode) === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
|
|
3444
3803
|
_forceRemove(currentNode);
|
|
3445
3804
|
return true;
|
|
3446
3805
|
}
|
|
@@ -3456,7 +3815,7 @@ document.open();document.write(html);document.close();
|
|
|
3456
3815
|
}
|
|
3457
3816
|
}
|
|
3458
3817
|
_executeHooks(hooks.afterSanitizeElements, currentNode, null);
|
|
3459
|
-
return
|
|
3818
|
+
return _handleHookDetachedNode(currentNode, root);
|
|
3460
3819
|
};
|
|
3461
3820
|
/**
|
|
3462
3821
|
* _isValidAttribute
|
|
@@ -3468,18 +3827,17 @@ document.open();document.write(html);document.close();
|
|
|
3468
3827
|
*/
|
|
3469
3828
|
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
|
|
3470
3829
|
if (FORBID_ATTR[lcName]) return false;
|
|
3830
|
+
if (_isPatchLinkageAttribute(lcName, lcTag)) return false;
|
|
3471
3831
|
if (SANITIZE_DOM && (lcName === "id" || lcName === "name") && (value in document || value in formElement)) return false;
|
|
3472
3832
|
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
3473
|
-
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName));
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
else if (value) return false;
|
|
3482
|
-
return true;
|
|
3833
|
+
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) return true;
|
|
3834
|
+
if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) return true;
|
|
3835
|
+
if (!nameIsPermitted) return _isBasicCustomElement(lcTag) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName, lcTag) || lcName === "is" && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value);
|
|
3836
|
+
if (URI_SAFE_ATTRIBUTES[lcName]) return true;
|
|
3837
|
+
if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ""))) return true;
|
|
3838
|
+
if ((lcName === "src" || lcName === "xlink:href" || lcName === "href") && lcTag !== "script" && stringIndexOf(value, "data:") === 0 && DATA_URI_TAGS[lcTag]) return true;
|
|
3839
|
+
if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ""))) return true;
|
|
3840
|
+
return !value;
|
|
3483
3841
|
};
|
|
3484
3842
|
const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, [
|
|
3485
3843
|
"annotation-xml",
|
|
@@ -3524,24 +3882,38 @@ document.open();document.write(html);document.close();
|
|
|
3524
3882
|
/**
|
|
3525
3883
|
* Write a modified attribute value back onto the element. On
|
|
3526
3884
|
* success, re-probe for clobbering introduced by the new value and
|
|
3527
|
-
* remove the element when found; otherwise
|
|
3528
|
-
*
|
|
3529
|
-
*
|
|
3885
|
+
* remove the element when found; otherwise, when this writeback is the
|
|
3886
|
+
* recreate half of the SANITIZE_NAMED_PROPS remove-and-recreate, pop the
|
|
3887
|
+
* removal entry that path recorded so it does not show as removed. On
|
|
3530
3888
|
* failure, remove the attribute instead.
|
|
3531
3889
|
*
|
|
3890
|
+
* Returns true only on a clean write (the value was set and the new value
|
|
3891
|
+
* introduced no clobbering). The caller uses that, together with its own
|
|
3892
|
+
* knowledge of whether this attribute pushed a DOMPurify.removed record, to
|
|
3893
|
+
* decide whether to pop that record. The pop must happen ONLY for the
|
|
3894
|
+
* named-prop remove-and-recreate; popping on any other value change (trim,
|
|
3895
|
+
* template scrubbing, Trusted Types) would consume an unrelated _forceRemove
|
|
3896
|
+
* subtree-cleanup record and let that detached subtree keep a live event
|
|
3897
|
+
* handler through the IN_PLACE neutralization pass (SO-001).
|
|
3898
|
+
*
|
|
3532
3899
|
* @param currentNode the element carrying the attribute
|
|
3533
3900
|
* @param name the attribute name as present on the element
|
|
3534
3901
|
* @param namespaceURI the attribute's namespace, if any
|
|
3535
3902
|
* @param value the new attribute value
|
|
3903
|
+
* @return true if the value was written without introducing clobbering
|
|
3536
3904
|
*/
|
|
3537
3905
|
const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
|
|
3538
3906
|
try {
|
|
3539
3907
|
if (namespaceURI) currentNode.setAttributeNS(namespaceURI, name, value);
|
|
3540
3908
|
else currentNode.setAttribute(name, value);
|
|
3541
|
-
if (_isClobbered(currentNode))
|
|
3542
|
-
|
|
3909
|
+
if (_isClobbered(currentNode)) {
|
|
3910
|
+
_forceRemove(currentNode);
|
|
3911
|
+
return false;
|
|
3912
|
+
}
|
|
3913
|
+
return true;
|
|
3543
3914
|
} catch (_) {
|
|
3544
3915
|
_removeAttribute(name, currentNode);
|
|
3916
|
+
return false;
|
|
3545
3917
|
}
|
|
3546
3918
|
};
|
|
3547
3919
|
/**
|
|
@@ -3553,11 +3925,14 @@ document.open();document.write(html);document.close();
|
|
|
3553
3925
|
* @protect setAttribute
|
|
3554
3926
|
*
|
|
3555
3927
|
* @param currentNode to sanitize
|
|
3928
|
+
* @param root the current walk root
|
|
3556
3929
|
*/
|
|
3557
|
-
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
3930
|
+
const _sanitizeAttributes = function _sanitizeAttributes(currentNode, root) {
|
|
3558
3931
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
3932
|
+
if (_handleHookDetachedNode(currentNode, root)) return;
|
|
3559
3933
|
const attributes = currentNode.attributes;
|
|
3560
3934
|
if (!attributes || _isClobbered(currentNode)) return;
|
|
3935
|
+
ALLOWED_ATTR = _forkSharedAllowlist(hooks.uponSanitizeAttribute, ALLOWED_ATTR, DEFAULT_ALLOWED_ATTR, SET_CONFIG_ALLOWED_ATTR);
|
|
3561
3936
|
const hookEvent = {
|
|
3562
3937
|
attrName: "",
|
|
3563
3938
|
attrValue: "",
|
|
@@ -3573,6 +3948,7 @@ document.open();document.write(html);document.close();
|
|
|
3573
3948
|
const lcName = transformCaseFunc(name);
|
|
3574
3949
|
const initValue = attrValue;
|
|
3575
3950
|
let value = name === "value" ? initValue : stringTrim(initValue);
|
|
3951
|
+
let recreatedNamedProp = false;
|
|
3576
3952
|
hookEvent.attrName = lcName;
|
|
3577
3953
|
hookEvent.attrValue = value;
|
|
3578
3954
|
hookEvent.keepAttr = true;
|
|
@@ -3580,35 +3956,39 @@ document.open();document.write(html);document.close();
|
|
|
3580
3956
|
_executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
|
|
3581
3957
|
value = hookEvent.attrValue;
|
|
3582
3958
|
if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name") && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
|
|
3583
|
-
_removeAttribute(name, currentNode);
|
|
3959
|
+
_removeAttribute(name, currentNode, attr);
|
|
3584
3960
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
3961
|
+
recreatedNamedProp = true;
|
|
3585
3962
|
}
|
|
3586
3963
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
3587
|
-
_removeAttribute(name, currentNode);
|
|
3964
|
+
_removeAttribute(name, currentNode, attr);
|
|
3588
3965
|
continue;
|
|
3589
3966
|
}
|
|
3590
3967
|
if (lcName === "attributename" && stringMatch(value, "href")) {
|
|
3591
|
-
_removeAttribute(name, currentNode);
|
|
3968
|
+
_removeAttribute(name, currentNode, attr);
|
|
3592
3969
|
continue;
|
|
3593
3970
|
}
|
|
3594
3971
|
if (hookEvent.forceKeepAttr) continue;
|
|
3595
3972
|
if (!hookEvent.keepAttr) {
|
|
3596
|
-
_removeAttribute(name, currentNode);
|
|
3973
|
+
_removeAttribute(name, currentNode, attr);
|
|
3597
3974
|
continue;
|
|
3598
3975
|
}
|
|
3599
3976
|
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
|
|
3600
|
-
_removeAttribute(name, currentNode);
|
|
3977
|
+
_removeAttribute(name, currentNode, attr);
|
|
3601
3978
|
continue;
|
|
3602
3979
|
}
|
|
3603
3980
|
if (SAFE_FOR_TEMPLATES) value = _stripTemplateExpressions(value);
|
|
3604
3981
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
3605
|
-
_removeAttribute(name, currentNode);
|
|
3982
|
+
_removeAttribute(name, currentNode, attr);
|
|
3606
3983
|
continue;
|
|
3607
3984
|
}
|
|
3608
3985
|
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
3609
|
-
if (value !== initValue)
|
|
3986
|
+
if (value !== initValue) {
|
|
3987
|
+
if (_setAttributeValue(currentNode, name, namespaceURI, value) && recreatedNamedProp) arrayPop(DOMPurify.removed);
|
|
3988
|
+
}
|
|
3610
3989
|
}
|
|
3611
3990
|
_executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
|
|
3991
|
+
_handleHookDetachedNode(currentNode, root);
|
|
3612
3992
|
};
|
|
3613
3993
|
/**
|
|
3614
3994
|
* _sanitizeShadowDOM
|
|
@@ -3621,10 +4001,10 @@ document.open();document.write(html);document.close();
|
|
|
3621
4001
|
_executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
|
|
3622
4002
|
while (shadowNode = shadowIterator.nextNode()) {
|
|
3623
4003
|
_executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
|
|
3624
|
-
_sanitizeElements(shadowNode);
|
|
3625
|
-
_sanitizeAttributes(shadowNode);
|
|
4004
|
+
_sanitizeElements(shadowNode, fragment);
|
|
4005
|
+
_sanitizeAttributes(shadowNode, fragment);
|
|
3626
4006
|
if (_isDocumentFragment(shadowNode.content)) _sanitizeShadowDOM2(shadowNode.content);
|
|
3627
|
-
if ((
|
|
4007
|
+
if (_readNodeType(shadowNode) === NODE_TYPE.element) {
|
|
3628
4008
|
const innerSr = getShadowRoot(shadowNode);
|
|
3629
4009
|
if (_isDocumentFragment(innerSr)) {
|
|
3630
4010
|
_sanitizeAttachedShadowRoots(innerSr);
|
|
@@ -3665,7 +4045,7 @@ document.open();document.write(html);document.close();
|
|
|
3665
4045
|
continue;
|
|
3666
4046
|
}
|
|
3667
4047
|
const node = item.node;
|
|
3668
|
-
const isElement = (
|
|
4048
|
+
const isElement = _readNodeType(node) === NODE_TYPE.element;
|
|
3669
4049
|
const childNodes = getChildNodes(node);
|
|
3670
4050
|
if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push({
|
|
3671
4051
|
node: childNodes[i],
|
|
@@ -3715,12 +4095,19 @@ document.open();document.write(html);document.close();
|
|
|
3715
4095
|
DOMPurify.removed = [];
|
|
3716
4096
|
const inPlace = IN_PLACE && typeof dirty !== "string" && _isNode(dirty);
|
|
3717
4097
|
if (inPlace) {
|
|
3718
|
-
|
|
4098
|
+
_neutralizePatchLinkage(dirty);
|
|
4099
|
+
const nn = _readNodeName(dirty);
|
|
3719
4100
|
if (typeof nn === "string") {
|
|
3720
4101
|
const tagName = transformCaseFunc(nn);
|
|
3721
|
-
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName])
|
|
4102
|
+
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
4103
|
+
_neutralizeRoot(dirty);
|
|
4104
|
+
throw typeErrorCreate("root node is forbidden and cannot be sanitized in-place");
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
if (_isClobbered(dirty)) {
|
|
4108
|
+
_neutralizeRoot(dirty);
|
|
4109
|
+
throw typeErrorCreate("root node is clobbered and cannot be sanitized in-place");
|
|
3722
4110
|
}
|
|
3723
|
-
if (_isClobbered(dirty)) throw typeErrorCreate("root node is clobbered and cannot be sanitized in-place");
|
|
3724
4111
|
try {
|
|
3725
4112
|
_sanitizeAttachedShadowRoots(dirty);
|
|
3726
4113
|
} catch (error) {
|
|
@@ -3733,28 +4120,39 @@ document.open();document.write(html);document.close();
|
|
|
3733
4120
|
if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === "BODY") body = importedNode;
|
|
3734
4121
|
else if (importedNode.nodeName === "HTML") body = importedNode;
|
|
3735
4122
|
else body.appendChild(importedNode);
|
|
3736
|
-
_sanitizeAttachedShadowRoots(
|
|
4123
|
+
_sanitizeAttachedShadowRoots(body);
|
|
3737
4124
|
} else {
|
|
3738
4125
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && dirty.indexOf("<") === -1) return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
|
|
3739
4126
|
body = _initDocument(dirty);
|
|
3740
4127
|
if (!body) return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : "";
|
|
3741
4128
|
}
|
|
3742
4129
|
if (body && FORCE_BODY) _forceRemove(body.firstChild);
|
|
3743
|
-
const
|
|
4130
|
+
const walkRoot = inPlace ? dirty : body;
|
|
3744
4131
|
try {
|
|
4132
|
+
const nodeIterator = _createNodeIterator(walkRoot);
|
|
3745
4133
|
while (currentNode = nodeIterator.nextNode()) {
|
|
3746
|
-
_sanitizeElements(currentNode);
|
|
3747
|
-
_sanitizeAttributes(currentNode);
|
|
4134
|
+
_sanitizeElements(currentNode, walkRoot);
|
|
4135
|
+
_sanitizeAttributes(currentNode, walkRoot);
|
|
3748
4136
|
if (_isDocumentFragment(currentNode.content)) _sanitizeShadowDOM2(currentNode.content);
|
|
3749
4137
|
}
|
|
3750
4138
|
} catch (error) {
|
|
3751
|
-
if (inPlace)
|
|
4139
|
+
if (inPlace) {
|
|
4140
|
+
_neutralizeRoot(dirty);
|
|
4141
|
+
arrayForEach(DOMPurify.removed, (entry) => {
|
|
4142
|
+
if (entry.element) _neutralizeSubtree(entry.element);
|
|
4143
|
+
});
|
|
4144
|
+
}
|
|
3752
4145
|
throw error;
|
|
3753
4146
|
}
|
|
3754
4147
|
if (inPlace) {
|
|
4148
|
+
let rootWasRemoved = false;
|
|
3755
4149
|
arrayForEach(DOMPurify.removed, (entry) => {
|
|
3756
|
-
if (entry.element)
|
|
4150
|
+
if (entry.element) {
|
|
4151
|
+
if (entry.element === dirty) rootWasRemoved = true;
|
|
4152
|
+
_neutralizeSubtree(entry.element);
|
|
4153
|
+
}
|
|
3757
4154
|
});
|
|
4155
|
+
if (rootWasRemoved) throw typeErrorCreate("a node selected for removal could not be safely returned; refusing to sanitize in place");
|
|
3758
4156
|
if (SAFE_FOR_TEMPLATES) _scrubTemplateExpressions2(dirty);
|
|
3759
4157
|
return dirty;
|
|
3760
4158
|
}
|
|
@@ -3812,7 +4210,7 @@ document.open();document.write(html);document.close();
|
|
|
3812
4210
|
};
|
|
3813
4211
|
return DOMPurify;
|
|
3814
4212
|
}
|
|
3815
|
-
var
|
|
4213
|
+
var purify_default = createDOMPurify();
|
|
3816
4214
|
//#endregion
|
|
3817
4215
|
//#region lib/types/client/html/basic-document.js
|
|
3818
4216
|
/** Static HTML preview with no scripts, network resources, forms or nested frames. */
|
|
@@ -3822,7 +4220,7 @@ document.open();document.write(html);document.close();
|
|
|
3822
4220
|
* @returns document with the restrictive CSP first in its head.
|
|
3823
4221
|
*/
|
|
3824
4222
|
function createBasicHtmlDocument(data) {
|
|
3825
|
-
const clean =
|
|
4223
|
+
const clean = purify_default.sanitize(decodeText(data), {
|
|
3826
4224
|
WHOLE_DOCUMENT: true,
|
|
3827
4225
|
FORBID_TAGS: [
|
|
3828
4226
|
"noscript",
|