dompurify 3.0.5 → 3.0.7
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 +63 -70
- package/dist/purify.cjs.js +318 -441
- package/dist/purify.cjs.js.map +1 -1
- package/dist/{purify.es.js → purify.es.mjs} +319 -442
- package/dist/purify.es.mjs.map +1 -0
- package/dist/purify.js +318 -441
- 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 +17 -9
- package/dist/purify.es.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.0.
|
|
1
|
+
/*! @license DOMPurify 3.0.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.0.7/LICENSE */
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
entries,
|
|
@@ -12,36 +12,30 @@ let {
|
|
|
12
12
|
seal,
|
|
13
13
|
create
|
|
14
14
|
} = Object; // eslint-disable-line import/no-mutable-exports
|
|
15
|
-
|
|
16
15
|
let {
|
|
17
16
|
apply,
|
|
18
17
|
construct
|
|
19
18
|
} = typeof Reflect !== 'undefined' && Reflect;
|
|
20
|
-
|
|
21
|
-
if (!apply) {
|
|
22
|
-
apply = function apply(fun, thisValue, args) {
|
|
23
|
-
return fun.apply(thisValue, args);
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
19
|
if (!freeze) {
|
|
28
20
|
freeze = function freeze(x) {
|
|
29
21
|
return x;
|
|
30
22
|
};
|
|
31
23
|
}
|
|
32
|
-
|
|
33
24
|
if (!seal) {
|
|
34
25
|
seal = function seal(x) {
|
|
35
26
|
return x;
|
|
36
27
|
};
|
|
37
28
|
}
|
|
38
|
-
|
|
29
|
+
if (!apply) {
|
|
30
|
+
apply = function apply(fun, thisValue, args) {
|
|
31
|
+
return fun.apply(thisValue, args);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
39
34
|
if (!construct) {
|
|
40
35
|
construct = function construct(Func, args) {
|
|
41
36
|
return new Func(...args);
|
|
42
37
|
};
|
|
43
38
|
}
|
|
44
|
-
|
|
45
39
|
const arrayForEach = unapply(Array.prototype.forEach);
|
|
46
40
|
const arrayPop = unapply(Array.prototype.pop);
|
|
47
41
|
const arrayPush = unapply(Array.prototype.push);
|
|
@@ -53,112 +47,150 @@ const stringIndexOf = unapply(String.prototype.indexOf);
|
|
|
53
47
|
const stringTrim = unapply(String.prototype.trim);
|
|
54
48
|
const regExpTest = unapply(RegExp.prototype.test);
|
|
55
49
|
const typeErrorCreate = unconstruct(TypeError);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a new function that calls the given function with a specified thisArg and arguments.
|
|
53
|
+
*
|
|
54
|
+
* @param {Function} func - The function to be wrapped and called.
|
|
55
|
+
* @returns {Function} A new function that calls the given function with a specified thisArg and arguments.
|
|
56
|
+
*/
|
|
56
57
|
function unapply(func) {
|
|
57
58
|
return function (thisArg) {
|
|
58
59
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
59
60
|
args[_key - 1] = arguments[_key];
|
|
60
61
|
}
|
|
61
|
-
|
|
62
62
|
return apply(func, thisArg, args);
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates a new function that constructs an instance of the given constructor function with the provided arguments.
|
|
68
|
+
*
|
|
69
|
+
* @param {Function} func - The constructor function to be wrapped and called.
|
|
70
|
+
* @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments.
|
|
71
|
+
*/
|
|
65
72
|
function unconstruct(func) {
|
|
66
73
|
return function () {
|
|
67
74
|
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
|
68
75
|
args[_key2] = arguments[_key2];
|
|
69
76
|
}
|
|
70
|
-
|
|
71
77
|
return construct(func, args);
|
|
72
78
|
};
|
|
73
79
|
}
|
|
74
|
-
/* Add properties to a lookup table */
|
|
75
|
-
|
|
76
|
-
function addToSet(set, array, transformCaseFunc) {
|
|
77
|
-
var _transformCaseFunc;
|
|
78
|
-
|
|
79
|
-
transformCaseFunc = (_transformCaseFunc = transformCaseFunc) !== null && _transformCaseFunc !== void 0 ? _transformCaseFunc : stringToLowerCase;
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Add properties to a lookup table
|
|
83
|
+
*
|
|
84
|
+
* @param {Object} set - The set to which elements will be added.
|
|
85
|
+
* @param {Array} array - The array containing elements to be added to the set.
|
|
86
|
+
* @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set.
|
|
87
|
+
* @returns {Object} The modified set with added elements.
|
|
88
|
+
*/
|
|
89
|
+
function addToSet(set, array) {
|
|
90
|
+
let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
|
|
81
91
|
if (setPrototypeOf) {
|
|
82
92
|
// Make 'in' and truthy checks like Boolean(set.constructor)
|
|
83
93
|
// independent of any properties defined on Object.prototype.
|
|
84
94
|
// Prevent prototype setters from intercepting set as a this value.
|
|
85
95
|
setPrototypeOf(set, null);
|
|
86
96
|
}
|
|
87
|
-
|
|
88
97
|
let l = array.length;
|
|
89
|
-
|
|
90
98
|
while (l--) {
|
|
91
99
|
let element = array[l];
|
|
92
|
-
|
|
93
100
|
if (typeof element === 'string') {
|
|
94
101
|
const lcElement = transformCaseFunc(element);
|
|
95
|
-
|
|
96
102
|
if (lcElement !== element) {
|
|
97
103
|
// Config presets (e.g. tags.js, attrs.js) are immutable.
|
|
98
104
|
if (!isFrozen(array)) {
|
|
99
105
|
array[l] = lcElement;
|
|
100
106
|
}
|
|
101
|
-
|
|
102
107
|
element = lcElement;
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
|
-
|
|
106
110
|
set[element] = true;
|
|
107
111
|
}
|
|
108
|
-
|
|
109
112
|
return set;
|
|
110
113
|
}
|
|
111
|
-
/* Shallow clone an object */
|
|
112
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Clean up an array to harden against CSPP
|
|
117
|
+
*
|
|
118
|
+
* @param {Array} array - The array to be cleaned.
|
|
119
|
+
* @returns {Array} The cleaned version of the array
|
|
120
|
+
*/
|
|
121
|
+
function cleanArray(array) {
|
|
122
|
+
for (let index = 0; index < array.length; index++) {
|
|
123
|
+
if (getOwnPropertyDescriptor(array, index) === undefined) {
|
|
124
|
+
array[index] = null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return array;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Shallow clone an object
|
|
132
|
+
*
|
|
133
|
+
* @param {Object} object - The object to be cloned.
|
|
134
|
+
* @returns {Object} A new object that copies the original.
|
|
135
|
+
*/
|
|
113
136
|
function clone(object) {
|
|
114
137
|
const newObject = create(null);
|
|
115
|
-
|
|
116
138
|
for (const [property, value] of entries(object)) {
|
|
117
|
-
|
|
139
|
+
if (getOwnPropertyDescriptor(object, property) !== undefined) {
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
newObject[property] = cleanArray(value);
|
|
142
|
+
} else if (typeof value === 'object' && value.constructor === Object) {
|
|
143
|
+
newObject[property] = clone(value);
|
|
144
|
+
} else {
|
|
145
|
+
newObject[property] = value;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
118
148
|
}
|
|
119
|
-
|
|
120
149
|
return newObject;
|
|
121
150
|
}
|
|
122
|
-
/* This method automatically checks if the prop is function
|
|
123
|
-
* or getter and behaves accordingly. */
|
|
124
151
|
|
|
152
|
+
/**
|
|
153
|
+
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
154
|
+
*
|
|
155
|
+
* @param {Object} object - The object to look up the getter function in its prototype chain.
|
|
156
|
+
* @param {String} prop - The property name for which to find the getter function.
|
|
157
|
+
* @returns {Function} The getter function found in the prototype chain or a fallback function.
|
|
158
|
+
*/
|
|
125
159
|
function lookupGetter(object, prop) {
|
|
126
160
|
while (object !== null) {
|
|
127
161
|
const desc = getOwnPropertyDescriptor(object, prop);
|
|
128
|
-
|
|
129
162
|
if (desc) {
|
|
130
163
|
if (desc.get) {
|
|
131
164
|
return unapply(desc.get);
|
|
132
165
|
}
|
|
133
|
-
|
|
134
166
|
if (typeof desc.value === 'function') {
|
|
135
167
|
return unapply(desc.value);
|
|
136
168
|
}
|
|
137
169
|
}
|
|
138
|
-
|
|
139
170
|
object = getPrototypeOf(object);
|
|
140
171
|
}
|
|
141
|
-
|
|
142
172
|
function fallbackValue(element) {
|
|
143
173
|
console.warn('fallback value for', element);
|
|
144
174
|
return null;
|
|
145
175
|
}
|
|
146
|
-
|
|
147
176
|
return fallbackValue;
|
|
148
177
|
}
|
|
149
178
|
|
|
150
|
-
const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
|
|
179
|
+
const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
|
|
151
180
|
|
|
181
|
+
// SVG
|
|
152
182
|
const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
|
|
153
|
-
const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
|
|
183
|
+
const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
|
|
184
|
+
|
|
185
|
+
// List of SVG elements that are disallowed by default.
|
|
154
186
|
// We still need to know them so that we can do namespace
|
|
155
187
|
// checks properly in case one wants to add them to
|
|
156
188
|
// allow-list.
|
|
157
|
-
|
|
158
189
|
const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
|
|
159
|
-
const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);
|
|
160
|
-
// even those that we disallow by default.
|
|
190
|
+
const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);
|
|
161
191
|
|
|
192
|
+
// Similarly to SVG, we want to know all MathML elements,
|
|
193
|
+
// even those that we disallow by default.
|
|
162
194
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
163
195
|
const text = freeze(['#text']);
|
|
164
196
|
|
|
@@ -167,19 +199,19 @@ const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseli
|
|
|
167
199
|
const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
|
|
168
200
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
169
201
|
|
|
202
|
+
// eslint-disable-next-line unicorn/better-regex
|
|
170
203
|
const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
|
|
171
|
-
|
|
172
204
|
const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
|
|
173
205
|
const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
|
|
174
206
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
|
|
175
|
-
|
|
176
207
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
177
|
-
|
|
178
208
|
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
|
179
209
|
);
|
|
210
|
+
|
|
180
211
|
const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
|
|
181
212
|
const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
|
|
182
213
|
);
|
|
214
|
+
|
|
183
215
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
184
216
|
|
|
185
217
|
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
@@ -195,44 +227,40 @@ var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
|
195
227
|
DOCTYPE_NAME: DOCTYPE_NAME
|
|
196
228
|
});
|
|
197
229
|
|
|
198
|
-
const getGlobal = ()
|
|
230
|
+
const getGlobal = function getGlobal() {
|
|
231
|
+
return typeof window === 'undefined' ? null : window;
|
|
232
|
+
};
|
|
233
|
+
|
|
199
234
|
/**
|
|
200
235
|
* Creates a no-op policy for internal use only.
|
|
201
236
|
* Don't export this function outside this module!
|
|
202
|
-
* @param {
|
|
237
|
+
* @param {TrustedTypePolicyFactory} trustedTypes The policy factory.
|
|
203
238
|
* @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
|
|
204
|
-
* @return {
|
|
239
|
+
* @return {TrustedTypePolicy} The policy created (or null, if Trusted Types
|
|
205
240
|
* are not supported or creating the policy failed).
|
|
206
241
|
*/
|
|
207
|
-
|
|
208
|
-
|
|
209
242
|
const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
|
|
210
243
|
if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
|
|
211
244
|
return null;
|
|
212
|
-
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Allow the callers to control the unique policy name
|
|
213
248
|
// by adding a data-tt-policy-suffix to the script element with the DOMPurify.
|
|
214
249
|
// Policy creation with duplicate names throws in Trusted Types.
|
|
215
|
-
|
|
216
|
-
|
|
217
250
|
let suffix = null;
|
|
218
251
|
const ATTR_NAME = 'data-tt-policy-suffix';
|
|
219
|
-
|
|
220
252
|
if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
|
|
221
253
|
suffix = purifyHostElement.getAttribute(ATTR_NAME);
|
|
222
254
|
}
|
|
223
|
-
|
|
224
255
|
const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
|
|
225
|
-
|
|
226
256
|
try {
|
|
227
257
|
return trustedTypes.createPolicy(policyName, {
|
|
228
258
|
createHTML(html) {
|
|
229
259
|
return html;
|
|
230
260
|
},
|
|
231
|
-
|
|
232
261
|
createScriptURL(scriptUrl) {
|
|
233
262
|
return scriptUrl;
|
|
234
263
|
}
|
|
235
|
-
|
|
236
264
|
});
|
|
237
265
|
} catch (_) {
|
|
238
266
|
// Policy creation failed (most likely another DOMPurify script has
|
|
@@ -242,37 +270,32 @@ const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedType
|
|
|
242
270
|
return null;
|
|
243
271
|
}
|
|
244
272
|
};
|
|
245
|
-
|
|
246
273
|
function createDOMPurify() {
|
|
247
274
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
248
|
-
|
|
249
275
|
const DOMPurify = root => createDOMPurify(root);
|
|
276
|
+
|
|
250
277
|
/**
|
|
251
278
|
* Version label, exposed for easier checks
|
|
252
279
|
* if DOMPurify is up to date or not
|
|
253
280
|
*/
|
|
281
|
+
DOMPurify.version = '3.0.7';
|
|
254
282
|
|
|
255
|
-
|
|
256
|
-
DOMPurify.version = '3.0.5';
|
|
257
283
|
/**
|
|
258
284
|
* Array of elements that DOMPurify removed during sanitation.
|
|
259
285
|
* Empty if nothing was removed.
|
|
260
286
|
*/
|
|
261
|
-
|
|
262
287
|
DOMPurify.removed = [];
|
|
263
|
-
|
|
264
288
|
if (!window || !window.document || window.document.nodeType !== 9) {
|
|
265
289
|
// Not running in a browser, provide a factory function
|
|
266
290
|
// so that you can pass your own Window
|
|
267
291
|
DOMPurify.isSupported = false;
|
|
268
292
|
return DOMPurify;
|
|
269
293
|
}
|
|
270
|
-
|
|
271
|
-
const originalDocument = window.document;
|
|
272
|
-
const currentScript = originalDocument.currentScript;
|
|
273
294
|
let {
|
|
274
295
|
document
|
|
275
296
|
} = window;
|
|
297
|
+
const originalDocument = document;
|
|
298
|
+
const currentScript = originalDocument.currentScript;
|
|
276
299
|
const {
|
|
277
300
|
DocumentFragment,
|
|
278
301
|
HTMLTemplateElement,
|
|
@@ -288,21 +311,20 @@ function createDOMPurify() {
|
|
|
288
311
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
289
312
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
290
313
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
291
|
-
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
314
|
+
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
315
|
+
|
|
316
|
+
// As per issue #47, the web-components registry is inherited by a
|
|
292
317
|
// new document created via createHTMLDocument. As per the spec
|
|
293
318
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
294
319
|
// a new empty registry is used when creating a template contents owner
|
|
295
320
|
// document, so we use that as our parent document to ensure nothing
|
|
296
321
|
// is inherited.
|
|
297
|
-
|
|
298
322
|
if (typeof HTMLTemplateElement === 'function') {
|
|
299
323
|
const template = document.createElement('template');
|
|
300
|
-
|
|
301
324
|
if (template.content && template.content.ownerDocument) {
|
|
302
325
|
document = template.content.ownerDocument;
|
|
303
326
|
}
|
|
304
327
|
}
|
|
305
|
-
|
|
306
328
|
let trustedTypesPolicy;
|
|
307
329
|
let emptyHTML = '';
|
|
308
330
|
const {
|
|
@@ -315,10 +337,10 @@ function createDOMPurify() {
|
|
|
315
337
|
importNode
|
|
316
338
|
} = originalDocument;
|
|
317
339
|
let hooks = {};
|
|
340
|
+
|
|
318
341
|
/**
|
|
319
342
|
* Expose whether this browser supports running the full DOMPurify.
|
|
320
343
|
*/
|
|
321
|
-
|
|
322
344
|
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
323
345
|
const {
|
|
324
346
|
MUSTACHE_EXPR,
|
|
@@ -332,27 +354,27 @@ function createDOMPurify() {
|
|
|
332
354
|
let {
|
|
333
355
|
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
334
356
|
} = EXPRESSIONS;
|
|
357
|
+
|
|
335
358
|
/**
|
|
336
359
|
* We consider the elements and attributes below to be safe. Ideally
|
|
337
360
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
338
361
|
*/
|
|
339
362
|
|
|
340
363
|
/* allowed element names */
|
|
341
|
-
|
|
342
364
|
let ALLOWED_TAGS = null;
|
|
343
365
|
const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
|
|
344
|
-
/* Allowed attribute names */
|
|
345
366
|
|
|
367
|
+
/* Allowed attribute names */
|
|
346
368
|
let ALLOWED_ATTR = null;
|
|
347
369
|
const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
|
|
370
|
+
|
|
348
371
|
/*
|
|
349
372
|
* Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
|
|
350
373
|
* @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
|
|
351
374
|
* @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
|
|
352
375
|
* @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
|
|
353
376
|
*/
|
|
354
|
-
|
|
355
|
-
let CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
|
|
377
|
+
let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
|
|
356
378
|
tagNameCheck: {
|
|
357
379
|
writable: true,
|
|
358
380
|
configurable: false,
|
|
@@ -372,59 +394,60 @@ function createDOMPurify() {
|
|
|
372
394
|
value: false
|
|
373
395
|
}
|
|
374
396
|
}));
|
|
375
|
-
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
|
|
376
397
|
|
|
398
|
+
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
|
|
377
399
|
let FORBID_TAGS = null;
|
|
378
|
-
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
|
|
379
400
|
|
|
401
|
+
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
|
|
380
402
|
let FORBID_ATTR = null;
|
|
381
|
-
/* Decide if ARIA attributes are okay */
|
|
382
403
|
|
|
404
|
+
/* Decide if ARIA attributes are okay */
|
|
383
405
|
let ALLOW_ARIA_ATTR = true;
|
|
384
|
-
/* Decide if custom data attributes are okay */
|
|
385
406
|
|
|
407
|
+
/* Decide if custom data attributes are okay */
|
|
386
408
|
let ALLOW_DATA_ATTR = true;
|
|
387
|
-
/* Decide if unknown protocols are okay */
|
|
388
409
|
|
|
410
|
+
/* Decide if unknown protocols are okay */
|
|
389
411
|
let ALLOW_UNKNOWN_PROTOCOLS = false;
|
|
412
|
+
|
|
390
413
|
/* Decide if self-closing tags in attributes are allowed.
|
|
391
414
|
* Usually removed due to a mXSS issue in jQuery 3.0 */
|
|
392
|
-
|
|
393
415
|
let ALLOW_SELF_CLOSE_IN_ATTR = true;
|
|
416
|
+
|
|
394
417
|
/* Output should be safe for common template engines.
|
|
395
418
|
* This means, DOMPurify removes data attributes, mustaches and ERB
|
|
396
419
|
*/
|
|
397
|
-
|
|
398
420
|
let SAFE_FOR_TEMPLATES = false;
|
|
399
|
-
/* Decide if document with <html>... should be returned */
|
|
400
421
|
|
|
422
|
+
/* Decide if document with <html>... should be returned */
|
|
401
423
|
let WHOLE_DOCUMENT = false;
|
|
402
|
-
/* Track whether config is already set on this instance of DOMPurify. */
|
|
403
424
|
|
|
425
|
+
/* Track whether config is already set on this instance of DOMPurify. */
|
|
404
426
|
let SET_CONFIG = false;
|
|
427
|
+
|
|
405
428
|
/* Decide if all elements (e.g. style, script) must be children of
|
|
406
429
|
* document.body. By default, browsers might move them to document.head */
|
|
407
|
-
|
|
408
430
|
let FORCE_BODY = false;
|
|
431
|
+
|
|
409
432
|
/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
|
|
410
433
|
* string (or a TrustedHTML object if Trusted Types are supported).
|
|
411
434
|
* If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
|
|
412
435
|
*/
|
|
413
|
-
|
|
414
436
|
let RETURN_DOM = false;
|
|
437
|
+
|
|
415
438
|
/* Decide if a DOM `DocumentFragment` should be returned, instead of a html
|
|
416
439
|
* string (or a TrustedHTML object if Trusted Types are supported) */
|
|
417
|
-
|
|
418
440
|
let RETURN_DOM_FRAGMENT = false;
|
|
441
|
+
|
|
419
442
|
/* Try to return a Trusted Type object instead of a string, return a string in
|
|
420
443
|
* case Trusted Types are not supported */
|
|
421
|
-
|
|
422
444
|
let RETURN_TRUSTED_TYPE = false;
|
|
445
|
+
|
|
423
446
|
/* Output should be free from DOM clobbering attacks?
|
|
424
447
|
* This sanitizes markups named with colliding, clobberable built-in DOM APIs.
|
|
425
448
|
*/
|
|
426
|
-
|
|
427
449
|
let SANITIZE_DOM = true;
|
|
450
|
+
|
|
428
451
|
/* Achieve full DOM Clobbering protection by isolating the namespace of named
|
|
429
452
|
* properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
|
|
430
453
|
*
|
|
@@ -438,98 +461,99 @@ function createDOMPurify() {
|
|
|
438
461
|
* Namespace isolation is implemented by prefixing `id` and `name` attributes
|
|
439
462
|
* with a constant string, i.e., `user-content-`
|
|
440
463
|
*/
|
|
441
|
-
|
|
442
464
|
let SANITIZE_NAMED_PROPS = false;
|
|
443
465
|
const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
|
|
444
|
-
/* Keep element content when removing element? */
|
|
445
466
|
|
|
467
|
+
/* Keep element content when removing element? */
|
|
446
468
|
let KEEP_CONTENT = true;
|
|
469
|
+
|
|
447
470
|
/* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
|
|
448
471
|
* of importing it into a new Document and returning a sanitized copy */
|
|
449
|
-
|
|
450
472
|
let IN_PLACE = false;
|
|
451
|
-
/* Allow usage of profiles like html, svg and mathMl */
|
|
452
473
|
|
|
474
|
+
/* Allow usage of profiles like html, svg and mathMl */
|
|
453
475
|
let USE_PROFILES = {};
|
|
454
|
-
/* Tags to ignore content of when KEEP_CONTENT is true */
|
|
455
476
|
|
|
477
|
+
/* Tags to ignore content of when KEEP_CONTENT is true */
|
|
456
478
|
let FORBID_CONTENTS = null;
|
|
457
479
|
const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
|
|
458
|
-
/* Tags that are safe for data: URIs */
|
|
459
480
|
|
|
481
|
+
/* Tags that are safe for data: URIs */
|
|
460
482
|
let DATA_URI_TAGS = null;
|
|
461
483
|
const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
|
|
462
|
-
/* Attributes safe for values like "javascript:" */
|
|
463
484
|
|
|
485
|
+
/* Attributes safe for values like "javascript:" */
|
|
464
486
|
let URI_SAFE_ATTRIBUTES = null;
|
|
465
487
|
const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
|
|
466
488
|
const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
|
|
467
489
|
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
468
490
|
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
|
469
491
|
/* Document namespace */
|
|
470
|
-
|
|
471
492
|
let NAMESPACE = HTML_NAMESPACE;
|
|
472
493
|
let IS_EMPTY_INPUT = false;
|
|
473
|
-
/* Allowed XHTML+XML namespaces */
|
|
474
494
|
|
|
495
|
+
/* Allowed XHTML+XML namespaces */
|
|
475
496
|
let ALLOWED_NAMESPACES = null;
|
|
476
497
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
477
|
-
/* Parsing of strict XHTML documents */
|
|
478
498
|
|
|
479
|
-
|
|
499
|
+
/* Parsing of strict XHTML documents */
|
|
500
|
+
let PARSER_MEDIA_TYPE = null;
|
|
480
501
|
const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
|
|
481
502
|
const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
|
|
482
|
-
let transformCaseFunc;
|
|
483
|
-
/* Keep a reference to config to pass to hooks */
|
|
503
|
+
let transformCaseFunc = null;
|
|
484
504
|
|
|
505
|
+
/* Keep a reference to config to pass to hooks */
|
|
485
506
|
let CONFIG = null;
|
|
486
|
-
/* Ideally, do not touch anything below this line */
|
|
487
507
|
|
|
508
|
+
/* Ideally, do not touch anything below this line */
|
|
488
509
|
/* ______________________________________________ */
|
|
489
510
|
|
|
490
511
|
const formElement = document.createElement('form');
|
|
491
|
-
|
|
492
512
|
const isRegexOrFunction = function isRegexOrFunction(testValue) {
|
|
493
513
|
return testValue instanceof RegExp || testValue instanceof Function;
|
|
494
514
|
};
|
|
515
|
+
|
|
495
516
|
/**
|
|
496
517
|
* _parseConfig
|
|
497
518
|
*
|
|
498
519
|
* @param {Object} cfg optional config literal
|
|
499
520
|
*/
|
|
500
521
|
// eslint-disable-next-line complexity
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const _parseConfig = function _parseConfig(cfg) {
|
|
522
|
+
const _parseConfig = function _parseConfig() {
|
|
523
|
+
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
504
524
|
if (CONFIG && CONFIG === cfg) {
|
|
505
525
|
return;
|
|
506
526
|
}
|
|
507
|
-
/* Shield configuration object from tampering */
|
|
508
|
-
|
|
509
527
|
|
|
528
|
+
/* Shield configuration object from tampering */
|
|
510
529
|
if (!cfg || typeof cfg !== 'object') {
|
|
511
530
|
cfg = {};
|
|
512
531
|
}
|
|
513
|
-
/* Shield configuration object from prototype pollution */
|
|
514
|
-
|
|
515
532
|
|
|
533
|
+
/* Shield configuration object from prototype pollution */
|
|
516
534
|
cfg = clone(cfg);
|
|
517
|
-
PARSER_MEDIA_TYPE =
|
|
518
|
-
|
|
535
|
+
PARSER_MEDIA_TYPE =
|
|
536
|
+
// eslint-disable-next-line unicorn/prefer-includes
|
|
537
|
+
SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
|
|
519
538
|
|
|
539
|
+
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
520
540
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
521
|
-
/* Set configuration parameters */
|
|
522
541
|
|
|
542
|
+
/* Set configuration parameters */
|
|
523
543
|
ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
|
|
524
544
|
ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
|
|
525
545
|
ALLOWED_NAMESPACES = 'ALLOWED_NAMESPACES' in cfg ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
|
|
526
|
-
URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES),
|
|
527
|
-
|
|
546
|
+
URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES),
|
|
547
|
+
// eslint-disable-line indent
|
|
548
|
+
cfg.ADD_URI_SAFE_ATTR,
|
|
549
|
+
// eslint-disable-line indent
|
|
528
550
|
transformCaseFunc // eslint-disable-line indent
|
|
529
551
|
) // eslint-disable-line indent
|
|
530
552
|
: DEFAULT_URI_SAFE_ATTRIBUTES;
|
|
531
|
-
DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS),
|
|
532
|
-
|
|
553
|
+
DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS),
|
|
554
|
+
// eslint-disable-line indent
|
|
555
|
+
cfg.ADD_DATA_URI_TAGS,
|
|
556
|
+
// eslint-disable-line indent
|
|
533
557
|
transformCaseFunc // eslint-disable-line indent
|
|
534
558
|
) // eslint-disable-line indent
|
|
535
559
|
: DEFAULT_DATA_URI_TAGS;
|
|
@@ -538,252 +562,207 @@ function createDOMPurify() {
|
|
|
538
562
|
FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
|
|
539
563
|
USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
|
|
540
564
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
541
|
-
|
|
542
565
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
543
|
-
|
|
544
566
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
|
|
545
|
-
|
|
546
567
|
ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
|
|
547
|
-
|
|
548
568
|
SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
|
|
549
|
-
|
|
550
569
|
WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
|
|
551
|
-
|
|
552
570
|
RETURN_DOM = cfg.RETURN_DOM || false; // Default false
|
|
553
|
-
|
|
554
571
|
RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
|
|
555
|
-
|
|
556
572
|
RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
|
|
557
|
-
|
|
558
573
|
FORCE_BODY = cfg.FORCE_BODY || false; // Default false
|
|
559
|
-
|
|
560
574
|
SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
|
|
561
|
-
|
|
562
575
|
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
|
|
563
|
-
|
|
564
576
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
|
|
565
|
-
|
|
566
577
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
567
|
-
|
|
568
578
|
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
|
|
569
579
|
NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
|
|
570
580
|
CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
|
|
571
|
-
|
|
572
581
|
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
|
|
573
582
|
CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
|
|
574
583
|
}
|
|
575
|
-
|
|
576
584
|
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
|
|
577
585
|
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
|
|
578
586
|
}
|
|
579
|
-
|
|
580
587
|
if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
|
|
581
588
|
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
|
|
582
589
|
}
|
|
583
|
-
|
|
584
590
|
if (SAFE_FOR_TEMPLATES) {
|
|
585
591
|
ALLOW_DATA_ATTR = false;
|
|
586
592
|
}
|
|
587
|
-
|
|
588
593
|
if (RETURN_DOM_FRAGMENT) {
|
|
589
594
|
RETURN_DOM = true;
|
|
590
595
|
}
|
|
591
|
-
/* Parse profile info */
|
|
592
|
-
|
|
593
596
|
|
|
597
|
+
/* Parse profile info */
|
|
594
598
|
if (USE_PROFILES) {
|
|
595
|
-
ALLOWED_TAGS = addToSet({},
|
|
599
|
+
ALLOWED_TAGS = addToSet({}, text);
|
|
596
600
|
ALLOWED_ATTR = [];
|
|
597
|
-
|
|
598
601
|
if (USE_PROFILES.html === true) {
|
|
599
602
|
addToSet(ALLOWED_TAGS, html$1);
|
|
600
603
|
addToSet(ALLOWED_ATTR, html);
|
|
601
604
|
}
|
|
602
|
-
|
|
603
605
|
if (USE_PROFILES.svg === true) {
|
|
604
606
|
addToSet(ALLOWED_TAGS, svg$1);
|
|
605
607
|
addToSet(ALLOWED_ATTR, svg);
|
|
606
608
|
addToSet(ALLOWED_ATTR, xml);
|
|
607
609
|
}
|
|
608
|
-
|
|
609
610
|
if (USE_PROFILES.svgFilters === true) {
|
|
610
611
|
addToSet(ALLOWED_TAGS, svgFilters);
|
|
611
612
|
addToSet(ALLOWED_ATTR, svg);
|
|
612
613
|
addToSet(ALLOWED_ATTR, xml);
|
|
613
614
|
}
|
|
614
|
-
|
|
615
615
|
if (USE_PROFILES.mathMl === true) {
|
|
616
616
|
addToSet(ALLOWED_TAGS, mathMl$1);
|
|
617
617
|
addToSet(ALLOWED_ATTR, mathMl);
|
|
618
618
|
addToSet(ALLOWED_ATTR, xml);
|
|
619
619
|
}
|
|
620
620
|
}
|
|
621
|
-
/* Merge configuration parameters */
|
|
622
|
-
|
|
623
621
|
|
|
622
|
+
/* Merge configuration parameters */
|
|
624
623
|
if (cfg.ADD_TAGS) {
|
|
625
624
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
626
625
|
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
627
626
|
}
|
|
628
|
-
|
|
629
627
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
630
628
|
}
|
|
631
|
-
|
|
632
629
|
if (cfg.ADD_ATTR) {
|
|
633
630
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
634
631
|
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
635
632
|
}
|
|
636
|
-
|
|
637
633
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
638
634
|
}
|
|
639
|
-
|
|
640
635
|
if (cfg.ADD_URI_SAFE_ATTR) {
|
|
641
636
|
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
642
637
|
}
|
|
643
|
-
|
|
644
638
|
if (cfg.FORBID_CONTENTS) {
|
|
645
639
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
646
640
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
647
641
|
}
|
|
648
|
-
|
|
649
642
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
650
643
|
}
|
|
651
|
-
/* Add #text in case KEEP_CONTENT is set to true */
|
|
652
|
-
|
|
653
644
|
|
|
645
|
+
/* Add #text in case KEEP_CONTENT is set to true */
|
|
654
646
|
if (KEEP_CONTENT) {
|
|
655
647
|
ALLOWED_TAGS['#text'] = true;
|
|
656
648
|
}
|
|
657
|
-
/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
|
|
658
|
-
|
|
659
649
|
|
|
650
|
+
/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
|
|
660
651
|
if (WHOLE_DOCUMENT) {
|
|
661
652
|
addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
|
|
662
653
|
}
|
|
663
|
-
/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
|
|
664
|
-
|
|
665
654
|
|
|
655
|
+
/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
|
|
666
656
|
if (ALLOWED_TAGS.table) {
|
|
667
657
|
addToSet(ALLOWED_TAGS, ['tbody']);
|
|
668
658
|
delete FORBID_TAGS.tbody;
|
|
669
659
|
}
|
|
670
|
-
|
|
671
660
|
if (cfg.TRUSTED_TYPES_POLICY) {
|
|
672
661
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
|
|
673
662
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
|
|
674
663
|
}
|
|
675
|
-
|
|
676
664
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
|
|
677
665
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
678
|
-
}
|
|
679
|
-
|
|
666
|
+
}
|
|
680
667
|
|
|
681
|
-
|
|
668
|
+
// Overwrite existing TrustedTypes policy.
|
|
669
|
+
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
682
670
|
|
|
671
|
+
// Sign local variables required by `sanitize`.
|
|
683
672
|
emptyHTML = trustedTypesPolicy.createHTML('');
|
|
684
673
|
} else {
|
|
685
674
|
// Uninitialized policy, attempt to initialize the internal dompurify policy.
|
|
686
675
|
if (trustedTypesPolicy === undefined) {
|
|
687
676
|
trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
688
|
-
}
|
|
689
|
-
|
|
677
|
+
}
|
|
690
678
|
|
|
679
|
+
// If creating the internal policy succeeded sign internal variables.
|
|
691
680
|
if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
|
|
692
681
|
emptyHTML = trustedTypesPolicy.createHTML('');
|
|
693
682
|
}
|
|
694
|
-
}
|
|
695
|
-
// Not available in IE8, Safari 5, etc.
|
|
696
|
-
|
|
683
|
+
}
|
|
697
684
|
|
|
685
|
+
// Prevent further manipulation of configuration.
|
|
686
|
+
// Not available in IE8, Safari 5, etc.
|
|
698
687
|
if (freeze) {
|
|
699
688
|
freeze(cfg);
|
|
700
689
|
}
|
|
701
|
-
|
|
702
690
|
CONFIG = cfg;
|
|
703
691
|
};
|
|
704
|
-
|
|
705
692
|
const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
706
|
-
const HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
|
|
693
|
+
const HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
|
|
694
|
+
|
|
695
|
+
// Certain elements are allowed in both SVG and HTML
|
|
707
696
|
// namespace. We need to specify them explicitly
|
|
708
697
|
// so that they don't get erroneously deleted from
|
|
709
698
|
// HTML namespace.
|
|
710
|
-
|
|
711
699
|
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
|
|
700
|
+
|
|
712
701
|
/* Keep track of all possible SVG and MathML tags
|
|
713
702
|
* so that we can perform the namespace checks
|
|
714
703
|
* correctly. */
|
|
704
|
+
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
705
|
+
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
715
706
|
|
|
716
|
-
const ALL_SVG_TAGS = addToSet({}, svg$1);
|
|
717
|
-
addToSet(ALL_SVG_TAGS, svgFilters);
|
|
718
|
-
addToSet(ALL_SVG_TAGS, svgDisallowed);
|
|
719
|
-
const ALL_MATHML_TAGS = addToSet({}, mathMl$1);
|
|
720
|
-
addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
|
|
721
707
|
/**
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
708
|
* @param {Element} element a DOM element whose namespace is being checked
|
|
725
709
|
* @returns {boolean} Return false if the element has a
|
|
726
710
|
* namespace that a spec-compliant parser would never
|
|
727
711
|
* return. Return true otherwise.
|
|
728
712
|
*/
|
|
729
|
-
|
|
730
713
|
const _checkValidNamespace = function _checkValidNamespace(element) {
|
|
731
|
-
let parent = getParentNode(element);
|
|
732
|
-
// can be null. We just simulate parent in this case.
|
|
714
|
+
let parent = getParentNode(element);
|
|
733
715
|
|
|
716
|
+
// In JSDOM, if we're inside shadow DOM, then parentNode
|
|
717
|
+
// can be null. We just simulate parent in this case.
|
|
734
718
|
if (!parent || !parent.tagName) {
|
|
735
719
|
parent = {
|
|
736
720
|
namespaceURI: NAMESPACE,
|
|
737
721
|
tagName: 'template'
|
|
738
722
|
};
|
|
739
723
|
}
|
|
740
|
-
|
|
741
724
|
const tagName = stringToLowerCase(element.tagName);
|
|
742
725
|
const parentTagName = stringToLowerCase(parent.tagName);
|
|
743
|
-
|
|
744
726
|
if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
745
727
|
return false;
|
|
746
728
|
}
|
|
747
|
-
|
|
748
729
|
if (element.namespaceURI === SVG_NAMESPACE) {
|
|
749
730
|
// The only way to switch from HTML namespace to SVG
|
|
750
731
|
// is via <svg>. If it happens via any other tag, then
|
|
751
732
|
// it should be killed.
|
|
752
733
|
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
753
734
|
return tagName === 'svg';
|
|
754
|
-
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// The only way to switch from MathML to SVG is via`
|
|
755
738
|
// svg if parent is either <annotation-xml> or MathML
|
|
756
739
|
// text integration points.
|
|
757
|
-
|
|
758
|
-
|
|
759
740
|
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
760
741
|
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
761
|
-
}
|
|
762
|
-
// spec. All others are disallowed in SVG namespace.
|
|
763
|
-
|
|
742
|
+
}
|
|
764
743
|
|
|
744
|
+
// We only allow elements that are defined in SVG
|
|
745
|
+
// spec. All others are disallowed in SVG namespace.
|
|
765
746
|
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
766
747
|
}
|
|
767
|
-
|
|
768
748
|
if (element.namespaceURI === MATHML_NAMESPACE) {
|
|
769
749
|
// The only way to switch from HTML namespace to MathML
|
|
770
750
|
// is via <math>. If it happens via any other tag, then
|
|
771
751
|
// it should be killed.
|
|
772
752
|
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
773
753
|
return tagName === 'math';
|
|
774
|
-
}
|
|
775
|
-
// <math> and HTML integration points
|
|
776
|
-
|
|
754
|
+
}
|
|
777
755
|
|
|
756
|
+
// The only way to switch from SVG to MathML is via
|
|
757
|
+
// <math> and HTML integration points
|
|
778
758
|
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
779
759
|
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
780
|
-
}
|
|
781
|
-
// spec. All others are disallowed in MathML namespace.
|
|
782
|
-
|
|
760
|
+
}
|
|
783
761
|
|
|
762
|
+
// We only allow elements that are defined in MathML
|
|
763
|
+
// spec. All others are disallowed in MathML namespace.
|
|
784
764
|
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
785
765
|
}
|
|
786
|
-
|
|
787
766
|
if (element.namespaceURI === HTML_NAMESPACE) {
|
|
788
767
|
// The only way to switch from SVG to HTML is via
|
|
789
768
|
// HTML integration points, and from MathML to HTML
|
|
@@ -791,39 +770,36 @@ function createDOMPurify() {
|
|
|
791
770
|
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
792
771
|
return false;
|
|
793
772
|
}
|
|
794
|
-
|
|
795
773
|
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
796
774
|
return false;
|
|
797
|
-
}
|
|
798
|
-
// or SVG and should never appear in HTML namespace
|
|
799
|
-
|
|
775
|
+
}
|
|
800
776
|
|
|
777
|
+
// We disallow tags that are specific for MathML
|
|
778
|
+
// or SVG and should never appear in HTML namespace
|
|
801
779
|
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
802
|
-
}
|
|
803
|
-
|
|
780
|
+
}
|
|
804
781
|
|
|
782
|
+
// For XHTML and XML documents that support custom namespaces
|
|
805
783
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
806
784
|
return true;
|
|
807
|
-
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// The code should never reach this place (this means
|
|
808
788
|
// that the element somehow got namespace that is not
|
|
809
789
|
// HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
|
|
810
790
|
// Return false just in case.
|
|
811
|
-
|
|
812
|
-
|
|
813
791
|
return false;
|
|
814
792
|
};
|
|
793
|
+
|
|
815
794
|
/**
|
|
816
795
|
* _forceRemove
|
|
817
796
|
*
|
|
818
797
|
* @param {Node} node a DOM node
|
|
819
798
|
*/
|
|
820
|
-
|
|
821
|
-
|
|
822
799
|
const _forceRemove = function _forceRemove(node) {
|
|
823
800
|
arrayPush(DOMPurify.removed, {
|
|
824
801
|
element: node
|
|
825
802
|
});
|
|
826
|
-
|
|
827
803
|
try {
|
|
828
804
|
// eslint-disable-next-line unicorn/prefer-dom-node-remove
|
|
829
805
|
node.parentNode.removeChild(node);
|
|
@@ -831,14 +807,13 @@ function createDOMPurify() {
|
|
|
831
807
|
node.remove();
|
|
832
808
|
}
|
|
833
809
|
};
|
|
810
|
+
|
|
834
811
|
/**
|
|
835
812
|
* _removeAttribute
|
|
836
813
|
*
|
|
837
814
|
* @param {String} name an Attribute name
|
|
838
815
|
* @param {Node} node a DOM node
|
|
839
816
|
*/
|
|
840
|
-
|
|
841
|
-
|
|
842
817
|
const _removeAttribute = function _removeAttribute(name, node) {
|
|
843
818
|
try {
|
|
844
819
|
arrayPush(DOMPurify.removed, {
|
|
@@ -851,9 +826,9 @@ function createDOMPurify() {
|
|
|
851
826
|
from: node
|
|
852
827
|
});
|
|
853
828
|
}
|
|
829
|
+
node.removeAttribute(name);
|
|
854
830
|
|
|
855
|
-
|
|
856
|
-
|
|
831
|
+
// We void attribute values for unremovable "is"" attributes
|
|
857
832
|
if (name === 'is' && !ALLOWED_ATTR[name]) {
|
|
858
833
|
if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
|
|
859
834
|
try {
|
|
@@ -866,19 +841,17 @@ function createDOMPurify() {
|
|
|
866
841
|
}
|
|
867
842
|
}
|
|
868
843
|
};
|
|
844
|
+
|
|
869
845
|
/**
|
|
870
846
|
* _initDocument
|
|
871
847
|
*
|
|
872
848
|
* @param {String} dirty a string of dirty markup
|
|
873
849
|
* @return {Document} a DOM, filled with the dirty markup
|
|
874
850
|
*/
|
|
875
|
-
|
|
876
|
-
|
|
877
851
|
const _initDocument = function _initDocument(dirty) {
|
|
878
852
|
/* Create a HTML document */
|
|
879
|
-
let doc;
|
|
880
|
-
let leadingWhitespace;
|
|
881
|
-
|
|
853
|
+
let doc = null;
|
|
854
|
+
let leadingWhitespace = null;
|
|
882
855
|
if (FORCE_BODY) {
|
|
883
856
|
dirty = '<remove></remove>' + dirty;
|
|
884
857
|
} else {
|
|
@@ -886,83 +859,74 @@ function createDOMPurify() {
|
|
|
886
859
|
const matches = stringMatch(dirty, /^[\r\n\t ]+/);
|
|
887
860
|
leadingWhitespace = matches && matches[0];
|
|
888
861
|
}
|
|
889
|
-
|
|
890
862
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
|
|
891
863
|
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
|
|
892
864
|
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
|
|
893
865
|
}
|
|
894
|
-
|
|
895
866
|
const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
|
|
896
867
|
/*
|
|
897
868
|
* Use the DOMParser API by default, fallback later if needs be
|
|
898
869
|
* DOMParser not work for svg when has multiple root element.
|
|
899
870
|
*/
|
|
900
|
-
|
|
901
871
|
if (NAMESPACE === HTML_NAMESPACE) {
|
|
902
872
|
try {
|
|
903
873
|
doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
|
|
904
874
|
} catch (_) {}
|
|
905
875
|
}
|
|
906
|
-
/* Use createHTMLDocument in case DOMParser is not available */
|
|
907
|
-
|
|
908
876
|
|
|
877
|
+
/* Use createHTMLDocument in case DOMParser is not available */
|
|
909
878
|
if (!doc || !doc.documentElement) {
|
|
910
879
|
doc = implementation.createDocument(NAMESPACE, 'template', null);
|
|
911
|
-
|
|
912
880
|
try {
|
|
913
881
|
doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
|
|
914
|
-
} catch (_) {
|
|
882
|
+
} catch (_) {
|
|
883
|
+
// Syntax error if dirtyPayload is invalid xml
|
|
915
884
|
}
|
|
916
885
|
}
|
|
917
|
-
|
|
918
886
|
const body = doc.body || doc.documentElement;
|
|
919
|
-
|
|
920
887
|
if (dirty && leadingWhitespace) {
|
|
921
888
|
body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
|
|
922
889
|
}
|
|
923
|
-
/* Work on whole document or just its body */
|
|
924
|
-
|
|
925
890
|
|
|
891
|
+
/* Work on whole document or just its body */
|
|
926
892
|
if (NAMESPACE === HTML_NAMESPACE) {
|
|
927
893
|
return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
|
|
928
894
|
}
|
|
929
|
-
|
|
930
895
|
return WHOLE_DOCUMENT ? doc.documentElement : body;
|
|
931
896
|
};
|
|
897
|
+
|
|
932
898
|
/**
|
|
933
|
-
*
|
|
899
|
+
* Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
|
|
934
900
|
*
|
|
935
|
-
* @param {
|
|
936
|
-
* @return {
|
|
901
|
+
* @param {Node} root The root element or node to start traversing on.
|
|
902
|
+
* @return {NodeIterator} The created NodeIterator
|
|
937
903
|
*/
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
|
|
904
|
+
const _createNodeIterator = function _createNodeIterator(root) {
|
|
905
|
+
return createNodeIterator.call(root.ownerDocument || root, root,
|
|
906
|
+
// eslint-disable-next-line no-bitwise
|
|
907
|
+
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null);
|
|
943
908
|
};
|
|
909
|
+
|
|
944
910
|
/**
|
|
945
911
|
* _isClobbered
|
|
946
912
|
*
|
|
947
913
|
* @param {Node} elm element to check for clobbering attacks
|
|
948
914
|
* @return {Boolean} true if clobbered, false if safe
|
|
949
915
|
*/
|
|
950
|
-
|
|
951
|
-
|
|
952
916
|
const _isClobbered = function _isClobbered(elm) {
|
|
953
917
|
return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function');
|
|
954
918
|
};
|
|
919
|
+
|
|
955
920
|
/**
|
|
956
|
-
*
|
|
921
|
+
* Checks whether the given object is a DOM node.
|
|
957
922
|
*
|
|
958
|
-
* @param {Node}
|
|
923
|
+
* @param {Node} object object to check whether it's a DOM node
|
|
959
924
|
* @return {Boolean} true is object is a DOM node
|
|
960
925
|
*/
|
|
961
|
-
|
|
962
|
-
|
|
963
926
|
const _isNode = function _isNode(object) {
|
|
964
|
-
return typeof Node === '
|
|
927
|
+
return typeof Node === 'function' && object instanceof Node;
|
|
965
928
|
};
|
|
929
|
+
|
|
966
930
|
/**
|
|
967
931
|
* _executeHook
|
|
968
932
|
* Execute user configurable hooks
|
|
@@ -971,17 +935,15 @@ function createDOMPurify() {
|
|
|
971
935
|
* @param {Node} currentNode node to work on with the hook
|
|
972
936
|
* @param {Object} data additional hook parameters
|
|
973
937
|
*/
|
|
974
|
-
|
|
975
|
-
|
|
976
938
|
const _executeHook = function _executeHook(entryPoint, currentNode, data) {
|
|
977
939
|
if (!hooks[entryPoint]) {
|
|
978
940
|
return;
|
|
979
941
|
}
|
|
980
|
-
|
|
981
942
|
arrayForEach(hooks[entryPoint], hook => {
|
|
982
943
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
983
944
|
});
|
|
984
945
|
};
|
|
946
|
+
|
|
985
947
|
/**
|
|
986
948
|
* _sanitizeElements
|
|
987
949
|
*
|
|
@@ -992,94 +954,79 @@ function createDOMPurify() {
|
|
|
992
954
|
* @param {Node} currentNode to check for permission to exist
|
|
993
955
|
* @return {Boolean} true if node was killed, false if left alive
|
|
994
956
|
*/
|
|
995
|
-
|
|
996
|
-
|
|
997
957
|
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
998
|
-
let content;
|
|
999
|
-
/* Execute a hook if present */
|
|
958
|
+
let content = null;
|
|
1000
959
|
|
|
960
|
+
/* Execute a hook if present */
|
|
1001
961
|
_executeHook('beforeSanitizeElements', currentNode, null);
|
|
1002
|
-
/* Check if element is clobbered or can clobber */
|
|
1003
|
-
|
|
1004
962
|
|
|
963
|
+
/* Check if element is clobbered or can clobber */
|
|
1005
964
|
if (_isClobbered(currentNode)) {
|
|
1006
965
|
_forceRemove(currentNode);
|
|
1007
|
-
|
|
1008
966
|
return true;
|
|
1009
967
|
}
|
|
1010
|
-
/* Now let's check the element's type and name */
|
|
1011
|
-
|
|
1012
968
|
|
|
969
|
+
/* Now let's check the element's type and name */
|
|
1013
970
|
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
1014
|
-
/* Execute a hook if present */
|
|
1015
971
|
|
|
972
|
+
/* Execute a hook if present */
|
|
1016
973
|
_executeHook('uponSanitizeElement', currentNode, {
|
|
1017
974
|
tagName,
|
|
1018
975
|
allowedTags: ALLOWED_TAGS
|
|
1019
976
|
});
|
|
1020
|
-
/* Detect mXSS attempts abusing namespace confusion */
|
|
1021
977
|
|
|
1022
|
-
|
|
1023
|
-
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) &&
|
|
978
|
+
/* Detect mXSS attempts abusing namespace confusion */
|
|
979
|
+
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
|
|
1024
980
|
_forceRemove(currentNode);
|
|
1025
|
-
|
|
1026
981
|
return true;
|
|
1027
982
|
}
|
|
1028
|
-
/* Remove element if anything forbids its presence */
|
|
1029
|
-
|
|
1030
983
|
|
|
984
|
+
/* Remove element if anything forbids its presence */
|
|
1031
985
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
1032
986
|
/* Check if we have a custom element to handle */
|
|
1033
|
-
if (!FORBID_TAGS[tagName] &&
|
|
1034
|
-
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName))
|
|
1035
|
-
|
|
987
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
988
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
|
|
989
|
+
return false;
|
|
990
|
+
}
|
|
991
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
1036
994
|
}
|
|
1037
|
-
/* Keep content except for bad-listed elements */
|
|
1038
|
-
|
|
1039
995
|
|
|
996
|
+
/* Keep content except for bad-listed elements */
|
|
1040
997
|
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
1041
998
|
const parentNode = getParentNode(currentNode) || currentNode.parentNode;
|
|
1042
999
|
const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
|
|
1043
|
-
|
|
1044
1000
|
if (childNodes && parentNode) {
|
|
1045
1001
|
const childCount = childNodes.length;
|
|
1046
|
-
|
|
1047
1002
|
for (let i = childCount - 1; i >= 0; --i) {
|
|
1048
1003
|
parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
|
|
1049
1004
|
}
|
|
1050
1005
|
}
|
|
1051
1006
|
}
|
|
1052
|
-
|
|
1053
1007
|
_forceRemove(currentNode);
|
|
1054
|
-
|
|
1055
1008
|
return true;
|
|
1056
1009
|
}
|
|
1057
|
-
/* Check whether element has a valid namespace */
|
|
1058
|
-
|
|
1059
1010
|
|
|
1011
|
+
/* Check whether element has a valid namespace */
|
|
1060
1012
|
if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
|
|
1061
1013
|
_forceRemove(currentNode);
|
|
1062
|
-
|
|
1063
1014
|
return true;
|
|
1064
1015
|
}
|
|
1065
|
-
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
1066
|
-
|
|
1067
1016
|
|
|
1017
|
+
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
1068
1018
|
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
|
|
1069
1019
|
_forceRemove(currentNode);
|
|
1070
|
-
|
|
1071
1020
|
return true;
|
|
1072
1021
|
}
|
|
1073
|
-
/* Sanitize element content to be template-safe */
|
|
1074
|
-
|
|
1075
1022
|
|
|
1023
|
+
/* Sanitize element content to be template-safe */
|
|
1076
1024
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
|
|
1077
1025
|
/* Get the element's text content */
|
|
1078
1026
|
content = currentNode.textContent;
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1027
|
+
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1028
|
+
content = stringReplace(content, expr, ' ');
|
|
1029
|
+
});
|
|
1083
1030
|
if (currentNode.textContent !== content) {
|
|
1084
1031
|
arrayPush(DOMPurify.removed, {
|
|
1085
1032
|
element: currentNode.cloneNode()
|
|
@@ -1087,13 +1034,12 @@ function createDOMPurify() {
|
|
|
1087
1034
|
currentNode.textContent = content;
|
|
1088
1035
|
}
|
|
1089
1036
|
}
|
|
1090
|
-
/* Execute a hook if present */
|
|
1091
|
-
|
|
1092
1037
|
|
|
1038
|
+
/* Execute a hook if present */
|
|
1093
1039
|
_executeHook('afterSanitizeElements', currentNode, null);
|
|
1094
|
-
|
|
1095
1040
|
return false;
|
|
1096
1041
|
};
|
|
1042
|
+
|
|
1097
1043
|
/**
|
|
1098
1044
|
* _isValidAttribute
|
|
1099
1045
|
*
|
|
@@ -1103,47 +1049,46 @@ function createDOMPurify() {
|
|
|
1103
1049
|
* @return {Boolean} Returns true if `value` is valid, otherwise false.
|
|
1104
1050
|
*/
|
|
1105
1051
|
// eslint-disable-next-line complexity
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
1052
|
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
|
|
1109
1053
|
/* Make sure attribute cannot clobber */
|
|
1110
1054
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
1111
1055
|
return false;
|
|
1112
1056
|
}
|
|
1057
|
+
|
|
1113
1058
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
1114
1059
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1115
1060
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1116
1061
|
We don't need to check the value; it's always URI safe. */
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
1062
|
if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
|
|
1120
|
-
if (
|
|
1063
|
+
if (
|
|
1064
|
+
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1121
1065
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
1122
1066
|
// and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
|
|
1123
|
-
|
|
1067
|
+
_isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) ||
|
|
1068
|
+
// Alternative, second condition checks if it's an `is`-attribute, AND
|
|
1124
1069
|
// the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
1125
1070
|
lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
|
|
1126
1071
|
return false;
|
|
1127
1072
|
}
|
|
1128
1073
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
1129
|
-
|
|
1130
1074
|
} else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
|
|
1131
1075
|
return false;
|
|
1132
1076
|
} else ;
|
|
1133
|
-
|
|
1134
1077
|
return true;
|
|
1135
1078
|
};
|
|
1079
|
+
|
|
1136
1080
|
/**
|
|
1137
|
-
*
|
|
1081
|
+
* _isBasicCustomElement
|
|
1138
1082
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
1139
1083
|
* for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
|
|
1084
|
+
*
|
|
1140
1085
|
* @param {string} tagName name of the tag of the node to sanitize
|
|
1086
|
+
* @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
1141
1087
|
*/
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
const _basicCustomElementTest = function _basicCustomElementTest(tagName) {
|
|
1088
|
+
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1145
1089
|
return tagName.indexOf('-') > 0;
|
|
1146
1090
|
};
|
|
1091
|
+
|
|
1147
1092
|
/**
|
|
1148
1093
|
* _sanitizeAttributes
|
|
1149
1094
|
*
|
|
@@ -1154,107 +1099,87 @@ function createDOMPurify() {
|
|
|
1154
1099
|
*
|
|
1155
1100
|
* @param {Node} currentNode to sanitize
|
|
1156
1101
|
*/
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
1102
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1160
|
-
let attr;
|
|
1161
|
-
let value;
|
|
1162
|
-
let lcName;
|
|
1163
|
-
let l;
|
|
1164
1103
|
/* Execute a hook if present */
|
|
1165
|
-
|
|
1166
1104
|
_executeHook('beforeSanitizeAttributes', currentNode, null);
|
|
1167
|
-
|
|
1168
1105
|
const {
|
|
1169
1106
|
attributes
|
|
1170
1107
|
} = currentNode;
|
|
1171
|
-
/* Check if we have attributes; if not we might have a text node */
|
|
1172
1108
|
|
|
1109
|
+
/* Check if we have attributes; if not we might have a text node */
|
|
1173
1110
|
if (!attributes) {
|
|
1174
1111
|
return;
|
|
1175
1112
|
}
|
|
1176
|
-
|
|
1177
1113
|
const hookEvent = {
|
|
1178
1114
|
attrName: '',
|
|
1179
1115
|
attrValue: '',
|
|
1180
1116
|
keepAttr: true,
|
|
1181
1117
|
allowedAttributes: ALLOWED_ATTR
|
|
1182
1118
|
};
|
|
1183
|
-
l = attributes.length;
|
|
1184
|
-
/* Go backwards over all attributes; safely remove bad ones */
|
|
1119
|
+
let l = attributes.length;
|
|
1185
1120
|
|
|
1121
|
+
/* Go backwards over all attributes; safely remove bad ones */
|
|
1186
1122
|
while (l--) {
|
|
1187
|
-
attr = attributes[l];
|
|
1123
|
+
const attr = attributes[l];
|
|
1188
1124
|
const {
|
|
1189
1125
|
name,
|
|
1190
|
-
namespaceURI
|
|
1126
|
+
namespaceURI,
|
|
1127
|
+
value: attrValue
|
|
1191
1128
|
} = attr;
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
/* Execute a hook if present */
|
|
1129
|
+
const lcName = transformCaseFunc(name);
|
|
1130
|
+
let value = name === 'value' ? attrValue : stringTrim(attrValue);
|
|
1195
1131
|
|
|
1132
|
+
/* Execute a hook if present */
|
|
1196
1133
|
hookEvent.attrName = lcName;
|
|
1197
1134
|
hookEvent.attrValue = value;
|
|
1198
1135
|
hookEvent.keepAttr = true;
|
|
1199
1136
|
hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
|
|
1200
|
-
|
|
1201
1137
|
_executeHook('uponSanitizeAttribute', currentNode, hookEvent);
|
|
1202
|
-
|
|
1203
1138
|
value = hookEvent.attrValue;
|
|
1204
1139
|
/* Did the hooks approve of the attribute? */
|
|
1205
|
-
|
|
1206
1140
|
if (hookEvent.forceKeepAttr) {
|
|
1207
1141
|
continue;
|
|
1208
1142
|
}
|
|
1209
|
-
/* Remove attribute */
|
|
1210
|
-
|
|
1211
1143
|
|
|
1144
|
+
/* Remove attribute */
|
|
1212
1145
|
_removeAttribute(name, currentNode);
|
|
1213
|
-
/* Did the hooks approve of the attribute? */
|
|
1214
|
-
|
|
1215
1146
|
|
|
1147
|
+
/* Did the hooks approve of the attribute? */
|
|
1216
1148
|
if (!hookEvent.keepAttr) {
|
|
1217
1149
|
continue;
|
|
1218
1150
|
}
|
|
1219
|
-
/* Work around a security issue in jQuery 3.0 */
|
|
1220
|
-
|
|
1221
1151
|
|
|
1152
|
+
/* Work around a security issue in jQuery 3.0 */
|
|
1222
1153
|
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
|
|
1223
1154
|
_removeAttribute(name, currentNode);
|
|
1224
|
-
|
|
1225
1155
|
continue;
|
|
1226
1156
|
}
|
|
1227
|
-
/* Sanitize attribute content to be template-safe */
|
|
1228
|
-
|
|
1229
1157
|
|
|
1158
|
+
/* Sanitize attribute content to be template-safe */
|
|
1230
1159
|
if (SAFE_FOR_TEMPLATES) {
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1160
|
+
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1161
|
+
value = stringReplace(value, expr, ' ');
|
|
1162
|
+
});
|
|
1234
1163
|
}
|
|
1235
|
-
/* Is `value` valid for this attribute? */
|
|
1236
|
-
|
|
1237
1164
|
|
|
1165
|
+
/* Is `value` valid for this attribute? */
|
|
1238
1166
|
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1239
|
-
|
|
1240
1167
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
1241
1168
|
continue;
|
|
1242
1169
|
}
|
|
1170
|
+
|
|
1243
1171
|
/* Full DOM Clobbering protection via namespace isolation,
|
|
1244
1172
|
* Prefix id and name attributes with `user-content-`
|
|
1245
1173
|
*/
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
1174
|
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
1249
1175
|
// Remove the attribute with this value
|
|
1250
|
-
_removeAttribute(name, currentNode);
|
|
1251
|
-
|
|
1176
|
+
_removeAttribute(name, currentNode);
|
|
1252
1177
|
|
|
1178
|
+
// Prefix the value and later re-create the attribute with the sanitized value
|
|
1253
1179
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
1254
1180
|
}
|
|
1255
|
-
/* Handle attributes that require Trusted Types */
|
|
1256
|
-
|
|
1257
1181
|
|
|
1182
|
+
/* Handle attributes that require Trusted Types */
|
|
1258
1183
|
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
|
|
1259
1184
|
if (namespaceURI) ; else {
|
|
1260
1185
|
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
@@ -1263,7 +1188,6 @@ function createDOMPurify() {
|
|
|
1263
1188
|
value = trustedTypesPolicy.createHTML(value);
|
|
1264
1189
|
break;
|
|
1265
1190
|
}
|
|
1266
|
-
|
|
1267
1191
|
case 'TrustedScriptURL':
|
|
1268
1192
|
{
|
|
1269
1193
|
value = trustedTypesPolicy.createScriptURL(value);
|
|
@@ -1272,9 +1196,8 @@ function createDOMPurify() {
|
|
|
1272
1196
|
}
|
|
1273
1197
|
}
|
|
1274
1198
|
}
|
|
1275
|
-
/* Handle invalid data-* attribute set by try-catching it */
|
|
1276
|
-
|
|
1277
1199
|
|
|
1200
|
+
/* Handle invalid data-* attribute set by try-catching it */
|
|
1278
1201
|
try {
|
|
1279
1202
|
if (namespaceURI) {
|
|
1280
1203
|
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
@@ -1282,88 +1205,73 @@ function createDOMPurify() {
|
|
|
1282
1205
|
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
1283
1206
|
currentNode.setAttribute(name, value);
|
|
1284
1207
|
}
|
|
1285
|
-
|
|
1286
1208
|
arrayPop(DOMPurify.removed);
|
|
1287
1209
|
} catch (_) {}
|
|
1288
1210
|
}
|
|
1289
|
-
/* Execute a hook if present */
|
|
1290
|
-
|
|
1291
1211
|
|
|
1212
|
+
/* Execute a hook if present */
|
|
1292
1213
|
_executeHook('afterSanitizeAttributes', currentNode, null);
|
|
1293
1214
|
};
|
|
1215
|
+
|
|
1294
1216
|
/**
|
|
1295
1217
|
* _sanitizeShadowDOM
|
|
1296
1218
|
*
|
|
1297
1219
|
* @param {DocumentFragment} fragment to iterate over recursively
|
|
1298
1220
|
*/
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
1221
|
const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
|
|
1302
|
-
let shadowNode;
|
|
1222
|
+
let shadowNode = null;
|
|
1223
|
+
const shadowIterator = _createNodeIterator(fragment);
|
|
1303
1224
|
|
|
1304
|
-
const shadowIterator = _createIterator(fragment);
|
|
1305
1225
|
/* Execute a hook if present */
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
1226
|
_executeHook('beforeSanitizeShadowDOM', fragment, null);
|
|
1309
|
-
|
|
1310
1227
|
while (shadowNode = shadowIterator.nextNode()) {
|
|
1311
1228
|
/* Execute a hook if present */
|
|
1312
1229
|
_executeHook('uponSanitizeShadowNode', shadowNode, null);
|
|
1313
|
-
/* Sanitize tags and elements */
|
|
1314
|
-
|
|
1315
1230
|
|
|
1231
|
+
/* Sanitize tags and elements */
|
|
1316
1232
|
if (_sanitizeElements(shadowNode)) {
|
|
1317
1233
|
continue;
|
|
1318
1234
|
}
|
|
1319
|
-
/* Deep shadow DOM detected */
|
|
1320
|
-
|
|
1321
1235
|
|
|
1236
|
+
/* Deep shadow DOM detected */
|
|
1322
1237
|
if (shadowNode.content instanceof DocumentFragment) {
|
|
1323
1238
|
_sanitizeShadowDOM(shadowNode.content);
|
|
1324
1239
|
}
|
|
1325
|
-
/* Check attributes, sanitize if necessary */
|
|
1326
|
-
|
|
1327
1240
|
|
|
1241
|
+
/* Check attributes, sanitize if necessary */
|
|
1328
1242
|
_sanitizeAttributes(shadowNode);
|
|
1329
1243
|
}
|
|
1330
|
-
/* Execute a hook if present */
|
|
1331
|
-
|
|
1332
1244
|
|
|
1245
|
+
/* Execute a hook if present */
|
|
1333
1246
|
_executeHook('afterSanitizeShadowDOM', fragment, null);
|
|
1334
1247
|
};
|
|
1248
|
+
|
|
1335
1249
|
/**
|
|
1336
1250
|
* Sanitize
|
|
1337
1251
|
* Public method providing core sanitation functionality
|
|
1338
1252
|
*
|
|
1339
1253
|
* @param {String|Node} dirty string or DOM node
|
|
1340
|
-
* @param {Object}
|
|
1254
|
+
* @param {Object} cfg object
|
|
1341
1255
|
*/
|
|
1342
1256
|
// eslint-disable-next-line complexity
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
1257
|
DOMPurify.sanitize = function (dirty) {
|
|
1346
1258
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
1347
|
-
let body;
|
|
1348
|
-
let importedNode;
|
|
1349
|
-
let currentNode;
|
|
1350
|
-
let returnNode;
|
|
1259
|
+
let body = null;
|
|
1260
|
+
let importedNode = null;
|
|
1261
|
+
let currentNode = null;
|
|
1262
|
+
let returnNode = null;
|
|
1351
1263
|
/* Make sure we have a string to sanitize.
|
|
1352
1264
|
DO NOT return early, as this will return the wrong type if
|
|
1353
1265
|
the user has requested a DOM object rather than a string */
|
|
1354
|
-
|
|
1355
1266
|
IS_EMPTY_INPUT = !dirty;
|
|
1356
|
-
|
|
1357
1267
|
if (IS_EMPTY_INPUT) {
|
|
1358
1268
|
dirty = '<!-->';
|
|
1359
1269
|
}
|
|
1360
|
-
/* Stringify, in case dirty is an object */
|
|
1361
|
-
|
|
1362
1270
|
|
|
1271
|
+
/* Stringify, in case dirty is an object */
|
|
1363
1272
|
if (typeof dirty !== 'string' && !_isNode(dirty)) {
|
|
1364
1273
|
if (typeof dirty.toString === 'function') {
|
|
1365
1274
|
dirty = dirty.toString();
|
|
1366
|
-
|
|
1367
1275
|
if (typeof dirty !== 'string') {
|
|
1368
1276
|
throw typeErrorCreate('dirty is not a string, aborting');
|
|
1369
1277
|
}
|
|
@@ -1371,33 +1279,28 @@ function createDOMPurify() {
|
|
|
1371
1279
|
throw typeErrorCreate('toString is not a function');
|
|
1372
1280
|
}
|
|
1373
1281
|
}
|
|
1374
|
-
/* Return dirty HTML if DOMPurify cannot run */
|
|
1375
|
-
|
|
1376
1282
|
|
|
1283
|
+
/* Return dirty HTML if DOMPurify cannot run */
|
|
1377
1284
|
if (!DOMPurify.isSupported) {
|
|
1378
1285
|
return dirty;
|
|
1379
1286
|
}
|
|
1380
|
-
/* Assign config vars */
|
|
1381
|
-
|
|
1382
1287
|
|
|
1288
|
+
/* Assign config vars */
|
|
1383
1289
|
if (!SET_CONFIG) {
|
|
1384
1290
|
_parseConfig(cfg);
|
|
1385
1291
|
}
|
|
1386
|
-
/* Clean up removed elements */
|
|
1387
|
-
|
|
1388
1292
|
|
|
1293
|
+
/* Clean up removed elements */
|
|
1389
1294
|
DOMPurify.removed = [];
|
|
1390
|
-
/* Check if dirty is correctly typed for IN_PLACE */
|
|
1391
1295
|
|
|
1296
|
+
/* Check if dirty is correctly typed for IN_PLACE */
|
|
1392
1297
|
if (typeof dirty === 'string') {
|
|
1393
1298
|
IN_PLACE = false;
|
|
1394
1299
|
}
|
|
1395
|
-
|
|
1396
1300
|
if (IN_PLACE) {
|
|
1397
1301
|
/* Do some early pre-sanitization to avoid unsafe root nodes */
|
|
1398
1302
|
if (dirty.nodeName) {
|
|
1399
1303
|
const tagName = transformCaseFunc(dirty.nodeName);
|
|
1400
|
-
|
|
1401
1304
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
1402
1305
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
1403
1306
|
}
|
|
@@ -1407,7 +1310,6 @@ function createDOMPurify() {
|
|
|
1407
1310
|
elements being stripped by the parser */
|
|
1408
1311
|
body = _initDocument('<!---->');
|
|
1409
1312
|
importedNode = body.ownerDocument.importNode(dirty, true);
|
|
1410
|
-
|
|
1411
1313
|
if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
|
|
1412
1314
|
/* Node is already a body, use as is */
|
|
1413
1315
|
body = importedNode;
|
|
@@ -1419,62 +1321,54 @@ function createDOMPurify() {
|
|
|
1419
1321
|
}
|
|
1420
1322
|
} else {
|
|
1421
1323
|
/* Exit directly if we have nothing to do */
|
|
1422
|
-
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
1324
|
+
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
1325
|
+
// eslint-disable-next-line unicorn/prefer-includes
|
|
1423
1326
|
dirty.indexOf('<') === -1) {
|
|
1424
1327
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
|
|
1425
1328
|
}
|
|
1426
|
-
/* Initialize the document to work on */
|
|
1427
|
-
|
|
1428
1329
|
|
|
1330
|
+
/* Initialize the document to work on */
|
|
1429
1331
|
body = _initDocument(dirty);
|
|
1430
|
-
/* Check we have a DOM node from the data */
|
|
1431
1332
|
|
|
1333
|
+
/* Check we have a DOM node from the data */
|
|
1432
1334
|
if (!body) {
|
|
1433
1335
|
return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
|
|
1434
1336
|
}
|
|
1435
1337
|
}
|
|
1436
|
-
/* Remove first element node (ours) if FORCE_BODY is set */
|
|
1437
|
-
|
|
1438
1338
|
|
|
1339
|
+
/* Remove first element node (ours) if FORCE_BODY is set */
|
|
1439
1340
|
if (body && FORCE_BODY) {
|
|
1440
1341
|
_forceRemove(body.firstChild);
|
|
1441
1342
|
}
|
|
1442
|
-
/* Get node iterator */
|
|
1443
1343
|
|
|
1344
|
+
/* Get node iterator */
|
|
1345
|
+
const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
|
|
1444
1346
|
|
|
1445
|
-
const nodeIterator = _createIterator(IN_PLACE ? dirty : body);
|
|
1446
1347
|
/* Now start iterating over the created document */
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
1348
|
while (currentNode = nodeIterator.nextNode()) {
|
|
1450
1349
|
/* Sanitize tags and elements */
|
|
1451
1350
|
if (_sanitizeElements(currentNode)) {
|
|
1452
1351
|
continue;
|
|
1453
1352
|
}
|
|
1454
|
-
/* Shadow DOM detected, sanitize it */
|
|
1455
|
-
|
|
1456
1353
|
|
|
1354
|
+
/* Shadow DOM detected, sanitize it */
|
|
1457
1355
|
if (currentNode.content instanceof DocumentFragment) {
|
|
1458
1356
|
_sanitizeShadowDOM(currentNode.content);
|
|
1459
1357
|
}
|
|
1460
|
-
/* Check attributes, sanitize if necessary */
|
|
1461
|
-
|
|
1462
1358
|
|
|
1359
|
+
/* Check attributes, sanitize if necessary */
|
|
1463
1360
|
_sanitizeAttributes(currentNode);
|
|
1464
1361
|
}
|
|
1465
|
-
/* If we sanitized `dirty` in-place, return it. */
|
|
1466
|
-
|
|
1467
1362
|
|
|
1363
|
+
/* If we sanitized `dirty` in-place, return it. */
|
|
1468
1364
|
if (IN_PLACE) {
|
|
1469
1365
|
return dirty;
|
|
1470
1366
|
}
|
|
1471
|
-
/* Return sanitized string or DOM */
|
|
1472
|
-
|
|
1473
1367
|
|
|
1368
|
+
/* Return sanitized string or DOM */
|
|
1474
1369
|
if (RETURN_DOM) {
|
|
1475
1370
|
if (RETURN_DOM_FRAGMENT) {
|
|
1476
1371
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
1477
|
-
|
|
1478
1372
|
while (body.firstChild) {
|
|
1479
1373
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
1480
1374
|
returnNode.appendChild(body.firstChild);
|
|
@@ -1482,7 +1376,6 @@ function createDOMPurify() {
|
|
|
1482
1376
|
} else {
|
|
1483
1377
|
returnNode = body;
|
|
1484
1378
|
}
|
|
1485
|
-
|
|
1486
1379
|
if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
|
|
1487
1380
|
/*
|
|
1488
1381
|
AdoptNode() is not used because internal state is not reset
|
|
@@ -1493,73 +1386,66 @@ function createDOMPurify() {
|
|
|
1493
1386
|
*/
|
|
1494
1387
|
returnNode = importNode.call(originalDocument, returnNode, true);
|
|
1495
1388
|
}
|
|
1496
|
-
|
|
1497
1389
|
return returnNode;
|
|
1498
1390
|
}
|
|
1499
|
-
|
|
1500
1391
|
let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
|
|
1501
|
-
/* Serialize doctype if allowed */
|
|
1502
1392
|
|
|
1393
|
+
/* Serialize doctype if allowed */
|
|
1503
1394
|
if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
|
|
1504
1395
|
serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
|
|
1505
1396
|
}
|
|
1506
|
-
/* Sanitize final string template-safe */
|
|
1507
|
-
|
|
1508
1397
|
|
|
1398
|
+
/* Sanitize final string template-safe */
|
|
1509
1399
|
if (SAFE_FOR_TEMPLATES) {
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1400
|
+
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1401
|
+
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
1402
|
+
});
|
|
1513
1403
|
}
|
|
1514
|
-
|
|
1515
1404
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
|
|
1516
1405
|
};
|
|
1406
|
+
|
|
1517
1407
|
/**
|
|
1518
1408
|
* Public method to set the configuration once
|
|
1519
1409
|
* setConfig
|
|
1520
1410
|
*
|
|
1521
1411
|
* @param {Object} cfg configuration object
|
|
1522
1412
|
*/
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
DOMPurify.setConfig = function (cfg) {
|
|
1413
|
+
DOMPurify.setConfig = function () {
|
|
1414
|
+
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
1526
1415
|
_parseConfig(cfg);
|
|
1527
|
-
|
|
1528
1416
|
SET_CONFIG = true;
|
|
1529
1417
|
};
|
|
1418
|
+
|
|
1530
1419
|
/**
|
|
1531
1420
|
* Public method to remove the configuration
|
|
1532
1421
|
* clearConfig
|
|
1533
1422
|
*
|
|
1534
1423
|
*/
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
1424
|
DOMPurify.clearConfig = function () {
|
|
1538
1425
|
CONFIG = null;
|
|
1539
1426
|
SET_CONFIG = false;
|
|
1540
1427
|
};
|
|
1428
|
+
|
|
1541
1429
|
/**
|
|
1542
1430
|
* Public method to check if an attribute value is valid.
|
|
1543
1431
|
* Uses last set config, if any. Otherwise, uses config defaults.
|
|
1544
1432
|
* isValidAttribute
|
|
1545
1433
|
*
|
|
1546
|
-
* @param {
|
|
1547
|
-
* @param {
|
|
1548
|
-
* @param {
|
|
1434
|
+
* @param {String} tag Tag name of containing element.
|
|
1435
|
+
* @param {String} attr Attribute name.
|
|
1436
|
+
* @param {String} value Attribute value.
|
|
1549
1437
|
* @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
|
|
1550
1438
|
*/
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
1439
|
DOMPurify.isValidAttribute = function (tag, attr, value) {
|
|
1554
1440
|
/* Initialize shared config vars if necessary. */
|
|
1555
1441
|
if (!CONFIG) {
|
|
1556
1442
|
_parseConfig({});
|
|
1557
1443
|
}
|
|
1558
|
-
|
|
1559
1444
|
const lcTag = transformCaseFunc(tag);
|
|
1560
1445
|
const lcName = transformCaseFunc(attr);
|
|
1561
1446
|
return _isValidAttribute(lcTag, lcName, value);
|
|
1562
1447
|
};
|
|
1448
|
+
|
|
1563
1449
|
/**
|
|
1564
1450
|
* AddHook
|
|
1565
1451
|
* Public method to add DOMPurify hooks
|
|
@@ -1567,16 +1453,14 @@ function createDOMPurify() {
|
|
|
1567
1453
|
* @param {String} entryPoint entry point for the hook to add
|
|
1568
1454
|
* @param {Function} hookFunction function to execute
|
|
1569
1455
|
*/
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
1456
|
DOMPurify.addHook = function (entryPoint, hookFunction) {
|
|
1573
1457
|
if (typeof hookFunction !== 'function') {
|
|
1574
1458
|
return;
|
|
1575
1459
|
}
|
|
1576
|
-
|
|
1577
1460
|
hooks[entryPoint] = hooks[entryPoint] || [];
|
|
1578
1461
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
1579
1462
|
};
|
|
1463
|
+
|
|
1580
1464
|
/**
|
|
1581
1465
|
* RemoveHook
|
|
1582
1466
|
* Public method to remove a DOMPurify hook at a given entryPoint
|
|
@@ -1585,41 +1469,34 @@ function createDOMPurify() {
|
|
|
1585
1469
|
* @param {String} entryPoint entry point for the hook to remove
|
|
1586
1470
|
* @return {Function} removed(popped) hook
|
|
1587
1471
|
*/
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
1472
|
DOMPurify.removeHook = function (entryPoint) {
|
|
1591
1473
|
if (hooks[entryPoint]) {
|
|
1592
1474
|
return arrayPop(hooks[entryPoint]);
|
|
1593
1475
|
}
|
|
1594
1476
|
};
|
|
1477
|
+
|
|
1595
1478
|
/**
|
|
1596
1479
|
* RemoveHooks
|
|
1597
1480
|
* Public method to remove all DOMPurify hooks at a given entryPoint
|
|
1598
1481
|
*
|
|
1599
1482
|
* @param {String} entryPoint entry point for the hooks to remove
|
|
1600
1483
|
*/
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
1484
|
DOMPurify.removeHooks = function (entryPoint) {
|
|
1604
1485
|
if (hooks[entryPoint]) {
|
|
1605
1486
|
hooks[entryPoint] = [];
|
|
1606
1487
|
}
|
|
1607
1488
|
};
|
|
1489
|
+
|
|
1608
1490
|
/**
|
|
1609
1491
|
* RemoveAllHooks
|
|
1610
1492
|
* Public method to remove all DOMPurify hooks
|
|
1611
|
-
*
|
|
1612
1493
|
*/
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
1494
|
DOMPurify.removeAllHooks = function () {
|
|
1616
1495
|
hooks = {};
|
|
1617
1496
|
};
|
|
1618
|
-
|
|
1619
1497
|
return DOMPurify;
|
|
1620
1498
|
}
|
|
1621
|
-
|
|
1622
1499
|
var purify = createDOMPurify();
|
|
1623
1500
|
|
|
1624
1501
|
export { purify as default };
|
|
1625
|
-
//# sourceMappingURL=purify.es.
|
|
1502
|
+
//# sourceMappingURL=purify.es.mjs.map
|