dompurify 3.0.1 → 3.2.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/LICENSE +202 -12
- package/README.md +132 -90
- package/dist/purify.cjs.d.ts +442 -0
- package/dist/purify.cjs.js +601 -1004
- package/dist/purify.cjs.js.map +1 -1
- package/dist/purify.es.d.mts +439 -0
- package/dist/purify.es.mjs +1357 -0
- package/dist/purify.es.mjs.map +1 -0
- package/dist/purify.js +601 -1004
- 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 +65 -22
- package/dist/purify.es.js +0 -1760
- package/dist/purify.es.js.map +0 -1
package/dist/purify.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.
|
|
1
|
+
/*! @license DOMPurify 3.2.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.2.7/LICENSE */
|
|
2
2
|
|
|
3
3
|
(function (global, factory) {
|
|
4
4
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
@@ -6,411 +6,284 @@
|
|
|
6
6
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.DOMPurify = factory());
|
|
7
7
|
})(this, (function () { 'use strict';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return _setPrototypeOf(o, p);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function _isNativeReflectConstruct() {
|
|
29
|
-
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
|
|
30
|
-
if (Reflect.construct.sham) return false;
|
|
31
|
-
if (typeof Proxy === "function") return true;
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
|
|
35
|
-
return true;
|
|
36
|
-
} catch (e) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function _construct(Parent, args, Class) {
|
|
42
|
-
if (_isNativeReflectConstruct()) {
|
|
43
|
-
_construct = Reflect.construct;
|
|
44
|
-
} else {
|
|
45
|
-
_construct = function _construct(Parent, args, Class) {
|
|
46
|
-
var a = [null];
|
|
47
|
-
a.push.apply(a, args);
|
|
48
|
-
var Constructor = Function.bind.apply(Parent, a);
|
|
49
|
-
var instance = new Constructor();
|
|
50
|
-
if (Class) _setPrototypeOf(instance, Class.prototype);
|
|
51
|
-
return instance;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return _construct.apply(null, arguments);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function _slicedToArray(arr, i) {
|
|
59
|
-
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function _toConsumableArray(arr) {
|
|
63
|
-
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function _arrayWithoutHoles(arr) {
|
|
67
|
-
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function _arrayWithHoles(arr) {
|
|
71
|
-
if (Array.isArray(arr)) return arr;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function _iterableToArray(iter) {
|
|
75
|
-
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function _iterableToArrayLimit(arr, i) {
|
|
79
|
-
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
80
|
-
|
|
81
|
-
if (_i == null) return;
|
|
82
|
-
var _arr = [];
|
|
83
|
-
var _n = true;
|
|
84
|
-
var _d = false;
|
|
85
|
-
|
|
86
|
-
var _s, _e;
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {
|
|
90
|
-
_arr.push(_s.value);
|
|
91
|
-
|
|
92
|
-
if (i && _arr.length === i) break;
|
|
93
|
-
}
|
|
94
|
-
} catch (err) {
|
|
95
|
-
_d = true;
|
|
96
|
-
_e = err;
|
|
97
|
-
} finally {
|
|
98
|
-
try {
|
|
99
|
-
if (!_n && _i["return"] != null) _i["return"]();
|
|
100
|
-
} finally {
|
|
101
|
-
if (_d) throw _e;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return _arr;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function _unsupportedIterableToArray(o, minLen) {
|
|
109
|
-
if (!o) return;
|
|
110
|
-
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
|
|
111
|
-
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
112
|
-
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
113
|
-
if (n === "Map" || n === "Set") return Array.from(o);
|
|
114
|
-
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function _arrayLikeToArray(arr, len) {
|
|
118
|
-
if (len == null || len > arr.length) len = arr.length;
|
|
119
|
-
|
|
120
|
-
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
|
121
|
-
|
|
122
|
-
return arr2;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function _nonIterableSpread() {
|
|
126
|
-
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function _nonIterableRest() {
|
|
130
|
-
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function _createForOfIteratorHelper(o, allowArrayLike) {
|
|
134
|
-
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
|
|
135
|
-
|
|
136
|
-
if (!it) {
|
|
137
|
-
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
|
|
138
|
-
if (it) o = it;
|
|
139
|
-
var i = 0;
|
|
140
|
-
|
|
141
|
-
var F = function () {};
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
s: F,
|
|
145
|
-
n: function () {
|
|
146
|
-
if (i >= o.length) return {
|
|
147
|
-
done: true
|
|
148
|
-
};
|
|
149
|
-
return {
|
|
150
|
-
done: false,
|
|
151
|
-
value: o[i++]
|
|
152
|
-
};
|
|
153
|
-
},
|
|
154
|
-
e: function (e) {
|
|
155
|
-
throw e;
|
|
156
|
-
},
|
|
157
|
-
f: F
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
var normalCompletion = true,
|
|
165
|
-
didErr = false,
|
|
166
|
-
err;
|
|
167
|
-
return {
|
|
168
|
-
s: function () {
|
|
169
|
-
it = it.call(o);
|
|
170
|
-
},
|
|
171
|
-
n: function () {
|
|
172
|
-
var step = it.next();
|
|
173
|
-
normalCompletion = step.done;
|
|
174
|
-
return step;
|
|
175
|
-
},
|
|
176
|
-
e: function (e) {
|
|
177
|
-
didErr = true;
|
|
178
|
-
err = e;
|
|
179
|
-
},
|
|
180
|
-
f: function () {
|
|
181
|
-
try {
|
|
182
|
-
if (!normalCompletion && it.return != null) it.return();
|
|
183
|
-
} finally {
|
|
184
|
-
if (didErr) throw err;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
var entries = Object.entries,
|
|
191
|
-
setPrototypeOf = Object.setPrototypeOf,
|
|
192
|
-
isFrozen = Object.isFrozen,
|
|
193
|
-
getPrototypeOf = Object.getPrototypeOf,
|
|
194
|
-
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
195
|
-
var freeze = Object.freeze,
|
|
196
|
-
seal = Object.seal,
|
|
197
|
-
create = Object.create; // eslint-disable-line import/no-mutable-exports
|
|
198
|
-
|
|
199
|
-
var _ref = typeof Reflect !== 'undefined' && Reflect,
|
|
200
|
-
apply = _ref.apply,
|
|
201
|
-
construct = _ref.construct;
|
|
202
|
-
|
|
203
|
-
if (!apply) {
|
|
204
|
-
apply = function apply(fun, thisValue, args) {
|
|
205
|
-
return fun.apply(thisValue, args);
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
9
|
+
const {
|
|
10
|
+
entries,
|
|
11
|
+
setPrototypeOf,
|
|
12
|
+
isFrozen,
|
|
13
|
+
getPrototypeOf,
|
|
14
|
+
getOwnPropertyDescriptor
|
|
15
|
+
} = Object;
|
|
16
|
+
let {
|
|
17
|
+
freeze,
|
|
18
|
+
seal,
|
|
19
|
+
create
|
|
20
|
+
} = Object; // eslint-disable-line import/no-mutable-exports
|
|
21
|
+
let {
|
|
22
|
+
apply,
|
|
23
|
+
construct
|
|
24
|
+
} = typeof Reflect !== 'undefined' && Reflect;
|
|
209
25
|
if (!freeze) {
|
|
210
26
|
freeze = function freeze(x) {
|
|
211
27
|
return x;
|
|
212
28
|
};
|
|
213
29
|
}
|
|
214
|
-
|
|
215
30
|
if (!seal) {
|
|
216
31
|
seal = function seal(x) {
|
|
217
32
|
return x;
|
|
218
33
|
};
|
|
219
34
|
}
|
|
220
|
-
|
|
35
|
+
if (!apply) {
|
|
36
|
+
apply = function apply(func, thisArg) {
|
|
37
|
+
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
|
|
38
|
+
args[_key - 2] = arguments[_key];
|
|
39
|
+
}
|
|
40
|
+
return func.apply(thisArg, args);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
221
43
|
if (!construct) {
|
|
222
|
-
construct = function construct(Func
|
|
223
|
-
|
|
44
|
+
construct = function construct(Func) {
|
|
45
|
+
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
46
|
+
args[_key2 - 1] = arguments[_key2];
|
|
47
|
+
}
|
|
48
|
+
return new Func(...args);
|
|
224
49
|
};
|
|
225
50
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
51
|
+
const arrayForEach = unapply(Array.prototype.forEach);
|
|
52
|
+
const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
53
|
+
const arrayPop = unapply(Array.prototype.pop);
|
|
54
|
+
const arrayPush = unapply(Array.prototype.push);
|
|
55
|
+
const arraySplice = unapply(Array.prototype.splice);
|
|
56
|
+
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
57
|
+
const stringToString = unapply(String.prototype.toString);
|
|
58
|
+
const stringMatch = unapply(String.prototype.match);
|
|
59
|
+
const stringReplace = unapply(String.prototype.replace);
|
|
60
|
+
const stringIndexOf = unapply(String.prototype.indexOf);
|
|
61
|
+
const stringTrim = unapply(String.prototype.trim);
|
|
62
|
+
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
63
|
+
const regExpTest = unapply(RegExp.prototype.test);
|
|
64
|
+
const typeErrorCreate = unconstruct(TypeError);
|
|
65
|
+
/**
|
|
66
|
+
* Creates a new function that calls the given function with a specified thisArg and arguments.
|
|
67
|
+
*
|
|
68
|
+
* @param func - The function to be wrapped and called.
|
|
69
|
+
* @returns A new function that calls the given function with a specified thisArg and arguments.
|
|
70
|
+
*/
|
|
238
71
|
function unapply(func) {
|
|
239
72
|
return function (thisArg) {
|
|
240
|
-
|
|
241
|
-
|
|
73
|
+
if (thisArg instanceof RegExp) {
|
|
74
|
+
thisArg.lastIndex = 0;
|
|
75
|
+
}
|
|
76
|
+
for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
|
|
77
|
+
args[_key3 - 1] = arguments[_key3];
|
|
242
78
|
}
|
|
243
|
-
|
|
244
79
|
return apply(func, thisArg, args);
|
|
245
80
|
};
|
|
246
81
|
}
|
|
247
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Creates a new function that constructs an instance of the given constructor function with the provided arguments.
|
|
84
|
+
*
|
|
85
|
+
* @param func - The constructor function to be wrapped and called.
|
|
86
|
+
* @returns A new function that constructs an instance of the given constructor function with the provided arguments.
|
|
87
|
+
*/
|
|
88
|
+
function unconstruct(Func) {
|
|
248
89
|
return function () {
|
|
249
|
-
for (var
|
|
250
|
-
args[
|
|
90
|
+
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
|
|
91
|
+
args[_key4] = arguments[_key4];
|
|
251
92
|
}
|
|
252
|
-
|
|
253
|
-
return construct(func, args);
|
|
93
|
+
return construct(Func, args);
|
|
254
94
|
};
|
|
255
95
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Add properties to a lookup table
|
|
98
|
+
*
|
|
99
|
+
* @param set - The set to which elements will be added.
|
|
100
|
+
* @param array - The array containing elements to be added to the set.
|
|
101
|
+
* @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
|
|
102
|
+
* @returns The modified set with added elements.
|
|
103
|
+
*/
|
|
104
|
+
function addToSet(set, array) {
|
|
105
|
+
let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
|
|
261
106
|
if (setPrototypeOf) {
|
|
262
107
|
// Make 'in' and truthy checks like Boolean(set.constructor)
|
|
263
108
|
// independent of any properties defined on Object.prototype.
|
|
264
109
|
// Prevent prototype setters from intercepting set as a this value.
|
|
265
110
|
setPrototypeOf(set, null);
|
|
266
111
|
}
|
|
267
|
-
|
|
268
|
-
var l = array.length;
|
|
269
|
-
|
|
112
|
+
let l = array.length;
|
|
270
113
|
while (l--) {
|
|
271
|
-
|
|
272
|
-
|
|
114
|
+
let element = array[l];
|
|
273
115
|
if (typeof element === 'string') {
|
|
274
|
-
|
|
275
|
-
|
|
116
|
+
const lcElement = transformCaseFunc(element);
|
|
276
117
|
if (lcElement !== element) {
|
|
277
118
|
// Config presets (e.g. tags.js, attrs.js) are immutable.
|
|
278
119
|
if (!isFrozen(array)) {
|
|
279
120
|
array[l] = lcElement;
|
|
280
121
|
}
|
|
281
|
-
|
|
282
122
|
element = lcElement;
|
|
283
123
|
}
|
|
284
124
|
}
|
|
285
|
-
|
|
286
125
|
set[element] = true;
|
|
287
126
|
}
|
|
288
|
-
|
|
289
127
|
return set;
|
|
290
128
|
}
|
|
291
|
-
|
|
292
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Clean up an array to harden against CSPP
|
|
131
|
+
*
|
|
132
|
+
* @param array - The array to be cleaned.
|
|
133
|
+
* @returns The cleaned version of the array
|
|
134
|
+
*/
|
|
135
|
+
function cleanArray(array) {
|
|
136
|
+
for (let index = 0; index < array.length; index++) {
|
|
137
|
+
const isPropertyExist = objectHasOwnProperty(array, index);
|
|
138
|
+
if (!isPropertyExist) {
|
|
139
|
+
array[index] = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return array;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Shallow clone an object
|
|
146
|
+
*
|
|
147
|
+
* @param object - The object to be cloned.
|
|
148
|
+
* @returns A new object that copies the original.
|
|
149
|
+
*/
|
|
293
150
|
function clone(object) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
newObject[property] = value;
|
|
151
|
+
const newObject = create(null);
|
|
152
|
+
for (const [property, value] of entries(object)) {
|
|
153
|
+
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
154
|
+
if (isPropertyExist) {
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
newObject[property] = cleanArray(value);
|
|
157
|
+
} else if (value && typeof value === 'object' && value.constructor === Object) {
|
|
158
|
+
newObject[property] = clone(value);
|
|
159
|
+
} else {
|
|
160
|
+
newObject[property] = value;
|
|
161
|
+
}
|
|
306
162
|
}
|
|
307
|
-
} catch (err) {
|
|
308
|
-
_iterator.e(err);
|
|
309
|
-
} finally {
|
|
310
|
-
_iterator.f();
|
|
311
163
|
}
|
|
312
|
-
|
|
313
164
|
return newObject;
|
|
314
165
|
}
|
|
315
|
-
|
|
316
|
-
* or getter and behaves accordingly.
|
|
317
|
-
|
|
166
|
+
/**
|
|
167
|
+
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
168
|
+
*
|
|
169
|
+
* @param object - The object to look up the getter function in its prototype chain.
|
|
170
|
+
* @param prop - The property name for which to find the getter function.
|
|
171
|
+
* @returns The getter function found in the prototype chain or a fallback function.
|
|
172
|
+
*/
|
|
318
173
|
function lookupGetter(object, prop) {
|
|
319
174
|
while (object !== null) {
|
|
320
|
-
|
|
321
|
-
|
|
175
|
+
const desc = getOwnPropertyDescriptor(object, prop);
|
|
322
176
|
if (desc) {
|
|
323
177
|
if (desc.get) {
|
|
324
178
|
return unapply(desc.get);
|
|
325
179
|
}
|
|
326
|
-
|
|
327
180
|
if (typeof desc.value === 'function') {
|
|
328
181
|
return unapply(desc.value);
|
|
329
182
|
}
|
|
330
183
|
}
|
|
331
|
-
|
|
332
184
|
object = getPrototypeOf(object);
|
|
333
185
|
}
|
|
334
|
-
|
|
335
|
-
function fallbackValue(element) {
|
|
336
|
-
console.warn('fallback value for', element);
|
|
186
|
+
function fallbackValue() {
|
|
337
187
|
return null;
|
|
338
188
|
}
|
|
339
|
-
|
|
340
189
|
return fallbackValue;
|
|
341
190
|
}
|
|
342
191
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
192
|
+
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', 'search', 'section', 'select', 'shadow', 'slot', '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']);
|
|
193
|
+
const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'slot', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
|
|
194
|
+
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']);
|
|
195
|
+
// List of SVG elements that are disallowed by default.
|
|
347
196
|
// We still need to know them so that we can do namespace
|
|
348
197
|
// checks properly in case one wants to add them to
|
|
349
198
|
// allow-list.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
199
|
+
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']);
|
|
200
|
+
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']);
|
|
201
|
+
// Similarly to SVG, we want to know all MathML elements,
|
|
353
202
|
// even those that we disallow by default.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
var ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
370
|
-
|
|
371
|
-
var IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
|
203
|
+
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
204
|
+
const text = freeze(['#text']);
|
|
205
|
+
|
|
206
|
+
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
|
|
207
|
+
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
|
|
208
|
+
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']);
|
|
209
|
+
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
210
|
+
|
|
211
|
+
// eslint-disable-next-line unicorn/better-regex
|
|
212
|
+
const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
|
|
213
|
+
const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
|
|
214
|
+
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
215
|
+
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
216
|
+
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
217
|
+
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
|
372
218
|
);
|
|
373
|
-
|
|
374
|
-
|
|
219
|
+
const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
|
|
220
|
+
const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
|
|
375
221
|
);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
222
|
+
const DOCTYPE_NAME = seal(/^html$/i);
|
|
223
|
+
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
224
|
+
|
|
225
|
+
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
226
|
+
__proto__: null,
|
|
227
|
+
ARIA_ATTR: ARIA_ATTR,
|
|
228
|
+
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
229
|
+
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
230
|
+
DATA_ATTR: DATA_ATTR,
|
|
231
|
+
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
232
|
+
ERB_EXPR: ERB_EXPR,
|
|
233
|
+
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
234
|
+
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
235
|
+
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
236
|
+
TMPLIT_EXPR: TMPLIT_EXPR
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
240
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
241
|
+
const NODE_TYPE = {
|
|
242
|
+
element: 1,
|
|
243
|
+
attribute: 2,
|
|
244
|
+
text: 3,
|
|
245
|
+
cdataSection: 4,
|
|
246
|
+
entityReference: 5,
|
|
247
|
+
// Deprecated
|
|
248
|
+
entityNode: 6,
|
|
249
|
+
// Deprecated
|
|
250
|
+
progressingInstruction: 7,
|
|
251
|
+
comment: 8,
|
|
252
|
+
document: 9,
|
|
253
|
+
documentType: 10,
|
|
254
|
+
documentFragment: 11,
|
|
255
|
+
notation: 12 // Deprecated
|
|
256
|
+
};
|
|
257
|
+
const getGlobal = function getGlobal() {
|
|
379
258
|
return typeof window === 'undefined' ? null : window;
|
|
380
259
|
};
|
|
381
260
|
/**
|
|
382
261
|
* Creates a no-op policy for internal use only.
|
|
383
262
|
* Don't export this function outside this module!
|
|
384
|
-
* @param
|
|
385
|
-
* @param
|
|
386
|
-
* @return
|
|
387
|
-
* are not supported).
|
|
263
|
+
* @param trustedTypes The policy factory.
|
|
264
|
+
* @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
|
|
265
|
+
* @return The policy created (or null, if Trusted Types
|
|
266
|
+
* are not supported or creating the policy failed).
|
|
388
267
|
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) {
|
|
392
|
-
if (_typeof(trustedTypes) !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
|
|
268
|
+
const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
|
|
269
|
+
if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
|
|
393
270
|
return null;
|
|
394
|
-
}
|
|
271
|
+
}
|
|
272
|
+
// Allow the callers to control the unique policy name
|
|
395
273
|
// by adding a data-tt-policy-suffix to the script element with the DOMPurify.
|
|
396
274
|
// Policy creation with duplicate names throws in Trusted Types.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) {
|
|
403
|
-
suffix = document.currentScript.getAttribute(ATTR_NAME);
|
|
275
|
+
let suffix = null;
|
|
276
|
+
const ATTR_NAME = 'data-tt-policy-suffix';
|
|
277
|
+
if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
|
|
278
|
+
suffix = purifyHostElement.getAttribute(ATTR_NAME);
|
|
404
279
|
}
|
|
405
|
-
|
|
406
|
-
var policyName = 'dompurify' + (suffix ? '#' + suffix : '');
|
|
407
|
-
|
|
280
|
+
const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
|
|
408
281
|
try {
|
|
409
282
|
return trustedTypes.createPolicy(policyName, {
|
|
410
|
-
createHTML
|
|
283
|
+
createHTML(html) {
|
|
411
284
|
return html;
|
|
412
285
|
},
|
|
413
|
-
createScriptURL
|
|
286
|
+
createScriptURL(scriptUrl) {
|
|
414
287
|
return scriptUrl;
|
|
415
288
|
}
|
|
416
289
|
});
|
|
@@ -422,109 +295,110 @@
|
|
|
422
295
|
return null;
|
|
423
296
|
}
|
|
424
297
|
};
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
298
|
+
const _createHooksMap = function _createHooksMap() {
|
|
299
|
+
return {
|
|
300
|
+
afterSanitizeAttributes: [],
|
|
301
|
+
afterSanitizeElements: [],
|
|
302
|
+
afterSanitizeShadowDOM: [],
|
|
303
|
+
beforeSanitizeAttributes: [],
|
|
304
|
+
beforeSanitizeElements: [],
|
|
305
|
+
beforeSanitizeShadowDOM: [],
|
|
306
|
+
uponSanitizeAttribute: [],
|
|
307
|
+
uponSanitizeElement: [],
|
|
308
|
+
uponSanitizeShadowNode: []
|
|
431
309
|
};
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
DOMPurify.version = '3.0.1';
|
|
439
|
-
/**
|
|
440
|
-
* Array of elements that DOMPurify removed during sanitation.
|
|
441
|
-
* Empty if nothing was removed.
|
|
442
|
-
*/
|
|
443
|
-
|
|
310
|
+
};
|
|
311
|
+
function createDOMPurify() {
|
|
312
|
+
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
313
|
+
const DOMPurify = root => createDOMPurify(root);
|
|
314
|
+
DOMPurify.version = '3.2.7';
|
|
444
315
|
DOMPurify.removed = [];
|
|
445
|
-
|
|
446
|
-
if (!window || !window.document || window.document.nodeType !== 9) {
|
|
316
|
+
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
447
317
|
// Not running in a browser, provide a factory function
|
|
448
318
|
// so that you can pass your own Window
|
|
449
319
|
DOMPurify.isSupported = false;
|
|
450
320
|
return DOMPurify;
|
|
451
321
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
322
|
+
let {
|
|
323
|
+
document
|
|
324
|
+
} = window;
|
|
325
|
+
const originalDocument = document;
|
|
326
|
+
const currentScript = originalDocument.currentScript;
|
|
327
|
+
const {
|
|
328
|
+
DocumentFragment,
|
|
329
|
+
HTMLTemplateElement,
|
|
330
|
+
Node,
|
|
331
|
+
Element,
|
|
332
|
+
NodeFilter,
|
|
333
|
+
NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
|
|
334
|
+
HTMLFormElement,
|
|
335
|
+
DOMParser,
|
|
336
|
+
trustedTypes
|
|
337
|
+
} = window;
|
|
338
|
+
const ElementPrototype = Element.prototype;
|
|
339
|
+
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
340
|
+
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
341
|
+
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
342
|
+
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
343
|
+
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
344
|
+
// As per issue #47, the web-components registry is inherited by a
|
|
470
345
|
// new document created via createHTMLDocument. As per the spec
|
|
471
346
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
472
347
|
// a new empty registry is used when creating a template contents owner
|
|
473
348
|
// document, so we use that as our parent document to ensure nothing
|
|
474
349
|
// is inherited.
|
|
475
|
-
|
|
476
350
|
if (typeof HTMLTemplateElement === 'function') {
|
|
477
|
-
|
|
478
|
-
|
|
351
|
+
const template = document.createElement('template');
|
|
479
352
|
if (template.content && template.content.ownerDocument) {
|
|
480
353
|
document = template.content.ownerDocument;
|
|
481
354
|
}
|
|
482
355
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
356
|
+
let trustedTypesPolicy;
|
|
357
|
+
let emptyHTML = '';
|
|
358
|
+
const {
|
|
359
|
+
implementation,
|
|
360
|
+
createNodeIterator,
|
|
361
|
+
createDocumentFragment,
|
|
362
|
+
getElementsByTagName
|
|
363
|
+
} = document;
|
|
364
|
+
const {
|
|
365
|
+
importNode
|
|
366
|
+
} = originalDocument;
|
|
367
|
+
let hooks = _createHooksMap();
|
|
494
368
|
/**
|
|
495
369
|
* Expose whether this browser supports running the full DOMPurify.
|
|
496
370
|
*/
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
371
|
+
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
372
|
+
const {
|
|
373
|
+
MUSTACHE_EXPR,
|
|
374
|
+
ERB_EXPR,
|
|
375
|
+
TMPLIT_EXPR,
|
|
376
|
+
DATA_ATTR,
|
|
377
|
+
ARIA_ATTR,
|
|
378
|
+
IS_SCRIPT_OR_DATA,
|
|
379
|
+
ATTR_WHITESPACE,
|
|
380
|
+
CUSTOM_ELEMENT
|
|
381
|
+
} = EXPRESSIONS;
|
|
382
|
+
let {
|
|
383
|
+
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
384
|
+
} = EXPRESSIONS;
|
|
507
385
|
/**
|
|
508
386
|
* We consider the elements and attributes below to be safe. Ideally
|
|
509
387
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
510
388
|
*/
|
|
511
|
-
|
|
512
389
|
/* allowed element names */
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(svgFilters), _toConsumableArray(mathMl$1), _toConsumableArray(text)));
|
|
390
|
+
let ALLOWED_TAGS = null;
|
|
391
|
+
const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
|
|
516
392
|
/* Allowed attribute names */
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(mathMl), _toConsumableArray(xml)));
|
|
393
|
+
let ALLOWED_ATTR = null;
|
|
394
|
+
const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
|
|
520
395
|
/*
|
|
521
|
-
* Configure how
|
|
396
|
+
* Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
|
|
522
397
|
* @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
|
|
523
398
|
* @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
|
|
524
399
|
* @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
|
|
525
400
|
*/
|
|
526
|
-
|
|
527
|
-
var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
|
|
401
|
+
let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
|
|
528
402
|
tagNameCheck: {
|
|
529
403
|
writable: true,
|
|
530
404
|
configurable: false,
|
|
@@ -545,58 +419,48 @@
|
|
|
545
419
|
}
|
|
546
420
|
}));
|
|
547
421
|
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
|
|
548
|
-
|
|
549
|
-
var FORBID_TAGS = null;
|
|
422
|
+
let FORBID_TAGS = null;
|
|
550
423
|
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
|
|
551
|
-
|
|
552
|
-
var FORBID_ATTR = null;
|
|
424
|
+
let FORBID_ATTR = null;
|
|
553
425
|
/* Decide if ARIA attributes are okay */
|
|
554
|
-
|
|
555
|
-
var ALLOW_ARIA_ATTR = true;
|
|
426
|
+
let ALLOW_ARIA_ATTR = true;
|
|
556
427
|
/* Decide if custom data attributes are okay */
|
|
557
|
-
|
|
558
|
-
var ALLOW_DATA_ATTR = true;
|
|
428
|
+
let ALLOW_DATA_ATTR = true;
|
|
559
429
|
/* Decide if unknown protocols are okay */
|
|
560
|
-
|
|
561
|
-
var ALLOW_UNKNOWN_PROTOCOLS = false;
|
|
430
|
+
let ALLOW_UNKNOWN_PROTOCOLS = false;
|
|
562
431
|
/* Decide if self-closing tags in attributes are allowed.
|
|
563
432
|
* Usually removed due to a mXSS issue in jQuery 3.0 */
|
|
564
|
-
|
|
565
|
-
var ALLOW_SELF_CLOSE_IN_ATTR = true;
|
|
433
|
+
let ALLOW_SELF_CLOSE_IN_ATTR = true;
|
|
566
434
|
/* Output should be safe for common template engines.
|
|
567
435
|
* This means, DOMPurify removes data attributes, mustaches and ERB
|
|
568
436
|
*/
|
|
569
|
-
|
|
570
|
-
|
|
437
|
+
let SAFE_FOR_TEMPLATES = false;
|
|
438
|
+
/* Output should be safe even for XML used within HTML and alike.
|
|
439
|
+
* This means, DOMPurify removes comments when containing risky content.
|
|
440
|
+
*/
|
|
441
|
+
let SAFE_FOR_XML = true;
|
|
571
442
|
/* Decide if document with <html>... should be returned */
|
|
572
|
-
|
|
573
|
-
var WHOLE_DOCUMENT = false;
|
|
443
|
+
let WHOLE_DOCUMENT = false;
|
|
574
444
|
/* Track whether config is already set on this instance of DOMPurify. */
|
|
575
|
-
|
|
576
|
-
var SET_CONFIG = false;
|
|
445
|
+
let SET_CONFIG = false;
|
|
577
446
|
/* Decide if all elements (e.g. style, script) must be children of
|
|
578
447
|
* document.body. By default, browsers might move them to document.head */
|
|
579
|
-
|
|
580
|
-
var FORCE_BODY = false;
|
|
448
|
+
let FORCE_BODY = false;
|
|
581
449
|
/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
|
|
582
450
|
* string (or a TrustedHTML object if Trusted Types are supported).
|
|
583
451
|
* If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
|
|
584
452
|
*/
|
|
585
|
-
|
|
586
|
-
var RETURN_DOM = false;
|
|
453
|
+
let RETURN_DOM = false;
|
|
587
454
|
/* Decide if a DOM `DocumentFragment` should be returned, instead of a html
|
|
588
455
|
* string (or a TrustedHTML object if Trusted Types are supported) */
|
|
589
|
-
|
|
590
|
-
var RETURN_DOM_FRAGMENT = false;
|
|
456
|
+
let RETURN_DOM_FRAGMENT = false;
|
|
591
457
|
/* Try to return a Trusted Type object instead of a string, return a string in
|
|
592
458
|
* case Trusted Types are not supported */
|
|
593
|
-
|
|
594
|
-
var RETURN_TRUSTED_TYPE = false;
|
|
459
|
+
let RETURN_TRUSTED_TYPE = false;
|
|
595
460
|
/* Output should be free from DOM clobbering attacks?
|
|
596
461
|
* This sanitizes markups named with colliding, clobberable built-in DOM APIs.
|
|
597
462
|
*/
|
|
598
|
-
|
|
599
|
-
var SANITIZE_DOM = true;
|
|
463
|
+
let SANITIZE_DOM = true;
|
|
600
464
|
/* Achieve full DOM Clobbering protection by isolating the namespace of named
|
|
601
465
|
* properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
|
|
602
466
|
*
|
|
@@ -610,180 +474,138 @@
|
|
|
610
474
|
* Namespace isolation is implemented by prefixing `id` and `name` attributes
|
|
611
475
|
* with a constant string, i.e., `user-content-`
|
|
612
476
|
*/
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
var SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
|
|
477
|
+
let SANITIZE_NAMED_PROPS = false;
|
|
478
|
+
const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
|
|
616
479
|
/* Keep element content when removing element? */
|
|
617
|
-
|
|
618
|
-
var KEEP_CONTENT = true;
|
|
480
|
+
let KEEP_CONTENT = true;
|
|
619
481
|
/* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
|
|
620
482
|
* of importing it into a new Document and returning a sanitized copy */
|
|
621
|
-
|
|
622
|
-
var IN_PLACE = false;
|
|
483
|
+
let IN_PLACE = false;
|
|
623
484
|
/* Allow usage of profiles like html, svg and mathMl */
|
|
624
|
-
|
|
625
|
-
var USE_PROFILES = {};
|
|
485
|
+
let USE_PROFILES = {};
|
|
626
486
|
/* Tags to ignore content of when KEEP_CONTENT is true */
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
var 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']);
|
|
487
|
+
let FORBID_CONTENTS = null;
|
|
488
|
+
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']);
|
|
630
489
|
/* Tags that are safe for data: URIs */
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
|
|
490
|
+
let DATA_URI_TAGS = null;
|
|
491
|
+
const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
|
|
634
492
|
/* Attributes safe for values like "javascript:" */
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
|
493
|
+
let URI_SAFE_ATTRIBUTES = null;
|
|
494
|
+
const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
|
|
495
|
+
const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
|
|
496
|
+
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
497
|
+
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
|
641
498
|
/* Document namespace */
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
var IS_EMPTY_INPUT = false;
|
|
499
|
+
let NAMESPACE = HTML_NAMESPACE;
|
|
500
|
+
let IS_EMPTY_INPUT = false;
|
|
645
501
|
/* Allowed XHTML+XML namespaces */
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
502
|
+
let ALLOWED_NAMESPACES = null;
|
|
503
|
+
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
504
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
505
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
|
|
506
|
+
// Certain elements are allowed in both SVG and HTML
|
|
507
|
+
// namespace. We need to specify them explicitly
|
|
508
|
+
// so that they don't get erroneously deleted from
|
|
509
|
+
// HTML namespace.
|
|
510
|
+
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
|
|
649
511
|
/* Parsing of strict XHTML documents */
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
var transformCaseFunc;
|
|
512
|
+
let PARSER_MEDIA_TYPE = null;
|
|
513
|
+
const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
|
|
514
|
+
const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
|
|
515
|
+
let transformCaseFunc = null;
|
|
655
516
|
/* Keep a reference to config to pass to hooks */
|
|
656
|
-
|
|
657
|
-
var CONFIG = null;
|
|
517
|
+
let CONFIG = null;
|
|
658
518
|
/* Ideally, do not touch anything below this line */
|
|
659
|
-
|
|
660
519
|
/* ______________________________________________ */
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
var isRegexOrFunction = function isRegexOrFunction(testValue) {
|
|
520
|
+
const formElement = document.createElement('form');
|
|
521
|
+
const isRegexOrFunction = function isRegexOrFunction(testValue) {
|
|
665
522
|
return testValue instanceof RegExp || testValue instanceof Function;
|
|
666
523
|
};
|
|
667
524
|
/**
|
|
668
525
|
* _parseConfig
|
|
669
526
|
*
|
|
670
|
-
* @param
|
|
527
|
+
* @param cfg optional config literal
|
|
671
528
|
*/
|
|
672
529
|
// eslint-disable-next-line complexity
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
var _parseConfig = function _parseConfig(cfg) {
|
|
530
|
+
const _parseConfig = function _parseConfig() {
|
|
531
|
+
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
676
532
|
if (CONFIG && CONFIG === cfg) {
|
|
677
533
|
return;
|
|
678
534
|
}
|
|
679
535
|
/* Shield configuration object from tampering */
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (!cfg || _typeof(cfg) !== 'object') {
|
|
536
|
+
if (!cfg || typeof cfg !== 'object') {
|
|
683
537
|
cfg = {};
|
|
684
538
|
}
|
|
685
539
|
/* Shield configuration object from prototype pollution */
|
|
686
|
-
|
|
687
|
-
|
|
688
540
|
cfg = clone(cfg);
|
|
689
|
-
PARSER_MEDIA_TYPE =
|
|
690
|
-
|
|
691
|
-
|
|
541
|
+
PARSER_MEDIA_TYPE =
|
|
542
|
+
// eslint-disable-next-line unicorn/prefer-includes
|
|
543
|
+
SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
|
|
544
|
+
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
692
545
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
693
546
|
/* Set configuration parameters */
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
cfg.
|
|
700
|
-
transformCaseFunc
|
|
701
|
-
)
|
|
702
|
-
:
|
|
703
|
-
DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent
|
|
704
|
-
cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent
|
|
705
|
-
transformCaseFunc // eslint-disable-line indent
|
|
706
|
-
) // eslint-disable-line indent
|
|
707
|
-
: DEFAULT_DATA_URI_TAGS;
|
|
708
|
-
FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
|
|
709
|
-
FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
|
|
710
|
-
FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
|
|
711
|
-
USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
|
|
547
|
+
ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
|
|
548
|
+
ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
|
|
549
|
+
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
|
|
550
|
+
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
|
|
551
|
+
DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
|
|
552
|
+
FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
|
|
553
|
+
FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
|
|
554
|
+
FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
|
|
555
|
+
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
|
|
712
556
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
713
|
-
|
|
714
557
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
715
|
-
|
|
716
558
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
|
|
717
|
-
|
|
718
559
|
ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
|
|
719
|
-
|
|
720
560
|
SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
|
|
721
|
-
|
|
561
|
+
SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
|
|
722
562
|
WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
|
|
723
|
-
|
|
724
563
|
RETURN_DOM = cfg.RETURN_DOM || false; // Default false
|
|
725
|
-
|
|
726
564
|
RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
|
|
727
|
-
|
|
728
565
|
RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
|
|
729
|
-
|
|
730
566
|
FORCE_BODY = cfg.FORCE_BODY || false; // Default false
|
|
731
|
-
|
|
732
567
|
SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
|
|
733
|
-
|
|
734
568
|
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
|
|
735
|
-
|
|
736
569
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
|
|
737
|
-
|
|
738
570
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
739
|
-
|
|
740
|
-
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$1;
|
|
571
|
+
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
|
|
741
572
|
NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
|
|
573
|
+
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
|
|
574
|
+
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
|
|
742
575
|
CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
|
|
743
|
-
|
|
744
576
|
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
|
|
745
577
|
CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
|
|
746
578
|
}
|
|
747
|
-
|
|
748
579
|
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
|
|
749
580
|
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
|
|
750
581
|
}
|
|
751
|
-
|
|
752
582
|
if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
|
|
753
583
|
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
|
|
754
584
|
}
|
|
755
|
-
|
|
756
585
|
if (SAFE_FOR_TEMPLATES) {
|
|
757
586
|
ALLOW_DATA_ATTR = false;
|
|
758
587
|
}
|
|
759
|
-
|
|
760
588
|
if (RETURN_DOM_FRAGMENT) {
|
|
761
589
|
RETURN_DOM = true;
|
|
762
590
|
}
|
|
763
591
|
/* Parse profile info */
|
|
764
|
-
|
|
765
|
-
|
|
766
592
|
if (USE_PROFILES) {
|
|
767
|
-
ALLOWED_TAGS = addToSet({},
|
|
593
|
+
ALLOWED_TAGS = addToSet({}, text);
|
|
768
594
|
ALLOWED_ATTR = [];
|
|
769
|
-
|
|
770
595
|
if (USE_PROFILES.html === true) {
|
|
771
596
|
addToSet(ALLOWED_TAGS, html$1);
|
|
772
597
|
addToSet(ALLOWED_ATTR, html);
|
|
773
598
|
}
|
|
774
|
-
|
|
775
599
|
if (USE_PROFILES.svg === true) {
|
|
776
600
|
addToSet(ALLOWED_TAGS, svg$1);
|
|
777
601
|
addToSet(ALLOWED_ATTR, svg);
|
|
778
602
|
addToSet(ALLOWED_ATTR, xml);
|
|
779
603
|
}
|
|
780
|
-
|
|
781
604
|
if (USE_PROFILES.svgFilters === true) {
|
|
782
605
|
addToSet(ALLOWED_TAGS, svgFilters);
|
|
783
606
|
addToSet(ALLOWED_ATTR, svg);
|
|
784
607
|
addToSet(ALLOWED_ATTR, xml);
|
|
785
608
|
}
|
|
786
|
-
|
|
787
609
|
if (USE_PROFILES.mathMl === true) {
|
|
788
610
|
addToSet(ALLOWED_TAGS, mathMl$1);
|
|
789
611
|
addToSet(ALLOWED_ATTR, mathMl);
|
|
@@ -791,146 +613,127 @@
|
|
|
791
613
|
}
|
|
792
614
|
}
|
|
793
615
|
/* Merge configuration parameters */
|
|
794
|
-
|
|
795
|
-
|
|
796
616
|
if (cfg.ADD_TAGS) {
|
|
797
617
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
798
618
|
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
799
619
|
}
|
|
800
|
-
|
|
801
620
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
802
621
|
}
|
|
803
|
-
|
|
804
622
|
if (cfg.ADD_ATTR) {
|
|
805
623
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
806
624
|
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
807
625
|
}
|
|
808
|
-
|
|
809
626
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
810
627
|
}
|
|
811
|
-
|
|
812
628
|
if (cfg.ADD_URI_SAFE_ATTR) {
|
|
813
629
|
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
814
630
|
}
|
|
815
|
-
|
|
816
631
|
if (cfg.FORBID_CONTENTS) {
|
|
817
632
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
818
633
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
819
634
|
}
|
|
820
|
-
|
|
821
635
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
822
636
|
}
|
|
823
637
|
/* Add #text in case KEEP_CONTENT is set to true */
|
|
824
|
-
|
|
825
|
-
|
|
826
638
|
if (KEEP_CONTENT) {
|
|
827
639
|
ALLOWED_TAGS['#text'] = true;
|
|
828
640
|
}
|
|
829
641
|
/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
|
|
830
|
-
|
|
831
|
-
|
|
832
642
|
if (WHOLE_DOCUMENT) {
|
|
833
643
|
addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
|
|
834
644
|
}
|
|
835
645
|
/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
|
|
836
|
-
|
|
837
|
-
|
|
838
646
|
if (ALLOWED_TAGS.table) {
|
|
839
647
|
addToSet(ALLOWED_TAGS, ['tbody']);
|
|
840
648
|
delete FORBID_TAGS.tbody;
|
|
841
|
-
}
|
|
649
|
+
}
|
|
650
|
+
if (cfg.TRUSTED_TYPES_POLICY) {
|
|
651
|
+
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
|
|
652
|
+
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
|
|
653
|
+
}
|
|
654
|
+
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
|
|
655
|
+
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
656
|
+
}
|
|
657
|
+
// Overwrite existing TrustedTypes policy.
|
|
658
|
+
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
659
|
+
// Sign local variables required by `sanitize`.
|
|
660
|
+
emptyHTML = trustedTypesPolicy.createHTML('');
|
|
661
|
+
} else {
|
|
662
|
+
// Uninitialized policy, attempt to initialize the internal dompurify policy.
|
|
663
|
+
if (trustedTypesPolicy === undefined) {
|
|
664
|
+
trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
665
|
+
}
|
|
666
|
+
// If creating the internal policy succeeded sign internal variables.
|
|
667
|
+
if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
|
|
668
|
+
emptyHTML = trustedTypesPolicy.createHTML('');
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
// Prevent further manipulation of configuration.
|
|
842
672
|
// Not available in IE8, Safari 5, etc.
|
|
843
|
-
|
|
844
|
-
|
|
845
673
|
if (freeze) {
|
|
846
674
|
freeze(cfg);
|
|
847
675
|
}
|
|
848
|
-
|
|
849
676
|
CONFIG = cfg;
|
|
850
677
|
};
|
|
851
|
-
|
|
852
|
-
var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
853
|
-
var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']); // Certain elements are allowed in both SVG and HTML
|
|
854
|
-
// namespace. We need to specify them explicitly
|
|
855
|
-
// so that they don't get erroneously deleted from
|
|
856
|
-
// HTML namespace.
|
|
857
|
-
|
|
858
|
-
var COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
|
|
859
678
|
/* Keep track of all possible SVG and MathML tags
|
|
860
679
|
* so that we can perform the namespace checks
|
|
861
680
|
* correctly. */
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
addToSet(ALL_SVG_TAGS, svgFilters);
|
|
865
|
-
addToSet(ALL_SVG_TAGS, svgDisallowed);
|
|
866
|
-
var ALL_MATHML_TAGS = addToSet({}, mathMl$1);
|
|
867
|
-
addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
|
|
681
|
+
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
682
|
+
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
868
683
|
/**
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
* @param {Element} element a DOM element whose namespace is being checked
|
|
872
|
-
* @returns {boolean} Return false if the element has a
|
|
684
|
+
* @param element a DOM element whose namespace is being checked
|
|
685
|
+
* @returns Return false if the element has a
|
|
873
686
|
* namespace that a spec-compliant parser would never
|
|
874
687
|
* return. Return true otherwise.
|
|
875
688
|
*/
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
689
|
+
const _checkValidNamespace = function _checkValidNamespace(element) {
|
|
690
|
+
let parent = getParentNode(element);
|
|
691
|
+
// In JSDOM, if we're inside shadow DOM, then parentNode
|
|
879
692
|
// can be null. We just simulate parent in this case.
|
|
880
|
-
|
|
881
693
|
if (!parent || !parent.tagName) {
|
|
882
694
|
parent = {
|
|
883
695
|
namespaceURI: NAMESPACE,
|
|
884
696
|
tagName: 'template'
|
|
885
697
|
};
|
|
886
698
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
var parentTagName = stringToLowerCase(parent.tagName);
|
|
890
|
-
|
|
699
|
+
const tagName = stringToLowerCase(element.tagName);
|
|
700
|
+
const parentTagName = stringToLowerCase(parent.tagName);
|
|
891
701
|
if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
892
702
|
return false;
|
|
893
703
|
}
|
|
894
|
-
|
|
895
704
|
if (element.namespaceURI === SVG_NAMESPACE) {
|
|
896
705
|
// The only way to switch from HTML namespace to SVG
|
|
897
706
|
// is via <svg>. If it happens via any other tag, then
|
|
898
707
|
// it should be killed.
|
|
899
708
|
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
900
709
|
return tagName === 'svg';
|
|
901
|
-
}
|
|
710
|
+
}
|
|
711
|
+
// The only way to switch from MathML to SVG is via`
|
|
902
712
|
// svg if parent is either <annotation-xml> or MathML
|
|
903
713
|
// text integration points.
|
|
904
|
-
|
|
905
|
-
|
|
906
714
|
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
907
715
|
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
908
|
-
}
|
|
716
|
+
}
|
|
717
|
+
// We only allow elements that are defined in SVG
|
|
909
718
|
// spec. All others are disallowed in SVG namespace.
|
|
910
|
-
|
|
911
|
-
|
|
912
719
|
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
913
720
|
}
|
|
914
|
-
|
|
915
721
|
if (element.namespaceURI === MATHML_NAMESPACE) {
|
|
916
722
|
// The only way to switch from HTML namespace to MathML
|
|
917
723
|
// is via <math>. If it happens via any other tag, then
|
|
918
724
|
// it should be killed.
|
|
919
725
|
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
920
726
|
return tagName === 'math';
|
|
921
|
-
}
|
|
727
|
+
}
|
|
728
|
+
// The only way to switch from SVG to MathML is via
|
|
922
729
|
// <math> and HTML integration points
|
|
923
|
-
|
|
924
|
-
|
|
925
730
|
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
926
731
|
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
927
|
-
}
|
|
732
|
+
}
|
|
733
|
+
// We only allow elements that are defined in MathML
|
|
928
734
|
// spec. All others are disallowed in MathML namespace.
|
|
929
|
-
|
|
930
|
-
|
|
931
735
|
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
932
736
|
}
|
|
933
|
-
|
|
934
737
|
if (element.namespaceURI === HTML_NAMESPACE) {
|
|
935
738
|
// The only way to switch from SVG to HTML is via
|
|
936
739
|
// HTML integration points, and from MathML to HTML
|
|
@@ -938,77 +741,67 @@
|
|
|
938
741
|
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
939
742
|
return false;
|
|
940
743
|
}
|
|
941
|
-
|
|
942
744
|
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
943
745
|
return false;
|
|
944
|
-
}
|
|
746
|
+
}
|
|
747
|
+
// We disallow tags that are specific for MathML
|
|
945
748
|
// or SVG and should never appear in HTML namespace
|
|
946
|
-
|
|
947
|
-
|
|
948
749
|
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
|
|
750
|
+
}
|
|
751
|
+
// For XHTML and XML documents that support custom namespaces
|
|
952
752
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
953
753
|
return true;
|
|
954
|
-
}
|
|
754
|
+
}
|
|
755
|
+
// The code should never reach this place (this means
|
|
955
756
|
// that the element somehow got namespace that is not
|
|
956
757
|
// HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
|
|
957
758
|
// Return false just in case.
|
|
958
|
-
|
|
959
|
-
|
|
960
759
|
return false;
|
|
961
760
|
};
|
|
962
761
|
/**
|
|
963
762
|
* _forceRemove
|
|
964
763
|
*
|
|
965
|
-
* @param
|
|
764
|
+
* @param node a DOM node
|
|
966
765
|
*/
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
var _forceRemove = function _forceRemove(node) {
|
|
766
|
+
const _forceRemove = function _forceRemove(node) {
|
|
970
767
|
arrayPush(DOMPurify.removed, {
|
|
971
768
|
element: node
|
|
972
769
|
});
|
|
973
|
-
|
|
974
770
|
try {
|
|
975
771
|
// eslint-disable-next-line unicorn/prefer-dom-node-remove
|
|
976
|
-
node.
|
|
772
|
+
getParentNode(node).removeChild(node);
|
|
977
773
|
} catch (_) {
|
|
978
|
-
|
|
774
|
+
remove(node);
|
|
979
775
|
}
|
|
980
776
|
};
|
|
981
777
|
/**
|
|
982
778
|
* _removeAttribute
|
|
983
779
|
*
|
|
984
|
-
* @param
|
|
985
|
-
* @param
|
|
780
|
+
* @param name an Attribute name
|
|
781
|
+
* @param element a DOM node
|
|
986
782
|
*/
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
var _removeAttribute = function _removeAttribute(name, node) {
|
|
783
|
+
const _removeAttribute = function _removeAttribute(name, element) {
|
|
990
784
|
try {
|
|
991
785
|
arrayPush(DOMPurify.removed, {
|
|
992
|
-
attribute:
|
|
993
|
-
from:
|
|
786
|
+
attribute: element.getAttributeNode(name),
|
|
787
|
+
from: element
|
|
994
788
|
});
|
|
995
789
|
} catch (_) {
|
|
996
790
|
arrayPush(DOMPurify.removed, {
|
|
997
791
|
attribute: null,
|
|
998
|
-
from:
|
|
792
|
+
from: element
|
|
999
793
|
});
|
|
1000
794
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
if (name === 'is' && !ALLOWED_ATTR[name]) {
|
|
795
|
+
element.removeAttribute(name);
|
|
796
|
+
// We void attribute values for unremovable "is" attributes
|
|
797
|
+
if (name === 'is') {
|
|
1005
798
|
if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
|
|
1006
799
|
try {
|
|
1007
|
-
_forceRemove(
|
|
800
|
+
_forceRemove(element);
|
|
1008
801
|
} catch (_) {}
|
|
1009
802
|
} else {
|
|
1010
803
|
try {
|
|
1011
|
-
|
|
804
|
+
element.setAttribute(name, '');
|
|
1012
805
|
} catch (_) {}
|
|
1013
806
|
}
|
|
1014
807
|
}
|
|
@@ -1016,217 +809,171 @@
|
|
|
1016
809
|
/**
|
|
1017
810
|
* _initDocument
|
|
1018
811
|
*
|
|
1019
|
-
* @param
|
|
1020
|
-
* @return
|
|
812
|
+
* @param dirty - a string of dirty markup
|
|
813
|
+
* @return a DOM, filled with the dirty markup
|
|
1021
814
|
*/
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
var _initDocument = function _initDocument(dirty) {
|
|
815
|
+
const _initDocument = function _initDocument(dirty) {
|
|
1025
816
|
/* Create a HTML document */
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
817
|
+
let doc = null;
|
|
818
|
+
let leadingWhitespace = null;
|
|
1029
819
|
if (FORCE_BODY) {
|
|
1030
820
|
dirty = '<remove></remove>' + dirty;
|
|
1031
821
|
} else {
|
|
1032
822
|
/* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
|
|
1033
|
-
|
|
823
|
+
const matches = stringMatch(dirty, /^[\r\n\t ]+/);
|
|
1034
824
|
leadingWhitespace = matches && matches[0];
|
|
1035
825
|
}
|
|
1036
|
-
|
|
1037
826
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
|
|
1038
827
|
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
|
|
1039
828
|
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
|
|
1040
829
|
}
|
|
1041
|
-
|
|
1042
|
-
var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
|
|
830
|
+
const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
|
|
1043
831
|
/*
|
|
1044
832
|
* Use the DOMParser API by default, fallback later if needs be
|
|
1045
833
|
* DOMParser not work for svg when has multiple root element.
|
|
1046
834
|
*/
|
|
1047
|
-
|
|
1048
835
|
if (NAMESPACE === HTML_NAMESPACE) {
|
|
1049
836
|
try {
|
|
1050
837
|
doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
|
|
1051
838
|
} catch (_) {}
|
|
1052
839
|
}
|
|
1053
840
|
/* Use createHTMLDocument in case DOMParser is not available */
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
841
|
if (!doc || !doc.documentElement) {
|
|
1057
842
|
doc = implementation.createDocument(NAMESPACE, 'template', null);
|
|
1058
|
-
|
|
1059
843
|
try {
|
|
1060
844
|
doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
|
|
1061
|
-
} catch (_) {
|
|
845
|
+
} catch (_) {
|
|
846
|
+
// Syntax error if dirtyPayload is invalid xml
|
|
1062
847
|
}
|
|
1063
848
|
}
|
|
1064
|
-
|
|
1065
|
-
var body = doc.body || doc.documentElement;
|
|
1066
|
-
|
|
849
|
+
const body = doc.body || doc.documentElement;
|
|
1067
850
|
if (dirty && leadingWhitespace) {
|
|
1068
851
|
body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
|
|
1069
852
|
}
|
|
1070
853
|
/* Work on whole document or just its body */
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
854
|
if (NAMESPACE === HTML_NAMESPACE) {
|
|
1074
855
|
return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
|
|
1075
856
|
}
|
|
1076
|
-
|
|
1077
857
|
return WHOLE_DOCUMENT ? doc.documentElement : body;
|
|
1078
858
|
};
|
|
1079
859
|
/**
|
|
1080
|
-
*
|
|
860
|
+
* Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
|
|
1081
861
|
*
|
|
1082
|
-
* @param
|
|
1083
|
-
* @return
|
|
862
|
+
* @param root The root element or node to start traversing on.
|
|
863
|
+
* @return The created NodeIterator
|
|
1084
864
|
*/
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
|
|
865
|
+
const _createNodeIterator = function _createNodeIterator(root) {
|
|
866
|
+
return createNodeIterator.call(root.ownerDocument || root, root,
|
|
867
|
+
// eslint-disable-next-line no-bitwise
|
|
868
|
+
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
1090
869
|
};
|
|
1091
870
|
/**
|
|
1092
871
|
* _isClobbered
|
|
1093
872
|
*
|
|
1094
|
-
* @param
|
|
1095
|
-
* @return
|
|
873
|
+
* @param element element to check for clobbering attacks
|
|
874
|
+
* @return true if clobbered, false if safe
|
|
1096
875
|
*/
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
var _isClobbered = function _isClobbered(elm) {
|
|
1100
|
-
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');
|
|
876
|
+
const _isClobbered = function _isClobbered(element) {
|
|
877
|
+
return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
|
|
1101
878
|
};
|
|
1102
879
|
/**
|
|
1103
|
-
*
|
|
880
|
+
* Checks whether the given object is a DOM node.
|
|
1104
881
|
*
|
|
1105
|
-
* @param
|
|
1106
|
-
* @return
|
|
882
|
+
* @param value object to check whether it's a DOM node
|
|
883
|
+
* @return true is object is a DOM node
|
|
1107
884
|
*/
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
var _isNode = function _isNode(object) {
|
|
1111
|
-
return _typeof(Node) === 'object' ? object instanceof Node : object && _typeof(object) === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string';
|
|
885
|
+
const _isNode = function _isNode(value) {
|
|
886
|
+
return typeof Node === 'function' && value instanceof Node;
|
|
1112
887
|
};
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
* Execute user configurable hooks
|
|
1116
|
-
*
|
|
1117
|
-
* @param {String} entryPoint Name of the hook's entry point
|
|
1118
|
-
* @param {Node} currentNode node to work on with the hook
|
|
1119
|
-
* @param {Object} data additional hook parameters
|
|
1120
|
-
*/
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
var _executeHook = function _executeHook(entryPoint, currentNode, data) {
|
|
1124
|
-
if (!hooks[entryPoint]) {
|
|
1125
|
-
return;
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
arrayForEach(hooks[entryPoint], function (hook) {
|
|
888
|
+
function _executeHooks(hooks, currentNode, data) {
|
|
889
|
+
arrayForEach(hooks, hook => {
|
|
1129
890
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
1130
891
|
});
|
|
1131
|
-
}
|
|
892
|
+
}
|
|
1132
893
|
/**
|
|
1133
894
|
* _sanitizeElements
|
|
1134
895
|
*
|
|
1135
896
|
* @protect nodeName
|
|
1136
897
|
* @protect textContent
|
|
1137
898
|
* @protect removeChild
|
|
1138
|
-
*
|
|
1139
|
-
* @
|
|
1140
|
-
* @return {Boolean} true if node was killed, false if left alive
|
|
899
|
+
* @param currentNode to check for permission to exist
|
|
900
|
+
* @return true if node was killed, false if left alive
|
|
1141
901
|
*/
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
var _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
1145
|
-
var content;
|
|
902
|
+
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
903
|
+
let content = null;
|
|
1146
904
|
/* Execute a hook if present */
|
|
1147
|
-
|
|
1148
|
-
_executeHook('beforeSanitizeElements', currentNode, null);
|
|
905
|
+
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
1149
906
|
/* Check if element is clobbered or can clobber */
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
907
|
if (_isClobbered(currentNode)) {
|
|
1153
908
|
_forceRemove(currentNode);
|
|
1154
|
-
|
|
1155
909
|
return true;
|
|
1156
910
|
}
|
|
1157
911
|
/* Now let's check the element's type and name */
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
var tagName = transformCaseFunc(currentNode.nodeName);
|
|
912
|
+
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
1161
913
|
/* Execute a hook if present */
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
tagName: tagName,
|
|
914
|
+
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
915
|
+
tagName,
|
|
1165
916
|
allowedTags: ALLOWED_TAGS
|
|
1166
917
|
});
|
|
1167
918
|
/* Detect mXSS attempts abusing namespace confusion */
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
919
|
+
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
|
|
920
|
+
_forceRemove(currentNode);
|
|
921
|
+
return true;
|
|
922
|
+
}
|
|
923
|
+
/* Remove any occurrence of processing instructions */
|
|
924
|
+
if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
|
|
925
|
+
_forceRemove(currentNode);
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
/* Remove any kind of possibly harmful comments */
|
|
929
|
+
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
|
|
1171
930
|
_forceRemove(currentNode);
|
|
1172
|
-
|
|
1173
931
|
return true;
|
|
1174
932
|
}
|
|
1175
933
|
/* Remove element if anything forbids its presence */
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
934
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
1179
935
|
/* Check if we have a custom element to handle */
|
|
1180
|
-
if (!FORBID_TAGS[tagName] &&
|
|
1181
|
-
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName))
|
|
1182
|
-
|
|
936
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
937
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
1183
943
|
}
|
|
1184
944
|
/* Keep content except for bad-listed elements */
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
945
|
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
946
|
+
const parentNode = getParentNode(currentNode) || currentNode.parentNode;
|
|
947
|
+
const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
|
|
1191
948
|
if (childNodes && parentNode) {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
949
|
+
const childCount = childNodes.length;
|
|
950
|
+
for (let i = childCount - 1; i >= 0; --i) {
|
|
951
|
+
const childClone = cloneNode(childNodes[i], true);
|
|
952
|
+
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
|
|
953
|
+
parentNode.insertBefore(childClone, getNextSibling(currentNode));
|
|
1196
954
|
}
|
|
1197
955
|
}
|
|
1198
956
|
}
|
|
1199
|
-
|
|
1200
957
|
_forceRemove(currentNode);
|
|
1201
|
-
|
|
1202
958
|
return true;
|
|
1203
959
|
}
|
|
1204
960
|
/* Check whether element has a valid namespace */
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
961
|
if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
|
|
1208
962
|
_forceRemove(currentNode);
|
|
1209
|
-
|
|
1210
963
|
return true;
|
|
1211
964
|
}
|
|
1212
|
-
/* Make sure that older browsers don't get
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
|
|
965
|
+
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
966
|
+
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
|
|
1216
967
|
_forceRemove(currentNode);
|
|
1217
|
-
|
|
1218
968
|
return true;
|
|
1219
969
|
}
|
|
1220
970
|
/* Sanitize element content to be template-safe */
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
|
|
971
|
+
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1224
972
|
/* Get the element's text content */
|
|
1225
973
|
content = currentNode.textContent;
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
974
|
+
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
975
|
+
content = stringReplace(content, expr, ' ');
|
|
976
|
+
});
|
|
1230
977
|
if (currentNode.textContent !== content) {
|
|
1231
978
|
arrayPush(DOMPurify.removed, {
|
|
1232
979
|
element: currentNode.cloneNode()
|
|
@@ -1235,24 +982,19 @@
|
|
|
1235
982
|
}
|
|
1236
983
|
}
|
|
1237
984
|
/* Execute a hook if present */
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
_executeHook('afterSanitizeElements', currentNode, null);
|
|
1241
|
-
|
|
985
|
+
_executeHooks(hooks.afterSanitizeElements, currentNode, null);
|
|
1242
986
|
return false;
|
|
1243
987
|
};
|
|
1244
988
|
/**
|
|
1245
989
|
* _isValidAttribute
|
|
1246
990
|
*
|
|
1247
|
-
* @param
|
|
1248
|
-
* @param
|
|
1249
|
-
* @param
|
|
1250
|
-
* @return
|
|
991
|
+
* @param lcTag Lowercase tag name of containing element.
|
|
992
|
+
* @param lcName Lowercase attribute name.
|
|
993
|
+
* @param value Attribute value.
|
|
994
|
+
* @return Returns true if `value` is valid, otherwise false.
|
|
1251
995
|
*/
|
|
1252
996
|
// eslint-disable-next-line complexity
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
|
|
997
|
+
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
|
|
1256
998
|
/* Make sure attribute cannot clobber */
|
|
1257
999
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
1258
1000
|
return false;
|
|
@@ -1261,35 +1003,33 @@
|
|
|
1261
1003
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1262
1004
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
1263
1005
|
We don't need to check the value; it's always URI safe. */
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
if ( // First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1006
|
+
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]) {
|
|
1007
|
+
if (
|
|
1008
|
+
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
1268
1009
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
1269
1010
|
// and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
|
|
1270
|
-
|
|
1011
|
+
_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, lcTag)) ||
|
|
1012
|
+
// Alternative, second condition checks if it's an `is`-attribute, AND
|
|
1271
1013
|
// the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
1272
1014
|
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 {
|
|
1273
1015
|
return false;
|
|
1274
1016
|
}
|
|
1275
1017
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
1276
|
-
|
|
1277
|
-
} else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; 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$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (!value) ; else {
|
|
1018
|
+
} 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) {
|
|
1278
1019
|
return false;
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1020
|
+
} else ;
|
|
1281
1021
|
return true;
|
|
1282
1022
|
};
|
|
1283
1023
|
/**
|
|
1284
|
-
*
|
|
1024
|
+
* _isBasicCustomElement
|
|
1285
1025
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
1286
1026
|
* for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
|
|
1287
|
-
*
|
|
1027
|
+
*
|
|
1028
|
+
* @param tagName name of the tag of the node to sanitize
|
|
1029
|
+
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
1288
1030
|
*/
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
var _basicCustomElementTest = function _basicCustomElementTest(tagName) {
|
|
1292
|
-
return tagName.indexOf('-') > 0;
|
|
1031
|
+
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1032
|
+
return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
|
|
1293
1033
|
};
|
|
1294
1034
|
/**
|
|
1295
1035
|
* _sanitizeAttributes
|
|
@@ -1299,246 +1039,196 @@
|
|
|
1299
1039
|
* @protect removeAttribute
|
|
1300
1040
|
* @protect setAttribute
|
|
1301
1041
|
*
|
|
1302
|
-
* @param
|
|
1042
|
+
* @param currentNode to sanitize
|
|
1303
1043
|
*/
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1307
|
-
var attr;
|
|
1308
|
-
var value;
|
|
1309
|
-
var lcName;
|
|
1310
|
-
var l;
|
|
1044
|
+
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1311
1045
|
/* Execute a hook if present */
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1046
|
+
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
1047
|
+
const {
|
|
1048
|
+
attributes
|
|
1049
|
+
} = currentNode;
|
|
1316
1050
|
/* Check if we have attributes; if not we might have a text node */
|
|
1317
|
-
|
|
1318
|
-
if (!attributes) {
|
|
1051
|
+
if (!attributes || _isClobbered(currentNode)) {
|
|
1319
1052
|
return;
|
|
1320
1053
|
}
|
|
1321
|
-
|
|
1322
|
-
var hookEvent = {
|
|
1054
|
+
const hookEvent = {
|
|
1323
1055
|
attrName: '',
|
|
1324
1056
|
attrValue: '',
|
|
1325
1057
|
keepAttr: true,
|
|
1326
|
-
allowedAttributes: ALLOWED_ATTR
|
|
1058
|
+
allowedAttributes: ALLOWED_ATTR,
|
|
1059
|
+
forceKeepAttr: undefined
|
|
1327
1060
|
};
|
|
1328
|
-
l = attributes.length;
|
|
1061
|
+
let l = attributes.length;
|
|
1329
1062
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1330
|
-
|
|
1331
1063
|
while (l--) {
|
|
1332
|
-
attr = attributes[l];
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1064
|
+
const attr = attributes[l];
|
|
1065
|
+
const {
|
|
1066
|
+
name,
|
|
1067
|
+
namespaceURI,
|
|
1068
|
+
value: attrValue
|
|
1069
|
+
} = attr;
|
|
1070
|
+
const lcName = transformCaseFunc(name);
|
|
1071
|
+
const initValue = attrValue;
|
|
1072
|
+
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
1338
1073
|
/* Execute a hook if present */
|
|
1339
|
-
|
|
1340
1074
|
hookEvent.attrName = lcName;
|
|
1341
1075
|
hookEvent.attrValue = value;
|
|
1342
1076
|
hookEvent.keepAttr = true;
|
|
1343
1077
|
hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
|
|
1344
|
-
|
|
1345
|
-
_executeHook('uponSanitizeAttribute', currentNode, hookEvent);
|
|
1346
|
-
|
|
1078
|
+
_executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
|
|
1347
1079
|
value = hookEvent.attrValue;
|
|
1080
|
+
/* Full DOM Clobbering protection via namespace isolation,
|
|
1081
|
+
* Prefix id and name attributes with `user-content-`
|
|
1082
|
+
*/
|
|
1083
|
+
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
1084
|
+
// Remove the attribute with this value
|
|
1085
|
+
_removeAttribute(name, currentNode);
|
|
1086
|
+
// Prefix the value and later re-create the attribute with the sanitized value
|
|
1087
|
+
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
1088
|
+
}
|
|
1089
|
+
/* Work around a security issue with comments inside attributes */
|
|
1090
|
+
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title|textarea)/i, value)) {
|
|
1091
|
+
_removeAttribute(name, currentNode);
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
/* Make sure we cannot easily use animated hrefs, even if animations are allowed */
|
|
1095
|
+
if (lcName === 'attributename' && stringMatch(value, 'href')) {
|
|
1096
|
+
_removeAttribute(name, currentNode);
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1348
1099
|
/* Did the hooks approve of the attribute? */
|
|
1349
|
-
|
|
1350
1100
|
if (hookEvent.forceKeepAttr) {
|
|
1351
1101
|
continue;
|
|
1352
1102
|
}
|
|
1353
|
-
/* Remove attribute */
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
_removeAttribute(name, currentNode);
|
|
1357
1103
|
/* Did the hooks approve of the attribute? */
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
1104
|
if (!hookEvent.keepAttr) {
|
|
1105
|
+
_removeAttribute(name, currentNode);
|
|
1361
1106
|
continue;
|
|
1362
1107
|
}
|
|
1363
1108
|
/* Work around a security issue in jQuery 3.0 */
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
1109
|
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
|
|
1367
1110
|
_removeAttribute(name, currentNode);
|
|
1368
|
-
|
|
1369
1111
|
continue;
|
|
1370
1112
|
}
|
|
1371
1113
|
/* Sanitize attribute content to be template-safe */
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
1114
|
if (SAFE_FOR_TEMPLATES) {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1115
|
+
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1116
|
+
value = stringReplace(value, expr, ' ');
|
|
1117
|
+
});
|
|
1378
1118
|
}
|
|
1379
1119
|
/* Is `value` valid for this attribute? */
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
var lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1383
|
-
|
|
1120
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1384
1121
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
1122
|
+
_removeAttribute(name, currentNode);
|
|
1385
1123
|
continue;
|
|
1386
1124
|
}
|
|
1387
|
-
/* Full DOM Clobbering protection via namespace isolation,
|
|
1388
|
-
* Prefix id and name attributes with `user-content-`
|
|
1389
|
-
*/
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
1393
|
-
// Remove the attribute with this value
|
|
1394
|
-
_removeAttribute(name, currentNode); // Prefix the value and later re-create the attribute with the sanitized value
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
1398
|
-
}
|
|
1399
1125
|
/* Handle attributes that require Trusted Types */
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
if (trustedTypesPolicy && _typeof(trustedTypes) === 'object' && typeof trustedTypes.getAttributeType === 'function') {
|
|
1126
|
+
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
|
|
1403
1127
|
if (namespaceURI) ; else {
|
|
1404
1128
|
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
1405
1129
|
case 'TrustedHTML':
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1130
|
+
{
|
|
1131
|
+
value = trustedTypesPolicy.createHTML(value);
|
|
1132
|
+
break;
|
|
1133
|
+
}
|
|
1409
1134
|
case 'TrustedScriptURL':
|
|
1410
|
-
|
|
1411
|
-
|
|
1135
|
+
{
|
|
1136
|
+
value = trustedTypesPolicy.createScriptURL(value);
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1412
1139
|
}
|
|
1413
1140
|
}
|
|
1414
1141
|
}
|
|
1415
1142
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1143
|
+
if (value !== initValue) {
|
|
1144
|
+
try {
|
|
1145
|
+
if (namespaceURI) {
|
|
1146
|
+
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
1147
|
+
} else {
|
|
1148
|
+
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
1149
|
+
currentNode.setAttribute(name, value);
|
|
1150
|
+
}
|
|
1151
|
+
if (_isClobbered(currentNode)) {
|
|
1152
|
+
_forceRemove(currentNode);
|
|
1153
|
+
} else {
|
|
1154
|
+
arrayPop(DOMPurify.removed);
|
|
1155
|
+
}
|
|
1156
|
+
} catch (_) {
|
|
1157
|
+
_removeAttribute(name, currentNode);
|
|
1424
1158
|
}
|
|
1425
|
-
|
|
1426
|
-
arrayPop(DOMPurify.removed);
|
|
1427
|
-
} catch (_) {}
|
|
1159
|
+
}
|
|
1428
1160
|
}
|
|
1429
1161
|
/* Execute a hook if present */
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
_executeHook('afterSanitizeAttributes', currentNode, null);
|
|
1162
|
+
_executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
|
|
1433
1163
|
};
|
|
1434
1164
|
/**
|
|
1435
1165
|
* _sanitizeShadowDOM
|
|
1436
1166
|
*
|
|
1437
|
-
* @param
|
|
1167
|
+
* @param fragment to iterate over recursively
|
|
1438
1168
|
*/
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
var shadowNode;
|
|
1443
|
-
|
|
1444
|
-
var shadowIterator = _createIterator(fragment);
|
|
1169
|
+
const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
|
|
1170
|
+
let shadowNode = null;
|
|
1171
|
+
const shadowIterator = _createNodeIterator(fragment);
|
|
1445
1172
|
/* Execute a hook if present */
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
_executeHook('beforeSanitizeShadowDOM', fragment, null);
|
|
1449
|
-
|
|
1173
|
+
_executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
|
|
1450
1174
|
while (shadowNode = shadowIterator.nextNode()) {
|
|
1451
1175
|
/* Execute a hook if present */
|
|
1452
|
-
|
|
1176
|
+
_executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
|
|
1453
1177
|
/* Sanitize tags and elements */
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
continue;
|
|
1458
|
-
}
|
|
1178
|
+
_sanitizeElements(shadowNode);
|
|
1179
|
+
/* Check attributes next */
|
|
1180
|
+
_sanitizeAttributes(shadowNode);
|
|
1459
1181
|
/* Deep shadow DOM detected */
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
1182
|
if (shadowNode.content instanceof DocumentFragment) {
|
|
1463
1183
|
_sanitizeShadowDOM(shadowNode.content);
|
|
1464
1184
|
}
|
|
1465
|
-
/* Check attributes, sanitize if necessary */
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
_sanitizeAttributes(shadowNode);
|
|
1469
1185
|
}
|
|
1470
1186
|
/* Execute a hook if present */
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
_executeHook('afterSanitizeShadowDOM', fragment, null);
|
|
1187
|
+
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
1474
1188
|
};
|
|
1475
|
-
/**
|
|
1476
|
-
* Sanitize
|
|
1477
|
-
* Public method providing core sanitation functionality
|
|
1478
|
-
*
|
|
1479
|
-
* @param {String|Node} dirty string or DOM node
|
|
1480
|
-
* @param {Object} configuration object
|
|
1481
|
-
*/
|
|
1482
1189
|
// eslint-disable-next-line complexity
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
1190
|
DOMPurify.sanitize = function (dirty) {
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1191
|
+
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
1192
|
+
let body = null;
|
|
1193
|
+
let importedNode = null;
|
|
1194
|
+
let currentNode = null;
|
|
1195
|
+
let returnNode = null;
|
|
1491
1196
|
/* Make sure we have a string to sanitize.
|
|
1492
1197
|
DO NOT return early, as this will return the wrong type if
|
|
1493
1198
|
the user has requested a DOM object rather than a string */
|
|
1494
|
-
|
|
1495
1199
|
IS_EMPTY_INPUT = !dirty;
|
|
1496
|
-
|
|
1497
1200
|
if (IS_EMPTY_INPUT) {
|
|
1498
1201
|
dirty = '<!-->';
|
|
1499
1202
|
}
|
|
1500
1203
|
/* Stringify, in case dirty is an object */
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
1204
|
if (typeof dirty !== 'string' && !_isNode(dirty)) {
|
|
1504
|
-
|
|
1505
|
-
if (typeof dirty.toString !== 'function') {
|
|
1506
|
-
throw typeErrorCreate('toString is not a function');
|
|
1507
|
-
} else {
|
|
1205
|
+
if (typeof dirty.toString === 'function') {
|
|
1508
1206
|
dirty = dirty.toString();
|
|
1509
|
-
|
|
1510
1207
|
if (typeof dirty !== 'string') {
|
|
1511
1208
|
throw typeErrorCreate('dirty is not a string, aborting');
|
|
1512
1209
|
}
|
|
1210
|
+
} else {
|
|
1211
|
+
throw typeErrorCreate('toString is not a function');
|
|
1513
1212
|
}
|
|
1514
1213
|
}
|
|
1515
1214
|
/* Return dirty HTML if DOMPurify cannot run */
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
1215
|
if (!DOMPurify.isSupported) {
|
|
1519
1216
|
return dirty;
|
|
1520
1217
|
}
|
|
1521
1218
|
/* Assign config vars */
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
1219
|
if (!SET_CONFIG) {
|
|
1525
1220
|
_parseConfig(cfg);
|
|
1526
1221
|
}
|
|
1527
1222
|
/* Clean up removed elements */
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
1223
|
DOMPurify.removed = [];
|
|
1531
1224
|
/* Check if dirty is correctly typed for IN_PLACE */
|
|
1532
|
-
|
|
1533
1225
|
if (typeof dirty === 'string') {
|
|
1534
1226
|
IN_PLACE = false;
|
|
1535
1227
|
}
|
|
1536
|
-
|
|
1537
1228
|
if (IN_PLACE) {
|
|
1538
1229
|
/* Do some early pre-sanitization to avoid unsafe root nodes */
|
|
1539
1230
|
if (dirty.nodeName) {
|
|
1540
|
-
|
|
1541
|
-
|
|
1231
|
+
const tagName = transformCaseFunc(dirty.nodeName);
|
|
1542
1232
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
1543
1233
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
1544
1234
|
}
|
|
@@ -1548,8 +1238,7 @@
|
|
|
1548
1238
|
elements being stripped by the parser */
|
|
1549
1239
|
body = _initDocument('<!---->');
|
|
1550
1240
|
importedNode = body.ownerDocument.importNode(dirty, true);
|
|
1551
|
-
|
|
1552
|
-
if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
|
|
1241
|
+
if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
|
|
1553
1242
|
/* Node is already a body, use as is */
|
|
1554
1243
|
body = importedNode;
|
|
1555
1244
|
} else if (importedNode.nodeName === 'HTML') {
|
|
@@ -1560,62 +1249,43 @@
|
|
|
1560
1249
|
}
|
|
1561
1250
|
} else {
|
|
1562
1251
|
/* Exit directly if we have nothing to do */
|
|
1563
|
-
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
1252
|
+
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
1253
|
+
// eslint-disable-next-line unicorn/prefer-includes
|
|
1564
1254
|
dirty.indexOf('<') === -1) {
|
|
1565
1255
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
|
|
1566
1256
|
}
|
|
1567
1257
|
/* Initialize the document to work on */
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
1258
|
body = _initDocument(dirty);
|
|
1571
1259
|
/* Check we have a DOM node from the data */
|
|
1572
|
-
|
|
1573
1260
|
if (!body) {
|
|
1574
1261
|
return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
|
|
1575
1262
|
}
|
|
1576
1263
|
}
|
|
1577
1264
|
/* Remove first element node (ours) if FORCE_BODY is set */
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
1265
|
if (body && FORCE_BODY) {
|
|
1581
1266
|
_forceRemove(body.firstChild);
|
|
1582
1267
|
}
|
|
1583
1268
|
/* Get node iterator */
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
var nodeIterator = _createIterator(IN_PLACE ? dirty : body);
|
|
1269
|
+
const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
|
|
1587
1270
|
/* Now start iterating over the created document */
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
1271
|
while (currentNode = nodeIterator.nextNode()) {
|
|
1591
1272
|
/* Sanitize tags and elements */
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1273
|
+
_sanitizeElements(currentNode);
|
|
1274
|
+
/* Check attributes next */
|
|
1275
|
+
_sanitizeAttributes(currentNode);
|
|
1595
1276
|
/* Shadow DOM detected, sanitize it */
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
1277
|
if (currentNode.content instanceof DocumentFragment) {
|
|
1599
1278
|
_sanitizeShadowDOM(currentNode.content);
|
|
1600
1279
|
}
|
|
1601
|
-
/* Check attributes, sanitize if necessary */
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
_sanitizeAttributes(currentNode);
|
|
1605
1280
|
}
|
|
1606
1281
|
/* If we sanitized `dirty` in-place, return it. */
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
1282
|
if (IN_PLACE) {
|
|
1610
1283
|
return dirty;
|
|
1611
1284
|
}
|
|
1612
1285
|
/* Return sanitized string or DOM */
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
1286
|
if (RETURN_DOM) {
|
|
1616
1287
|
if (RETURN_DOM_FRAGMENT) {
|
|
1617
1288
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
1618
|
-
|
|
1619
1289
|
while (body.firstChild) {
|
|
1620
1290
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
1621
1291
|
returnNode.appendChild(body.firstChild);
|
|
@@ -1623,8 +1293,7 @@
|
|
|
1623
1293
|
} else {
|
|
1624
1294
|
returnNode = body;
|
|
1625
1295
|
}
|
|
1626
|
-
|
|
1627
|
-
if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmod) {
|
|
1296
|
+
if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
|
|
1628
1297
|
/*
|
|
1629
1298
|
AdoptNode() is not used because internal state is not reset
|
|
1630
1299
|
(e.g. the past names map of a HTMLFormElement), this is safe
|
|
@@ -1634,132 +1303,60 @@
|
|
|
1634
1303
|
*/
|
|
1635
1304
|
returnNode = importNode.call(originalDocument, returnNode, true);
|
|
1636
1305
|
}
|
|
1637
|
-
|
|
1638
1306
|
return returnNode;
|
|
1639
1307
|
}
|
|
1640
|
-
|
|
1641
|
-
var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
|
|
1308
|
+
let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
|
|
1642
1309
|
/* Serialize doctype if allowed */
|
|
1643
|
-
|
|
1644
1310
|
if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
|
|
1645
1311
|
serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
|
|
1646
1312
|
}
|
|
1647
1313
|
/* Sanitize final string template-safe */
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
1314
|
if (SAFE_FOR_TEMPLATES) {
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1315
|
+
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1316
|
+
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
1317
|
+
});
|
|
1654
1318
|
}
|
|
1655
|
-
|
|
1656
1319
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
|
|
1657
1320
|
};
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
* setConfig
|
|
1661
|
-
*
|
|
1662
|
-
* @param {Object} cfg configuration object
|
|
1663
|
-
*/
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
DOMPurify.setConfig = function (cfg) {
|
|
1321
|
+
DOMPurify.setConfig = function () {
|
|
1322
|
+
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
1667
1323
|
_parseConfig(cfg);
|
|
1668
|
-
|
|
1669
1324
|
SET_CONFIG = true;
|
|
1670
1325
|
};
|
|
1671
|
-
/**
|
|
1672
|
-
* Public method to remove the configuration
|
|
1673
|
-
* clearConfig
|
|
1674
|
-
*
|
|
1675
|
-
*/
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
1326
|
DOMPurify.clearConfig = function () {
|
|
1679
1327
|
CONFIG = null;
|
|
1680
1328
|
SET_CONFIG = false;
|
|
1681
1329
|
};
|
|
1682
|
-
/**
|
|
1683
|
-
* Public method to check if an attribute value is valid.
|
|
1684
|
-
* Uses last set config, if any. Otherwise, uses config defaults.
|
|
1685
|
-
* isValidAttribute
|
|
1686
|
-
*
|
|
1687
|
-
* @param {string} tag Tag name of containing element.
|
|
1688
|
-
* @param {string} attr Attribute name.
|
|
1689
|
-
* @param {string} value Attribute value.
|
|
1690
|
-
* @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
|
|
1691
|
-
*/
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
1330
|
DOMPurify.isValidAttribute = function (tag, attr, value) {
|
|
1695
1331
|
/* Initialize shared config vars if necessary. */
|
|
1696
1332
|
if (!CONFIG) {
|
|
1697
1333
|
_parseConfig({});
|
|
1698
1334
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
var lcName = transformCaseFunc(attr);
|
|
1335
|
+
const lcTag = transformCaseFunc(tag);
|
|
1336
|
+
const lcName = transformCaseFunc(attr);
|
|
1702
1337
|
return _isValidAttribute(lcTag, lcName, value);
|
|
1703
1338
|
};
|
|
1704
|
-
/**
|
|
1705
|
-
* AddHook
|
|
1706
|
-
* Public method to add DOMPurify hooks
|
|
1707
|
-
*
|
|
1708
|
-
* @param {String} entryPoint entry point for the hook to add
|
|
1709
|
-
* @param {Function} hookFunction function to execute
|
|
1710
|
-
*/
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
1339
|
DOMPurify.addHook = function (entryPoint, hookFunction) {
|
|
1714
1340
|
if (typeof hookFunction !== 'function') {
|
|
1715
1341
|
return;
|
|
1716
1342
|
}
|
|
1717
|
-
|
|
1718
|
-
hooks[entryPoint] = hooks[entryPoint] || [];
|
|
1719
1343
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
1720
1344
|
};
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
*
|
|
1726
|
-
* @param {String} entryPoint entry point for the hook to remove
|
|
1727
|
-
* @return {Function} removed(popped) hook
|
|
1728
|
-
*/
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
DOMPurify.removeHook = function (entryPoint) {
|
|
1732
|
-
if (hooks[entryPoint]) {
|
|
1733
|
-
return arrayPop(hooks[entryPoint]);
|
|
1345
|
+
DOMPurify.removeHook = function (entryPoint, hookFunction) {
|
|
1346
|
+
if (hookFunction !== undefined) {
|
|
1347
|
+
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
|
|
1348
|
+
return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
|
|
1734
1349
|
}
|
|
1350
|
+
return arrayPop(hooks[entryPoint]);
|
|
1735
1351
|
};
|
|
1736
|
-
/**
|
|
1737
|
-
* RemoveHooks
|
|
1738
|
-
* Public method to remove all DOMPurify hooks at a given entryPoint
|
|
1739
|
-
*
|
|
1740
|
-
* @param {String} entryPoint entry point for the hooks to remove
|
|
1741
|
-
*/
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
1352
|
DOMPurify.removeHooks = function (entryPoint) {
|
|
1745
|
-
|
|
1746
|
-
hooks[entryPoint] = [];
|
|
1747
|
-
}
|
|
1353
|
+
hooks[entryPoint] = [];
|
|
1748
1354
|
};
|
|
1749
|
-
/**
|
|
1750
|
-
* RemoveAllHooks
|
|
1751
|
-
* Public method to remove all DOMPurify hooks
|
|
1752
|
-
*
|
|
1753
|
-
*/
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
1355
|
DOMPurify.removeAllHooks = function () {
|
|
1757
|
-
hooks =
|
|
1356
|
+
hooks = _createHooksMap();
|
|
1758
1357
|
};
|
|
1759
|
-
|
|
1760
1358
|
return DOMPurify;
|
|
1761
1359
|
}
|
|
1762
|
-
|
|
1763
1360
|
var purify = createDOMPurify();
|
|
1764
1361
|
|
|
1765
1362
|
return purify;
|