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