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.js
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 (global, factory) {
|
|
4
4
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
@@ -333,6 +333,13 @@
|
|
|
333
333
|
);
|
|
334
334
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
335
335
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
336
|
+
// Markup-significant character probes used by _sanitizeElements.
|
|
337
|
+
// Shared module-level instances are safe despite the sticky /g flags:
|
|
338
|
+
// unapply() resets lastIndex for RegExp receivers before every call.
|
|
339
|
+
const ELEMENT_MARKUP_PROBE = seal(/<[/\w!]/g);
|
|
340
|
+
const COMMENT_MARKUP_PROBE = seal(/<[/\w]/g);
|
|
341
|
+
const FALLBACK_TAG_CLOSE = seal(/<\/no(script|embed|frames)/i);
|
|
342
|
+
const SELF_CLOSING_TAG = seal(/\/>/i);
|
|
336
343
|
|
|
337
344
|
/* eslint-disable @typescript-eslint/indent */
|
|
338
345
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
@@ -345,7 +352,7 @@
|
|
|
345
352
|
// Deprecated
|
|
346
353
|
entityNode: 6,
|
|
347
354
|
// Deprecated
|
|
348
|
-
|
|
355
|
+
processingInstruction: 7,
|
|
349
356
|
comment: 8,
|
|
350
357
|
document: 9,
|
|
351
358
|
documentType: 10,
|
|
@@ -406,10 +413,25 @@
|
|
|
406
413
|
uponSanitizeShadowNode: []
|
|
407
414
|
};
|
|
408
415
|
};
|
|
416
|
+
/**
|
|
417
|
+
* Resolve a set-valued configuration option: a fresh set built from
|
|
418
|
+
* cfg[key] when it is an own array property (seeded with a clone of
|
|
419
|
+
* options.base when given, case-normalized via options.transform),
|
|
420
|
+
* the fallback set otherwise.
|
|
421
|
+
*
|
|
422
|
+
* @param cfg the cloned, prototype-free configuration object
|
|
423
|
+
* @param key the configuration property to read
|
|
424
|
+
* @param fallback the set to use when the option is absent or not an array
|
|
425
|
+
* @param options transform and optional base set to merge into
|
|
426
|
+
* @returns the resolved set
|
|
427
|
+
*/
|
|
428
|
+
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
|
|
429
|
+
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
|
|
430
|
+
};
|
|
409
431
|
function createDOMPurify() {
|
|
410
432
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
411
433
|
const DOMPurify = root => createDOMPurify(root);
|
|
412
|
-
DOMPurify.version = '3.4.
|
|
434
|
+
DOMPurify.version = '3.4.10';
|
|
413
435
|
DOMPurify.removed = [];
|
|
414
436
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
415
437
|
// Not running in a browser, provide a factory function
|
|
@@ -666,8 +688,10 @@
|
|
|
666
688
|
/* Allowed XHTML+XML namespaces */
|
|
667
689
|
let ALLOWED_NAMESPACES = null;
|
|
668
690
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
669
|
-
|
|
670
|
-
let
|
|
691
|
+
const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze(['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
692
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
|
|
693
|
+
const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);
|
|
694
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
|
|
671
695
|
// Certain elements are allowed in both SVG and HTML
|
|
672
696
|
// namespace. We need to specify them explicitly
|
|
673
697
|
// so that they don't get erroneously deleted from
|
|
@@ -709,14 +733,32 @@
|
|
|
709
733
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
710
734
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
711
735
|
/* Set configuration parameters */
|
|
712
|
-
ALLOWED_TAGS =
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
736
|
+
ALLOWED_TAGS = _resolveSetOption(cfg, 'ALLOWED_TAGS', DEFAULT_ALLOWED_TAGS, {
|
|
737
|
+
transform: transformCaseFunc
|
|
738
|
+
});
|
|
739
|
+
ALLOWED_ATTR = _resolveSetOption(cfg, 'ALLOWED_ATTR', DEFAULT_ALLOWED_ATTR, {
|
|
740
|
+
transform: transformCaseFunc
|
|
741
|
+
});
|
|
742
|
+
ALLOWED_NAMESPACES = _resolveSetOption(cfg, 'ALLOWED_NAMESPACES', DEFAULT_ALLOWED_NAMESPACES, {
|
|
743
|
+
transform: stringToString
|
|
744
|
+
});
|
|
745
|
+
URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, 'ADD_URI_SAFE_ATTR', DEFAULT_URI_SAFE_ATTRIBUTES, {
|
|
746
|
+
transform: transformCaseFunc,
|
|
747
|
+
base: DEFAULT_URI_SAFE_ATTRIBUTES
|
|
748
|
+
});
|
|
749
|
+
DATA_URI_TAGS = _resolveSetOption(cfg, 'ADD_DATA_URI_TAGS', DEFAULT_DATA_URI_TAGS, {
|
|
750
|
+
transform: transformCaseFunc,
|
|
751
|
+
base: DEFAULT_DATA_URI_TAGS
|
|
752
|
+
});
|
|
753
|
+
FORBID_CONTENTS = _resolveSetOption(cfg, 'FORBID_CONTENTS', DEFAULT_FORBID_CONTENTS, {
|
|
754
|
+
transform: transformCaseFunc
|
|
755
|
+
});
|
|
756
|
+
FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {
|
|
757
|
+
transform: transformCaseFunc
|
|
758
|
+
});
|
|
759
|
+
FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {
|
|
760
|
+
transform: transformCaseFunc
|
|
761
|
+
});
|
|
720
762
|
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
|
|
721
763
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
722
764
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
@@ -735,8 +777,8 @@
|
|
|
735
777
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
736
778
|
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
|
|
737
779
|
NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
|
|
738
|
-
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({},
|
|
739
|
-
HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({},
|
|
780
|
+
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
|
|
781
|
+
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
|
|
740
782
|
const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
|
|
741
783
|
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
742
784
|
if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
|
|
@@ -748,6 +790,7 @@
|
|
|
748
790
|
if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
|
|
749
791
|
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
|
|
750
792
|
}
|
|
793
|
+
seal(CUSTOM_ELEMENT_HANDLING);
|
|
751
794
|
if (SAFE_FOR_TEMPLATES) {
|
|
752
795
|
ALLOW_DATA_ATTR = false;
|
|
753
796
|
}
|
|
@@ -913,6 +956,77 @@
|
|
|
913
956
|
* correctly. */
|
|
914
957
|
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
915
958
|
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
959
|
+
/**
|
|
960
|
+
* Namespace rules for an element in the SVG namespace.
|
|
961
|
+
*
|
|
962
|
+
* @param tagName the element's lowercase tag name
|
|
963
|
+
* @param parent the (possibly simulated) parent node
|
|
964
|
+
* @param parentTagName the parent's lowercase tag name
|
|
965
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
966
|
+
*/
|
|
967
|
+
const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) {
|
|
968
|
+
// The only way to switch from HTML namespace to SVG
|
|
969
|
+
// is via <svg>. If it happens via any other tag, then
|
|
970
|
+
// it should be killed.
|
|
971
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
972
|
+
return tagName === 'svg';
|
|
973
|
+
}
|
|
974
|
+
// The only way to switch from MathML to SVG is via <svg>
|
|
975
|
+
// if the parent is either <annotation-xml> or a MathML
|
|
976
|
+
// text integration point.
|
|
977
|
+
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
978
|
+
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
979
|
+
}
|
|
980
|
+
// We only allow elements that are defined in SVG
|
|
981
|
+
// spec. All others are disallowed in SVG namespace.
|
|
982
|
+
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
983
|
+
};
|
|
984
|
+
/**
|
|
985
|
+
* Namespace rules for an element in the MathML namespace.
|
|
986
|
+
*
|
|
987
|
+
* @param tagName the element's lowercase tag name
|
|
988
|
+
* @param parent the (possibly simulated) parent node
|
|
989
|
+
* @param parentTagName the parent's lowercase tag name
|
|
990
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
991
|
+
*/
|
|
992
|
+
const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) {
|
|
993
|
+
// The only way to switch from HTML namespace to MathML
|
|
994
|
+
// is via <math>. If it happens via any other tag, then
|
|
995
|
+
// it should be killed.
|
|
996
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
997
|
+
return tagName === 'math';
|
|
998
|
+
}
|
|
999
|
+
// The only way to switch from SVG to MathML is via
|
|
1000
|
+
// <math> and HTML integration points
|
|
1001
|
+
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
1002
|
+
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
1003
|
+
}
|
|
1004
|
+
// We only allow elements that are defined in MathML
|
|
1005
|
+
// spec. All others are disallowed in MathML namespace.
|
|
1006
|
+
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
1007
|
+
};
|
|
1008
|
+
/**
|
|
1009
|
+
* Namespace rules for an element in the HTML namespace.
|
|
1010
|
+
*
|
|
1011
|
+
* @param tagName the element's lowercase tag name
|
|
1012
|
+
* @param parent the (possibly simulated) parent node
|
|
1013
|
+
* @param parentTagName the parent's lowercase tag name
|
|
1014
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
1015
|
+
*/
|
|
1016
|
+
const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) {
|
|
1017
|
+
// The only way to switch from SVG to HTML is via
|
|
1018
|
+
// HTML integration points, and from MathML to HTML
|
|
1019
|
+
// is via MathML text integration points
|
|
1020
|
+
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
// We disallow tags that are specific for MathML
|
|
1027
|
+
// or SVG and should never appear in HTML namespace
|
|
1028
|
+
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
1029
|
+
};
|
|
916
1030
|
/**
|
|
917
1031
|
* @param element a DOM element whose namespace is being checked
|
|
918
1032
|
* @returns Return false if the element has a
|
|
@@ -935,51 +1049,13 @@
|
|
|
935
1049
|
return false;
|
|
936
1050
|
}
|
|
937
1051
|
if (element.namespaceURI === SVG_NAMESPACE) {
|
|
938
|
-
|
|
939
|
-
// is via <svg>. If it happens via any other tag, then
|
|
940
|
-
// it should be killed.
|
|
941
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
942
|
-
return tagName === 'svg';
|
|
943
|
-
}
|
|
944
|
-
// The only way to switch from MathML to SVG is via`
|
|
945
|
-
// svg if parent is either <annotation-xml> or MathML
|
|
946
|
-
// text integration points.
|
|
947
|
-
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
948
|
-
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
949
|
-
}
|
|
950
|
-
// We only allow elements that are defined in SVG
|
|
951
|
-
// spec. All others are disallowed in SVG namespace.
|
|
952
|
-
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
1052
|
+
return _checkSvgNamespace(tagName, parent, parentTagName);
|
|
953
1053
|
}
|
|
954
1054
|
if (element.namespaceURI === MATHML_NAMESPACE) {
|
|
955
|
-
|
|
956
|
-
// is via <math>. If it happens via any other tag, then
|
|
957
|
-
// it should be killed.
|
|
958
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
959
|
-
return tagName === 'math';
|
|
960
|
-
}
|
|
961
|
-
// The only way to switch from SVG to MathML is via
|
|
962
|
-
// <math> and HTML integration points
|
|
963
|
-
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
964
|
-
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
965
|
-
}
|
|
966
|
-
// We only allow elements that are defined in MathML
|
|
967
|
-
// spec. All others are disallowed in MathML namespace.
|
|
968
|
-
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
1055
|
+
return _checkMathMlNamespace(tagName, parent, parentTagName);
|
|
969
1056
|
}
|
|
970
1057
|
if (element.namespaceURI === HTML_NAMESPACE) {
|
|
971
|
-
|
|
972
|
-
// HTML integration points, and from MathML to HTML
|
|
973
|
-
// is via MathML text integration points
|
|
974
|
-
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
975
|
-
return false;
|
|
976
|
-
}
|
|
977
|
-
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
978
|
-
return false;
|
|
979
|
-
}
|
|
980
|
-
// We disallow tags that are specific for MathML
|
|
981
|
-
// or SVG and should never appear in HTML namespace
|
|
982
|
-
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
1058
|
+
return _checkHtmlNamespace(tagName, parent, parentTagName);
|
|
983
1059
|
}
|
|
984
1060
|
// For XHTML and XML documents that support custom namespaces
|
|
985
1061
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
@@ -1045,7 +1121,7 @@
|
|
|
1045
1121
|
* @param root the in-place root to empty
|
|
1046
1122
|
*/
|
|
1047
1123
|
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
1048
|
-
const childNodes = getChildNodes
|
|
1124
|
+
const childNodes = getChildNodes(root);
|
|
1049
1125
|
if (childNodes) {
|
|
1050
1126
|
const snapshot = [];
|
|
1051
1127
|
arrayForEach(childNodes, child => {
|
|
@@ -1059,7 +1135,7 @@
|
|
|
1059
1135
|
}
|
|
1060
1136
|
});
|
|
1061
1137
|
}
|
|
1062
|
-
const attributes = getAttributes
|
|
1138
|
+
const attributes = getAttributes(root);
|
|
1063
1139
|
if (attributes) {
|
|
1064
1140
|
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
1065
1141
|
const attribute = attributes[i];
|
|
@@ -1117,7 +1193,7 @@
|
|
|
1117
1193
|
* @param element the element to strip
|
|
1118
1194
|
*/
|
|
1119
1195
|
const _stripDisallowedAttributes = function _stripDisallowedAttributes(element) {
|
|
1120
|
-
const attributes = getAttributes
|
|
1196
|
+
const attributes = getAttributes(element);
|
|
1121
1197
|
if (!attributes) {
|
|
1122
1198
|
return;
|
|
1123
1199
|
}
|
|
@@ -1164,7 +1240,7 @@
|
|
|
1164
1240
|
if (nodeType === NODE_TYPE.element) {
|
|
1165
1241
|
_stripDisallowedAttributes(node);
|
|
1166
1242
|
}
|
|
1167
|
-
const childNodes = getChildNodes
|
|
1243
|
+
const childNodes = getChildNodes(node);
|
|
1168
1244
|
if (childNodes) {
|
|
1169
1245
|
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
1170
1246
|
stack.push(childNodes[i]);
|
|
@@ -1233,6 +1309,20 @@
|
|
|
1233
1309
|
// eslint-disable-next-line no-bitwise
|
|
1234
1310
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
1235
1311
|
};
|
|
1312
|
+
/**
|
|
1313
|
+
* Replace template expression syntax (mustache, ERB, template
|
|
1314
|
+
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
|
|
1315
|
+
* sites. Order matters: mustache, then ERB, then template literal.
|
|
1316
|
+
*
|
|
1317
|
+
* @param value the string to scrub
|
|
1318
|
+
* @returns the scrubbed string
|
|
1319
|
+
*/
|
|
1320
|
+
const _stripTemplateExpressions = function _stripTemplateExpressions(value) {
|
|
1321
|
+
value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
|
|
1322
|
+
value = stringReplace(value, ERB_EXPR$1, ' ');
|
|
1323
|
+
value = stringReplace(value, TMPLIT_EXPR$1, ' ');
|
|
1324
|
+
return value;
|
|
1325
|
+
};
|
|
1236
1326
|
/**
|
|
1237
1327
|
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
1238
1328
|
* character data of an element subtree. Used as the final safety net for
|
|
@@ -1253,29 +1343,27 @@
|
|
|
1253
1343
|
* @param node The root element whose character data should be scrubbed.
|
|
1254
1344
|
*/
|
|
1255
1345
|
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
1256
|
-
var _node$querySelectorAl
|
|
1346
|
+
var _node$querySelectorAl;
|
|
1257
1347
|
node.normalize();
|
|
1258
1348
|
const walker = createNodeIterator.call(node.ownerDocument || node, node,
|
|
1259
1349
|
// eslint-disable-next-line no-bitwise
|
|
1260
1350
|
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
1261
1351
|
let currentNode = walker.nextNode();
|
|
1262
1352
|
while (currentNode) {
|
|
1263
|
-
|
|
1264
|
-
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1265
|
-
data = stringReplace(data, expr, ' ');
|
|
1266
|
-
});
|
|
1267
|
-
currentNode.data = data;
|
|
1353
|
+
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
1268
1354
|
currentNode = walker.nextNode();
|
|
1269
1355
|
}
|
|
1270
1356
|
// NodeIterator does not descend into <template>.content per the DOM spec,
|
|
1271
1357
|
// so we must explicitly recurse into each template's content fragment,
|
|
1272
1358
|
// mirroring the approach used by _sanitizeShadowDOM.
|
|
1273
|
-
const templates = (_node$querySelectorAl =
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1359
|
+
const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, 'template');
|
|
1360
|
+
if (templates) {
|
|
1361
|
+
arrayForEach(templates, tmpl => {
|
|
1362
|
+
if (_isDocumentFragment(tmpl.content)) {
|
|
1363
|
+
_scrubTemplateExpressions2(tmpl.content);
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1279
1367
|
};
|
|
1280
1368
|
/**
|
|
1281
1369
|
* _isClobbered
|
|
@@ -1371,67 +1459,64 @@
|
|
|
1371
1459
|
}
|
|
1372
1460
|
};
|
|
1373
1461
|
function _executeHooks(hooks, currentNode, data) {
|
|
1462
|
+
if (hooks.length === 0) {
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1374
1465
|
arrayForEach(hooks, hook => {
|
|
1375
1466
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
1376
1467
|
});
|
|
1377
1468
|
}
|
|
1378
1469
|
/**
|
|
1379
|
-
*
|
|
1470
|
+
* Structural-threat checks that condemn a node regardless of the
|
|
1471
|
+
* allowlists: mXSS via namespace confusion, risky CSS construction,
|
|
1472
|
+
* processing instructions, markup-bearing comments. Pure predicate;
|
|
1473
|
+
* the caller removes. Check order is load-bearing.
|
|
1380
1474
|
*
|
|
1381
|
-
* @
|
|
1382
|
-
* @
|
|
1383
|
-
* @
|
|
1384
|
-
* @param currentNode to check for permission to exist
|
|
1385
|
-
* @return true if node was killed, false if left alive
|
|
1475
|
+
* @param currentNode the node to inspect
|
|
1476
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
1477
|
+
* @return true if the node must be removed
|
|
1386
1478
|
*/
|
|
1387
|
-
const
|
|
1388
|
-
let content = null;
|
|
1389
|
-
/* Execute a hook if present */
|
|
1390
|
-
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
1391
|
-
/* Check if element is clobbered or can clobber */
|
|
1392
|
-
if (_isClobbered(currentNode)) {
|
|
1393
|
-
_forceRemove(currentNode);
|
|
1394
|
-
return true;
|
|
1395
|
-
}
|
|
1396
|
-
/* Now let's check the element's type and name */
|
|
1397
|
-
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
1398
|
-
/* Execute a hook if present */
|
|
1399
|
-
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
1400
|
-
tagName,
|
|
1401
|
-
allowedTags: ALLOWED_TAGS
|
|
1402
|
-
});
|
|
1479
|
+
const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
|
|
1403
1480
|
/* Detect mXSS attempts abusing namespace confusion */
|
|
1404
|
-
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(
|
|
1405
|
-
_forceRemove(currentNode);
|
|
1481
|
+
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) {
|
|
1406
1482
|
return true;
|
|
1407
1483
|
}
|
|
1408
1484
|
/* Remove risky CSS construction leading to mXSS */
|
|
1409
1485
|
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
|
|
1410
|
-
_forceRemove(currentNode);
|
|
1411
1486
|
return true;
|
|
1412
1487
|
}
|
|
1413
1488
|
/* Remove any occurrence of processing instructions */
|
|
1414
|
-
if (currentNode.nodeType === NODE_TYPE.
|
|
1415
|
-
_forceRemove(currentNode);
|
|
1489
|
+
if (currentNode.nodeType === NODE_TYPE.processingInstruction) {
|
|
1416
1490
|
return true;
|
|
1417
1491
|
}
|
|
1418
1492
|
/* Remove any kind of possibly harmful comments */
|
|
1419
|
-
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(
|
|
1420
|
-
_forceRemove(currentNode);
|
|
1493
|
+
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) {
|
|
1421
1494
|
return true;
|
|
1422
1495
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1496
|
+
return false;
|
|
1497
|
+
};
|
|
1498
|
+
/**
|
|
1499
|
+
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
1500
|
+
* allowed custom elements (false return exits _sanitizeElements
|
|
1501
|
+
* early - namespace/fallback checks and the afterSanitizeElements
|
|
1502
|
+
* hook are intentionally skipped for kept custom elements), else
|
|
1503
|
+
* hoist content per KEEP_CONTENT and remove.
|
|
1504
|
+
*
|
|
1505
|
+
* @param currentNode the disallowed node
|
|
1506
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
1507
|
+
* @return true if the node was removed, false if kept
|
|
1508
|
+
*/
|
|
1509
|
+
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
|
|
1510
|
+
/* Check if we have a custom element to handle */
|
|
1511
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
1512
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
|
|
1513
|
+
return false;
|
|
1514
|
+
}
|
|
1515
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
|
|
1516
|
+
return false;
|
|
1433
1517
|
}
|
|
1434
|
-
|
|
1518
|
+
}
|
|
1519
|
+
/* Keep content except for bad-listed elements.
|
|
1435
1520
|
Use the cached prototype getters exclusively — the previous code
|
|
1436
1521
|
had `|| currentNode.parentNode` / `|| currentNode.childNodes`
|
|
1437
1522
|
fallbacks, but the cached getters always return the canonical
|
|
@@ -1439,12 +1524,12 @@
|
|
|
1439
1524
|
path was dead in safe cases and a clobbering surface in unsafe
|
|
1440
1525
|
ones. Falsy cached results stay falsy; the `if (childNodes &&
|
|
1441
1526
|
parentNode)` check already gates correctly. */
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1527
|
+
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
1528
|
+
const parentNode = getParentNode(currentNode);
|
|
1529
|
+
const childNodes = getChildNodes(currentNode);
|
|
1530
|
+
if (childNodes && parentNode) {
|
|
1531
|
+
const childCount = childNodes.length;
|
|
1532
|
+
/* In-place: hoist the *original* children so the iterator visits
|
|
1448
1533
|
and sanitises them through the same allowlist pass as every other
|
|
1449
1534
|
node. The caller built the tree in the live document, so the
|
|
1450
1535
|
originals carry already-queued resource events (`<img onerror>`,
|
|
@@ -1454,24 +1539,57 @@
|
|
|
1454
1539
|
root is pre-validated as an allowed tag and so is never the node
|
|
1455
1540
|
being removed, which keeps `parentNode` inside the iterator root
|
|
1456
1541
|
and the relocated child inside the serialised tree.
|
|
1457
|
-
|
|
1542
|
+
Otherwise (string / DOM-copy paths): clone. The iterator is rooted
|
|
1458
1543
|
at — and the result serialised from — `body`, so a restrictive
|
|
1459
1544
|
ALLOWED_TAGS that removes `body` itself must leave its content in
|
|
1460
1545
|
place, which only cloning does; and those paths parse into an
|
|
1461
1546
|
inert document, so their discarded originals never had a queued
|
|
1462
1547
|
event to neutralise.
|
|
1463
|
-
|
|
1548
|
+
`childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
|
|
1464
1549
|
valid whether we move (drops the trailing entry) or clone (leaves
|
|
1465
1550
|
the list intact). */
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
}
|
|
1551
|
+
for (let i = childCount - 1; i >= 0; --i) {
|
|
1552
|
+
const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
|
|
1553
|
+
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
1470
1554
|
}
|
|
1471
1555
|
}
|
|
1556
|
+
}
|
|
1557
|
+
_forceRemove(currentNode);
|
|
1558
|
+
return true;
|
|
1559
|
+
};
|
|
1560
|
+
/**
|
|
1561
|
+
* _sanitizeElements
|
|
1562
|
+
*
|
|
1563
|
+
* @protect nodeName
|
|
1564
|
+
* @protect textContent
|
|
1565
|
+
* @protect removeChild
|
|
1566
|
+
* @param currentNode to check for permission to exist
|
|
1567
|
+
* @return true if node was killed, false if left alive
|
|
1568
|
+
*/
|
|
1569
|
+
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
1570
|
+
/* Execute a hook if present */
|
|
1571
|
+
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
1572
|
+
/* Check if element is clobbered or can clobber */
|
|
1573
|
+
if (_isClobbered(currentNode)) {
|
|
1472
1574
|
_forceRemove(currentNode);
|
|
1473
1575
|
return true;
|
|
1474
1576
|
}
|
|
1577
|
+
/* Now let's check the element's type and name */
|
|
1578
|
+
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
1579
|
+
/* Execute a hook if present */
|
|
1580
|
+
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
1581
|
+
tagName,
|
|
1582
|
+
allowedTags: ALLOWED_TAGS
|
|
1583
|
+
});
|
|
1584
|
+
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
1585
|
+
if (_isUnsafeNode(currentNode, tagName)) {
|
|
1586
|
+
_forceRemove(currentNode);
|
|
1587
|
+
return true;
|
|
1588
|
+
}
|
|
1589
|
+
/* Remove element if anything forbids its presence */
|
|
1590
|
+
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
1591
|
+
return _sanitizeDisallowedNode(currentNode, tagName);
|
|
1592
|
+
}
|
|
1475
1593
|
/* Check whether element has a valid namespace.
|
|
1476
1594
|
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
1477
1595
|
nodeType getter rather than `instanceof Element`, which is realm-
|
|
@@ -1484,17 +1602,14 @@
|
|
|
1484
1602
|
return true;
|
|
1485
1603
|
}
|
|
1486
1604
|
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
1487
|
-
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(
|
|
1605
|
+
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(FALLBACK_TAG_CLOSE, currentNode.innerHTML)) {
|
|
1488
1606
|
_forceRemove(currentNode);
|
|
1489
1607
|
return true;
|
|
1490
1608
|
}
|
|
1491
1609
|
/* Sanitize element content to be template-safe */
|
|
1492
1610
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1493
1611
|
/* Get the element's text content */
|
|
1494
|
-
content = currentNode.textContent;
|
|
1495
|
-
arrayForEach([MUSTACHE_EXPR$1, ERB_EXPR$1, TMPLIT_EXPR$1], expr => {
|
|
1496
|
-
content = stringReplace(content, expr, ' ');
|
|
1497
|
-
});
|
|
1612
|
+
const content = _stripTemplateExpressions(currentNode.textContent);
|
|
1498
1613
|
if (currentNode.textContent !== content) {
|
|
1499
1614
|
arrayPush(DOMPurify.removed, {
|
|
1500
1615
|
element: currentNode.cloneNode()
|
|
@@ -1529,7 +1644,7 @@
|
|
|
1529
1644
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1530
1645
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1531
1646
|
We don't need to check the value; it's always URI safe. */
|
|
1532
|
-
if (ALLOW_DATA_ATTR &&
|
|
1647
|
+
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted) {
|
|
1533
1648
|
if (
|
|
1534
1649
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1535
1650
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -1561,6 +1676,63 @@
|
|
|
1561
1676
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1562
1677
|
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
1563
1678
|
};
|
|
1679
|
+
/**
|
|
1680
|
+
* Wrap an attribute value in the matching Trusted Types object when
|
|
1681
|
+
* the active policy requires it. Namespaced attributes pass through
|
|
1682
|
+
* unchanged (no TT support yet, see
|
|
1683
|
+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
|
|
1684
|
+
*
|
|
1685
|
+
* @param lcTag lowercase tag name of the containing element
|
|
1686
|
+
* @param lcName lowercase attribute name
|
|
1687
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
1688
|
+
* @param value the attribute value to wrap
|
|
1689
|
+
* @return the value, wrapped when Trusted Types demand it
|
|
1690
|
+
*/
|
|
1691
|
+
const _applyTrustedTypesToAttribute = function _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value) {
|
|
1692
|
+
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function' && !namespaceURI) {
|
|
1693
|
+
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
1694
|
+
case 'TrustedHTML':
|
|
1695
|
+
{
|
|
1696
|
+
return _createTrustedHTML(value);
|
|
1697
|
+
}
|
|
1698
|
+
case 'TrustedScriptURL':
|
|
1699
|
+
{
|
|
1700
|
+
return _createTrustedScriptURL(value);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return value;
|
|
1705
|
+
};
|
|
1706
|
+
/**
|
|
1707
|
+
* Write a modified attribute value back onto the element. On
|
|
1708
|
+
* success, re-probe for clobbering introduced by the new value and
|
|
1709
|
+
* remove the element when found; otherwise pop the removal entry
|
|
1710
|
+
* recorded by the earlier _removeAttribute (long-standing pairing
|
|
1711
|
+
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
|
|
1712
|
+
* failure, remove the attribute instead.
|
|
1713
|
+
*
|
|
1714
|
+
* @param currentNode the element carrying the attribute
|
|
1715
|
+
* @param name the attribute name as present on the element
|
|
1716
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
1717
|
+
* @param value the new attribute value
|
|
1718
|
+
*/
|
|
1719
|
+
const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
|
|
1720
|
+
try {
|
|
1721
|
+
if (namespaceURI) {
|
|
1722
|
+
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
1723
|
+
} else {
|
|
1724
|
+
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
1725
|
+
currentNode.setAttribute(name, value);
|
|
1726
|
+
}
|
|
1727
|
+
if (_isClobbered(currentNode)) {
|
|
1728
|
+
_forceRemove(currentNode);
|
|
1729
|
+
} else {
|
|
1730
|
+
arrayPop(DOMPurify.removed);
|
|
1731
|
+
}
|
|
1732
|
+
} catch (_) {
|
|
1733
|
+
_removeAttribute(name, currentNode);
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1564
1736
|
/**
|
|
1565
1737
|
* _sanitizeAttributes
|
|
1566
1738
|
*
|
|
@@ -1587,6 +1759,7 @@
|
|
|
1587
1759
|
forceKeepAttr: undefined
|
|
1588
1760
|
};
|
|
1589
1761
|
let l = attributes.length;
|
|
1762
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1590
1763
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1591
1764
|
while (l--) {
|
|
1592
1765
|
const attr = attributes[l];
|
|
@@ -1624,7 +1797,7 @@
|
|
|
1624
1797
|
_removeAttribute(name, currentNode);
|
|
1625
1798
|
continue;
|
|
1626
1799
|
}
|
|
1627
|
-
/* Did the hooks
|
|
1800
|
+
/* Did the hooks force-keep the attribute? */
|
|
1628
1801
|
if (hookEvent.forceKeepAttr) {
|
|
1629
1802
|
continue;
|
|
1630
1803
|
}
|
|
@@ -1634,56 +1807,24 @@
|
|
|
1634
1807
|
continue;
|
|
1635
1808
|
}
|
|
1636
1809
|
/* Work around a security issue in jQuery 3.0 */
|
|
1637
|
-
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(
|
|
1810
|
+
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
|
|
1638
1811
|
_removeAttribute(name, currentNode);
|
|
1639
1812
|
continue;
|
|
1640
1813
|
}
|
|
1641
1814
|
/* Sanitize attribute content to be template-safe */
|
|
1642
1815
|
if (SAFE_FOR_TEMPLATES) {
|
|
1643
|
-
|
|
1644
|
-
value = stringReplace(value, expr, ' ');
|
|
1645
|
-
});
|
|
1816
|
+
value = _stripTemplateExpressions(value);
|
|
1646
1817
|
}
|
|
1647
1818
|
/* Is `value` valid for this attribute? */
|
|
1648
|
-
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1649
1819
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
1650
1820
|
_removeAttribute(name, currentNode);
|
|
1651
1821
|
continue;
|
|
1652
1822
|
}
|
|
1653
1823
|
/* Handle attributes that require Trusted Types */
|
|
1654
|
-
|
|
1655
|
-
if (namespaceURI) ; else {
|
|
1656
|
-
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
1657
|
-
case 'TrustedHTML':
|
|
1658
|
-
{
|
|
1659
|
-
value = _createTrustedHTML(value);
|
|
1660
|
-
break;
|
|
1661
|
-
}
|
|
1662
|
-
case 'TrustedScriptURL':
|
|
1663
|
-
{
|
|
1664
|
-
value = _createTrustedScriptURL(value);
|
|
1665
|
-
break;
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1824
|
+
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
1670
1825
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
1671
1826
|
if (value !== initValue) {
|
|
1672
|
-
|
|
1673
|
-
if (namespaceURI) {
|
|
1674
|
-
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
1675
|
-
} else {
|
|
1676
|
-
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
1677
|
-
currentNode.setAttribute(name, value);
|
|
1678
|
-
}
|
|
1679
|
-
if (_isClobbered(currentNode)) {
|
|
1680
|
-
_forceRemove(currentNode);
|
|
1681
|
-
} else {
|
|
1682
|
-
arrayPop(DOMPurify.removed);
|
|
1683
|
-
}
|
|
1684
|
-
} catch (_) {
|
|
1685
|
-
_removeAttribute(name, currentNode);
|
|
1686
|
-
}
|
|
1827
|
+
_setAttributeValue(currentNode, name, namespaceURI, value);
|
|
1687
1828
|
}
|
|
1688
1829
|
}
|
|
1689
1830
|
/* Execute a hook if present */
|
|
@@ -1725,7 +1866,7 @@
|
|
|
1725
1866
|
iterator also surfaces. */
|
|
1726
1867
|
const shadowNodeType = getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType;
|
|
1727
1868
|
if (shadowNodeType === NODE_TYPE.element) {
|
|
1728
|
-
const innerSr = getShadowRoot
|
|
1869
|
+
const innerSr = getShadowRoot(shadowNode);
|
|
1729
1870
|
if (_isDocumentFragment(innerSr)) {
|
|
1730
1871
|
_sanitizeAttachedShadowRoots(innerSr);
|
|
1731
1872
|
_sanitizeShadowDOM2(innerSr);
|
|
@@ -1787,7 +1928,7 @@
|
|
|
1787
1928
|
/* (pushed last → processed first) Children, snapshotted in reverse so
|
|
1788
1929
|
the first child is processed first. Snapshotting matters because a
|
|
1789
1930
|
hook may detach siblings mid-walk. */
|
|
1790
|
-
const childNodes = getChildNodes
|
|
1931
|
+
const childNodes = getChildNodes(node);
|
|
1791
1932
|
if (childNodes) {
|
|
1792
1933
|
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
1793
1934
|
stack.push({
|
|
@@ -1817,7 +1958,7 @@
|
|
|
1817
1958
|
silently skipped foreign-realm shadow roots (e.g.
|
|
1818
1959
|
iframe.contentDocument attachShadow). */
|
|
1819
1960
|
if (isElement) {
|
|
1820
|
-
const sr = getShadowRoot
|
|
1961
|
+
const sr = getShadowRoot(node);
|
|
1821
1962
|
if (_isDocumentFragment(sr)) {
|
|
1822
1963
|
/* Push the deferred sanitise first so it pops after the shadow
|
|
1823
1964
|
walk we push next, i.e. nested shadow roots are discovered
|
|
@@ -2029,9 +2170,7 @@
|
|
|
2029
2170
|
}
|
|
2030
2171
|
/* Sanitize final string template-safe */
|
|
2031
2172
|
if (SAFE_FOR_TEMPLATES) {
|
|
2032
|
-
|
|
2033
|
-
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
2034
|
-
});
|
|
2173
|
+
serializedHTML = _stripTemplateExpressions(serializedHTML);
|
|
2035
2174
|
}
|
|
2036
2175
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
|
|
2037
2176
|
};
|