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