dompurify 3.4.9 → 3.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -9
- package/dist/purify.cjs.d.ts +3 -3
- package/dist/purify.cjs.js +312 -173
- package/dist/purify.cjs.js.map +1 -1
- package/dist/purify.cov.cjs.js +26330 -0
- package/dist/purify.cov.cjs.js.map +1 -0
- package/dist/purify.es.d.mts +3 -3
- package/dist/purify.es.mjs +312 -173
- package/dist/purify.es.mjs.map +1 -1
- package/dist/purify.js +312 -173
- package/dist/purify.js.map +1 -1
- package/dist/purify.min.js +2 -2
- package/dist/purify.min.js.map +1 -1
- package/package.json +12 -5
- package/src/purify.ts +442 -629
- package/src/regexp.ts +8 -0
- package/src/types.ts +356 -0
package/dist/purify.es.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.4.
|
|
1
|
+
/*! @license DOMPurify 3.4.10 | (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.10/LICENSE */
|
|
2
2
|
|
|
3
3
|
function _arrayLikeToArray(r, a) {
|
|
4
4
|
(null == a || a > r.length) && (a = r.length);
|
|
@@ -327,6 +327,13 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
|
|
|
327
327
|
);
|
|
328
328
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
329
329
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
330
|
+
// Markup-significant character probes used by _sanitizeElements.
|
|
331
|
+
// Shared module-level instances are safe despite the sticky /g flags:
|
|
332
|
+
// unapply() resets lastIndex for RegExp receivers before every call.
|
|
333
|
+
const ELEMENT_MARKUP_PROBE = seal(/<[/\w!]/g);
|
|
334
|
+
const COMMENT_MARKUP_PROBE = seal(/<[/\w]/g);
|
|
335
|
+
const FALLBACK_TAG_CLOSE = seal(/<\/no(script|embed|frames)/i);
|
|
336
|
+
const SELF_CLOSING_TAG = seal(/\/>/i);
|
|
330
337
|
|
|
331
338
|
/* eslint-disable @typescript-eslint/indent */
|
|
332
339
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
@@ -339,7 +346,7 @@ const NODE_TYPE = {
|
|
|
339
346
|
// Deprecated
|
|
340
347
|
entityNode: 6,
|
|
341
348
|
// Deprecated
|
|
342
|
-
|
|
349
|
+
processingInstruction: 7,
|
|
343
350
|
comment: 8,
|
|
344
351
|
document: 9,
|
|
345
352
|
documentType: 10,
|
|
@@ -400,10 +407,25 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
400
407
|
uponSanitizeShadowNode: []
|
|
401
408
|
};
|
|
402
409
|
};
|
|
410
|
+
/**
|
|
411
|
+
* Resolve a set-valued configuration option: a fresh set built from
|
|
412
|
+
* cfg[key] when it is an own array property (seeded with a clone of
|
|
413
|
+
* options.base when given, case-normalized via options.transform),
|
|
414
|
+
* the fallback set otherwise.
|
|
415
|
+
*
|
|
416
|
+
* @param cfg the cloned, prototype-free configuration object
|
|
417
|
+
* @param key the configuration property to read
|
|
418
|
+
* @param fallback the set to use when the option is absent or not an array
|
|
419
|
+
* @param options transform and optional base set to merge into
|
|
420
|
+
* @returns the resolved set
|
|
421
|
+
*/
|
|
422
|
+
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
|
|
423
|
+
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
|
|
424
|
+
};
|
|
403
425
|
function createDOMPurify() {
|
|
404
426
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
405
427
|
const DOMPurify = root => createDOMPurify(root);
|
|
406
|
-
DOMPurify.version = '3.4.
|
|
428
|
+
DOMPurify.version = '3.4.10';
|
|
407
429
|
DOMPurify.removed = [];
|
|
408
430
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
409
431
|
// Not running in a browser, provide a factory function
|
|
@@ -660,8 +682,10 @@ function createDOMPurify() {
|
|
|
660
682
|
/* Allowed XHTML+XML namespaces */
|
|
661
683
|
let ALLOWED_NAMESPACES = null;
|
|
662
684
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
663
|
-
|
|
664
|
-
let
|
|
685
|
+
const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze(['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
686
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
|
|
687
|
+
const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);
|
|
688
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
|
|
665
689
|
// Certain elements are allowed in both SVG and HTML
|
|
666
690
|
// namespace. We need to specify them explicitly
|
|
667
691
|
// so that they don't get erroneously deleted from
|
|
@@ -703,14 +727,32 @@ function createDOMPurify() {
|
|
|
703
727
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
704
728
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
705
729
|
/* Set configuration parameters */
|
|
706
|
-
ALLOWED_TAGS =
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
730
|
+
ALLOWED_TAGS = _resolveSetOption(cfg, 'ALLOWED_TAGS', DEFAULT_ALLOWED_TAGS, {
|
|
731
|
+
transform: transformCaseFunc
|
|
732
|
+
});
|
|
733
|
+
ALLOWED_ATTR = _resolveSetOption(cfg, 'ALLOWED_ATTR', DEFAULT_ALLOWED_ATTR, {
|
|
734
|
+
transform: transformCaseFunc
|
|
735
|
+
});
|
|
736
|
+
ALLOWED_NAMESPACES = _resolveSetOption(cfg, 'ALLOWED_NAMESPACES', DEFAULT_ALLOWED_NAMESPACES, {
|
|
737
|
+
transform: stringToString
|
|
738
|
+
});
|
|
739
|
+
URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, 'ADD_URI_SAFE_ATTR', DEFAULT_URI_SAFE_ATTRIBUTES, {
|
|
740
|
+
transform: transformCaseFunc,
|
|
741
|
+
base: DEFAULT_URI_SAFE_ATTRIBUTES
|
|
742
|
+
});
|
|
743
|
+
DATA_URI_TAGS = _resolveSetOption(cfg, 'ADD_DATA_URI_TAGS', DEFAULT_DATA_URI_TAGS, {
|
|
744
|
+
transform: transformCaseFunc,
|
|
745
|
+
base: DEFAULT_DATA_URI_TAGS
|
|
746
|
+
});
|
|
747
|
+
FORBID_CONTENTS = _resolveSetOption(cfg, 'FORBID_CONTENTS', DEFAULT_FORBID_CONTENTS, {
|
|
748
|
+
transform: transformCaseFunc
|
|
749
|
+
});
|
|
750
|
+
FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {
|
|
751
|
+
transform: transformCaseFunc
|
|
752
|
+
});
|
|
753
|
+
FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {
|
|
754
|
+
transform: transformCaseFunc
|
|
755
|
+
});
|
|
714
756
|
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
|
|
715
757
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
716
758
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
@@ -729,8 +771,8 @@ function createDOMPurify() {
|
|
|
729
771
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
730
772
|
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
|
|
731
773
|
NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
|
|
732
|
-
MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({},
|
|
733
|
-
HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({},
|
|
774
|
+
MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS); // Default built-in map
|
|
775
|
+
HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS); // Default built-in map
|
|
734
776
|
const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
|
|
735
777
|
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
736
778
|
if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
|
|
@@ -742,6 +784,7 @@ function createDOMPurify() {
|
|
|
742
784
|
if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
|
|
743
785
|
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
|
|
744
786
|
}
|
|
787
|
+
seal(CUSTOM_ELEMENT_HANDLING);
|
|
745
788
|
if (SAFE_FOR_TEMPLATES) {
|
|
746
789
|
ALLOW_DATA_ATTR = false;
|
|
747
790
|
}
|
|
@@ -907,6 +950,77 @@ function createDOMPurify() {
|
|
|
907
950
|
* correctly. */
|
|
908
951
|
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
909
952
|
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
953
|
+
/**
|
|
954
|
+
* Namespace rules for an element in the SVG namespace.
|
|
955
|
+
*
|
|
956
|
+
* @param tagName the element's lowercase tag name
|
|
957
|
+
* @param parent the (possibly simulated) parent node
|
|
958
|
+
* @param parentTagName the parent's lowercase tag name
|
|
959
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
960
|
+
*/
|
|
961
|
+
const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) {
|
|
962
|
+
// The only way to switch from HTML namespace to SVG
|
|
963
|
+
// is via <svg>. If it happens via any other tag, then
|
|
964
|
+
// it should be killed.
|
|
965
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
966
|
+
return tagName === 'svg';
|
|
967
|
+
}
|
|
968
|
+
// The only way to switch from MathML to SVG is via <svg>
|
|
969
|
+
// if the parent is either <annotation-xml> or a MathML
|
|
970
|
+
// text integration point.
|
|
971
|
+
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
972
|
+
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
973
|
+
}
|
|
974
|
+
// We only allow elements that are defined in SVG
|
|
975
|
+
// spec. All others are disallowed in SVG namespace.
|
|
976
|
+
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
977
|
+
};
|
|
978
|
+
/**
|
|
979
|
+
* Namespace rules for an element in the MathML namespace.
|
|
980
|
+
*
|
|
981
|
+
* @param tagName the element's lowercase tag name
|
|
982
|
+
* @param parent the (possibly simulated) parent node
|
|
983
|
+
* @param parentTagName the parent's lowercase tag name
|
|
984
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
985
|
+
*/
|
|
986
|
+
const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) {
|
|
987
|
+
// The only way to switch from HTML namespace to MathML
|
|
988
|
+
// is via <math>. If it happens via any other tag, then
|
|
989
|
+
// it should be killed.
|
|
990
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
991
|
+
return tagName === 'math';
|
|
992
|
+
}
|
|
993
|
+
// The only way to switch from SVG to MathML is via
|
|
994
|
+
// <math> and HTML integration points
|
|
995
|
+
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
996
|
+
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
997
|
+
}
|
|
998
|
+
// We only allow elements that are defined in MathML
|
|
999
|
+
// spec. All others are disallowed in MathML namespace.
|
|
1000
|
+
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
1001
|
+
};
|
|
1002
|
+
/**
|
|
1003
|
+
* Namespace rules for an element in the HTML namespace.
|
|
1004
|
+
*
|
|
1005
|
+
* @param tagName the element's lowercase tag name
|
|
1006
|
+
* @param parent the (possibly simulated) parent node
|
|
1007
|
+
* @param parentTagName the parent's lowercase tag name
|
|
1008
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
1009
|
+
*/
|
|
1010
|
+
const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) {
|
|
1011
|
+
// The only way to switch from SVG to HTML is via
|
|
1012
|
+
// HTML integration points, and from MathML to HTML
|
|
1013
|
+
// is via MathML text integration points
|
|
1014
|
+
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
// We disallow tags that are specific for MathML
|
|
1021
|
+
// or SVG and should never appear in HTML namespace
|
|
1022
|
+
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
1023
|
+
};
|
|
910
1024
|
/**
|
|
911
1025
|
* @param element a DOM element whose namespace is being checked
|
|
912
1026
|
* @returns Return false if the element has a
|
|
@@ -929,51 +1043,13 @@ function createDOMPurify() {
|
|
|
929
1043
|
return false;
|
|
930
1044
|
}
|
|
931
1045
|
if (element.namespaceURI === SVG_NAMESPACE) {
|
|
932
|
-
|
|
933
|
-
// is via <svg>. If it happens via any other tag, then
|
|
934
|
-
// it should be killed.
|
|
935
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
936
|
-
return tagName === 'svg';
|
|
937
|
-
}
|
|
938
|
-
// The only way to switch from MathML to SVG is via`
|
|
939
|
-
// svg if parent is either <annotation-xml> or MathML
|
|
940
|
-
// text integration points.
|
|
941
|
-
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
942
|
-
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
943
|
-
}
|
|
944
|
-
// We only allow elements that are defined in SVG
|
|
945
|
-
// spec. All others are disallowed in SVG namespace.
|
|
946
|
-
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
1046
|
+
return _checkSvgNamespace(tagName, parent, parentTagName);
|
|
947
1047
|
}
|
|
948
1048
|
if (element.namespaceURI === MATHML_NAMESPACE) {
|
|
949
|
-
|
|
950
|
-
// is via <math>. If it happens via any other tag, then
|
|
951
|
-
// it should be killed.
|
|
952
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
953
|
-
return tagName === 'math';
|
|
954
|
-
}
|
|
955
|
-
// The only way to switch from SVG to MathML is via
|
|
956
|
-
// <math> and HTML integration points
|
|
957
|
-
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
958
|
-
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
959
|
-
}
|
|
960
|
-
// We only allow elements that are defined in MathML
|
|
961
|
-
// spec. All others are disallowed in MathML namespace.
|
|
962
|
-
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
1049
|
+
return _checkMathMlNamespace(tagName, parent, parentTagName);
|
|
963
1050
|
}
|
|
964
1051
|
if (element.namespaceURI === HTML_NAMESPACE) {
|
|
965
|
-
|
|
966
|
-
// HTML integration points, and from MathML to HTML
|
|
967
|
-
// is via MathML text integration points
|
|
968
|
-
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
969
|
-
return false;
|
|
970
|
-
}
|
|
971
|
-
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
972
|
-
return false;
|
|
973
|
-
}
|
|
974
|
-
// We disallow tags that are specific for MathML
|
|
975
|
-
// or SVG and should never appear in HTML namespace
|
|
976
|
-
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
1052
|
+
return _checkHtmlNamespace(tagName, parent, parentTagName);
|
|
977
1053
|
}
|
|
978
1054
|
// For XHTML and XML documents that support custom namespaces
|
|
979
1055
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
@@ -1039,7 +1115,7 @@ function createDOMPurify() {
|
|
|
1039
1115
|
* @param root the in-place root to empty
|
|
1040
1116
|
*/
|
|
1041
1117
|
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
1042
|
-
const childNodes = getChildNodes
|
|
1118
|
+
const childNodes = getChildNodes(root);
|
|
1043
1119
|
if (childNodes) {
|
|
1044
1120
|
const snapshot = [];
|
|
1045
1121
|
arrayForEach(childNodes, child => {
|
|
@@ -1053,7 +1129,7 @@ function createDOMPurify() {
|
|
|
1053
1129
|
}
|
|
1054
1130
|
});
|
|
1055
1131
|
}
|
|
1056
|
-
const attributes = getAttributes
|
|
1132
|
+
const attributes = getAttributes(root);
|
|
1057
1133
|
if (attributes) {
|
|
1058
1134
|
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
1059
1135
|
const attribute = attributes[i];
|
|
@@ -1111,7 +1187,7 @@ function createDOMPurify() {
|
|
|
1111
1187
|
* @param element the element to strip
|
|
1112
1188
|
*/
|
|
1113
1189
|
const _stripDisallowedAttributes = function _stripDisallowedAttributes(element) {
|
|
1114
|
-
const attributes = getAttributes
|
|
1190
|
+
const attributes = getAttributes(element);
|
|
1115
1191
|
if (!attributes) {
|
|
1116
1192
|
return;
|
|
1117
1193
|
}
|
|
@@ -1158,7 +1234,7 @@ function createDOMPurify() {
|
|
|
1158
1234
|
if (nodeType === NODE_TYPE.element) {
|
|
1159
1235
|
_stripDisallowedAttributes(node);
|
|
1160
1236
|
}
|
|
1161
|
-
const childNodes = getChildNodes
|
|
1237
|
+
const childNodes = getChildNodes(node);
|
|
1162
1238
|
if (childNodes) {
|
|
1163
1239
|
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
1164
1240
|
stack.push(childNodes[i]);
|
|
@@ -1227,6 +1303,20 @@ function createDOMPurify() {
|
|
|
1227
1303
|
// eslint-disable-next-line no-bitwise
|
|
1228
1304
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
1229
1305
|
};
|
|
1306
|
+
/**
|
|
1307
|
+
* Replace template expression syntax (mustache, ERB, template
|
|
1308
|
+
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
|
|
1309
|
+
* sites. Order matters: mustache, then ERB, then template literal.
|
|
1310
|
+
*
|
|
1311
|
+
* @param value the string to scrub
|
|
1312
|
+
* @returns the scrubbed string
|
|
1313
|
+
*/
|
|
1314
|
+
const _stripTemplateExpressions = function _stripTemplateExpressions(value) {
|
|
1315
|
+
value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
|
|
1316
|
+
value = stringReplace(value, ERB_EXPR$1, ' ');
|
|
1317
|
+
value = stringReplace(value, TMPLIT_EXPR$1, ' ');
|
|
1318
|
+
return value;
|
|
1319
|
+
};
|
|
1230
1320
|
/**
|
|
1231
1321
|
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
1232
1322
|
* character data of an element subtree. Used as the final safety net for
|
|
@@ -1247,29 +1337,27 @@ function createDOMPurify() {
|
|
|
1247
1337
|
* @param node The root element whose character data should be scrubbed.
|
|
1248
1338
|
*/
|
|
1249
1339
|
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
1250
|
-
var _node$querySelectorAl
|
|
1340
|
+
var _node$querySelectorAl;
|
|
1251
1341
|
node.normalize();
|
|
1252
1342
|
const walker = createNodeIterator.call(node.ownerDocument || node, node,
|
|
1253
1343
|
// eslint-disable-next-line no-bitwise
|
|
1254
1344
|
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
1255
1345
|
let currentNode = walker.nextNode();
|
|
1256
1346
|
while (currentNode) {
|
|
1257
|
-
|
|
1258
|
-
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1259
|
-
data = stringReplace(data, expr, ' ');
|
|
1260
|
-
});
|
|
1261
|
-
currentNode.data = data;
|
|
1347
|
+
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
1262
1348
|
currentNode = walker.nextNode();
|
|
1263
1349
|
}
|
|
1264
1350
|
// NodeIterator does not descend into <template>.content per the DOM spec,
|
|
1265
1351
|
// so we must explicitly recurse into each template's content fragment,
|
|
1266
1352
|
// mirroring the approach used by _sanitizeShadowDOM.
|
|
1267
|
-
const templates = (_node$querySelectorAl =
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1353
|
+
const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, 'template');
|
|
1354
|
+
if (templates) {
|
|
1355
|
+
arrayForEach(templates, tmpl => {
|
|
1356
|
+
if (_isDocumentFragment(tmpl.content)) {
|
|
1357
|
+
_scrubTemplateExpressions2(tmpl.content);
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1273
1361
|
};
|
|
1274
1362
|
/**
|
|
1275
1363
|
* _isClobbered
|
|
@@ -1365,67 +1453,64 @@ function createDOMPurify() {
|
|
|
1365
1453
|
}
|
|
1366
1454
|
};
|
|
1367
1455
|
function _executeHooks(hooks, currentNode, data) {
|
|
1456
|
+
if (hooks.length === 0) {
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1368
1459
|
arrayForEach(hooks, hook => {
|
|
1369
1460
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
1370
1461
|
});
|
|
1371
1462
|
}
|
|
1372
1463
|
/**
|
|
1373
|
-
*
|
|
1464
|
+
* Structural-threat checks that condemn a node regardless of the
|
|
1465
|
+
* allowlists: mXSS via namespace confusion, risky CSS construction,
|
|
1466
|
+
* processing instructions, markup-bearing comments. Pure predicate;
|
|
1467
|
+
* the caller removes. Check order is load-bearing.
|
|
1374
1468
|
*
|
|
1375
|
-
* @
|
|
1376
|
-
* @
|
|
1377
|
-
* @
|
|
1378
|
-
* @param currentNode to check for permission to exist
|
|
1379
|
-
* @return true if node was killed, false if left alive
|
|
1469
|
+
* @param currentNode the node to inspect
|
|
1470
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
1471
|
+
* @return true if the node must be removed
|
|
1380
1472
|
*/
|
|
1381
|
-
const
|
|
1382
|
-
let content = null;
|
|
1383
|
-
/* Execute a hook if present */
|
|
1384
|
-
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
1385
|
-
/* Check if element is clobbered or can clobber */
|
|
1386
|
-
if (_isClobbered(currentNode)) {
|
|
1387
|
-
_forceRemove(currentNode);
|
|
1388
|
-
return true;
|
|
1389
|
-
}
|
|
1390
|
-
/* Now let's check the element's type and name */
|
|
1391
|
-
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
1392
|
-
/* Execute a hook if present */
|
|
1393
|
-
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
1394
|
-
tagName,
|
|
1395
|
-
allowedTags: ALLOWED_TAGS
|
|
1396
|
-
});
|
|
1473
|
+
const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
|
|
1397
1474
|
/* Detect mXSS attempts abusing namespace confusion */
|
|
1398
|
-
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(
|
|
1399
|
-
_forceRemove(currentNode);
|
|
1475
|
+
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) {
|
|
1400
1476
|
return true;
|
|
1401
1477
|
}
|
|
1402
1478
|
/* Remove risky CSS construction leading to mXSS */
|
|
1403
1479
|
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
|
|
1404
|
-
_forceRemove(currentNode);
|
|
1405
1480
|
return true;
|
|
1406
1481
|
}
|
|
1407
1482
|
/* Remove any occurrence of processing instructions */
|
|
1408
|
-
if (currentNode.nodeType === NODE_TYPE.
|
|
1409
|
-
_forceRemove(currentNode);
|
|
1483
|
+
if (currentNode.nodeType === NODE_TYPE.processingInstruction) {
|
|
1410
1484
|
return true;
|
|
1411
1485
|
}
|
|
1412
1486
|
/* Remove any kind of possibly harmful comments */
|
|
1413
|
-
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(
|
|
1414
|
-
_forceRemove(currentNode);
|
|
1487
|
+
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) {
|
|
1415
1488
|
return true;
|
|
1416
1489
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1490
|
+
return false;
|
|
1491
|
+
};
|
|
1492
|
+
/**
|
|
1493
|
+
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
1494
|
+
* allowed custom elements (false return exits _sanitizeElements
|
|
1495
|
+
* early - namespace/fallback checks and the afterSanitizeElements
|
|
1496
|
+
* hook are intentionally skipped for kept custom elements), else
|
|
1497
|
+
* hoist content per KEEP_CONTENT and remove.
|
|
1498
|
+
*
|
|
1499
|
+
* @param currentNode the disallowed node
|
|
1500
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
1501
|
+
* @return true if the node was removed, false if kept
|
|
1502
|
+
*/
|
|
1503
|
+
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
|
|
1504
|
+
/* Check if we have a custom element to handle */
|
|
1505
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
1506
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
|
|
1510
|
+
return false;
|
|
1427
1511
|
}
|
|
1428
|
-
|
|
1512
|
+
}
|
|
1513
|
+
/* Keep content except for bad-listed elements.
|
|
1429
1514
|
Use the cached prototype getters exclusively — the previous code
|
|
1430
1515
|
had `|| currentNode.parentNode` / `|| currentNode.childNodes`
|
|
1431
1516
|
fallbacks, but the cached getters always return the canonical
|
|
@@ -1433,12 +1518,12 @@ function createDOMPurify() {
|
|
|
1433
1518
|
path was dead in safe cases and a clobbering surface in unsafe
|
|
1434
1519
|
ones. Falsy cached results stay falsy; the `if (childNodes &&
|
|
1435
1520
|
parentNode)` check already gates correctly. */
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1521
|
+
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
1522
|
+
const parentNode = getParentNode(currentNode);
|
|
1523
|
+
const childNodes = getChildNodes(currentNode);
|
|
1524
|
+
if (childNodes && parentNode) {
|
|
1525
|
+
const childCount = childNodes.length;
|
|
1526
|
+
/* In-place: hoist the *original* children so the iterator visits
|
|
1442
1527
|
and sanitises them through the same allowlist pass as every other
|
|
1443
1528
|
node. The caller built the tree in the live document, so the
|
|
1444
1529
|
originals carry already-queued resource events (`<img onerror>`,
|
|
@@ -1448,24 +1533,57 @@ function createDOMPurify() {
|
|
|
1448
1533
|
root is pre-validated as an allowed tag and so is never the node
|
|
1449
1534
|
being removed, which keeps `parentNode` inside the iterator root
|
|
1450
1535
|
and the relocated child inside the serialised tree.
|
|
1451
|
-
|
|
1536
|
+
Otherwise (string / DOM-copy paths): clone. The iterator is rooted
|
|
1452
1537
|
at — and the result serialised from — `body`, so a restrictive
|
|
1453
1538
|
ALLOWED_TAGS that removes `body` itself must leave its content in
|
|
1454
1539
|
place, which only cloning does; and those paths parse into an
|
|
1455
1540
|
inert document, so their discarded originals never had a queued
|
|
1456
1541
|
event to neutralise.
|
|
1457
|
-
|
|
1542
|
+
`childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
|
|
1458
1543
|
valid whether we move (drops the trailing entry) or clone (leaves
|
|
1459
1544
|
the list intact). */
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
}
|
|
1545
|
+
for (let i = childCount - 1; i >= 0; --i) {
|
|
1546
|
+
const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
|
|
1547
|
+
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
1464
1548
|
}
|
|
1465
1549
|
}
|
|
1550
|
+
}
|
|
1551
|
+
_forceRemove(currentNode);
|
|
1552
|
+
return true;
|
|
1553
|
+
};
|
|
1554
|
+
/**
|
|
1555
|
+
* _sanitizeElements
|
|
1556
|
+
*
|
|
1557
|
+
* @protect nodeName
|
|
1558
|
+
* @protect textContent
|
|
1559
|
+
* @protect removeChild
|
|
1560
|
+
* @param currentNode to check for permission to exist
|
|
1561
|
+
* @return true if node was killed, false if left alive
|
|
1562
|
+
*/
|
|
1563
|
+
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
1564
|
+
/* Execute a hook if present */
|
|
1565
|
+
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
1566
|
+
/* Check if element is clobbered or can clobber */
|
|
1567
|
+
if (_isClobbered(currentNode)) {
|
|
1466
1568
|
_forceRemove(currentNode);
|
|
1467
1569
|
return true;
|
|
1468
1570
|
}
|
|
1571
|
+
/* Now let's check the element's type and name */
|
|
1572
|
+
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
1573
|
+
/* Execute a hook if present */
|
|
1574
|
+
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
1575
|
+
tagName,
|
|
1576
|
+
allowedTags: ALLOWED_TAGS
|
|
1577
|
+
});
|
|
1578
|
+
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
1579
|
+
if (_isUnsafeNode(currentNode, tagName)) {
|
|
1580
|
+
_forceRemove(currentNode);
|
|
1581
|
+
return true;
|
|
1582
|
+
}
|
|
1583
|
+
/* Remove element if anything forbids its presence */
|
|
1584
|
+
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
1585
|
+
return _sanitizeDisallowedNode(currentNode, tagName);
|
|
1586
|
+
}
|
|
1469
1587
|
/* Check whether element has a valid namespace.
|
|
1470
1588
|
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
1471
1589
|
nodeType getter rather than `instanceof Element`, which is realm-
|
|
@@ -1478,17 +1596,14 @@ function createDOMPurify() {
|
|
|
1478
1596
|
return true;
|
|
1479
1597
|
}
|
|
1480
1598
|
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
1481
|
-
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(
|
|
1599
|
+
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(FALLBACK_TAG_CLOSE, currentNode.innerHTML)) {
|
|
1482
1600
|
_forceRemove(currentNode);
|
|
1483
1601
|
return true;
|
|
1484
1602
|
}
|
|
1485
1603
|
/* Sanitize element content to be template-safe */
|
|
1486
1604
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1487
1605
|
/* Get the element's text content */
|
|
1488
|
-
content = currentNode.textContent;
|
|
1489
|
-
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1490
|
-
content = stringReplace(content, expr, ' ');
|
|
1491
|
-
});
|
|
1606
|
+
const content = _stripTemplateExpressions(currentNode.textContent);
|
|
1492
1607
|
if (currentNode.textContent !== content) {
|
|
1493
1608
|
arrayPush(DOMPurify.removed, {
|
|
1494
1609
|
element: currentNode.cloneNode()
|
|
@@ -1523,7 +1638,7 @@ function createDOMPurify() {
|
|
|
1523
1638
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1524
1639
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1525
1640
|
We don't need to check the value; it's always URI safe. */
|
|
1526
|
-
if (ALLOW_DATA_ATTR &&
|
|
1641
|
+
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted) {
|
|
1527
1642
|
if (
|
|
1528
1643
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1529
1644
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -1555,6 +1670,63 @@ function createDOMPurify() {
|
|
|
1555
1670
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1556
1671
|
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
1557
1672
|
};
|
|
1673
|
+
/**
|
|
1674
|
+
* Wrap an attribute value in the matching Trusted Types object when
|
|
1675
|
+
* the active policy requires it. Namespaced attributes pass through
|
|
1676
|
+
* unchanged (no TT support yet, see
|
|
1677
|
+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
|
|
1678
|
+
*
|
|
1679
|
+
* @param lcTag lowercase tag name of the containing element
|
|
1680
|
+
* @param lcName lowercase attribute name
|
|
1681
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
1682
|
+
* @param value the attribute value to wrap
|
|
1683
|
+
* @return the value, wrapped when Trusted Types demand it
|
|
1684
|
+
*/
|
|
1685
|
+
const _applyTrustedTypesToAttribute = function _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value) {
|
|
1686
|
+
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function' && !namespaceURI) {
|
|
1687
|
+
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
1688
|
+
case 'TrustedHTML':
|
|
1689
|
+
{
|
|
1690
|
+
return _createTrustedHTML(value);
|
|
1691
|
+
}
|
|
1692
|
+
case 'TrustedScriptURL':
|
|
1693
|
+
{
|
|
1694
|
+
return _createTrustedScriptURL(value);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
return value;
|
|
1699
|
+
};
|
|
1700
|
+
/**
|
|
1701
|
+
* Write a modified attribute value back onto the element. On
|
|
1702
|
+
* success, re-probe for clobbering introduced by the new value and
|
|
1703
|
+
* remove the element when found; otherwise pop the removal entry
|
|
1704
|
+
* recorded by the earlier _removeAttribute (long-standing pairing
|
|
1705
|
+
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
|
|
1706
|
+
* failure, remove the attribute instead.
|
|
1707
|
+
*
|
|
1708
|
+
* @param currentNode the element carrying the attribute
|
|
1709
|
+
* @param name the attribute name as present on the element
|
|
1710
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
1711
|
+
* @param value the new attribute value
|
|
1712
|
+
*/
|
|
1713
|
+
const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
|
|
1714
|
+
try {
|
|
1715
|
+
if (namespaceURI) {
|
|
1716
|
+
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
1717
|
+
} else {
|
|
1718
|
+
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
1719
|
+
currentNode.setAttribute(name, value);
|
|
1720
|
+
}
|
|
1721
|
+
if (_isClobbered(currentNode)) {
|
|
1722
|
+
_forceRemove(currentNode);
|
|
1723
|
+
} else {
|
|
1724
|
+
arrayPop(DOMPurify.removed);
|
|
1725
|
+
}
|
|
1726
|
+
} catch (_) {
|
|
1727
|
+
_removeAttribute(name, currentNode);
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1558
1730
|
/**
|
|
1559
1731
|
* _sanitizeAttributes
|
|
1560
1732
|
*
|
|
@@ -1581,6 +1753,7 @@ function createDOMPurify() {
|
|
|
1581
1753
|
forceKeepAttr: undefined
|
|
1582
1754
|
};
|
|
1583
1755
|
let l = attributes.length;
|
|
1756
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1584
1757
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1585
1758
|
while (l--) {
|
|
1586
1759
|
const attr = attributes[l];
|
|
@@ -1618,7 +1791,7 @@ function createDOMPurify() {
|
|
|
1618
1791
|
_removeAttribute(name, currentNode);
|
|
1619
1792
|
continue;
|
|
1620
1793
|
}
|
|
1621
|
-
/* Did the hooks
|
|
1794
|
+
/* Did the hooks force-keep the attribute? */
|
|
1622
1795
|
if (hookEvent.forceKeepAttr) {
|
|
1623
1796
|
continue;
|
|
1624
1797
|
}
|
|
@@ -1628,56 +1801,24 @@ function createDOMPurify() {
|
|
|
1628
1801
|
continue;
|
|
1629
1802
|
}
|
|
1630
1803
|
/* Work around a security issue in jQuery 3.0 */
|
|
1631
|
-
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(
|
|
1804
|
+
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
|
|
1632
1805
|
_removeAttribute(name, currentNode);
|
|
1633
1806
|
continue;
|
|
1634
1807
|
}
|
|
1635
1808
|
/* Sanitize attribute content to be template-safe */
|
|
1636
1809
|
if (SAFE_FOR_TEMPLATES) {
|
|
1637
|
-
|
|
1638
|
-
value = stringReplace(value, expr, ' ');
|
|
1639
|
-
});
|
|
1810
|
+
value = _stripTemplateExpressions(value);
|
|
1640
1811
|
}
|
|
1641
1812
|
/* Is `value` valid for this attribute? */
|
|
1642
|
-
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1643
1813
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
1644
1814
|
_removeAttribute(name, currentNode);
|
|
1645
1815
|
continue;
|
|
1646
1816
|
}
|
|
1647
1817
|
/* Handle attributes that require Trusted Types */
|
|
1648
|
-
|
|
1649
|
-
if (namespaceURI) ; else {
|
|
1650
|
-
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
1651
|
-
case 'TrustedHTML':
|
|
1652
|
-
{
|
|
1653
|
-
value = _createTrustedHTML(value);
|
|
1654
|
-
break;
|
|
1655
|
-
}
|
|
1656
|
-
case 'TrustedScriptURL':
|
|
1657
|
-
{
|
|
1658
|
-
value = _createTrustedScriptURL(value);
|
|
1659
|
-
break;
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1818
|
+
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
1664
1819
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
1665
1820
|
if (value !== initValue) {
|
|
1666
|
-
|
|
1667
|
-
if (namespaceURI) {
|
|
1668
|
-
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
1669
|
-
} else {
|
|
1670
|
-
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
1671
|
-
currentNode.setAttribute(name, value);
|
|
1672
|
-
}
|
|
1673
|
-
if (_isClobbered(currentNode)) {
|
|
1674
|
-
_forceRemove(currentNode);
|
|
1675
|
-
} else {
|
|
1676
|
-
arrayPop(DOMPurify.removed);
|
|
1677
|
-
}
|
|
1678
|
-
} catch (_) {
|
|
1679
|
-
_removeAttribute(name, currentNode);
|
|
1680
|
-
}
|
|
1821
|
+
_setAttributeValue(currentNode, name, namespaceURI, value);
|
|
1681
1822
|
}
|
|
1682
1823
|
}
|
|
1683
1824
|
/* Execute a hook if present */
|
|
@@ -1719,7 +1860,7 @@ function createDOMPurify() {
|
|
|
1719
1860
|
iterator also surfaces. */
|
|
1720
1861
|
const shadowNodeType = getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType;
|
|
1721
1862
|
if (shadowNodeType === NODE_TYPE.element) {
|
|
1722
|
-
const innerSr = getShadowRoot
|
|
1863
|
+
const innerSr = getShadowRoot(shadowNode);
|
|
1723
1864
|
if (_isDocumentFragment(innerSr)) {
|
|
1724
1865
|
_sanitizeAttachedShadowRoots(innerSr);
|
|
1725
1866
|
_sanitizeShadowDOM2(innerSr);
|
|
@@ -1781,7 +1922,7 @@ function createDOMPurify() {
|
|
|
1781
1922
|
/* (pushed last → processed first) Children, snapshotted in reverse so
|
|
1782
1923
|
the first child is processed first. Snapshotting matters because a
|
|
1783
1924
|
hook may detach siblings mid-walk. */
|
|
1784
|
-
const childNodes = getChildNodes
|
|
1925
|
+
const childNodes = getChildNodes(node);
|
|
1785
1926
|
if (childNodes) {
|
|
1786
1927
|
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
1787
1928
|
stack.push({
|
|
@@ -1811,7 +1952,7 @@ function createDOMPurify() {
|
|
|
1811
1952
|
silently skipped foreign-realm shadow roots (e.g.
|
|
1812
1953
|
iframe.contentDocument attachShadow). */
|
|
1813
1954
|
if (isElement) {
|
|
1814
|
-
const sr = getShadowRoot
|
|
1955
|
+
const sr = getShadowRoot(node);
|
|
1815
1956
|
if (_isDocumentFragment(sr)) {
|
|
1816
1957
|
/* Push the deferred sanitise first so it pops after the shadow
|
|
1817
1958
|
walk we push next, i.e. nested shadow roots are discovered
|
|
@@ -2023,9 +2164,7 @@ function createDOMPurify() {
|
|
|
2023
2164
|
}
|
|
2024
2165
|
/* Sanitize final string template-safe */
|
|
2025
2166
|
if (SAFE_FOR_TEMPLATES) {
|
|
2026
|
-
|
|
2027
|
-
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
2028
|
-
});
|
|
2167
|
+
serializedHTML = _stripTemplateExpressions(serializedHTML);
|
|
2029
2168
|
}
|
|
2030
2169
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
|
|
2031
2170
|
};
|