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