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