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