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/src/purify.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/indent */
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
TrustedHTML,
|
|
5
|
-
TrustedTypesWindow,
|
|
6
|
-
} from 'trusted-types/lib/index.js';
|
|
7
3
|
import type { Config, UseProfilesConfig } from './config';
|
|
4
|
+
import type { DOMPurify, HooksMap, HookFunction, WindowLike } from './types';
|
|
8
5
|
import * as TAGS from './tags.js';
|
|
9
6
|
import * as ATTRS from './attrs.js';
|
|
10
7
|
import * as EXPRESSIONS from './regexp.js';
|
|
@@ -13,6 +10,7 @@ import {
|
|
|
13
10
|
clone,
|
|
14
11
|
entries,
|
|
15
12
|
freeze,
|
|
13
|
+
seal,
|
|
16
14
|
arrayForEach,
|
|
17
15
|
arrayIsArray,
|
|
18
16
|
arrayLastIndexOf,
|
|
@@ -36,6 +34,21 @@ import {
|
|
|
36
34
|
|
|
37
35
|
export type { Config } from './config';
|
|
38
36
|
|
|
37
|
+
export type {
|
|
38
|
+
DOMPurify,
|
|
39
|
+
RemovedElement,
|
|
40
|
+
RemovedAttribute,
|
|
41
|
+
HookName,
|
|
42
|
+
NodeHook,
|
|
43
|
+
ElementHook,
|
|
44
|
+
DocumentFragmentHook,
|
|
45
|
+
UponSanitizeElementHook,
|
|
46
|
+
UponSanitizeAttributeHook,
|
|
47
|
+
UponSanitizeElementHookEvent,
|
|
48
|
+
UponSanitizeAttributeHookEvent,
|
|
49
|
+
WindowLike,
|
|
50
|
+
} from './types';
|
|
51
|
+
|
|
39
52
|
declare const VERSION: string;
|
|
40
53
|
|
|
41
54
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
@@ -46,7 +59,7 @@ const NODE_TYPE = {
|
|
|
46
59
|
cdataSection: 4,
|
|
47
60
|
entityReference: 5, // Deprecated
|
|
48
61
|
entityNode: 6, // Deprecated
|
|
49
|
-
|
|
62
|
+
processingInstruction: 7,
|
|
50
63
|
comment: 8,
|
|
51
64
|
document: 9,
|
|
52
65
|
documentType: 10,
|
|
@@ -122,6 +135,36 @@ const _createHooksMap = function (): HooksMap {
|
|
|
122
135
|
};
|
|
123
136
|
};
|
|
124
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Resolve a set-valued configuration option: a fresh set built from
|
|
140
|
+
* cfg[key] when it is an own array property (seeded with a clone of
|
|
141
|
+
* options.base when given, case-normalized via options.transform),
|
|
142
|
+
* the fallback set otherwise.
|
|
143
|
+
*
|
|
144
|
+
* @param cfg the cloned, prototype-free configuration object
|
|
145
|
+
* @param key the configuration property to read
|
|
146
|
+
* @param fallback the set to use when the option is absent or not an array
|
|
147
|
+
* @param options transform and optional base set to merge into
|
|
148
|
+
* @returns the resolved set
|
|
149
|
+
*/
|
|
150
|
+
const _resolveSetOption = function (
|
|
151
|
+
cfg: Config,
|
|
152
|
+
key: keyof Config,
|
|
153
|
+
fallback: Record<string, boolean>,
|
|
154
|
+
options: {
|
|
155
|
+
transform: Parameters<typeof addToSet>[2];
|
|
156
|
+
base?: Record<string, boolean>;
|
|
157
|
+
}
|
|
158
|
+
): Record<string, boolean> {
|
|
159
|
+
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key])
|
|
160
|
+
? addToSet(
|
|
161
|
+
options.base ? clone(options.base) : {},
|
|
162
|
+
cfg[key] as readonly unknown[],
|
|
163
|
+
options.transform
|
|
164
|
+
)
|
|
165
|
+
: fallback;
|
|
166
|
+
};
|
|
167
|
+
|
|
125
168
|
function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
126
169
|
const DOMPurify: DOMPurify = (root: WindowLike) => createDOMPurify(root);
|
|
127
170
|
|
|
@@ -526,15 +569,20 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
526
569
|
stringToString
|
|
527
570
|
);
|
|
528
571
|
|
|
529
|
-
|
|
572
|
+
const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze([
|
|
530
573
|
'mi',
|
|
531
574
|
'mo',
|
|
532
575
|
'mn',
|
|
533
576
|
'ms',
|
|
534
577
|
'mtext',
|
|
535
578
|
]);
|
|
579
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet(
|
|
580
|
+
{},
|
|
581
|
+
DEFAULT_MATHML_TEXT_INTEGRATION_POINTS
|
|
582
|
+
);
|
|
536
583
|
|
|
537
|
-
|
|
584
|
+
const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);
|
|
585
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
|
|
538
586
|
|
|
539
587
|
// Certain elements are allowed in both SVG and HTML
|
|
540
588
|
// namespace. We need to specify them explicitly
|
|
@@ -600,52 +648,48 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
600
648
|
: stringToLowerCase;
|
|
601
649
|
|
|
602
650
|
/* Set configuration parameters */
|
|
603
|
-
ALLOWED_TAGS =
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
DATA_URI_TAGS =
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
FORBID_ATTR =
|
|
646
|
-
objectHasOwnProperty(cfg, 'FORBID_ATTR') && arrayIsArray(cfg.FORBID_ATTR)
|
|
647
|
-
? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc)
|
|
648
|
-
: clone({});
|
|
651
|
+
ALLOWED_TAGS = _resolveSetOption(
|
|
652
|
+
cfg,
|
|
653
|
+
'ALLOWED_TAGS',
|
|
654
|
+
DEFAULT_ALLOWED_TAGS,
|
|
655
|
+
{ transform: transformCaseFunc }
|
|
656
|
+
);
|
|
657
|
+
ALLOWED_ATTR = _resolveSetOption(
|
|
658
|
+
cfg,
|
|
659
|
+
'ALLOWED_ATTR',
|
|
660
|
+
DEFAULT_ALLOWED_ATTR,
|
|
661
|
+
{ transform: transformCaseFunc }
|
|
662
|
+
);
|
|
663
|
+
ALLOWED_NAMESPACES = _resolveSetOption(
|
|
664
|
+
cfg,
|
|
665
|
+
'ALLOWED_NAMESPACES',
|
|
666
|
+
DEFAULT_ALLOWED_NAMESPACES,
|
|
667
|
+
{ transform: stringToString }
|
|
668
|
+
);
|
|
669
|
+
URI_SAFE_ATTRIBUTES = _resolveSetOption(
|
|
670
|
+
cfg,
|
|
671
|
+
'ADD_URI_SAFE_ATTR',
|
|
672
|
+
DEFAULT_URI_SAFE_ATTRIBUTES,
|
|
673
|
+
{ transform: transformCaseFunc, base: DEFAULT_URI_SAFE_ATTRIBUTES }
|
|
674
|
+
);
|
|
675
|
+
DATA_URI_TAGS = _resolveSetOption(
|
|
676
|
+
cfg,
|
|
677
|
+
'ADD_DATA_URI_TAGS',
|
|
678
|
+
DEFAULT_DATA_URI_TAGS,
|
|
679
|
+
{ transform: transformCaseFunc, base: DEFAULT_DATA_URI_TAGS }
|
|
680
|
+
);
|
|
681
|
+
FORBID_CONTENTS = _resolveSetOption(
|
|
682
|
+
cfg,
|
|
683
|
+
'FORBID_CONTENTS',
|
|
684
|
+
DEFAULT_FORBID_CONTENTS,
|
|
685
|
+
{ transform: transformCaseFunc }
|
|
686
|
+
);
|
|
687
|
+
FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {
|
|
688
|
+
transform: transformCaseFunc,
|
|
689
|
+
});
|
|
690
|
+
FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {
|
|
691
|
+
transform: transformCaseFunc,
|
|
692
|
+
});
|
|
649
693
|
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES')
|
|
650
694
|
? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object'
|
|
651
695
|
? clone(cfg.USE_PROFILES)
|
|
@@ -679,14 +723,14 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
679
723
|
cfg.MATHML_TEXT_INTEGRATION_POINTS &&
|
|
680
724
|
typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object'
|
|
681
725
|
? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS)
|
|
682
|
-
: addToSet({},
|
|
726
|
+
: addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS); // Default built-in map
|
|
683
727
|
|
|
684
728
|
HTML_INTEGRATION_POINTS =
|
|
685
729
|
objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') &&
|
|
686
730
|
cfg.HTML_INTEGRATION_POINTS &&
|
|
687
731
|
typeof cfg.HTML_INTEGRATION_POINTS === 'object'
|
|
688
732
|
? clone(cfg.HTML_INTEGRATION_POINTS)
|
|
689
|
-
: addToSet({},
|
|
733
|
+
: addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS); // Default built-in map
|
|
690
734
|
|
|
691
735
|
const customElementHandling =
|
|
692
736
|
objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') &&
|
|
@@ -723,6 +767,8 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
723
767
|
customElementHandling.allowCustomizedBuiltInElements; // Default undefined
|
|
724
768
|
}
|
|
725
769
|
|
|
770
|
+
seal(CUSTOM_ELEMENT_HANDLING);
|
|
771
|
+
|
|
726
772
|
if (SAFE_FOR_TEMPLATES) {
|
|
727
773
|
ALLOW_DATA_ATTR = false;
|
|
728
774
|
}
|
|
@@ -944,6 +990,111 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
944
990
|
...TAGS.mathMlDisallowed,
|
|
945
991
|
]);
|
|
946
992
|
|
|
993
|
+
/**
|
|
994
|
+
* Namespace rules for an element in the SVG namespace.
|
|
995
|
+
*
|
|
996
|
+
* @param tagName the element's lowercase tag name
|
|
997
|
+
* @param parent the (possibly simulated) parent node
|
|
998
|
+
* @param parentTagName the parent's lowercase tag name
|
|
999
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
1000
|
+
*/
|
|
1001
|
+
const _checkSvgNamespace = function (
|
|
1002
|
+
tagName: string,
|
|
1003
|
+
parent: { namespaceURI?: string },
|
|
1004
|
+
parentTagName: string
|
|
1005
|
+
): boolean {
|
|
1006
|
+
// The only way to switch from HTML namespace to SVG
|
|
1007
|
+
// is via <svg>. If it happens via any other tag, then
|
|
1008
|
+
// it should be killed.
|
|
1009
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
1010
|
+
return tagName === 'svg';
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// The only way to switch from MathML to SVG is via <svg>
|
|
1014
|
+
// if the parent is either <annotation-xml> or a MathML
|
|
1015
|
+
// text integration point.
|
|
1016
|
+
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
1017
|
+
return (
|
|
1018
|
+
tagName === 'svg' &&
|
|
1019
|
+
(parentTagName === 'annotation-xml' ||
|
|
1020
|
+
MATHML_TEXT_INTEGRATION_POINTS[parentTagName])
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// We only allow elements that are defined in SVG
|
|
1025
|
+
// spec. All others are disallowed in SVG namespace.
|
|
1026
|
+
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Namespace rules for an element in the MathML namespace.
|
|
1031
|
+
*
|
|
1032
|
+
* @param tagName the element's lowercase tag name
|
|
1033
|
+
* @param parent the (possibly simulated) parent node
|
|
1034
|
+
* @param parentTagName the parent's lowercase tag name
|
|
1035
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
1036
|
+
*/
|
|
1037
|
+
const _checkMathMlNamespace = function (
|
|
1038
|
+
tagName: string,
|
|
1039
|
+
parent: { namespaceURI?: string },
|
|
1040
|
+
parentTagName: string
|
|
1041
|
+
): boolean {
|
|
1042
|
+
// The only way to switch from HTML namespace to MathML
|
|
1043
|
+
// is via <math>. If it happens via any other tag, then
|
|
1044
|
+
// it should be killed.
|
|
1045
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
1046
|
+
return tagName === 'math';
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// The only way to switch from SVG to MathML is via
|
|
1050
|
+
// <math> and HTML integration points
|
|
1051
|
+
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
1052
|
+
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// We only allow elements that are defined in MathML
|
|
1056
|
+
// spec. All others are disallowed in MathML namespace.
|
|
1057
|
+
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Namespace rules for an element in the HTML namespace.
|
|
1062
|
+
*
|
|
1063
|
+
* @param tagName the element's lowercase tag name
|
|
1064
|
+
* @param parent the (possibly simulated) parent node
|
|
1065
|
+
* @param parentTagName the parent's lowercase tag name
|
|
1066
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
1067
|
+
*/
|
|
1068
|
+
const _checkHtmlNamespace = function (
|
|
1069
|
+
tagName: string,
|
|
1070
|
+
parent: { namespaceURI?: string },
|
|
1071
|
+
parentTagName: string
|
|
1072
|
+
): boolean {
|
|
1073
|
+
// The only way to switch from SVG to HTML is via
|
|
1074
|
+
// HTML integration points, and from MathML to HTML
|
|
1075
|
+
// is via MathML text integration points
|
|
1076
|
+
if (
|
|
1077
|
+
parent.namespaceURI === SVG_NAMESPACE &&
|
|
1078
|
+
!HTML_INTEGRATION_POINTS[parentTagName]
|
|
1079
|
+
) {
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if (
|
|
1084
|
+
parent.namespaceURI === MATHML_NAMESPACE &&
|
|
1085
|
+
!MATHML_TEXT_INTEGRATION_POINTS[parentTagName]
|
|
1086
|
+
) {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// We disallow tags that are specific for MathML
|
|
1091
|
+
// or SVG and should never appear in HTML namespace
|
|
1092
|
+
return (
|
|
1093
|
+
!ALL_MATHML_TAGS[tagName] &&
|
|
1094
|
+
(COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName])
|
|
1095
|
+
);
|
|
1096
|
+
};
|
|
1097
|
+
|
|
947
1098
|
/**
|
|
948
1099
|
* @param element a DOM element whose namespace is being checked
|
|
949
1100
|
* @returns Return false if the element has a
|
|
@@ -970,72 +1121,15 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
970
1121
|
}
|
|
971
1122
|
|
|
972
1123
|
if (element.namespaceURI === SVG_NAMESPACE) {
|
|
973
|
-
|
|
974
|
-
// is via <svg>. If it happens via any other tag, then
|
|
975
|
-
// it should be killed.
|
|
976
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
977
|
-
return tagName === 'svg';
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
// The only way to switch from MathML to SVG is via`
|
|
981
|
-
// svg if parent is either <annotation-xml> or MathML
|
|
982
|
-
// text integration points.
|
|
983
|
-
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
984
|
-
return (
|
|
985
|
-
tagName === 'svg' &&
|
|
986
|
-
(parentTagName === 'annotation-xml' ||
|
|
987
|
-
MATHML_TEXT_INTEGRATION_POINTS[parentTagName])
|
|
988
|
-
);
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
// We only allow elements that are defined in SVG
|
|
992
|
-
// spec. All others are disallowed in SVG namespace.
|
|
993
|
-
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
1124
|
+
return _checkSvgNamespace(tagName, parent, parentTagName);
|
|
994
1125
|
}
|
|
995
1126
|
|
|
996
1127
|
if (element.namespaceURI === MATHML_NAMESPACE) {
|
|
997
|
-
|
|
998
|
-
// is via <math>. If it happens via any other tag, then
|
|
999
|
-
// it should be killed.
|
|
1000
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
1001
|
-
return tagName === 'math';
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// The only way to switch from SVG to MathML is via
|
|
1005
|
-
// <math> and HTML integration points
|
|
1006
|
-
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
1007
|
-
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// We only allow elements that are defined in MathML
|
|
1011
|
-
// spec. All others are disallowed in MathML namespace.
|
|
1012
|
-
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
1128
|
+
return _checkMathMlNamespace(tagName, parent, parentTagName);
|
|
1013
1129
|
}
|
|
1014
1130
|
|
|
1015
1131
|
if (element.namespaceURI === HTML_NAMESPACE) {
|
|
1016
|
-
|
|
1017
|
-
// HTML integration points, and from MathML to HTML
|
|
1018
|
-
// is via MathML text integration points
|
|
1019
|
-
if (
|
|
1020
|
-
parent.namespaceURI === SVG_NAMESPACE &&
|
|
1021
|
-
!HTML_INTEGRATION_POINTS[parentTagName]
|
|
1022
|
-
) {
|
|
1023
|
-
return false;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
if (
|
|
1027
|
-
parent.namespaceURI === MATHML_NAMESPACE &&
|
|
1028
|
-
!MATHML_TEXT_INTEGRATION_POINTS[parentTagName]
|
|
1029
|
-
) {
|
|
1030
|
-
return false;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
// We disallow tags that are specific for MathML
|
|
1034
|
-
// or SVG and should never appear in HTML namespace
|
|
1035
|
-
return (
|
|
1036
|
-
!ALL_MATHML_TAGS[tagName] &&
|
|
1037
|
-
(COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName])
|
|
1038
|
-
);
|
|
1132
|
+
return _checkHtmlNamespace(tagName, parent, parentTagName);
|
|
1039
1133
|
}
|
|
1040
1134
|
|
|
1041
1135
|
// For XHTML and XML documents that support custom namespaces
|
|
@@ -1112,9 +1206,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1112
1206
|
* @param root the in-place root to empty
|
|
1113
1207
|
*/
|
|
1114
1208
|
const _neutralizeRoot = function (root: Node): void {
|
|
1115
|
-
const childNodes = getChildNodes
|
|
1116
|
-
? getChildNodes(root)
|
|
1117
|
-
: (root as Element).childNodes;
|
|
1209
|
+
const childNodes = getChildNodes(root);
|
|
1118
1210
|
if (childNodes) {
|
|
1119
1211
|
const snapshot: Node[] = [];
|
|
1120
1212
|
arrayForEach(childNodes, (child) => {
|
|
@@ -1129,7 +1221,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1129
1221
|
});
|
|
1130
1222
|
}
|
|
1131
1223
|
|
|
1132
|
-
const attributes = getAttributes
|
|
1224
|
+
const attributes = getAttributes(root);
|
|
1133
1225
|
if (attributes) {
|
|
1134
1226
|
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
1135
1227
|
const attribute = attributes[i];
|
|
@@ -1191,9 +1283,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1191
1283
|
* @param element the element to strip
|
|
1192
1284
|
*/
|
|
1193
1285
|
const _stripDisallowedAttributes = function (element: Element): void {
|
|
1194
|
-
const attributes = getAttributes
|
|
1195
|
-
? getAttributes(element)
|
|
1196
|
-
: element.attributes;
|
|
1286
|
+
const attributes = getAttributes(element);
|
|
1197
1287
|
if (!attributes) {
|
|
1198
1288
|
return;
|
|
1199
1289
|
}
|
|
@@ -1246,9 +1336,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1246
1336
|
_stripDisallowedAttributes(node as Element);
|
|
1247
1337
|
}
|
|
1248
1338
|
|
|
1249
|
-
const childNodes = getChildNodes
|
|
1250
|
-
? getChildNodes(node)
|
|
1251
|
-
: (node as Element).childNodes;
|
|
1339
|
+
const childNodes = getChildNodes(node);
|
|
1252
1340
|
if (childNodes) {
|
|
1253
1341
|
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
1254
1342
|
stack.push(childNodes[i]);
|
|
@@ -1350,6 +1438,21 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1350
1438
|
);
|
|
1351
1439
|
};
|
|
1352
1440
|
|
|
1441
|
+
/**
|
|
1442
|
+
* Replace template expression syntax (mustache, ERB, template
|
|
1443
|
+
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
|
|
1444
|
+
* sites. Order matters: mustache, then ERB, then template literal.
|
|
1445
|
+
*
|
|
1446
|
+
* @param value the string to scrub
|
|
1447
|
+
* @returns the scrubbed string
|
|
1448
|
+
*/
|
|
1449
|
+
const _stripTemplateExpressions = function (value: string): string {
|
|
1450
|
+
value = stringReplace(value, MUSTACHE_EXPR, ' ');
|
|
1451
|
+
value = stringReplace(value, ERB_EXPR, ' ');
|
|
1452
|
+
value = stringReplace(value, TMPLIT_EXPR, ' ');
|
|
1453
|
+
return value;
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1353
1456
|
/**
|
|
1354
1457
|
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
1355
1458
|
* character data of an element subtree. Used as the final safety net for
|
|
@@ -1384,23 +1487,21 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1384
1487
|
|
|
1385
1488
|
let currentNode = walker.nextNode() as CharacterData | null;
|
|
1386
1489
|
while (currentNode) {
|
|
1387
|
-
|
|
1388
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr: RegExp) => {
|
|
1389
|
-
data = stringReplace(data, expr, ' ');
|
|
1390
|
-
});
|
|
1391
|
-
currentNode.data = data;
|
|
1490
|
+
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
1392
1491
|
currentNode = walker.nextNode() as CharacterData | null;
|
|
1393
1492
|
}
|
|
1394
1493
|
|
|
1395
1494
|
// NodeIterator does not descend into <template>.content per the DOM spec,
|
|
1396
1495
|
// so we must explicitly recurse into each template's content fragment,
|
|
1397
1496
|
// mirroring the approach used by _sanitizeShadowDOM.
|
|
1398
|
-
const templates = node.querySelectorAll?.('template')
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1497
|
+
const templates = node.querySelectorAll?.('template');
|
|
1498
|
+
if (templates) {
|
|
1499
|
+
arrayForEach(templates, (tmpl: HTMLTemplateElement) => {
|
|
1500
|
+
if (_isDocumentFragment(tmpl.content)) {
|
|
1501
|
+
_scrubTemplateExpressions(tmpl.content as unknown as Element);
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1404
1505
|
};
|
|
1405
1506
|
|
|
1406
1507
|
/**
|
|
@@ -1517,52 +1618,34 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1517
1618
|
currentNode: Parameters<T>[0],
|
|
1518
1619
|
data: Parameters<T>[1]
|
|
1519
1620
|
): void {
|
|
1621
|
+
if (hooks.length === 0) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1520
1625
|
arrayForEach(hooks, (hook: T) => {
|
|
1521
1626
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
1522
1627
|
});
|
|
1523
1628
|
}
|
|
1524
1629
|
|
|
1525
1630
|
/**
|
|
1526
|
-
*
|
|
1631
|
+
* Structural-threat checks that condemn a node regardless of the
|
|
1632
|
+
* allowlists: mXSS via namespace confusion, risky CSS construction,
|
|
1633
|
+
* processing instructions, markup-bearing comments. Pure predicate;
|
|
1634
|
+
* the caller removes. Check order is load-bearing.
|
|
1527
1635
|
*
|
|
1528
|
-
* @
|
|
1529
|
-
* @
|
|
1530
|
-
* @
|
|
1531
|
-
* @param currentNode to check for permission to exist
|
|
1532
|
-
* @return true if node was killed, false if left alive
|
|
1636
|
+
* @param currentNode the node to inspect
|
|
1637
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
1638
|
+
* @return true if the node must be removed
|
|
1533
1639
|
*/
|
|
1534
|
-
const
|
|
1535
|
-
let content = null;
|
|
1536
|
-
|
|
1537
|
-
/* Execute a hook if present */
|
|
1538
|
-
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
1539
|
-
|
|
1540
|
-
/* Check if element is clobbered or can clobber */
|
|
1541
|
-
if (_isClobbered(currentNode)) {
|
|
1542
|
-
_forceRemove(currentNode);
|
|
1543
|
-
return true;
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
/* Now let's check the element's type and name */
|
|
1547
|
-
const tagName = transformCaseFunc(
|
|
1548
|
-
getNodeName ? getNodeName(currentNode) : currentNode.nodeName
|
|
1549
|
-
);
|
|
1550
|
-
|
|
1551
|
-
/* Execute a hook if present */
|
|
1552
|
-
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
1553
|
-
tagName,
|
|
1554
|
-
allowedTags: ALLOWED_TAGS,
|
|
1555
|
-
});
|
|
1556
|
-
|
|
1640
|
+
const _isUnsafeNode = function (currentNode: any, tagName: string): boolean {
|
|
1557
1641
|
/* Detect mXSS attempts abusing namespace confusion */
|
|
1558
1642
|
if (
|
|
1559
1643
|
SAFE_FOR_XML &&
|
|
1560
1644
|
currentNode.hasChildNodes() &&
|
|
1561
1645
|
!_isNode(currentNode.firstElementChild) &&
|
|
1562
|
-
regExpTest(
|
|
1563
|
-
regExpTest(
|
|
1646
|
+
regExpTest(EXPRESSIONS.ELEMENT_MARKUP_PROBE, currentNode.textContent) &&
|
|
1647
|
+
regExpTest(EXPRESSIONS.ELEMENT_MARKUP_PROBE, currentNode.innerHTML)
|
|
1564
1648
|
) {
|
|
1565
|
-
_forceRemove(currentNode);
|
|
1566
1649
|
return true;
|
|
1567
1650
|
}
|
|
1568
1651
|
|
|
@@ -1573,13 +1656,11 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1573
1656
|
tagName === 'style' &&
|
|
1574
1657
|
_isNode(currentNode.firstElementChild)
|
|
1575
1658
|
) {
|
|
1576
|
-
_forceRemove(currentNode);
|
|
1577
1659
|
return true;
|
|
1578
1660
|
}
|
|
1579
1661
|
|
|
1580
1662
|
/* Remove any occurrence of processing instructions */
|
|
1581
|
-
if (currentNode.nodeType === NODE_TYPE.
|
|
1582
|
-
_forceRemove(currentNode);
|
|
1663
|
+
if (currentNode.nodeType === NODE_TYPE.processingInstruction) {
|
|
1583
1664
|
return true;
|
|
1584
1665
|
}
|
|
1585
1666
|
|
|
@@ -1587,39 +1668,47 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1587
1668
|
if (
|
|
1588
1669
|
SAFE_FOR_XML &&
|
|
1589
1670
|
currentNode.nodeType === NODE_TYPE.comment &&
|
|
1590
|
-
regExpTest(
|
|
1671
|
+
regExpTest(EXPRESSIONS.COMMENT_MARKUP_PROBE, currentNode.data)
|
|
1591
1672
|
) {
|
|
1592
|
-
_forceRemove(currentNode);
|
|
1593
1673
|
return true;
|
|
1594
1674
|
}
|
|
1595
1675
|
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
FORBID_TAGS[tagName] ||
|
|
1599
|
-
(!(
|
|
1600
|
-
EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function &&
|
|
1601
|
-
EXTRA_ELEMENT_HANDLING.tagCheck(tagName)
|
|
1602
|
-
) &&
|
|
1603
|
-
!ALLOWED_TAGS[tagName])
|
|
1604
|
-
) {
|
|
1605
|
-
/* Check if we have a custom element to handle */
|
|
1606
|
-
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
1607
|
-
if (
|
|
1608
|
-
CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&
|
|
1609
|
-
regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)
|
|
1610
|
-
) {
|
|
1611
|
-
return false;
|
|
1612
|
-
}
|
|
1676
|
+
return false;
|
|
1677
|
+
};
|
|
1613
1678
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1679
|
+
/**
|
|
1680
|
+
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
1681
|
+
* allowed custom elements (false return exits _sanitizeElements
|
|
1682
|
+
* early - namespace/fallback checks and the afterSanitizeElements
|
|
1683
|
+
* hook are intentionally skipped for kept custom elements), else
|
|
1684
|
+
* hoist content per KEEP_CONTENT and remove.
|
|
1685
|
+
*
|
|
1686
|
+
* @param currentNode the disallowed node
|
|
1687
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
1688
|
+
* @return true if the node was removed, false if kept
|
|
1689
|
+
*/
|
|
1690
|
+
const _sanitizeDisallowedNode = function (
|
|
1691
|
+
currentNode: any,
|
|
1692
|
+
tagName: string
|
|
1693
|
+
): boolean {
|
|
1694
|
+
/* Check if we have a custom element to handle */
|
|
1695
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
1696
|
+
if (
|
|
1697
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&
|
|
1698
|
+
regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)
|
|
1699
|
+
) {
|
|
1700
|
+
return false;
|
|
1620
1701
|
}
|
|
1621
1702
|
|
|
1622
|
-
|
|
1703
|
+
if (
|
|
1704
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&
|
|
1705
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)
|
|
1706
|
+
) {
|
|
1707
|
+
return false;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/* Keep content except for bad-listed elements.
|
|
1623
1712
|
Use the cached prototype getters exclusively — the previous code
|
|
1624
1713
|
had `|| currentNode.parentNode` / `|| currentNode.childNodes`
|
|
1625
1714
|
fallbacks, but the cached getters always return the canonical
|
|
@@ -1627,14 +1716,14 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1627
1716
|
path was dead in safe cases and a clobbering surface in unsafe
|
|
1628
1717
|
ones. Falsy cached results stay falsy; the `if (childNodes &&
|
|
1629
1718
|
parentNode)` check already gates correctly. */
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1719
|
+
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
1720
|
+
const parentNode = getParentNode(currentNode);
|
|
1721
|
+
const childNodes = getChildNodes(currentNode);
|
|
1633
1722
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1723
|
+
if (childNodes && parentNode) {
|
|
1724
|
+
const childCount = childNodes.length;
|
|
1636
1725
|
|
|
1637
|
-
|
|
1726
|
+
/* In-place: hoist the *original* children so the iterator visits
|
|
1638
1727
|
and sanitises them through the same allowlist pass as every other
|
|
1639
1728
|
node. The caller built the tree in the live document, so the
|
|
1640
1729
|
originals carry already-queued resource events (`<img onerror>`,
|
|
@@ -1655,19 +1744,67 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1655
1744
|
`childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
|
|
1656
1745
|
valid whether we move (drops the trailing entry) or clone (leaves
|
|
1657
1746
|
the list intact). */
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
}
|
|
1747
|
+
for (let i = childCount - 1; i >= 0; --i) {
|
|
1748
|
+
const hoisted = IN_PLACE
|
|
1749
|
+
? childNodes[i]
|
|
1750
|
+
: cloneNode(childNodes[i], true);
|
|
1751
|
+
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
1664
1752
|
}
|
|
1665
1753
|
}
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
_forceRemove(currentNode);
|
|
1757
|
+
return true;
|
|
1758
|
+
};
|
|
1759
|
+
|
|
1760
|
+
/**
|
|
1761
|
+
* _sanitizeElements
|
|
1762
|
+
*
|
|
1763
|
+
* @protect nodeName
|
|
1764
|
+
* @protect textContent
|
|
1765
|
+
* @protect removeChild
|
|
1766
|
+
* @param currentNode to check for permission to exist
|
|
1767
|
+
* @return true if node was killed, false if left alive
|
|
1768
|
+
*/
|
|
1769
|
+
const _sanitizeElements = function (currentNode: any): boolean {
|
|
1770
|
+
/* Execute a hook if present */
|
|
1771
|
+
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
1666
1772
|
|
|
1773
|
+
/* Check if element is clobbered or can clobber */
|
|
1774
|
+
if (_isClobbered(currentNode)) {
|
|
1667
1775
|
_forceRemove(currentNode);
|
|
1668
1776
|
return true;
|
|
1669
1777
|
}
|
|
1670
1778
|
|
|
1779
|
+
/* Now let's check the element's type and name */
|
|
1780
|
+
const tagName = transformCaseFunc(
|
|
1781
|
+
getNodeName ? getNodeName(currentNode) : currentNode.nodeName
|
|
1782
|
+
);
|
|
1783
|
+
|
|
1784
|
+
/* Execute a hook if present */
|
|
1785
|
+
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
1786
|
+
tagName,
|
|
1787
|
+
allowedTags: ALLOWED_TAGS,
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
1791
|
+
if (_isUnsafeNode(currentNode, tagName)) {
|
|
1792
|
+
_forceRemove(currentNode);
|
|
1793
|
+
return true;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
/* Remove element if anything forbids its presence */
|
|
1797
|
+
if (
|
|
1798
|
+
FORBID_TAGS[tagName] ||
|
|
1799
|
+
(!(
|
|
1800
|
+
EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function &&
|
|
1801
|
+
EXTRA_ELEMENT_HANDLING.tagCheck(tagName)
|
|
1802
|
+
) &&
|
|
1803
|
+
!ALLOWED_TAGS[tagName])
|
|
1804
|
+
) {
|
|
1805
|
+
return _sanitizeDisallowedNode(currentNode, tagName);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1671
1808
|
/* Check whether element has a valid namespace.
|
|
1672
1809
|
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
1673
1810
|
nodeType getter rather than `instanceof Element`, which is realm-
|
|
@@ -1685,7 +1822,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1685
1822
|
(tagName === 'noscript' ||
|
|
1686
1823
|
tagName === 'noembed' ||
|
|
1687
1824
|
tagName === 'noframes') &&
|
|
1688
|
-
regExpTest(
|
|
1825
|
+
regExpTest(EXPRESSIONS.FALLBACK_TAG_CLOSE, currentNode.innerHTML)
|
|
1689
1826
|
) {
|
|
1690
1827
|
_forceRemove(currentNode);
|
|
1691
1828
|
return true;
|
|
@@ -1694,11 +1831,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1694
1831
|
/* Sanitize element content to be template-safe */
|
|
1695
1832
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1696
1833
|
/* Get the element's text content */
|
|
1697
|
-
content = currentNode.textContent;
|
|
1698
|
-
|
|
1699
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr: RegExp) => {
|
|
1700
|
-
content = stringReplace(content, expr, ' ');
|
|
1701
|
-
});
|
|
1834
|
+
const content = _stripTemplateExpressions(currentNode.textContent);
|
|
1702
1835
|
|
|
1703
1836
|
if (currentNode.textContent !== content) {
|
|
1704
1837
|
arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });
|
|
@@ -1749,16 +1882,12 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1749
1882
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1750
1883
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1751
1884
|
We don't need to check the value; it's always URI safe. */
|
|
1752
|
-
if (
|
|
1753
|
-
ALLOW_DATA_ATTR &&
|
|
1754
|
-
!FORBID_ATTR[lcName] &&
|
|
1755
|
-
regExpTest(DATA_ATTR, lcName)
|
|
1756
|
-
) {
|
|
1885
|
+
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR, lcName)) {
|
|
1757
1886
|
// This attribute is safe
|
|
1758
1887
|
} else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) {
|
|
1759
1888
|
// This attribute is safe
|
|
1760
1889
|
/* Otherwise, check the name is permitted */
|
|
1761
|
-
} else if (!nameIsPermitted
|
|
1890
|
+
} else if (!nameIsPermitted) {
|
|
1762
1891
|
if (
|
|
1763
1892
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1764
1893
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -1852,6 +1981,85 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1852
1981
|
);
|
|
1853
1982
|
};
|
|
1854
1983
|
|
|
1984
|
+
/**
|
|
1985
|
+
* Wrap an attribute value in the matching Trusted Types object when
|
|
1986
|
+
* the active policy requires it. Namespaced attributes pass through
|
|
1987
|
+
* unchanged (no TT support yet, see
|
|
1988
|
+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
|
|
1989
|
+
*
|
|
1990
|
+
* @param lcTag lowercase tag name of the containing element
|
|
1991
|
+
* @param lcName lowercase attribute name
|
|
1992
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
1993
|
+
* @param value the attribute value to wrap
|
|
1994
|
+
* @return the value, wrapped when Trusted Types demand it
|
|
1995
|
+
*/
|
|
1996
|
+
const _applyTrustedTypesToAttribute = function (
|
|
1997
|
+
lcTag: string,
|
|
1998
|
+
lcName: string,
|
|
1999
|
+
namespaceURI: string | null,
|
|
2000
|
+
value: string
|
|
2001
|
+
): string {
|
|
2002
|
+
if (
|
|
2003
|
+
trustedTypesPolicy &&
|
|
2004
|
+
typeof trustedTypes === 'object' &&
|
|
2005
|
+
typeof trustedTypes.getAttributeType === 'function' &&
|
|
2006
|
+
!namespaceURI
|
|
2007
|
+
) {
|
|
2008
|
+
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
2009
|
+
case 'TrustedHTML': {
|
|
2010
|
+
return _createTrustedHTML(value);
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
case 'TrustedScriptURL': {
|
|
2014
|
+
return _createTrustedScriptURL(value);
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
default: {
|
|
2018
|
+
break;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
return value;
|
|
2024
|
+
};
|
|
2025
|
+
|
|
2026
|
+
/**
|
|
2027
|
+
* Write a modified attribute value back onto the element. On
|
|
2028
|
+
* success, re-probe for clobbering introduced by the new value and
|
|
2029
|
+
* remove the element when found; otherwise pop the removal entry
|
|
2030
|
+
* recorded by the earlier _removeAttribute (long-standing pairing
|
|
2031
|
+
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
|
|
2032
|
+
* failure, remove the attribute instead.
|
|
2033
|
+
*
|
|
2034
|
+
* @param currentNode the element carrying the attribute
|
|
2035
|
+
* @param name the attribute name as present on the element
|
|
2036
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
2037
|
+
* @param value the new attribute value
|
|
2038
|
+
*/
|
|
2039
|
+
const _setAttributeValue = function (
|
|
2040
|
+
currentNode: Element,
|
|
2041
|
+
name: string,
|
|
2042
|
+
namespaceURI: string | null,
|
|
2043
|
+
value: string
|
|
2044
|
+
): void {
|
|
2045
|
+
try {
|
|
2046
|
+
if (namespaceURI) {
|
|
2047
|
+
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
2048
|
+
} else {
|
|
2049
|
+
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
2050
|
+
currentNode.setAttribute(name, value);
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
if (_isClobbered(currentNode)) {
|
|
2054
|
+
_forceRemove(currentNode);
|
|
2055
|
+
} else {
|
|
2056
|
+
arrayPop(DOMPurify.removed);
|
|
2057
|
+
}
|
|
2058
|
+
} catch (_) {
|
|
2059
|
+
_removeAttribute(name, currentNode);
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
|
|
1855
2063
|
/**
|
|
1856
2064
|
* _sanitizeAttributes
|
|
1857
2065
|
*
|
|
@@ -1881,6 +2089,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1881
2089
|
forceKeepAttr: undefined,
|
|
1882
2090
|
};
|
|
1883
2091
|
let l = attributes.length;
|
|
2092
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1884
2093
|
|
|
1885
2094
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1886
2095
|
while (l--) {
|
|
@@ -1933,7 +2142,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1933
2142
|
continue;
|
|
1934
2143
|
}
|
|
1935
2144
|
|
|
1936
|
-
/* Did the hooks
|
|
2145
|
+
/* Did the hooks force-keep the attribute? */
|
|
1937
2146
|
if (hookEvent.forceKeepAttr) {
|
|
1938
2147
|
continue;
|
|
1939
2148
|
}
|
|
@@ -1945,70 +2154,31 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1945
2154
|
}
|
|
1946
2155
|
|
|
1947
2156
|
/* Work around a security issue in jQuery 3.0 */
|
|
1948
|
-
if (
|
|
2157
|
+
if (
|
|
2158
|
+
!ALLOW_SELF_CLOSE_IN_ATTR &&
|
|
2159
|
+
regExpTest(EXPRESSIONS.SELF_CLOSING_TAG, value)
|
|
2160
|
+
) {
|
|
1949
2161
|
_removeAttribute(name, currentNode);
|
|
1950
2162
|
continue;
|
|
1951
2163
|
}
|
|
1952
2164
|
|
|
1953
2165
|
/* Sanitize attribute content to be template-safe */
|
|
1954
2166
|
if (SAFE_FOR_TEMPLATES) {
|
|
1955
|
-
|
|
1956
|
-
value = stringReplace(value, expr, ' ');
|
|
1957
|
-
});
|
|
2167
|
+
value = _stripTemplateExpressions(value);
|
|
1958
2168
|
}
|
|
1959
2169
|
|
|
1960
2170
|
/* Is `value` valid for this attribute? */
|
|
1961
|
-
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1962
2171
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
1963
2172
|
_removeAttribute(name, currentNode);
|
|
1964
2173
|
continue;
|
|
1965
2174
|
}
|
|
1966
2175
|
|
|
1967
2176
|
/* Handle attributes that require Trusted Types */
|
|
1968
|
-
|
|
1969
|
-
trustedTypesPolicy &&
|
|
1970
|
-
typeof trustedTypes === 'object' &&
|
|
1971
|
-
typeof trustedTypes.getAttributeType === 'function'
|
|
1972
|
-
) {
|
|
1973
|
-
if (namespaceURI) {
|
|
1974
|
-
/* Namespaces are not yet supported, see https://bugs.chromium.org/p/chromium/issues/detail?id=1305293 */
|
|
1975
|
-
} else {
|
|
1976
|
-
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
1977
|
-
case 'TrustedHTML': {
|
|
1978
|
-
value = _createTrustedHTML(value);
|
|
1979
|
-
break;
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
case 'TrustedScriptURL': {
|
|
1983
|
-
value = _createTrustedScriptURL(value);
|
|
1984
|
-
break;
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
default: {
|
|
1988
|
-
break;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
2177
|
+
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
1993
2178
|
|
|
1994
2179
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
1995
2180
|
if (value !== initValue) {
|
|
1996
|
-
|
|
1997
|
-
if (namespaceURI) {
|
|
1998
|
-
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
1999
|
-
} else {
|
|
2000
|
-
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
2001
|
-
currentNode.setAttribute(name, value);
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
if (_isClobbered(currentNode)) {
|
|
2005
|
-
_forceRemove(currentNode);
|
|
2006
|
-
} else {
|
|
2007
|
-
arrayPop(DOMPurify.removed);
|
|
2008
|
-
}
|
|
2009
|
-
} catch (_) {
|
|
2010
|
-
_removeAttribute(name, currentNode);
|
|
2011
|
-
}
|
|
2181
|
+
_setAttributeValue(currentNode, name, namespaceURI, value);
|
|
2012
2182
|
}
|
|
2013
2183
|
}
|
|
2014
2184
|
|
|
@@ -2060,9 +2230,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2060
2230
|
? getNodeType(shadowNode)
|
|
2061
2231
|
: shadowNode.nodeType;
|
|
2062
2232
|
if (shadowNodeType === NODE_TYPE.element) {
|
|
2063
|
-
const innerSr = getShadowRoot
|
|
2064
|
-
? getShadowRoot(shadowNode)
|
|
2065
|
-
: (shadowNode as Element).shadowRoot;
|
|
2233
|
+
const innerSr = getShadowRoot(shadowNode);
|
|
2066
2234
|
if (_isDocumentFragment(innerSr)) {
|
|
2067
2235
|
_sanitizeAttachedShadowRoots(innerSr);
|
|
2068
2236
|
_sanitizeShadowDOM(innerSr);
|
|
@@ -2129,9 +2297,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2129
2297
|
/* (pushed last → processed first) Children, snapshotted in reverse so
|
|
2130
2298
|
the first child is processed first. Snapshotting matters because a
|
|
2131
2299
|
hook may detach siblings mid-walk. */
|
|
2132
|
-
const childNodes = getChildNodes
|
|
2133
|
-
? getChildNodes(node)
|
|
2134
|
-
: (node as Element).childNodes;
|
|
2300
|
+
const childNodes = getChildNodes(node);
|
|
2135
2301
|
if (childNodes) {
|
|
2136
2302
|
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
2137
2303
|
stack.push({ node: childNodes[i], shadow: null });
|
|
@@ -2160,9 +2326,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2160
2326
|
silently skipped foreign-realm shadow roots (e.g.
|
|
2161
2327
|
iframe.contentDocument attachShadow). */
|
|
2162
2328
|
if (isElement) {
|
|
2163
|
-
const sr = getShadowRoot
|
|
2164
|
-
? getShadowRoot(node)
|
|
2165
|
-
: (node as Element).shadowRoot;
|
|
2329
|
+
const sr = getShadowRoot(node);
|
|
2166
2330
|
if (_isDocumentFragment(sr)) {
|
|
2167
2331
|
/* Push the deferred sanitise first so it pops after the shadow
|
|
2168
2332
|
walk we push next, i.e. nested shadow roots are discovered
|
|
@@ -2421,9 +2585,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2421
2585
|
|
|
2422
2586
|
/* Sanitize final string template-safe */
|
|
2423
2587
|
if (SAFE_FOR_TEMPLATES) {
|
|
2424
|
-
|
|
2425
|
-
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
2426
|
-
});
|
|
2588
|
+
serializedHTML = _stripTemplateExpressions(serializedHTML);
|
|
2427
2589
|
}
|
|
2428
2590
|
|
|
2429
2591
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE
|
|
@@ -2497,352 +2659,3 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2497
2659
|
}
|
|
2498
2660
|
|
|
2499
2661
|
export default createDOMPurify();
|
|
2500
|
-
|
|
2501
|
-
export interface DOMPurify {
|
|
2502
|
-
/**
|
|
2503
|
-
* Creates a DOMPurify instance using the given window-like object. Defaults to `window`.
|
|
2504
|
-
*/
|
|
2505
|
-
(root?: WindowLike): DOMPurify;
|
|
2506
|
-
|
|
2507
|
-
/**
|
|
2508
|
-
* Version label, exposed for easier checks
|
|
2509
|
-
* if DOMPurify is up to date or not
|
|
2510
|
-
*/
|
|
2511
|
-
version: string;
|
|
2512
|
-
|
|
2513
|
-
/**
|
|
2514
|
-
* Array of elements that DOMPurify removed during sanitation.
|
|
2515
|
-
* Empty if nothing was removed.
|
|
2516
|
-
*/
|
|
2517
|
-
removed: Array<RemovedElement | RemovedAttribute>;
|
|
2518
|
-
|
|
2519
|
-
/**
|
|
2520
|
-
* Expose whether this browser supports running the full DOMPurify.
|
|
2521
|
-
*/
|
|
2522
|
-
isSupported: boolean;
|
|
2523
|
-
|
|
2524
|
-
/**
|
|
2525
|
-
* Set the configuration once.
|
|
2526
|
-
*
|
|
2527
|
-
* @param cfg configuration object
|
|
2528
|
-
*/
|
|
2529
|
-
setConfig(cfg?: Config): void;
|
|
2530
|
-
|
|
2531
|
-
/**
|
|
2532
|
-
* Removes the configuration.
|
|
2533
|
-
*/
|
|
2534
|
-
clearConfig(): void;
|
|
2535
|
-
|
|
2536
|
-
/**
|
|
2537
|
-
* Provides core sanitation functionality.
|
|
2538
|
-
*
|
|
2539
|
-
* @param dirty string or DOM node
|
|
2540
|
-
* @param cfg object
|
|
2541
|
-
* @returns Sanitized TrustedHTML.
|
|
2542
|
-
*/
|
|
2543
|
-
sanitize(
|
|
2544
|
-
dirty: string | Node,
|
|
2545
|
-
cfg: Config & { RETURN_TRUSTED_TYPE: true }
|
|
2546
|
-
): TrustedHTML;
|
|
2547
|
-
|
|
2548
|
-
/**
|
|
2549
|
-
* Provides core sanitation functionality.
|
|
2550
|
-
*
|
|
2551
|
-
* @param dirty DOM node
|
|
2552
|
-
* @param cfg object
|
|
2553
|
-
* @returns Sanitized DOM node.
|
|
2554
|
-
*/
|
|
2555
|
-
sanitize(dirty: Node, cfg: Config & { IN_PLACE: true }): Node;
|
|
2556
|
-
|
|
2557
|
-
/**
|
|
2558
|
-
* Provides core sanitation functionality.
|
|
2559
|
-
*
|
|
2560
|
-
* @param dirty string or DOM node
|
|
2561
|
-
* @param cfg object
|
|
2562
|
-
* @returns Sanitized DOM node.
|
|
2563
|
-
*/
|
|
2564
|
-
sanitize(dirty: string | Node, cfg: Config & { RETURN_DOM: true }): Node;
|
|
2565
|
-
|
|
2566
|
-
/**
|
|
2567
|
-
* Provides core sanitation functionality.
|
|
2568
|
-
*
|
|
2569
|
-
* @param dirty string or DOM node
|
|
2570
|
-
* @param cfg object
|
|
2571
|
-
* @returns Sanitized document fragment.
|
|
2572
|
-
*/
|
|
2573
|
-
sanitize(
|
|
2574
|
-
dirty: string | Node,
|
|
2575
|
-
cfg: Config & { RETURN_DOM_FRAGMENT: true }
|
|
2576
|
-
): DocumentFragment;
|
|
2577
|
-
|
|
2578
|
-
/**
|
|
2579
|
-
* Provides core sanitation functionality.
|
|
2580
|
-
*
|
|
2581
|
-
* @param dirty string or DOM node
|
|
2582
|
-
* @param cfg object
|
|
2583
|
-
* @returns Sanitized string.
|
|
2584
|
-
*/
|
|
2585
|
-
sanitize(dirty: string | Node, cfg?: Config): string;
|
|
2586
|
-
|
|
2587
|
-
/**
|
|
2588
|
-
* Checks if an attribute value is valid.
|
|
2589
|
-
* Uses last set config, if any. Otherwise, uses config defaults.
|
|
2590
|
-
*
|
|
2591
|
-
* @param tag Tag name of containing element.
|
|
2592
|
-
* @param attr Attribute name.
|
|
2593
|
-
* @param value Attribute value.
|
|
2594
|
-
* @returns Returns true if `value` is valid. Otherwise, returns false.
|
|
2595
|
-
*/
|
|
2596
|
-
isValidAttribute(tag: string, attr: string, value: string): boolean;
|
|
2597
|
-
|
|
2598
|
-
/**
|
|
2599
|
-
* Adds a DOMPurify hook.
|
|
2600
|
-
*
|
|
2601
|
-
* @param entryPoint entry point for the hook to add
|
|
2602
|
-
* @param hookFunction function to execute
|
|
2603
|
-
*/
|
|
2604
|
-
addHook(entryPoint: BasicHookName, hookFunction: NodeHook): void;
|
|
2605
|
-
|
|
2606
|
-
/**
|
|
2607
|
-
* Adds a DOMPurify hook.
|
|
2608
|
-
*
|
|
2609
|
-
* @param entryPoint entry point for the hook to add
|
|
2610
|
-
* @param hookFunction function to execute
|
|
2611
|
-
*/
|
|
2612
|
-
addHook(entryPoint: ElementHookName, hookFunction: ElementHook): void;
|
|
2613
|
-
|
|
2614
|
-
/**
|
|
2615
|
-
* Adds a DOMPurify hook.
|
|
2616
|
-
*
|
|
2617
|
-
* @param entryPoint entry point for the hook to add
|
|
2618
|
-
* @param hookFunction function to execute
|
|
2619
|
-
*/
|
|
2620
|
-
addHook(
|
|
2621
|
-
entryPoint: DocumentFragmentHookName,
|
|
2622
|
-
hookFunction: DocumentFragmentHook
|
|
2623
|
-
): void;
|
|
2624
|
-
|
|
2625
|
-
/**
|
|
2626
|
-
* Adds a DOMPurify hook.
|
|
2627
|
-
*
|
|
2628
|
-
* @param entryPoint entry point for the hook to add
|
|
2629
|
-
* @param hookFunction function to execute
|
|
2630
|
-
*/
|
|
2631
|
-
addHook(
|
|
2632
|
-
entryPoint: 'uponSanitizeElement',
|
|
2633
|
-
hookFunction: UponSanitizeElementHook
|
|
2634
|
-
): void;
|
|
2635
|
-
|
|
2636
|
-
/**
|
|
2637
|
-
* Adds a DOMPurify hook.
|
|
2638
|
-
*
|
|
2639
|
-
* @param entryPoint entry point for the hook to add
|
|
2640
|
-
* @param hookFunction function to execute
|
|
2641
|
-
*/
|
|
2642
|
-
addHook(
|
|
2643
|
-
entryPoint: 'uponSanitizeAttribute',
|
|
2644
|
-
hookFunction: UponSanitizeAttributeHook
|
|
2645
|
-
): void;
|
|
2646
|
-
|
|
2647
|
-
/**
|
|
2648
|
-
* Remove a DOMPurify hook at a given entryPoint
|
|
2649
|
-
* (pops it from the stack of hooks if hook not specified)
|
|
2650
|
-
*
|
|
2651
|
-
* @param entryPoint entry point for the hook to remove
|
|
2652
|
-
* @param hookFunction optional specific hook to remove
|
|
2653
|
-
* @returns removed hook
|
|
2654
|
-
*/
|
|
2655
|
-
removeHook(
|
|
2656
|
-
entryPoint: BasicHookName,
|
|
2657
|
-
hookFunction?: NodeHook
|
|
2658
|
-
): NodeHook | undefined;
|
|
2659
|
-
|
|
2660
|
-
/**
|
|
2661
|
-
* Remove a DOMPurify hook at a given entryPoint
|
|
2662
|
-
* (pops it from the stack of hooks if hook not specified)
|
|
2663
|
-
*
|
|
2664
|
-
* @param entryPoint entry point for the hook to remove
|
|
2665
|
-
* @param hookFunction optional specific hook to remove
|
|
2666
|
-
* @returns removed hook
|
|
2667
|
-
*/
|
|
2668
|
-
removeHook(
|
|
2669
|
-
entryPoint: ElementHookName,
|
|
2670
|
-
hookFunction?: ElementHook
|
|
2671
|
-
): ElementHook | undefined;
|
|
2672
|
-
|
|
2673
|
-
/**
|
|
2674
|
-
* Remove a DOMPurify hook at a given entryPoint
|
|
2675
|
-
* (pops it from the stack of hooks if hook not specified)
|
|
2676
|
-
*
|
|
2677
|
-
* @param entryPoint entry point for the hook to remove
|
|
2678
|
-
* @param hookFunction optional specific hook to remove
|
|
2679
|
-
* @returns removed hook
|
|
2680
|
-
*/
|
|
2681
|
-
removeHook(
|
|
2682
|
-
entryPoint: DocumentFragmentHookName,
|
|
2683
|
-
hookFunction?: DocumentFragmentHook
|
|
2684
|
-
): DocumentFragmentHook | undefined;
|
|
2685
|
-
|
|
2686
|
-
/**
|
|
2687
|
-
* Remove a DOMPurify hook at a given entryPoint
|
|
2688
|
-
* (pops it from the stack of hooks if hook not specified)
|
|
2689
|
-
*
|
|
2690
|
-
* @param entryPoint entry point for the hook to remove
|
|
2691
|
-
* @param hookFunction optional specific hook to remove
|
|
2692
|
-
* @returns removed hook
|
|
2693
|
-
*/
|
|
2694
|
-
removeHook(
|
|
2695
|
-
entryPoint: 'uponSanitizeElement',
|
|
2696
|
-
hookFunction?: UponSanitizeElementHook
|
|
2697
|
-
): UponSanitizeElementHook | undefined;
|
|
2698
|
-
|
|
2699
|
-
/**
|
|
2700
|
-
* Remove a DOMPurify hook at a given entryPoint
|
|
2701
|
-
* (pops it from the stack of hooks if hook not specified)
|
|
2702
|
-
*
|
|
2703
|
-
* @param entryPoint entry point for the hook to remove
|
|
2704
|
-
* @param hookFunction optional specific hook to remove
|
|
2705
|
-
* @returns removed hook
|
|
2706
|
-
*/
|
|
2707
|
-
removeHook(
|
|
2708
|
-
entryPoint: 'uponSanitizeAttribute',
|
|
2709
|
-
hookFunction?: UponSanitizeAttributeHook
|
|
2710
|
-
): UponSanitizeAttributeHook | undefined;
|
|
2711
|
-
|
|
2712
|
-
/**
|
|
2713
|
-
* Removes all DOMPurify hooks at a given entryPoint
|
|
2714
|
-
*
|
|
2715
|
-
* @param entryPoint entry point for the hooks to remove
|
|
2716
|
-
*/
|
|
2717
|
-
removeHooks(entryPoint: HookName): void;
|
|
2718
|
-
|
|
2719
|
-
/**
|
|
2720
|
-
* Removes all DOMPurify hooks.
|
|
2721
|
-
*/
|
|
2722
|
-
removeAllHooks(): void;
|
|
2723
|
-
}
|
|
2724
|
-
|
|
2725
|
-
/**
|
|
2726
|
-
* An element removed by DOMPurify.
|
|
2727
|
-
*/
|
|
2728
|
-
export interface RemovedElement {
|
|
2729
|
-
/**
|
|
2730
|
-
* The element that was removed.
|
|
2731
|
-
*/
|
|
2732
|
-
element: Node;
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
/**
|
|
2736
|
-
* An element removed by DOMPurify.
|
|
2737
|
-
*/
|
|
2738
|
-
export interface RemovedAttribute {
|
|
2739
|
-
/**
|
|
2740
|
-
* The attribute that was removed.
|
|
2741
|
-
*/
|
|
2742
|
-
attribute: Attr | null;
|
|
2743
|
-
|
|
2744
|
-
/**
|
|
2745
|
-
* The element that the attribute was removed.
|
|
2746
|
-
*/
|
|
2747
|
-
from: Node;
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
type BasicHookName =
|
|
2751
|
-
| 'beforeSanitizeElements'
|
|
2752
|
-
| 'afterSanitizeElements'
|
|
2753
|
-
| 'uponSanitizeShadowNode';
|
|
2754
|
-
type ElementHookName = 'beforeSanitizeAttributes' | 'afterSanitizeAttributes';
|
|
2755
|
-
type DocumentFragmentHookName =
|
|
2756
|
-
| 'beforeSanitizeShadowDOM'
|
|
2757
|
-
| 'afterSanitizeShadowDOM';
|
|
2758
|
-
type UponSanitizeElementHookName = 'uponSanitizeElement';
|
|
2759
|
-
type UponSanitizeAttributeHookName = 'uponSanitizeAttribute';
|
|
2760
|
-
|
|
2761
|
-
interface HooksMap {
|
|
2762
|
-
beforeSanitizeElements: NodeHook[];
|
|
2763
|
-
afterSanitizeElements: NodeHook[];
|
|
2764
|
-
beforeSanitizeShadowDOM: DocumentFragmentHook[];
|
|
2765
|
-
uponSanitizeShadowNode: NodeHook[];
|
|
2766
|
-
afterSanitizeShadowDOM: DocumentFragmentHook[];
|
|
2767
|
-
beforeSanitizeAttributes: ElementHook[];
|
|
2768
|
-
afterSanitizeAttributes: ElementHook[];
|
|
2769
|
-
uponSanitizeElement: UponSanitizeElementHook[];
|
|
2770
|
-
uponSanitizeAttribute: UponSanitizeAttributeHook[];
|
|
2771
|
-
}
|
|
2772
|
-
|
|
2773
|
-
type ArrayElement<T> = T extends Array<infer U> ? U : never;
|
|
2774
|
-
|
|
2775
|
-
type HookFunction = ArrayElement<HooksMap[keyof HooksMap]>;
|
|
2776
|
-
|
|
2777
|
-
export type HookName =
|
|
2778
|
-
| BasicHookName
|
|
2779
|
-
| ElementHookName
|
|
2780
|
-
| DocumentFragmentHookName
|
|
2781
|
-
| UponSanitizeElementHookName
|
|
2782
|
-
| UponSanitizeAttributeHookName;
|
|
2783
|
-
|
|
2784
|
-
export type NodeHook = (
|
|
2785
|
-
this: DOMPurify,
|
|
2786
|
-
currentNode: Node,
|
|
2787
|
-
hookEvent: null,
|
|
2788
|
-
config: Config
|
|
2789
|
-
) => void;
|
|
2790
|
-
|
|
2791
|
-
export type ElementHook = (
|
|
2792
|
-
this: DOMPurify,
|
|
2793
|
-
currentNode: Element,
|
|
2794
|
-
hookEvent: null,
|
|
2795
|
-
config: Config
|
|
2796
|
-
) => void;
|
|
2797
|
-
|
|
2798
|
-
export type DocumentFragmentHook = (
|
|
2799
|
-
this: DOMPurify,
|
|
2800
|
-
currentNode: DocumentFragment,
|
|
2801
|
-
hookEvent: null,
|
|
2802
|
-
config: Config
|
|
2803
|
-
) => void;
|
|
2804
|
-
|
|
2805
|
-
export type UponSanitizeElementHook = (
|
|
2806
|
-
this: DOMPurify,
|
|
2807
|
-
currentNode: Node,
|
|
2808
|
-
hookEvent: UponSanitizeElementHookEvent,
|
|
2809
|
-
config: Config
|
|
2810
|
-
) => void;
|
|
2811
|
-
|
|
2812
|
-
export type UponSanitizeAttributeHook = (
|
|
2813
|
-
this: DOMPurify,
|
|
2814
|
-
currentNode: Element,
|
|
2815
|
-
hookEvent: UponSanitizeAttributeHookEvent,
|
|
2816
|
-
config: Config
|
|
2817
|
-
) => void;
|
|
2818
|
-
|
|
2819
|
-
export interface UponSanitizeElementHookEvent {
|
|
2820
|
-
tagName: string;
|
|
2821
|
-
allowedTags: Record<string, boolean>;
|
|
2822
|
-
}
|
|
2823
|
-
|
|
2824
|
-
export interface UponSanitizeAttributeHookEvent {
|
|
2825
|
-
attrName: string;
|
|
2826
|
-
attrValue: string;
|
|
2827
|
-
keepAttr: boolean;
|
|
2828
|
-
allowedAttributes: Record<string, boolean>;
|
|
2829
|
-
forceKeepAttr: boolean | undefined;
|
|
2830
|
-
}
|
|
2831
|
-
|
|
2832
|
-
/**
|
|
2833
|
-
* A `Window`-like object containing the properties and types that DOMPurify requires.
|
|
2834
|
-
*/
|
|
2835
|
-
export type WindowLike = Pick<
|
|
2836
|
-
typeof globalThis,
|
|
2837
|
-
| 'DocumentFragment'
|
|
2838
|
-
| 'HTMLTemplateElement'
|
|
2839
|
-
| 'Node'
|
|
2840
|
-
| 'Element'
|
|
2841
|
-
| 'NodeFilter'
|
|
2842
|
-
| 'NamedNodeMap'
|
|
2843
|
-
| 'HTMLFormElement'
|
|
2844
|
-
| 'DOMParser'
|
|
2845
|
-
> & {
|
|
2846
|
-
document?: Document;
|
|
2847
|
-
MozNamedAttrMap?: typeof window.NamedNodeMap;
|
|
2848
|
-
} & Pick<TrustedTypesWindow, 'trustedTypes'>;
|