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