dompurify 3.4.9 → 3.4.11
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 +36 -13
- package/dist/purify.cjs.d.ts +3 -3
- package/dist/purify.cjs.js +359 -190
- package/dist/purify.cjs.js.map +1 -1
- package/dist/purify.cov.cjs.js +26427 -0
- package/dist/purify.cov.cjs.js.map +1 -0
- package/dist/purify.es.d.mts +3 -3
- package/dist/purify.es.mjs +359 -190
- package/dist/purify.es.mjs.map +1 -1
- package/dist/purify.js +359 -190
- 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 +23 -8
- package/src/config.ts +0 -2
- package/src/purify.ts +498 -656
- package/src/regexp.ts +8 -0
- package/src/types.ts +354 -0
package/src/purify.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/indent */
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
TrustedHTML,
|
|
5
|
-
TrustedTypesWindow,
|
|
6
|
-
} from 'trusted-types/lib/index.js';
|
|
7
1
|
import type { Config, UseProfilesConfig } from './config';
|
|
2
|
+
import type { DOMPurify, HooksMap, HookFunction, WindowLike } from './types';
|
|
8
3
|
import * as TAGS from './tags.js';
|
|
9
4
|
import * as ATTRS from './attrs.js';
|
|
10
5
|
import * as EXPRESSIONS from './regexp.js';
|
|
@@ -13,6 +8,7 @@ import {
|
|
|
13
8
|
clone,
|
|
14
9
|
entries,
|
|
15
10
|
freeze,
|
|
11
|
+
seal,
|
|
16
12
|
arrayForEach,
|
|
17
13
|
arrayIsArray,
|
|
18
14
|
arrayLastIndexOf,
|
|
@@ -36,6 +32,21 @@ import {
|
|
|
36
32
|
|
|
37
33
|
export type { Config } from './config';
|
|
38
34
|
|
|
35
|
+
export type {
|
|
36
|
+
DOMPurify,
|
|
37
|
+
RemovedElement,
|
|
38
|
+
RemovedAttribute,
|
|
39
|
+
HookName,
|
|
40
|
+
NodeHook,
|
|
41
|
+
ElementHook,
|
|
42
|
+
DocumentFragmentHook,
|
|
43
|
+
UponSanitizeElementHook,
|
|
44
|
+
UponSanitizeAttributeHook,
|
|
45
|
+
UponSanitizeElementHookEvent,
|
|
46
|
+
UponSanitizeAttributeHookEvent,
|
|
47
|
+
WindowLike,
|
|
48
|
+
} from './types';
|
|
49
|
+
|
|
39
50
|
declare const VERSION: string;
|
|
40
51
|
|
|
41
52
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
@@ -46,7 +57,7 @@ const NODE_TYPE = {
|
|
|
46
57
|
cdataSection: 4,
|
|
47
58
|
entityReference: 5, // Deprecated
|
|
48
59
|
entityNode: 6, // Deprecated
|
|
49
|
-
|
|
60
|
+
processingInstruction: 7,
|
|
50
61
|
comment: 8,
|
|
51
62
|
document: 9,
|
|
52
63
|
documentType: 10,
|
|
@@ -122,6 +133,36 @@ const _createHooksMap = function (): HooksMap {
|
|
|
122
133
|
};
|
|
123
134
|
};
|
|
124
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Resolve a set-valued configuration option: a fresh set built from
|
|
138
|
+
* cfg[key] when it is an own array property (seeded with a clone of
|
|
139
|
+
* options.base when given, case-normalized via options.transform),
|
|
140
|
+
* the fallback set otherwise.
|
|
141
|
+
*
|
|
142
|
+
* @param cfg the cloned, prototype-free configuration object
|
|
143
|
+
* @param key the configuration property to read
|
|
144
|
+
* @param fallback the set to use when the option is absent or not an array
|
|
145
|
+
* @param options transform and optional base set to merge into
|
|
146
|
+
* @returns the resolved set
|
|
147
|
+
*/
|
|
148
|
+
const _resolveSetOption = function (
|
|
149
|
+
cfg: Config,
|
|
150
|
+
key: keyof Config,
|
|
151
|
+
fallback: Record<string, boolean>,
|
|
152
|
+
options: {
|
|
153
|
+
transform: Parameters<typeof addToSet>[2];
|
|
154
|
+
base?: Record<string, boolean>;
|
|
155
|
+
}
|
|
156
|
+
): Record<string, boolean> {
|
|
157
|
+
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key])
|
|
158
|
+
? addToSet(
|
|
159
|
+
options.base ? clone(options.base) : {},
|
|
160
|
+
cfg[key] as readonly unknown[],
|
|
161
|
+
options.transform
|
|
162
|
+
)
|
|
163
|
+
: fallback;
|
|
164
|
+
};
|
|
165
|
+
|
|
125
166
|
function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
126
167
|
const DOMPurify: DOMPurify = (root: WindowLike) => createDOMPurify(root);
|
|
127
168
|
|
|
@@ -392,6 +433,14 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
392
433
|
/* Track whether config is already set on this instance of DOMPurify. */
|
|
393
434
|
let SET_CONFIG = false;
|
|
394
435
|
|
|
436
|
+
/* Pristine allowlist bindings captured at setConfig() time. On the
|
|
437
|
+
* persistent-config path sanitize() restores the sets from these before
|
|
438
|
+
* the per-walk hook clone-guard, so a hook's in-call widening cannot
|
|
439
|
+
* carry across calls. Null until setConfig() is called; reset by
|
|
440
|
+
* clearConfig(). */
|
|
441
|
+
let SET_CONFIG_ALLOWED_TAGS = null;
|
|
442
|
+
let SET_CONFIG_ALLOWED_ATTR = null;
|
|
443
|
+
|
|
395
444
|
/* Decide if all elements (e.g. style, script) must be children of
|
|
396
445
|
* document.body. By default, browsers might move them to document.head */
|
|
397
446
|
let FORCE_BODY = false;
|
|
@@ -526,15 +575,20 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
526
575
|
stringToString
|
|
527
576
|
);
|
|
528
577
|
|
|
529
|
-
|
|
578
|
+
const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze([
|
|
530
579
|
'mi',
|
|
531
580
|
'mo',
|
|
532
581
|
'mn',
|
|
533
582
|
'ms',
|
|
534
583
|
'mtext',
|
|
535
584
|
]);
|
|
585
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet(
|
|
586
|
+
{},
|
|
587
|
+
DEFAULT_MATHML_TEXT_INTEGRATION_POINTS
|
|
588
|
+
);
|
|
536
589
|
|
|
537
|
-
|
|
590
|
+
const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);
|
|
591
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
|
|
538
592
|
|
|
539
593
|
// Certain elements are allowed in both SVG and HTML
|
|
540
594
|
// namespace. We need to specify them explicitly
|
|
@@ -600,52 +654,48 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
600
654
|
: stringToLowerCase;
|
|
601
655
|
|
|
602
656
|
/* 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({});
|
|
657
|
+
ALLOWED_TAGS = _resolveSetOption(
|
|
658
|
+
cfg,
|
|
659
|
+
'ALLOWED_TAGS',
|
|
660
|
+
DEFAULT_ALLOWED_TAGS,
|
|
661
|
+
{ transform: transformCaseFunc }
|
|
662
|
+
);
|
|
663
|
+
ALLOWED_ATTR = _resolveSetOption(
|
|
664
|
+
cfg,
|
|
665
|
+
'ALLOWED_ATTR',
|
|
666
|
+
DEFAULT_ALLOWED_ATTR,
|
|
667
|
+
{ transform: transformCaseFunc }
|
|
668
|
+
);
|
|
669
|
+
ALLOWED_NAMESPACES = _resolveSetOption(
|
|
670
|
+
cfg,
|
|
671
|
+
'ALLOWED_NAMESPACES',
|
|
672
|
+
DEFAULT_ALLOWED_NAMESPACES,
|
|
673
|
+
{ transform: stringToString }
|
|
674
|
+
);
|
|
675
|
+
URI_SAFE_ATTRIBUTES = _resolveSetOption(
|
|
676
|
+
cfg,
|
|
677
|
+
'ADD_URI_SAFE_ATTR',
|
|
678
|
+
DEFAULT_URI_SAFE_ATTRIBUTES,
|
|
679
|
+
{ transform: transformCaseFunc, base: DEFAULT_URI_SAFE_ATTRIBUTES }
|
|
680
|
+
);
|
|
681
|
+
DATA_URI_TAGS = _resolveSetOption(
|
|
682
|
+
cfg,
|
|
683
|
+
'ADD_DATA_URI_TAGS',
|
|
684
|
+
DEFAULT_DATA_URI_TAGS,
|
|
685
|
+
{ transform: transformCaseFunc, base: DEFAULT_DATA_URI_TAGS }
|
|
686
|
+
);
|
|
687
|
+
FORBID_CONTENTS = _resolveSetOption(
|
|
688
|
+
cfg,
|
|
689
|
+
'FORBID_CONTENTS',
|
|
690
|
+
DEFAULT_FORBID_CONTENTS,
|
|
691
|
+
{ transform: transformCaseFunc }
|
|
692
|
+
);
|
|
693
|
+
FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {
|
|
694
|
+
transform: transformCaseFunc,
|
|
695
|
+
});
|
|
696
|
+
FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {
|
|
697
|
+
transform: transformCaseFunc,
|
|
698
|
+
});
|
|
649
699
|
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES')
|
|
650
700
|
? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object'
|
|
651
701
|
? clone(cfg.USE_PROFILES)
|
|
@@ -679,14 +729,14 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
679
729
|
cfg.MATHML_TEXT_INTEGRATION_POINTS &&
|
|
680
730
|
typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object'
|
|
681
731
|
? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS)
|
|
682
|
-
: addToSet({},
|
|
732
|
+
: addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS); // Default built-in map
|
|
683
733
|
|
|
684
734
|
HTML_INTEGRATION_POINTS =
|
|
685
735
|
objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') &&
|
|
686
736
|
cfg.HTML_INTEGRATION_POINTS &&
|
|
687
737
|
typeof cfg.HTML_INTEGRATION_POINTS === 'object'
|
|
688
738
|
? clone(cfg.HTML_INTEGRATION_POINTS)
|
|
689
|
-
: addToSet({},
|
|
739
|
+
: addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS); // Default built-in map
|
|
690
740
|
|
|
691
741
|
const customElementHandling =
|
|
692
742
|
objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') &&
|
|
@@ -723,6 +773,8 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
723
773
|
customElementHandling.allowCustomizedBuiltInElements; // Default undefined
|
|
724
774
|
}
|
|
725
775
|
|
|
776
|
+
seal(CUSTOM_ELEMENT_HANDLING);
|
|
777
|
+
|
|
726
778
|
if (SAFE_FOR_TEMPLATES) {
|
|
727
779
|
ALLOW_DATA_ATTR = false;
|
|
728
780
|
}
|
|
@@ -898,30 +950,6 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
898
950
|
}
|
|
899
951
|
}
|
|
900
952
|
|
|
901
|
-
/*
|
|
902
|
-
* Mirror the clone-before-mutate pattern already applied above for
|
|
903
|
-
* cfg.ADD_TAGS / cfg.ADD_ATTR: if any uponSanitize* hook is
|
|
904
|
-
* registered AND the set still points at the default constant,
|
|
905
|
-
* clone it. The hook then mutates the clone (in-call widening
|
|
906
|
-
* still works exactly as documented) and the next default-cfg
|
|
907
|
-
* call rebinds to the untouched original via the reassignment at
|
|
908
|
-
* the top of this function.
|
|
909
|
-
*/
|
|
910
|
-
if (
|
|
911
|
-
(hooks.uponSanitizeElement.length > 0 ||
|
|
912
|
-
hooks.uponSanitizeAttribute.length > 0) &&
|
|
913
|
-
ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS
|
|
914
|
-
) {
|
|
915
|
-
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
if (
|
|
919
|
-
hooks.uponSanitizeAttribute.length > 0 &&
|
|
920
|
-
ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR
|
|
921
|
-
) {
|
|
922
|
-
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
923
|
-
}
|
|
924
|
-
|
|
925
953
|
// Prevent further manipulation of configuration.
|
|
926
954
|
// Not available in IE8, Safari 5, etc.
|
|
927
955
|
if (freeze) {
|
|
@@ -944,6 +972,111 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
944
972
|
...TAGS.mathMlDisallowed,
|
|
945
973
|
]);
|
|
946
974
|
|
|
975
|
+
/**
|
|
976
|
+
* Namespace rules for an element in the SVG namespace.
|
|
977
|
+
*
|
|
978
|
+
* @param tagName the element's lowercase tag name
|
|
979
|
+
* @param parent the (possibly simulated) parent node
|
|
980
|
+
* @param parentTagName the parent's lowercase tag name
|
|
981
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
982
|
+
*/
|
|
983
|
+
const _checkSvgNamespace = function (
|
|
984
|
+
tagName: string,
|
|
985
|
+
parent: { namespaceURI?: string },
|
|
986
|
+
parentTagName: string
|
|
987
|
+
): boolean {
|
|
988
|
+
// The only way to switch from HTML namespace to SVG
|
|
989
|
+
// is via <svg>. If it happens via any other tag, then
|
|
990
|
+
// it should be killed.
|
|
991
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
992
|
+
return tagName === 'svg';
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// The only way to switch from MathML to SVG is via <svg>
|
|
996
|
+
// if the parent is either <annotation-xml> or a MathML
|
|
997
|
+
// text integration point.
|
|
998
|
+
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
999
|
+
return (
|
|
1000
|
+
tagName === 'svg' &&
|
|
1001
|
+
(parentTagName === 'annotation-xml' ||
|
|
1002
|
+
MATHML_TEXT_INTEGRATION_POINTS[parentTagName])
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// We only allow elements that are defined in SVG
|
|
1007
|
+
// spec. All others are disallowed in SVG namespace.
|
|
1008
|
+
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Namespace rules for an element in the MathML namespace.
|
|
1013
|
+
*
|
|
1014
|
+
* @param tagName the element's lowercase tag name
|
|
1015
|
+
* @param parent the (possibly simulated) parent node
|
|
1016
|
+
* @param parentTagName the parent's lowercase tag name
|
|
1017
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
1018
|
+
*/
|
|
1019
|
+
const _checkMathMlNamespace = function (
|
|
1020
|
+
tagName: string,
|
|
1021
|
+
parent: { namespaceURI?: string },
|
|
1022
|
+
parentTagName: string
|
|
1023
|
+
): boolean {
|
|
1024
|
+
// The only way to switch from HTML namespace to MathML
|
|
1025
|
+
// is via <math>. If it happens via any other tag, then
|
|
1026
|
+
// it should be killed.
|
|
1027
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
1028
|
+
return tagName === 'math';
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// The only way to switch from SVG to MathML is via
|
|
1032
|
+
// <math> and HTML integration points
|
|
1033
|
+
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
1034
|
+
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// We only allow elements that are defined in MathML
|
|
1038
|
+
// spec. All others are disallowed in MathML namespace.
|
|
1039
|
+
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Namespace rules for an element in the HTML namespace.
|
|
1044
|
+
*
|
|
1045
|
+
* @param tagName the element's lowercase tag name
|
|
1046
|
+
* @param parent the (possibly simulated) parent node
|
|
1047
|
+
* @param parentTagName the parent's lowercase tag name
|
|
1048
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
1049
|
+
*/
|
|
1050
|
+
const _checkHtmlNamespace = function (
|
|
1051
|
+
tagName: string,
|
|
1052
|
+
parent: { namespaceURI?: string },
|
|
1053
|
+
parentTagName: string
|
|
1054
|
+
): boolean {
|
|
1055
|
+
// The only way to switch from SVG to HTML is via
|
|
1056
|
+
// HTML integration points, and from MathML to HTML
|
|
1057
|
+
// is via MathML text integration points
|
|
1058
|
+
if (
|
|
1059
|
+
parent.namespaceURI === SVG_NAMESPACE &&
|
|
1060
|
+
!HTML_INTEGRATION_POINTS[parentTagName]
|
|
1061
|
+
) {
|
|
1062
|
+
return false;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
if (
|
|
1066
|
+
parent.namespaceURI === MATHML_NAMESPACE &&
|
|
1067
|
+
!MATHML_TEXT_INTEGRATION_POINTS[parentTagName]
|
|
1068
|
+
) {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// We disallow tags that are specific for MathML
|
|
1073
|
+
// or SVG and should never appear in HTML namespace
|
|
1074
|
+
return (
|
|
1075
|
+
!ALL_MATHML_TAGS[tagName] &&
|
|
1076
|
+
(COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName])
|
|
1077
|
+
);
|
|
1078
|
+
};
|
|
1079
|
+
|
|
947
1080
|
/**
|
|
948
1081
|
* @param element a DOM element whose namespace is being checked
|
|
949
1082
|
* @returns Return false if the element has a
|
|
@@ -970,72 +1103,15 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
970
1103
|
}
|
|
971
1104
|
|
|
972
1105
|
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]);
|
|
1106
|
+
return _checkSvgNamespace(tagName, parent, parentTagName);
|
|
994
1107
|
}
|
|
995
1108
|
|
|
996
1109
|
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]);
|
|
1110
|
+
return _checkMathMlNamespace(tagName, parent, parentTagName);
|
|
1013
1111
|
}
|
|
1014
1112
|
|
|
1015
1113
|
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
|
-
);
|
|
1114
|
+
return _checkHtmlNamespace(tagName, parent, parentTagName);
|
|
1039
1115
|
}
|
|
1040
1116
|
|
|
1041
1117
|
// For XHTML and XML documents that support custom namespaces
|
|
@@ -1112,9 +1188,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1112
1188
|
* @param root the in-place root to empty
|
|
1113
1189
|
*/
|
|
1114
1190
|
const _neutralizeRoot = function (root: Node): void {
|
|
1115
|
-
const childNodes = getChildNodes
|
|
1116
|
-
? getChildNodes(root)
|
|
1117
|
-
: (root as Element).childNodes;
|
|
1191
|
+
const childNodes = getChildNodes(root);
|
|
1118
1192
|
if (childNodes) {
|
|
1119
1193
|
const snapshot: Node[] = [];
|
|
1120
1194
|
arrayForEach(childNodes, (child) => {
|
|
@@ -1129,7 +1203,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1129
1203
|
});
|
|
1130
1204
|
}
|
|
1131
1205
|
|
|
1132
|
-
const attributes = getAttributes
|
|
1206
|
+
const attributes = getAttributes(root);
|
|
1133
1207
|
if (attributes) {
|
|
1134
1208
|
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
1135
1209
|
const attribute = attributes[i];
|
|
@@ -1191,9 +1265,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1191
1265
|
* @param element the element to strip
|
|
1192
1266
|
*/
|
|
1193
1267
|
const _stripDisallowedAttributes = function (element: Element): void {
|
|
1194
|
-
const attributes = getAttributes
|
|
1195
|
-
? getAttributes(element)
|
|
1196
|
-
: element.attributes;
|
|
1268
|
+
const attributes = getAttributes(element);
|
|
1197
1269
|
if (!attributes) {
|
|
1198
1270
|
return;
|
|
1199
1271
|
}
|
|
@@ -1246,9 +1318,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1246
1318
|
_stripDisallowedAttributes(node as Element);
|
|
1247
1319
|
}
|
|
1248
1320
|
|
|
1249
|
-
const childNodes = getChildNodes
|
|
1250
|
-
? getChildNodes(node)
|
|
1251
|
-
: (node as Element).childNodes;
|
|
1321
|
+
const childNodes = getChildNodes(node);
|
|
1252
1322
|
if (childNodes) {
|
|
1253
1323
|
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
1254
1324
|
stack.push(childNodes[i]);
|
|
@@ -1350,6 +1420,21 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1350
1420
|
);
|
|
1351
1421
|
};
|
|
1352
1422
|
|
|
1423
|
+
/**
|
|
1424
|
+
* Replace template expression syntax (mustache, ERB, template
|
|
1425
|
+
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
|
|
1426
|
+
* sites. Order matters: mustache, then ERB, then template literal.
|
|
1427
|
+
*
|
|
1428
|
+
* @param value the string to scrub
|
|
1429
|
+
* @returns the scrubbed string
|
|
1430
|
+
*/
|
|
1431
|
+
const _stripTemplateExpressions = function (value: string): string {
|
|
1432
|
+
value = stringReplace(value, MUSTACHE_EXPR, ' ');
|
|
1433
|
+
value = stringReplace(value, ERB_EXPR, ' ');
|
|
1434
|
+
value = stringReplace(value, TMPLIT_EXPR, ' ');
|
|
1435
|
+
return value;
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1353
1438
|
/**
|
|
1354
1439
|
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
1355
1440
|
* character data of an element subtree. Used as the final safety net for
|
|
@@ -1384,23 +1469,21 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1384
1469
|
|
|
1385
1470
|
let currentNode = walker.nextNode() as CharacterData | null;
|
|
1386
1471
|
while (currentNode) {
|
|
1387
|
-
|
|
1388
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr: RegExp) => {
|
|
1389
|
-
data = stringReplace(data, expr, ' ');
|
|
1390
|
-
});
|
|
1391
|
-
currentNode.data = data;
|
|
1472
|
+
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
1392
1473
|
currentNode = walker.nextNode() as CharacterData | null;
|
|
1393
1474
|
}
|
|
1394
1475
|
|
|
1395
1476
|
// NodeIterator does not descend into <template>.content per the DOM spec,
|
|
1396
1477
|
// so we must explicitly recurse into each template's content fragment,
|
|
1397
1478
|
// mirroring the approach used by _sanitizeShadowDOM.
|
|
1398
|
-
const templates = node.querySelectorAll?.('template')
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1479
|
+
const templates = node.querySelectorAll?.('template');
|
|
1480
|
+
if (templates) {
|
|
1481
|
+
arrayForEach(templates, (tmpl: HTMLTemplateElement) => {
|
|
1482
|
+
if (_isDocumentFragment(tmpl.content)) {
|
|
1483
|
+
_scrubTemplateExpressions(tmpl.content as unknown as Element);
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1404
1487
|
};
|
|
1405
1488
|
|
|
1406
1489
|
/**
|
|
@@ -1517,52 +1600,34 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1517
1600
|
currentNode: Parameters<T>[0],
|
|
1518
1601
|
data: Parameters<T>[1]
|
|
1519
1602
|
): void {
|
|
1603
|
+
if (hooks.length === 0) {
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1520
1607
|
arrayForEach(hooks, (hook: T) => {
|
|
1521
1608
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
1522
1609
|
});
|
|
1523
1610
|
}
|
|
1524
1611
|
|
|
1525
1612
|
/**
|
|
1526
|
-
*
|
|
1613
|
+
* Structural-threat checks that condemn a node regardless of the
|
|
1614
|
+
* allowlists: mXSS via namespace confusion, risky CSS construction,
|
|
1615
|
+
* processing instructions, markup-bearing comments. Pure predicate;
|
|
1616
|
+
* the caller removes. Check order is load-bearing.
|
|
1527
1617
|
*
|
|
1528
|
-
* @
|
|
1529
|
-
* @
|
|
1530
|
-
* @
|
|
1531
|
-
* @param currentNode to check for permission to exist
|
|
1532
|
-
* @return true if node was killed, false if left alive
|
|
1618
|
+
* @param currentNode the node to inspect
|
|
1619
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
1620
|
+
* @return true if the node must be removed
|
|
1533
1621
|
*/
|
|
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
|
-
|
|
1622
|
+
const _isUnsafeNode = function (currentNode: any, tagName: string): boolean {
|
|
1557
1623
|
/* Detect mXSS attempts abusing namespace confusion */
|
|
1558
1624
|
if (
|
|
1559
1625
|
SAFE_FOR_XML &&
|
|
1560
1626
|
currentNode.hasChildNodes() &&
|
|
1561
1627
|
!_isNode(currentNode.firstElementChild) &&
|
|
1562
|
-
regExpTest(
|
|
1563
|
-
regExpTest(
|
|
1628
|
+
regExpTest(EXPRESSIONS.ELEMENT_MARKUP_PROBE, currentNode.textContent) &&
|
|
1629
|
+
regExpTest(EXPRESSIONS.ELEMENT_MARKUP_PROBE, currentNode.innerHTML)
|
|
1564
1630
|
) {
|
|
1565
|
-
_forceRemove(currentNode);
|
|
1566
1631
|
return true;
|
|
1567
1632
|
}
|
|
1568
1633
|
|
|
@@ -1573,13 +1638,11 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1573
1638
|
tagName === 'style' &&
|
|
1574
1639
|
_isNode(currentNode.firstElementChild)
|
|
1575
1640
|
) {
|
|
1576
|
-
_forceRemove(currentNode);
|
|
1577
1641
|
return true;
|
|
1578
1642
|
}
|
|
1579
1643
|
|
|
1580
1644
|
/* Remove any occurrence of processing instructions */
|
|
1581
|
-
if (currentNode.nodeType === NODE_TYPE.
|
|
1582
|
-
_forceRemove(currentNode);
|
|
1645
|
+
if (currentNode.nodeType === NODE_TYPE.processingInstruction) {
|
|
1583
1646
|
return true;
|
|
1584
1647
|
}
|
|
1585
1648
|
|
|
@@ -1587,39 +1650,47 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1587
1650
|
if (
|
|
1588
1651
|
SAFE_FOR_XML &&
|
|
1589
1652
|
currentNode.nodeType === NODE_TYPE.comment &&
|
|
1590
|
-
regExpTest(
|
|
1653
|
+
regExpTest(EXPRESSIONS.COMMENT_MARKUP_PROBE, currentNode.data)
|
|
1591
1654
|
) {
|
|
1592
|
-
_forceRemove(currentNode);
|
|
1593
1655
|
return true;
|
|
1594
1656
|
}
|
|
1595
1657
|
|
|
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
|
-
}
|
|
1658
|
+
return false;
|
|
1659
|
+
};
|
|
1613
1660
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1661
|
+
/**
|
|
1662
|
+
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
1663
|
+
* allowed custom elements (false return exits _sanitizeElements
|
|
1664
|
+
* early - namespace/fallback checks and the afterSanitizeElements
|
|
1665
|
+
* hook are intentionally skipped for kept custom elements), else
|
|
1666
|
+
* hoist content per KEEP_CONTENT and remove.
|
|
1667
|
+
*
|
|
1668
|
+
* @param currentNode the disallowed node
|
|
1669
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
1670
|
+
* @return true if the node was removed, false if kept
|
|
1671
|
+
*/
|
|
1672
|
+
const _sanitizeDisallowedNode = function (
|
|
1673
|
+
currentNode: any,
|
|
1674
|
+
tagName: string
|
|
1675
|
+
): boolean {
|
|
1676
|
+
/* Check if we have a custom element to handle */
|
|
1677
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
1678
|
+
if (
|
|
1679
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&
|
|
1680
|
+
regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)
|
|
1681
|
+
) {
|
|
1682
|
+
return false;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
if (
|
|
1686
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&
|
|
1687
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)
|
|
1688
|
+
) {
|
|
1689
|
+
return false;
|
|
1620
1690
|
}
|
|
1691
|
+
}
|
|
1621
1692
|
|
|
1622
|
-
|
|
1693
|
+
/* Keep content except for bad-listed elements.
|
|
1623
1694
|
Use the cached prototype getters exclusively — the previous code
|
|
1624
1695
|
had `|| currentNode.parentNode` / `|| currentNode.childNodes`
|
|
1625
1696
|
fallbacks, but the cached getters always return the canonical
|
|
@@ -1627,14 +1698,14 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1627
1698
|
path was dead in safe cases and a clobbering surface in unsafe
|
|
1628
1699
|
ones. Falsy cached results stay falsy; the `if (childNodes &&
|
|
1629
1700
|
parentNode)` check already gates correctly. */
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1701
|
+
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
1702
|
+
const parentNode = getParentNode(currentNode);
|
|
1703
|
+
const childNodes = getChildNodes(currentNode);
|
|
1633
1704
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1705
|
+
if (childNodes && parentNode) {
|
|
1706
|
+
const childCount = childNodes.length;
|
|
1636
1707
|
|
|
1637
|
-
|
|
1708
|
+
/* In-place: hoist the *original* children so the iterator visits
|
|
1638
1709
|
and sanitises them through the same allowlist pass as every other
|
|
1639
1710
|
node. The caller built the tree in the live document, so the
|
|
1640
1711
|
originals carry already-queued resource events (`<img onerror>`,
|
|
@@ -1655,19 +1726,67 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1655
1726
|
`childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
|
|
1656
1727
|
valid whether we move (drops the trailing entry) or clone (leaves
|
|
1657
1728
|
the list intact). */
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
}
|
|
1729
|
+
for (let i = childCount - 1; i >= 0; --i) {
|
|
1730
|
+
const hoisted = IN_PLACE
|
|
1731
|
+
? childNodes[i]
|
|
1732
|
+
: cloneNode(childNodes[i], true);
|
|
1733
|
+
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
1664
1734
|
}
|
|
1665
1735
|
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
_forceRemove(currentNode);
|
|
1739
|
+
return true;
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
/**
|
|
1743
|
+
* _sanitizeElements
|
|
1744
|
+
*
|
|
1745
|
+
* @protect nodeName
|
|
1746
|
+
* @protect textContent
|
|
1747
|
+
* @protect removeChild
|
|
1748
|
+
* @param currentNode to check for permission to exist
|
|
1749
|
+
* @return true if node was killed, false if left alive
|
|
1750
|
+
*/
|
|
1751
|
+
const _sanitizeElements = function (currentNode: any): boolean {
|
|
1752
|
+
/* Execute a hook if present */
|
|
1753
|
+
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
1754
|
+
|
|
1755
|
+
/* Check if element is clobbered or can clobber */
|
|
1756
|
+
if (_isClobbered(currentNode)) {
|
|
1757
|
+
_forceRemove(currentNode);
|
|
1758
|
+
return true;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
/* Now let's check the element's type and name */
|
|
1762
|
+
const tagName = transformCaseFunc(
|
|
1763
|
+
getNodeName ? getNodeName(currentNode) : currentNode.nodeName
|
|
1764
|
+
);
|
|
1765
|
+
|
|
1766
|
+
/* Execute a hook if present */
|
|
1767
|
+
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
1768
|
+
tagName,
|
|
1769
|
+
allowedTags: ALLOWED_TAGS,
|
|
1770
|
+
});
|
|
1666
1771
|
|
|
1772
|
+
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
1773
|
+
if (_isUnsafeNode(currentNode, tagName)) {
|
|
1667
1774
|
_forceRemove(currentNode);
|
|
1668
1775
|
return true;
|
|
1669
1776
|
}
|
|
1670
1777
|
|
|
1778
|
+
/* Remove element if anything forbids its presence */
|
|
1779
|
+
if (
|
|
1780
|
+
FORBID_TAGS[tagName] ||
|
|
1781
|
+
(!(
|
|
1782
|
+
EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function &&
|
|
1783
|
+
EXTRA_ELEMENT_HANDLING.tagCheck(tagName)
|
|
1784
|
+
) &&
|
|
1785
|
+
!ALLOWED_TAGS[tagName])
|
|
1786
|
+
) {
|
|
1787
|
+
return _sanitizeDisallowedNode(currentNode, tagName);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1671
1790
|
/* Check whether element has a valid namespace.
|
|
1672
1791
|
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
1673
1792
|
nodeType getter rather than `instanceof Element`, which is realm-
|
|
@@ -1685,7 +1804,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1685
1804
|
(tagName === 'noscript' ||
|
|
1686
1805
|
tagName === 'noembed' ||
|
|
1687
1806
|
tagName === 'noframes') &&
|
|
1688
|
-
regExpTest(
|
|
1807
|
+
regExpTest(EXPRESSIONS.FALLBACK_TAG_CLOSE, currentNode.innerHTML)
|
|
1689
1808
|
) {
|
|
1690
1809
|
_forceRemove(currentNode);
|
|
1691
1810
|
return true;
|
|
@@ -1694,11 +1813,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1694
1813
|
/* Sanitize element content to be template-safe */
|
|
1695
1814
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1696
1815
|
/* 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
|
-
});
|
|
1816
|
+
const content = _stripTemplateExpressions(currentNode.textContent);
|
|
1702
1817
|
|
|
1703
1818
|
if (currentNode.textContent !== content) {
|
|
1704
1819
|
arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });
|
|
@@ -1749,16 +1864,12 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1749
1864
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1750
1865
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1751
1866
|
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
|
-
) {
|
|
1867
|
+
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR, lcName)) {
|
|
1757
1868
|
// This attribute is safe
|
|
1758
1869
|
} else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) {
|
|
1759
1870
|
// This attribute is safe
|
|
1760
1871
|
/* Otherwise, check the name is permitted */
|
|
1761
|
-
} else if (!nameIsPermitted
|
|
1872
|
+
} else if (!nameIsPermitted) {
|
|
1762
1873
|
if (
|
|
1763
1874
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1764
1875
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -1852,6 +1963,85 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1852
1963
|
);
|
|
1853
1964
|
};
|
|
1854
1965
|
|
|
1966
|
+
/**
|
|
1967
|
+
* Wrap an attribute value in the matching Trusted Types object when
|
|
1968
|
+
* the active policy requires it. Namespaced attributes pass through
|
|
1969
|
+
* unchanged (no TT support yet, see
|
|
1970
|
+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
|
|
1971
|
+
*
|
|
1972
|
+
* @param lcTag lowercase tag name of the containing element
|
|
1973
|
+
* @param lcName lowercase attribute name
|
|
1974
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
1975
|
+
* @param value the attribute value to wrap
|
|
1976
|
+
* @return the value, wrapped when Trusted Types demand it
|
|
1977
|
+
*/
|
|
1978
|
+
const _applyTrustedTypesToAttribute = function (
|
|
1979
|
+
lcTag: string,
|
|
1980
|
+
lcName: string,
|
|
1981
|
+
namespaceURI: string | null,
|
|
1982
|
+
value: string
|
|
1983
|
+
): string {
|
|
1984
|
+
if (
|
|
1985
|
+
trustedTypesPolicy &&
|
|
1986
|
+
typeof trustedTypes === 'object' &&
|
|
1987
|
+
typeof trustedTypes.getAttributeType === 'function' &&
|
|
1988
|
+
!namespaceURI
|
|
1989
|
+
) {
|
|
1990
|
+
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
1991
|
+
case 'TrustedHTML': {
|
|
1992
|
+
return _createTrustedHTML(value);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
case 'TrustedScriptURL': {
|
|
1996
|
+
return _createTrustedScriptURL(value);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
default: {
|
|
2000
|
+
break;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
return value;
|
|
2006
|
+
};
|
|
2007
|
+
|
|
2008
|
+
/**
|
|
2009
|
+
* Write a modified attribute value back onto the element. On
|
|
2010
|
+
* success, re-probe for clobbering introduced by the new value and
|
|
2011
|
+
* remove the element when found; otherwise pop the removal entry
|
|
2012
|
+
* recorded by the earlier _removeAttribute (long-standing pairing
|
|
2013
|
+
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
|
|
2014
|
+
* failure, remove the attribute instead.
|
|
2015
|
+
*
|
|
2016
|
+
* @param currentNode the element carrying the attribute
|
|
2017
|
+
* @param name the attribute name as present on the element
|
|
2018
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
2019
|
+
* @param value the new attribute value
|
|
2020
|
+
*/
|
|
2021
|
+
const _setAttributeValue = function (
|
|
2022
|
+
currentNode: Element,
|
|
2023
|
+
name: string,
|
|
2024
|
+
namespaceURI: string | null,
|
|
2025
|
+
value: string
|
|
2026
|
+
): void {
|
|
2027
|
+
try {
|
|
2028
|
+
if (namespaceURI) {
|
|
2029
|
+
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
2030
|
+
} else {
|
|
2031
|
+
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
2032
|
+
currentNode.setAttribute(name, value);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
if (_isClobbered(currentNode)) {
|
|
2036
|
+
_forceRemove(currentNode);
|
|
2037
|
+
} else {
|
|
2038
|
+
arrayPop(DOMPurify.removed);
|
|
2039
|
+
}
|
|
2040
|
+
} catch (_) {
|
|
2041
|
+
_removeAttribute(name, currentNode);
|
|
2042
|
+
}
|
|
2043
|
+
};
|
|
2044
|
+
|
|
1855
2045
|
/**
|
|
1856
2046
|
* _sanitizeAttributes
|
|
1857
2047
|
*
|
|
@@ -1881,6 +2071,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1881
2071
|
forceKeepAttr: undefined,
|
|
1882
2072
|
};
|
|
1883
2073
|
let l = attributes.length;
|
|
2074
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1884
2075
|
|
|
1885
2076
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1886
2077
|
while (l--) {
|
|
@@ -1933,7 +2124,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1933
2124
|
continue;
|
|
1934
2125
|
}
|
|
1935
2126
|
|
|
1936
|
-
/* Did the hooks
|
|
2127
|
+
/* Did the hooks force-keep the attribute? */
|
|
1937
2128
|
if (hookEvent.forceKeepAttr) {
|
|
1938
2129
|
continue;
|
|
1939
2130
|
}
|
|
@@ -1945,70 +2136,31 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
1945
2136
|
}
|
|
1946
2137
|
|
|
1947
2138
|
/* Work around a security issue in jQuery 3.0 */
|
|
1948
|
-
if (
|
|
2139
|
+
if (
|
|
2140
|
+
!ALLOW_SELF_CLOSE_IN_ATTR &&
|
|
2141
|
+
regExpTest(EXPRESSIONS.SELF_CLOSING_TAG, value)
|
|
2142
|
+
) {
|
|
1949
2143
|
_removeAttribute(name, currentNode);
|
|
1950
2144
|
continue;
|
|
1951
2145
|
}
|
|
1952
2146
|
|
|
1953
2147
|
/* Sanitize attribute content to be template-safe */
|
|
1954
2148
|
if (SAFE_FOR_TEMPLATES) {
|
|
1955
|
-
|
|
1956
|
-
value = stringReplace(value, expr, ' ');
|
|
1957
|
-
});
|
|
2149
|
+
value = _stripTemplateExpressions(value);
|
|
1958
2150
|
}
|
|
1959
2151
|
|
|
1960
2152
|
/* Is `value` valid for this attribute? */
|
|
1961
|
-
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1962
2153
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
1963
2154
|
_removeAttribute(name, currentNode);
|
|
1964
2155
|
continue;
|
|
1965
2156
|
}
|
|
1966
2157
|
|
|
1967
2158
|
/* 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
|
-
}
|
|
2159
|
+
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
1993
2160
|
|
|
1994
2161
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
1995
2162
|
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
|
-
}
|
|
2163
|
+
_setAttributeValue(currentNode, name, namespaceURI, value);
|
|
2012
2164
|
}
|
|
2013
2165
|
}
|
|
2014
2166
|
|
|
@@ -2060,9 +2212,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2060
2212
|
? getNodeType(shadowNode)
|
|
2061
2213
|
: shadowNode.nodeType;
|
|
2062
2214
|
if (shadowNodeType === NODE_TYPE.element) {
|
|
2063
|
-
const innerSr = getShadowRoot
|
|
2064
|
-
? getShadowRoot(shadowNode)
|
|
2065
|
-
: (shadowNode as Element).shadowRoot;
|
|
2215
|
+
const innerSr = getShadowRoot(shadowNode);
|
|
2066
2216
|
if (_isDocumentFragment(innerSr)) {
|
|
2067
2217
|
_sanitizeAttachedShadowRoots(innerSr);
|
|
2068
2218
|
_sanitizeShadowDOM(innerSr);
|
|
@@ -2129,9 +2279,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2129
2279
|
/* (pushed last → processed first) Children, snapshotted in reverse so
|
|
2130
2280
|
the first child is processed first. Snapshotting matters because a
|
|
2131
2281
|
hook may detach siblings mid-walk. */
|
|
2132
|
-
const childNodes = getChildNodes
|
|
2133
|
-
? getChildNodes(node)
|
|
2134
|
-
: (node as Element).childNodes;
|
|
2282
|
+
const childNodes = getChildNodes(node);
|
|
2135
2283
|
if (childNodes) {
|
|
2136
2284
|
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
2137
2285
|
stack.push({ node: childNodes[i], shadow: null });
|
|
@@ -2160,9 +2308,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2160
2308
|
silently skipped foreign-realm shadow roots (e.g.
|
|
2161
2309
|
iframe.contentDocument attachShadow). */
|
|
2162
2310
|
if (isElement) {
|
|
2163
|
-
const sr = getShadowRoot
|
|
2164
|
-
? getShadowRoot(node)
|
|
2165
|
-
: (node as Element).shadowRoot;
|
|
2311
|
+
const sr = getShadowRoot(node);
|
|
2166
2312
|
if (_isDocumentFragment(sr)) {
|
|
2167
2313
|
/* Push the deferred sanitise first so it pops after the shadow
|
|
2168
2314
|
walk we push next, i.e. nested shadow roots are discovered
|
|
@@ -2202,10 +2348,37 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2202
2348
|
}
|
|
2203
2349
|
|
|
2204
2350
|
/* Assign config vars */
|
|
2205
|
-
if (
|
|
2351
|
+
if (SET_CONFIG) {
|
|
2352
|
+
/* Persistent setConfig() path: _parseConfig is skipped, so the sets are
|
|
2353
|
+
* not re-derived per call. Restore them from the pristine bindings
|
|
2354
|
+
* captured at setConfig() time so a previous call's hook clone (mutated
|
|
2355
|
+
* below) does not carry over. */
|
|
2356
|
+
ALLOWED_TAGS = SET_CONFIG_ALLOWED_TAGS;
|
|
2357
|
+
ALLOWED_ATTR = SET_CONFIG_ALLOWED_ATTR;
|
|
2358
|
+
} else {
|
|
2206
2359
|
_parseConfig(cfg);
|
|
2207
2360
|
}
|
|
2208
2361
|
|
|
2362
|
+
/* Clone the hook-mutable allowlists before the walk whenever an
|
|
2363
|
+
* uponSanitize* hook is registered. The hook event exposes ALLOWED_TAGS
|
|
2364
|
+
* and ALLOWED_ATTR by reference (as allowedTags / allowedAttributes), so
|
|
2365
|
+
* a hook that widens them would otherwise mutate the shared set
|
|
2366
|
+
* permanently: across later calls and across every element. Cloning per
|
|
2367
|
+
* walk keeps documented in-call widening working while scoping it to the
|
|
2368
|
+
* call. A single guard for both config paths - the per-call path rebinds
|
|
2369
|
+
* the sets in _parseConfig each call, the persistent path restores them
|
|
2370
|
+
* from the captured bindings just above - so the two cannot diverge. */
|
|
2371
|
+
if (
|
|
2372
|
+
hooks.uponSanitizeElement.length > 0 ||
|
|
2373
|
+
hooks.uponSanitizeAttribute.length > 0
|
|
2374
|
+
) {
|
|
2375
|
+
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
if (hooks.uponSanitizeAttribute.length > 0) {
|
|
2379
|
+
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2209
2382
|
/* Clean up removed elements */
|
|
2210
2383
|
DOMPurify.removed = [];
|
|
2211
2384
|
|
|
@@ -2421,9 +2594,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2421
2594
|
|
|
2422
2595
|
/* Sanitize final string template-safe */
|
|
2423
2596
|
if (SAFE_FOR_TEMPLATES) {
|
|
2424
|
-
|
|
2425
|
-
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
2426
|
-
});
|
|
2597
|
+
serializedHTML = _stripTemplateExpressions(serializedHTML);
|
|
2427
2598
|
}
|
|
2428
2599
|
|
|
2429
2600
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE
|
|
@@ -2434,11 +2605,15 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2434
2605
|
DOMPurify.setConfig = function (cfg = {}) {
|
|
2435
2606
|
_parseConfig(cfg);
|
|
2436
2607
|
SET_CONFIG = true;
|
|
2608
|
+
SET_CONFIG_ALLOWED_TAGS = ALLOWED_TAGS;
|
|
2609
|
+
SET_CONFIG_ALLOWED_ATTR = ALLOWED_ATTR;
|
|
2437
2610
|
};
|
|
2438
2611
|
|
|
2439
2612
|
DOMPurify.clearConfig = function () {
|
|
2440
2613
|
CONFIG = null;
|
|
2441
2614
|
SET_CONFIG = false;
|
|
2615
|
+
SET_CONFIG_ALLOWED_TAGS = null;
|
|
2616
|
+
SET_CONFIG_ALLOWED_ATTR = null;
|
|
2442
2617
|
|
|
2443
2618
|
// Drop any caller-supplied Trusted Types policy so it cannot poison later
|
|
2444
2619
|
// `RETURN_TRUSTED_TYPE` output. The internal default policy (cached, and
|
|
@@ -2467,6 +2642,14 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2467
2642
|
return;
|
|
2468
2643
|
}
|
|
2469
2644
|
|
|
2645
|
+
/* Reject unknown entry points. Without this, a non-hook key (e.g.
|
|
2646
|
+
* '__proto__') indexes off the prototype chain rather than a real
|
|
2647
|
+
* hook array, and arrayPush then writes to Object.prototype. Guard
|
|
2648
|
+
* with an own-property check against the known hook names. */
|
|
2649
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2470
2653
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
2471
2654
|
};
|
|
2472
2655
|
|
|
@@ -2474,6 +2657,10 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2474
2657
|
entryPoint: keyof HooksMap,
|
|
2475
2658
|
hookFunction: HookFunction
|
|
2476
2659
|
) {
|
|
2660
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
2661
|
+
return undefined;
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2477
2664
|
if (hookFunction !== undefined) {
|
|
2478
2665
|
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
|
|
2479
2666
|
|
|
@@ -2486,6 +2673,10 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2486
2673
|
};
|
|
2487
2674
|
|
|
2488
2675
|
DOMPurify.removeHooks = function (entryPoint: keyof HooksMap) {
|
|
2676
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2489
2680
|
hooks[entryPoint] = [];
|
|
2490
2681
|
};
|
|
2491
2682
|
|
|
@@ -2497,352 +2688,3 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
|
|
|
2497
2688
|
}
|
|
2498
2689
|
|
|
2499
2690
|
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'>;
|