@deepseek-ai/dsh-client-ui-sidebar-documentpreview 0.2.1-alpha.1 → 0.2.1-alpha.3
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.js +438 -92
- package/locale/en.json +5 -0
- package/locale/zh.json +6 -0
- package/package.json +30 -30
package/lib/client.js
CHANGED
|
@@ -1080,7 +1080,7 @@ window.__ModuleLoader__.load({
|
|
|
1080
1080
|
return {
|
|
1081
1081
|
id: TEXTPREVIEW_ID,
|
|
1082
1082
|
kind: TEXTPREVIEW_KIND,
|
|
1083
|
-
patterns: ["dsh-resource://file
|
|
1083
|
+
patterns: ["dsh-resource://file/[\\s\\S]+"],
|
|
1084
1084
|
priority: "fallback",
|
|
1085
1085
|
canOpen: (address) => parseFileAddress(address)?.scope === "session",
|
|
1086
1086
|
title: basenameOf
|
|
@@ -1756,8 +1756,11 @@ document.open();document.write(html);document.close();
|
|
|
1756
1756
|
})()<\/script>`;
|
|
1757
1757
|
}
|
|
1758
1758
|
//#endregion
|
|
1759
|
-
//#region ../../../node_modules/.pnpm/dompurify@3.4.
|
|
1760
|
-
/*! @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
|
+
}
|
|
1761
1764
|
function _arrayLikeToArray(r, a) {
|
|
1762
1765
|
(null == a || a > r.length) && (a = r.length);
|
|
1763
1766
|
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
@@ -1769,12 +1772,14 @@ document.open();document.write(html);document.close();
|
|
|
1769
1772
|
function _iterableToArrayLimit(r, l) {
|
|
1770
1773
|
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
1771
1774
|
if (null != t) {
|
|
1772
|
-
var e, n, i, u, a = [], f =
|
|
1775
|
+
var e, n, i, u, a = [], f = !0, o = !1;
|
|
1773
1776
|
try {
|
|
1774
|
-
if (i = (t = t.call(r)).next, 0 === l)
|
|
1775
|
-
|
|
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);
|
|
1776
1781
|
} catch (r) {
|
|
1777
|
-
o =
|
|
1782
|
+
o = !0, n = r;
|
|
1778
1783
|
} finally {
|
|
1779
1784
|
try {
|
|
1780
1785
|
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
@@ -1788,6 +1793,7 @@ document.open();document.write(html);document.close();
|
|
|
1788
1793
|
function _nonIterableRest() {
|
|
1789
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.");
|
|
1790
1795
|
}
|
|
1796
|
+
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
|
|
1791
1797
|
function _slicedToArray(r, e) {
|
|
1792
1798
|
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
1793
1799
|
}
|
|
@@ -1798,9 +1804,64 @@ document.open();document.write(html);document.close();
|
|
|
1798
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;
|
|
1799
1805
|
}
|
|
1800
1806
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
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;
|
|
1804
1865
|
if (!freeze) freeze = function freeze(x) {
|
|
1805
1866
|
return x;
|
|
1806
1867
|
};
|
|
@@ -1816,9 +1877,11 @@ document.open();document.write(html);document.close();
|
|
|
1816
1877
|
return new Func(...args);
|
|
1817
1878
|
};
|
|
1818
1879
|
const arrayForEach = unapply(Array.prototype.forEach);
|
|
1880
|
+
Array.prototype.indexOf;
|
|
1819
1881
|
const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
1820
1882
|
const arrayPop = unapply(Array.prototype.pop);
|
|
1821
1883
|
const arrayPush = unapply(Array.prototype.push);
|
|
1884
|
+
Array.prototype.slice;
|
|
1822
1885
|
const arraySplice = unapply(Array.prototype.splice);
|
|
1823
1886
|
const arrayIsArray = Array.isArray;
|
|
1824
1887
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
@@ -2396,6 +2459,7 @@ document.open();document.write(html);document.close();
|
|
|
2396
2459
|
"direction",
|
|
2397
2460
|
"display",
|
|
2398
2461
|
"divisor",
|
|
2462
|
+
"dominant-baseline",
|
|
2399
2463
|
"dur",
|
|
2400
2464
|
"edgemode",
|
|
2401
2465
|
"elevation",
|
|
@@ -2477,6 +2541,7 @@ document.open();document.write(html);document.close();
|
|
|
2477
2541
|
"patterncontentunits",
|
|
2478
2542
|
"patterntransform",
|
|
2479
2543
|
"patternunits",
|
|
2544
|
+
"pointer-events",
|
|
2480
2545
|
"points",
|
|
2481
2546
|
"preservealpha",
|
|
2482
2547
|
"preserveaspectratio",
|
|
@@ -2523,6 +2588,7 @@ document.open();document.write(html);document.close();
|
|
|
2523
2588
|
"transform-origin",
|
|
2524
2589
|
"text-anchor",
|
|
2525
2590
|
"text-decoration",
|
|
2591
|
+
"text-orientation",
|
|
2526
2592
|
"text-rendering",
|
|
2527
2593
|
"textlength",
|
|
2528
2594
|
"type",
|
|
@@ -2530,6 +2596,7 @@ document.open();document.write(html);document.close();
|
|
|
2530
2596
|
"u2",
|
|
2531
2597
|
"unicode",
|
|
2532
2598
|
"values",
|
|
2599
|
+
"vector-effect",
|
|
2533
2600
|
"viewbox",
|
|
2534
2601
|
"visibility",
|
|
2535
2602
|
"version",
|
|
@@ -2643,6 +2710,24 @@ document.open();document.write(html);document.close();
|
|
|
2643
2710
|
documentFragment: 11,
|
|
2644
2711
|
notation: 12
|
|
2645
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
|
+
}();
|
|
2646
2731
|
const getGlobal = function getGlobal() {
|
|
2647
2732
|
return typeof window === "undefined" ? null : window;
|
|
2648
2733
|
};
|
|
@@ -2702,10 +2787,25 @@ document.open();document.write(html);document.close();
|
|
|
2702
2787
|
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
|
|
2703
2788
|
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone$1(options.base) : {}, cfg[key], options.transform) : fallback;
|
|
2704
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
|
+
};
|
|
2705
2805
|
function createDOMPurify() {
|
|
2706
2806
|
let window = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();
|
|
2707
2807
|
const DOMPurify = (root) => createDOMPurify(root);
|
|
2708
|
-
DOMPurify.version = "3.4.
|
|
2808
|
+
DOMPurify.version = "3.4.16";
|
|
2709
2809
|
DOMPurify.removed = [];
|
|
2710
2810
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
2711
2811
|
DOMPurify.isSupported = false;
|
|
@@ -2722,6 +2822,7 @@ document.open();document.write(html);document.close();
|
|
|
2722
2822
|
const ElementPrototype = Element.prototype;
|
|
2723
2823
|
const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
|
|
2724
2824
|
const remove = lookupGetter(ElementPrototype, "remove");
|
|
2825
|
+
const removeAttributeNode = lookupGetter(ElementPrototype, "removeAttributeNode");
|
|
2725
2826
|
const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
|
|
2726
2827
|
const getChildNodes = lookupGetter(ElementPrototype, "childNodes");
|
|
2727
2828
|
const getParentNode = lookupGetter(ElementPrototype, "parentNode");
|
|
@@ -2729,6 +2830,13 @@ document.open();document.write(html);document.close();
|
|
|
2729
2830
|
const getAttributes = lookupGetter(ElementPrototype, "attributes");
|
|
2730
2831
|
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeType") : null;
|
|
2731
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
|
+
};
|
|
2732
2840
|
if (typeof HTMLTemplateElement === "function") {
|
|
2733
2841
|
const template = document.createElement("template");
|
|
2734
2842
|
if (template.content && template.content.ownerDocument) document = template.content.ownerDocument;
|
|
@@ -2986,9 +3094,9 @@ document.open();document.write(html);document.close();
|
|
|
2986
3094
|
IN_PLACE = cfg.IN_PLACE || false;
|
|
2987
3095
|
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI;
|
|
2988
3096
|
NAMESPACE = typeof cfg.NAMESPACE === "string" ? cfg.NAMESPACE : HTML_NAMESPACE;
|
|
2989
|
-
MATHML_TEXT_INTEGRATION_POINTS =
|
|
2990
|
-
HTML_INTEGRATION_POINTS =
|
|
2991
|
-
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));
|
|
2992
3100
|
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
2993
3101
|
if (objectHasOwnProperty(customElementHandling, "tagNameCheck") && isRegexOrFunction(customElementHandling.tagNameCheck)) CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck;
|
|
2994
3102
|
if (objectHasOwnProperty(customElementHandling, "attributeNameCheck") && isRegexOrFunction(customElementHandling.attributeNameCheck)) CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck;
|
|
@@ -3035,11 +3143,6 @@ document.open();document.write(html);document.close();
|
|
|
3035
3143
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
3036
3144
|
}
|
|
3037
3145
|
}
|
|
3038
|
-
if (objectHasOwnProperty(cfg, "ADD_URI_SAFE_ATTR") && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
3039
|
-
if (objectHasOwnProperty(cfg, "FORBID_CONTENTS") && arrayIsArray(cfg.FORBID_CONTENTS)) {
|
|
3040
|
-
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone$1(FORBID_CONTENTS);
|
|
3041
|
-
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
3042
|
-
}
|
|
3043
3146
|
if (objectHasOwnProperty(cfg, "ADD_FORBID_CONTENTS") && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
|
|
3044
3147
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone$1(FORBID_CONTENTS);
|
|
3045
3148
|
addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
|
|
@@ -3156,6 +3259,32 @@ document.open();document.write(html);document.close();
|
|
|
3156
3259
|
}
|
|
3157
3260
|
};
|
|
3158
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
|
+
/**
|
|
3159
3288
|
* _neutralizeRoot
|
|
3160
3289
|
*
|
|
3161
3290
|
* Fail-closed teardown of an in-place root after the sanitize walk aborts
|
|
@@ -3172,6 +3301,7 @@ document.open();document.write(html);document.close();
|
|
|
3172
3301
|
* @param root the in-place root to empty
|
|
3173
3302
|
*/
|
|
3174
3303
|
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
3304
|
+
_neutralizeSubtree(root);
|
|
3175
3305
|
const childNodes = getChildNodes(root);
|
|
3176
3306
|
if (childNodes) {
|
|
3177
3307
|
const snapshot = [];
|
|
@@ -3188,30 +3318,45 @@ document.open();document.write(html);document.close();
|
|
|
3188
3318
|
if (attributes) for (let i = attributes.length - 1; i >= 0; --i) {
|
|
3189
3319
|
const attribute = attributes[i];
|
|
3190
3320
|
const name = attribute && attribute.name;
|
|
3191
|
-
if (typeof name === "string")
|
|
3192
|
-
root.removeAttribute(name);
|
|
3193
|
-
} catch (_) {}
|
|
3321
|
+
if (typeof name === "string") _stripAttributeNode(root, attribute, name);
|
|
3194
3322
|
}
|
|
3195
3323
|
};
|
|
3196
3324
|
/**
|
|
3197
3325
|
* _removeAttribute
|
|
3198
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
|
+
*
|
|
3199
3338
|
* @param name an Attribute name
|
|
3200
3339
|
* @param element a DOM node
|
|
3340
|
+
* @param attr the exact Attr node to remove, when the caller has it
|
|
3201
3341
|
*/
|
|
3202
|
-
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
|
+
});
|
|
3203
3352
|
try {
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
from: element
|
|
3207
|
-
});
|
|
3353
|
+
if (attr) removeAttributeNode(element, attr);
|
|
3354
|
+
else element.removeAttribute(name);
|
|
3208
3355
|
} catch (_) {
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
});
|
|
3356
|
+
try {
|
|
3357
|
+
element.removeAttribute(name);
|
|
3358
|
+
} catch (_) {}
|
|
3213
3359
|
}
|
|
3214
|
-
element.removeAttribute(name);
|
|
3215
3360
|
if (name === "is") if (RETURN_DOM || RETURN_DOM_FRAGMENT) try {
|
|
3216
3361
|
_forceRemove(element);
|
|
3217
3362
|
} catch (_) {}
|
|
@@ -3236,9 +3381,7 @@ document.open();document.write(html);document.close();
|
|
|
3236
3381
|
const attribute = attributes[i];
|
|
3237
3382
|
const name = attribute && attribute.name;
|
|
3238
3383
|
if (typeof name !== "string" || ALLOWED_ATTR[transformCaseFunc(name)]) continue;
|
|
3239
|
-
|
|
3240
|
-
element.removeAttribute(name);
|
|
3241
|
-
} catch (_) {}
|
|
3384
|
+
_stripAttributeNode(element, attribute, name);
|
|
3242
3385
|
}
|
|
3243
3386
|
};
|
|
3244
3387
|
/**
|
|
@@ -3267,7 +3410,82 @@ document.open();document.write(html);document.close();
|
|
|
3267
3410
|
const stack = [root];
|
|
3268
3411
|
while (stack.length > 0) {
|
|
3269
3412
|
const node = stack.pop();
|
|
3270
|
-
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
|
+
}
|
|
3271
3489
|
const childNodes = getChildNodes(node);
|
|
3272
3490
|
if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
|
|
3273
3491
|
}
|
|
@@ -3309,7 +3527,8 @@ document.open();document.write(html);document.close();
|
|
|
3309
3527
|
* @return The created NodeIterator
|
|
3310
3528
|
*/
|
|
3311
3529
|
const _createNodeIterator = function _createNodeIterator(root) {
|
|
3312
|
-
|
|
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);
|
|
3313
3532
|
};
|
|
3314
3533
|
/**
|
|
3315
3534
|
* Replace template expression syntax (mustache, ERB, template
|
|
@@ -3347,7 +3566,8 @@ document.open();document.write(html);document.close();
|
|
|
3347
3566
|
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
3348
3567
|
var _node$querySelectorAl;
|
|
3349
3568
|
node.normalize();
|
|
3350
|
-
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);
|
|
3351
3571
|
let currentNode = walker.nextNode();
|
|
3352
3572
|
while (currentNode) {
|
|
3353
3573
|
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
@@ -3374,7 +3594,7 @@ document.open();document.write(html);document.close();
|
|
|
3374
3594
|
const realTagName = getNodeName ? getNodeName(element) : null;
|
|
3375
3595
|
if (typeof realTagName !== "string") return false;
|
|
3376
3596
|
if (transformCaseFunc(realTagName) !== "form") return false;
|
|
3377
|
-
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);
|
|
3378
3598
|
};
|
|
3379
3599
|
/**
|
|
3380
3600
|
* Checks whether the given value is a DocumentFragment from any realm.
|
|
@@ -3432,34 +3652,57 @@ document.open();document.write(html);document.close();
|
|
|
3432
3652
|
*/
|
|
3433
3653
|
const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
|
|
3434
3654
|
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) return true;
|
|
3435
|
-
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;
|
|
3436
3656
|
if (currentNode.nodeType === NODE_TYPE.processingInstruction) return true;
|
|
3437
3657
|
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) return true;
|
|
3438
3658
|
return false;
|
|
3439
3659
|
};
|
|
3440
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
|
+
/**
|
|
3441
3681
|
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
3442
3682
|
* allowed custom elements (false return exits _sanitizeElements
|
|
3443
|
-
* early - namespace
|
|
3444
|
-
*
|
|
3445
|
-
*
|
|
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).
|
|
3446
3692
|
*
|
|
3447
3693
|
* @param currentNode the disallowed node
|
|
3448
3694
|
* @param tagName the node's transformCaseFunc'd tag name
|
|
3449
3695
|
* @return true if the node was removed, false if kept
|
|
3450
3696
|
*/
|
|
3451
|
-
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
|
|
3452
|
-
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName))
|
|
3453
|
-
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
|
|
3454
|
-
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
|
|
3455
|
-
}
|
|
3697
|
+
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName, root) {
|
|
3698
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
|
|
3456
3699
|
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
3457
3700
|
const parentNode = getParentNode(currentNode);
|
|
3458
3701
|
const childNodes = getChildNodes(currentNode);
|
|
3459
3702
|
if (childNodes && parentNode) {
|
|
3460
3703
|
const childCount = childNodes.length;
|
|
3461
3704
|
for (let i = childCount - 1; i >= 0; --i) {
|
|
3462
|
-
const hoisted =
|
|
3705
|
+
const hoisted = currentNode === root ? cloneNode(childNodes[i], true) : childNodes[i];
|
|
3463
3706
|
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
3464
3707
|
}
|
|
3465
3708
|
}
|
|
@@ -3468,6 +3711,60 @@ document.open();document.write(html);document.close();
|
|
|
3468
3711
|
return true;
|
|
3469
3712
|
};
|
|
3470
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
|
+
/**
|
|
3471
3768
|
* _sanitizeElements
|
|
3472
3769
|
*
|
|
3473
3770
|
* @protect nodeName
|
|
@@ -3476,23 +3773,33 @@ document.open();document.write(html);document.close();
|
|
|
3476
3773
|
* @param currentNode to check for permission to exist
|
|
3477
3774
|
* @return true if node was killed, false if left alive
|
|
3478
3775
|
*/
|
|
3479
|
-
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
3776
|
+
const _sanitizeElements = function _sanitizeElements(currentNode, root) {
|
|
3480
3777
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
3778
|
+
if (_handleHookDetachedNode(currentNode, root)) return true;
|
|
3481
3779
|
if (_isClobbered(currentNode)) {
|
|
3482
3780
|
_forceRemove(currentNode);
|
|
3483
3781
|
return true;
|
|
3484
3782
|
}
|
|
3485
|
-
const tagName = transformCaseFunc(
|
|
3783
|
+
const tagName = transformCaseFunc(_readNodeName(currentNode));
|
|
3784
|
+
ALLOWED_TAGS = _forkSharedAllowlist(hooks.uponSanitizeElement, ALLOWED_TAGS, DEFAULT_ALLOWED_TAGS, SET_CONFIG_ALLOWED_TAGS);
|
|
3486
3785
|
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
3487
3786
|
tagName,
|
|
3488
3787
|
allowedTags: ALLOWED_TAGS
|
|
3489
3788
|
});
|
|
3789
|
+
if (_handleHookDetachedNode(currentNode, root)) return true;
|
|
3490
3790
|
if (_isUnsafeNode(currentNode, tagName)) {
|
|
3491
3791
|
_forceRemove(currentNode);
|
|
3492
3792
|
return true;
|
|
3493
3793
|
}
|
|
3494
|
-
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName])
|
|
3495
|
-
|
|
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)) {
|
|
3496
3803
|
_forceRemove(currentNode);
|
|
3497
3804
|
return true;
|
|
3498
3805
|
}
|
|
@@ -3508,7 +3815,7 @@ document.open();document.write(html);document.close();
|
|
|
3508
3815
|
}
|
|
3509
3816
|
}
|
|
3510
3817
|
_executeHooks(hooks.afterSanitizeElements, currentNode, null);
|
|
3511
|
-
return
|
|
3818
|
+
return _handleHookDetachedNode(currentNode, root);
|
|
3512
3819
|
};
|
|
3513
3820
|
/**
|
|
3514
3821
|
* _isValidAttribute
|
|
@@ -3520,18 +3827,17 @@ document.open();document.write(html);document.close();
|
|
|
3520
3827
|
*/
|
|
3521
3828
|
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
|
|
3522
3829
|
if (FORBID_ATTR[lcName]) return false;
|
|
3830
|
+
if (_isPatchLinkageAttribute(lcName, lcTag)) return false;
|
|
3523
3831
|
if (SANITIZE_DOM && (lcName === "id" || lcName === "name") && (value in document || value in formElement)) return false;
|
|
3524
3832
|
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
3525
|
-
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName));
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
else if (value) return false;
|
|
3534
|
-
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;
|
|
3535
3841
|
};
|
|
3536
3842
|
const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, [
|
|
3537
3843
|
"annotation-xml",
|
|
@@ -3576,24 +3882,38 @@ document.open();document.write(html);document.close();
|
|
|
3576
3882
|
/**
|
|
3577
3883
|
* Write a modified attribute value back onto the element. On
|
|
3578
3884
|
* success, re-probe for clobbering introduced by the new value and
|
|
3579
|
-
* remove the element when found; otherwise
|
|
3580
|
-
*
|
|
3581
|
-
*
|
|
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
|
|
3582
3888
|
* failure, remove the attribute instead.
|
|
3583
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
|
+
*
|
|
3584
3899
|
* @param currentNode the element carrying the attribute
|
|
3585
3900
|
* @param name the attribute name as present on the element
|
|
3586
3901
|
* @param namespaceURI the attribute's namespace, if any
|
|
3587
3902
|
* @param value the new attribute value
|
|
3903
|
+
* @return true if the value was written without introducing clobbering
|
|
3588
3904
|
*/
|
|
3589
3905
|
const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
|
|
3590
3906
|
try {
|
|
3591
3907
|
if (namespaceURI) currentNode.setAttributeNS(namespaceURI, name, value);
|
|
3592
3908
|
else currentNode.setAttribute(name, value);
|
|
3593
|
-
if (_isClobbered(currentNode))
|
|
3594
|
-
|
|
3909
|
+
if (_isClobbered(currentNode)) {
|
|
3910
|
+
_forceRemove(currentNode);
|
|
3911
|
+
return false;
|
|
3912
|
+
}
|
|
3913
|
+
return true;
|
|
3595
3914
|
} catch (_) {
|
|
3596
3915
|
_removeAttribute(name, currentNode);
|
|
3916
|
+
return false;
|
|
3597
3917
|
}
|
|
3598
3918
|
};
|
|
3599
3919
|
/**
|
|
@@ -3605,11 +3925,14 @@ document.open();document.write(html);document.close();
|
|
|
3605
3925
|
* @protect setAttribute
|
|
3606
3926
|
*
|
|
3607
3927
|
* @param currentNode to sanitize
|
|
3928
|
+
* @param root the current walk root
|
|
3608
3929
|
*/
|
|
3609
|
-
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
3930
|
+
const _sanitizeAttributes = function _sanitizeAttributes(currentNode, root) {
|
|
3610
3931
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
3932
|
+
if (_handleHookDetachedNode(currentNode, root)) return;
|
|
3611
3933
|
const attributes = currentNode.attributes;
|
|
3612
3934
|
if (!attributes || _isClobbered(currentNode)) return;
|
|
3935
|
+
ALLOWED_ATTR = _forkSharedAllowlist(hooks.uponSanitizeAttribute, ALLOWED_ATTR, DEFAULT_ALLOWED_ATTR, SET_CONFIG_ALLOWED_ATTR);
|
|
3613
3936
|
const hookEvent = {
|
|
3614
3937
|
attrName: "",
|
|
3615
3938
|
attrValue: "",
|
|
@@ -3625,6 +3948,7 @@ document.open();document.write(html);document.close();
|
|
|
3625
3948
|
const lcName = transformCaseFunc(name);
|
|
3626
3949
|
const initValue = attrValue;
|
|
3627
3950
|
let value = name === "value" ? initValue : stringTrim(initValue);
|
|
3951
|
+
let recreatedNamedProp = false;
|
|
3628
3952
|
hookEvent.attrName = lcName;
|
|
3629
3953
|
hookEvent.attrValue = value;
|
|
3630
3954
|
hookEvent.keepAttr = true;
|
|
@@ -3632,35 +3956,39 @@ document.open();document.write(html);document.close();
|
|
|
3632
3956
|
_executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
|
|
3633
3957
|
value = hookEvent.attrValue;
|
|
3634
3958
|
if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name") && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
|
|
3635
|
-
_removeAttribute(name, currentNode);
|
|
3959
|
+
_removeAttribute(name, currentNode, attr);
|
|
3636
3960
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
3961
|
+
recreatedNamedProp = true;
|
|
3637
3962
|
}
|
|
3638
3963
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
3639
|
-
_removeAttribute(name, currentNode);
|
|
3964
|
+
_removeAttribute(name, currentNode, attr);
|
|
3640
3965
|
continue;
|
|
3641
3966
|
}
|
|
3642
3967
|
if (lcName === "attributename" && stringMatch(value, "href")) {
|
|
3643
|
-
_removeAttribute(name, currentNode);
|
|
3968
|
+
_removeAttribute(name, currentNode, attr);
|
|
3644
3969
|
continue;
|
|
3645
3970
|
}
|
|
3646
3971
|
if (hookEvent.forceKeepAttr) continue;
|
|
3647
3972
|
if (!hookEvent.keepAttr) {
|
|
3648
|
-
_removeAttribute(name, currentNode);
|
|
3973
|
+
_removeAttribute(name, currentNode, attr);
|
|
3649
3974
|
continue;
|
|
3650
3975
|
}
|
|
3651
3976
|
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
|
|
3652
|
-
_removeAttribute(name, currentNode);
|
|
3977
|
+
_removeAttribute(name, currentNode, attr);
|
|
3653
3978
|
continue;
|
|
3654
3979
|
}
|
|
3655
3980
|
if (SAFE_FOR_TEMPLATES) value = _stripTemplateExpressions(value);
|
|
3656
3981
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
3657
|
-
_removeAttribute(name, currentNode);
|
|
3982
|
+
_removeAttribute(name, currentNode, attr);
|
|
3658
3983
|
continue;
|
|
3659
3984
|
}
|
|
3660
3985
|
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
3661
|
-
if (value !== initValue)
|
|
3986
|
+
if (value !== initValue) {
|
|
3987
|
+
if (_setAttributeValue(currentNode, name, namespaceURI, value) && recreatedNamedProp) arrayPop(DOMPurify.removed);
|
|
3988
|
+
}
|
|
3662
3989
|
}
|
|
3663
3990
|
_executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
|
|
3991
|
+
_handleHookDetachedNode(currentNode, root);
|
|
3664
3992
|
};
|
|
3665
3993
|
/**
|
|
3666
3994
|
* _sanitizeShadowDOM
|
|
@@ -3673,10 +4001,10 @@ document.open();document.write(html);document.close();
|
|
|
3673
4001
|
_executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
|
|
3674
4002
|
while (shadowNode = shadowIterator.nextNode()) {
|
|
3675
4003
|
_executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
|
|
3676
|
-
_sanitizeElements(shadowNode);
|
|
3677
|
-
_sanitizeAttributes(shadowNode);
|
|
4004
|
+
_sanitizeElements(shadowNode, fragment);
|
|
4005
|
+
_sanitizeAttributes(shadowNode, fragment);
|
|
3678
4006
|
if (_isDocumentFragment(shadowNode.content)) _sanitizeShadowDOM2(shadowNode.content);
|
|
3679
|
-
if ((
|
|
4007
|
+
if (_readNodeType(shadowNode) === NODE_TYPE.element) {
|
|
3680
4008
|
const innerSr = getShadowRoot(shadowNode);
|
|
3681
4009
|
if (_isDocumentFragment(innerSr)) {
|
|
3682
4010
|
_sanitizeAttachedShadowRoots(innerSr);
|
|
@@ -3717,7 +4045,7 @@ document.open();document.write(html);document.close();
|
|
|
3717
4045
|
continue;
|
|
3718
4046
|
}
|
|
3719
4047
|
const node = item.node;
|
|
3720
|
-
const isElement = (
|
|
4048
|
+
const isElement = _readNodeType(node) === NODE_TYPE.element;
|
|
3721
4049
|
const childNodes = getChildNodes(node);
|
|
3722
4050
|
if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push({
|
|
3723
4051
|
node: childNodes[i],
|
|
@@ -3767,12 +4095,19 @@ document.open();document.write(html);document.close();
|
|
|
3767
4095
|
DOMPurify.removed = [];
|
|
3768
4096
|
const inPlace = IN_PLACE && typeof dirty !== "string" && _isNode(dirty);
|
|
3769
4097
|
if (inPlace) {
|
|
3770
|
-
|
|
4098
|
+
_neutralizePatchLinkage(dirty);
|
|
4099
|
+
const nn = _readNodeName(dirty);
|
|
3771
4100
|
if (typeof nn === "string") {
|
|
3772
4101
|
const tagName = transformCaseFunc(nn);
|
|
3773
|
-
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");
|
|
3774
4110
|
}
|
|
3775
|
-
if (_isClobbered(dirty)) throw typeErrorCreate("root node is clobbered and cannot be sanitized in-place");
|
|
3776
4111
|
try {
|
|
3777
4112
|
_sanitizeAttachedShadowRoots(dirty);
|
|
3778
4113
|
} catch (error) {
|
|
@@ -3785,28 +4120,39 @@ document.open();document.write(html);document.close();
|
|
|
3785
4120
|
if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === "BODY") body = importedNode;
|
|
3786
4121
|
else if (importedNode.nodeName === "HTML") body = importedNode;
|
|
3787
4122
|
else body.appendChild(importedNode);
|
|
3788
|
-
_sanitizeAttachedShadowRoots(
|
|
4123
|
+
_sanitizeAttachedShadowRoots(body);
|
|
3789
4124
|
} else {
|
|
3790
4125
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && dirty.indexOf("<") === -1) return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
|
|
3791
4126
|
body = _initDocument(dirty);
|
|
3792
4127
|
if (!body) return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : "";
|
|
3793
4128
|
}
|
|
3794
4129
|
if (body && FORCE_BODY) _forceRemove(body.firstChild);
|
|
3795
|
-
const
|
|
4130
|
+
const walkRoot = inPlace ? dirty : body;
|
|
3796
4131
|
try {
|
|
4132
|
+
const nodeIterator = _createNodeIterator(walkRoot);
|
|
3797
4133
|
while (currentNode = nodeIterator.nextNode()) {
|
|
3798
|
-
_sanitizeElements(currentNode);
|
|
3799
|
-
_sanitizeAttributes(currentNode);
|
|
4134
|
+
_sanitizeElements(currentNode, walkRoot);
|
|
4135
|
+
_sanitizeAttributes(currentNode, walkRoot);
|
|
3800
4136
|
if (_isDocumentFragment(currentNode.content)) _sanitizeShadowDOM2(currentNode.content);
|
|
3801
4137
|
}
|
|
3802
4138
|
} catch (error) {
|
|
3803
|
-
if (inPlace)
|
|
4139
|
+
if (inPlace) {
|
|
4140
|
+
_neutralizeRoot(dirty);
|
|
4141
|
+
arrayForEach(DOMPurify.removed, (entry) => {
|
|
4142
|
+
if (entry.element) _neutralizeSubtree(entry.element);
|
|
4143
|
+
});
|
|
4144
|
+
}
|
|
3804
4145
|
throw error;
|
|
3805
4146
|
}
|
|
3806
4147
|
if (inPlace) {
|
|
4148
|
+
let rootWasRemoved = false;
|
|
3807
4149
|
arrayForEach(DOMPurify.removed, (entry) => {
|
|
3808
|
-
if (entry.element)
|
|
4150
|
+
if (entry.element) {
|
|
4151
|
+
if (entry.element === dirty) rootWasRemoved = true;
|
|
4152
|
+
_neutralizeSubtree(entry.element);
|
|
4153
|
+
}
|
|
3809
4154
|
});
|
|
4155
|
+
if (rootWasRemoved) throw typeErrorCreate("a node selected for removal could not be safely returned; refusing to sanitize in place");
|
|
3810
4156
|
if (SAFE_FOR_TEMPLATES) _scrubTemplateExpressions2(dirty);
|
|
3811
4157
|
return dirty;
|
|
3812
4158
|
}
|
|
@@ -3864,7 +4210,7 @@ document.open();document.write(html);document.close();
|
|
|
3864
4210
|
};
|
|
3865
4211
|
return DOMPurify;
|
|
3866
4212
|
}
|
|
3867
|
-
var
|
|
4213
|
+
var purify_default = createDOMPurify();
|
|
3868
4214
|
//#endregion
|
|
3869
4215
|
//#region lib/types/client/html/basic-document.js
|
|
3870
4216
|
/** Static HTML preview with no scripts, network resources, forms or nested frames. */
|
|
@@ -3874,7 +4220,7 @@ document.open();document.write(html);document.close();
|
|
|
3874
4220
|
* @returns document with the restrictive CSP first in its head.
|
|
3875
4221
|
*/
|
|
3876
4222
|
function createBasicHtmlDocument(data) {
|
|
3877
|
-
const clean =
|
|
4223
|
+
const clean = purify_default.sanitize(decodeText(data), {
|
|
3878
4224
|
WHOLE_DOCUMENT: true,
|
|
3879
4225
|
FORBID_TAGS: [
|
|
3880
4226
|
"noscript",
|