dompurify 3.1.6 → 3.2.0
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 +3 -7
- package/dist/purify.cjs.d.ts +401 -0
- package/dist/purify.cjs.js +89 -311
- package/dist/purify.cjs.js.map +1 -1
- package/dist/purify.es.d.mts +399 -0
- package/dist/purify.es.mjs +89 -311
- package/dist/purify.es.mjs.map +1 -1
- package/dist/purify.js +89 -311
- package/dist/purify.js.map +1 -1
- package/dist/purify.min.js +1339 -2
- package/dist/purify.min.js.map +1 -1
- package/package.json +47 -12
package/dist/purify.es.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.
|
|
1
|
+
/*! @license DOMPurify 3.2.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.0/LICENSE */
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
entries,
|
|
@@ -48,12 +48,11 @@ const stringTrim = unapply(String.prototype.trim);
|
|
|
48
48
|
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
49
49
|
const regExpTest = unapply(RegExp.prototype.test);
|
|
50
50
|
const typeErrorCreate = unconstruct(TypeError);
|
|
51
|
-
|
|
52
51
|
/**
|
|
53
52
|
* Creates a new function that calls the given function with a specified thisArg and arguments.
|
|
54
53
|
*
|
|
55
|
-
* @param
|
|
56
|
-
* @returns
|
|
54
|
+
* @param func - The function to be wrapped and called.
|
|
55
|
+
* @returns A new function that calls the given function with a specified thisArg and arguments.
|
|
57
56
|
*/
|
|
58
57
|
function unapply(func) {
|
|
59
58
|
return function (thisArg) {
|
|
@@ -63,12 +62,11 @@ function unapply(func) {
|
|
|
63
62
|
return apply(func, thisArg, args);
|
|
64
63
|
};
|
|
65
64
|
}
|
|
66
|
-
|
|
67
65
|
/**
|
|
68
66
|
* Creates a new function that constructs an instance of the given constructor function with the provided arguments.
|
|
69
67
|
*
|
|
70
|
-
* @param
|
|
71
|
-
* @returns
|
|
68
|
+
* @param func - The constructor function to be wrapped and called.
|
|
69
|
+
* @returns A new function that constructs an instance of the given constructor function with the provided arguments.
|
|
72
70
|
*/
|
|
73
71
|
function unconstruct(func) {
|
|
74
72
|
return function () {
|
|
@@ -78,14 +76,13 @@ function unconstruct(func) {
|
|
|
78
76
|
return construct(func, args);
|
|
79
77
|
};
|
|
80
78
|
}
|
|
81
|
-
|
|
82
79
|
/**
|
|
83
80
|
* Add properties to a lookup table
|
|
84
81
|
*
|
|
85
|
-
* @param
|
|
86
|
-
* @param
|
|
87
|
-
* @param
|
|
88
|
-
* @returns
|
|
82
|
+
* @param set - The set to which elements will be added.
|
|
83
|
+
* @param array - The array containing elements to be added to the set.
|
|
84
|
+
* @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
|
|
85
|
+
* @returns The modified set with added elements.
|
|
89
86
|
*/
|
|
90
87
|
function addToSet(set, array) {
|
|
91
88
|
let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
|
|
@@ -112,12 +109,11 @@ function addToSet(set, array) {
|
|
|
112
109
|
}
|
|
113
110
|
return set;
|
|
114
111
|
}
|
|
115
|
-
|
|
116
112
|
/**
|
|
117
113
|
* Clean up an array to harden against CSPP
|
|
118
114
|
*
|
|
119
|
-
* @param
|
|
120
|
-
* @returns
|
|
115
|
+
* @param array - The array to be cleaned.
|
|
116
|
+
* @returns The cleaned version of the array
|
|
121
117
|
*/
|
|
122
118
|
function cleanArray(array) {
|
|
123
119
|
for (let index = 0; index < array.length; index++) {
|
|
@@ -128,12 +124,11 @@ function cleanArray(array) {
|
|
|
128
124
|
}
|
|
129
125
|
return array;
|
|
130
126
|
}
|
|
131
|
-
|
|
132
127
|
/**
|
|
133
128
|
* Shallow clone an object
|
|
134
129
|
*
|
|
135
|
-
* @param
|
|
136
|
-
* @returns
|
|
130
|
+
* @param object - The object to be cloned.
|
|
131
|
+
* @returns A new object that copies the original.
|
|
137
132
|
*/
|
|
138
133
|
function clone(object) {
|
|
139
134
|
const newObject = create(null);
|
|
@@ -151,13 +146,12 @@ function clone(object) {
|
|
|
151
146
|
}
|
|
152
147
|
return newObject;
|
|
153
148
|
}
|
|
154
|
-
|
|
155
149
|
/**
|
|
156
150
|
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
157
151
|
*
|
|
158
|
-
* @param
|
|
159
|
-
* @param
|
|
160
|
-
* @returns
|
|
152
|
+
* @param object - The object to look up the getter function in its prototype chain.
|
|
153
|
+
* @param prop - The property name for which to find the getter function.
|
|
154
|
+
* @returns The getter function found in the prototype chain or a fallback function.
|
|
161
155
|
*/
|
|
162
156
|
function lookupGetter(object, prop) {
|
|
163
157
|
while (object !== null) {
|
|
@@ -179,25 +173,22 @@ function lookupGetter(object, prop) {
|
|
|
179
173
|
}
|
|
180
174
|
|
|
181
175
|
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']);
|
|
182
|
-
|
|
183
176
|
// SVG
|
|
184
177
|
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']);
|
|
185
178
|
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
179
|
// List of SVG elements that are disallowed by default.
|
|
188
180
|
// We still need to know them so that we can do namespace
|
|
189
181
|
// checks properly in case one wants to add them to
|
|
190
182
|
// allow-list.
|
|
191
183
|
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']);
|
|
192
184
|
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']);
|
|
193
|
-
|
|
194
185
|
// Similarly to SVG, we want to know all MathML elements,
|
|
195
186
|
// even those that we disallow by default.
|
|
196
187
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
197
188
|
const text = freeze(['#text']);
|
|
198
189
|
|
|
199
190
|
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
|
|
200
|
-
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
|
|
191
|
+
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
|
|
201
192
|
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
193
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
203
194
|
|
|
@@ -217,18 +208,19 @@ const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
|
217
208
|
|
|
218
209
|
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
219
210
|
__proto__: null,
|
|
220
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
221
|
-
ERB_EXPR: ERB_EXPR,
|
|
222
|
-
TMPLIT_EXPR: TMPLIT_EXPR,
|
|
223
|
-
DATA_ATTR: DATA_ATTR,
|
|
224
211
|
ARIA_ATTR: ARIA_ATTR,
|
|
225
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
226
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
227
212
|
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
213
|
+
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
214
|
+
DATA_ATTR: DATA_ATTR,
|
|
228
215
|
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
229
|
-
|
|
216
|
+
ERB_EXPR: ERB_EXPR,
|
|
217
|
+
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
218
|
+
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
219
|
+
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
220
|
+
TMPLIT_EXPR: TMPLIT_EXPR
|
|
230
221
|
});
|
|
231
222
|
|
|
223
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
232
224
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
233
225
|
const NODE_TYPE = {
|
|
234
226
|
element: 1,
|
|
@@ -249,20 +241,18 @@ const NODE_TYPE = {
|
|
|
249
241
|
const getGlobal = function getGlobal() {
|
|
250
242
|
return typeof window === 'undefined' ? null : window;
|
|
251
243
|
};
|
|
252
|
-
|
|
253
244
|
/**
|
|
254
245
|
* Creates a no-op policy for internal use only.
|
|
255
246
|
* Don't export this function outside this module!
|
|
256
|
-
* @param
|
|
257
|
-
* @param
|
|
258
|
-
* @return
|
|
247
|
+
* @param trustedTypes The policy factory.
|
|
248
|
+
* @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
|
|
249
|
+
* @return The policy created (or null, if Trusted Types
|
|
259
250
|
* are not supported or creating the policy failed).
|
|
260
251
|
*/
|
|
261
252
|
const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
|
|
262
253
|
if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
|
|
263
254
|
return null;
|
|
264
255
|
}
|
|
265
|
-
|
|
266
256
|
// Allow the callers to control the unique policy name
|
|
267
257
|
// by adding a data-tt-policy-suffix to the script element with the DOMPurify.
|
|
268
258
|
// Policy creation with duplicate names throws in Trusted Types.
|
|
@@ -292,17 +282,7 @@ const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedType
|
|
|
292
282
|
function createDOMPurify() {
|
|
293
283
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
294
284
|
const DOMPurify = root => createDOMPurify(root);
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Version label, exposed for easier checks
|
|
298
|
-
* if DOMPurify is up to date or not
|
|
299
|
-
*/
|
|
300
|
-
DOMPurify.version = '3.1.6';
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Array of elements that DOMPurify removed during sanitation.
|
|
304
|
-
* Empty if nothing was removed.
|
|
305
|
-
*/
|
|
285
|
+
DOMPurify.version = '3.2.0';
|
|
306
286
|
DOMPurify.removed = [];
|
|
307
287
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {
|
|
308
288
|
// Not running in a browser, provide a factory function
|
|
@@ -332,7 +312,6 @@ function createDOMPurify() {
|
|
|
332
312
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
333
313
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
334
314
|
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
335
|
-
|
|
336
315
|
// As per issue #47, the web-components registry is inherited by a
|
|
337
316
|
// new document created via createHTMLDocument. As per the spec
|
|
338
317
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -357,7 +336,6 @@ function createDOMPurify() {
|
|
|
357
336
|
importNode
|
|
358
337
|
} = originalDocument;
|
|
359
338
|
let hooks = {};
|
|
360
|
-
|
|
361
339
|
/**
|
|
362
340
|
* Expose whether this browser supports running the full DOMPurify.
|
|
363
341
|
*/
|
|
@@ -375,22 +353,18 @@ function createDOMPurify() {
|
|
|
375
353
|
let {
|
|
376
354
|
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
377
355
|
} = EXPRESSIONS;
|
|
378
|
-
|
|
379
356
|
/**
|
|
380
357
|
* We consider the elements and attributes below to be safe. Ideally
|
|
381
358
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
382
359
|
*/
|
|
383
|
-
|
|
384
360
|
/* allowed element names */
|
|
385
361
|
let ALLOWED_TAGS = null;
|
|
386
362
|
const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
|
|
387
|
-
|
|
388
363
|
/* Allowed attribute names */
|
|
389
364
|
let ALLOWED_ATTR = null;
|
|
390
365
|
const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
|
|
391
|
-
|
|
392
366
|
/*
|
|
393
|
-
* Configure how
|
|
367
|
+
* Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
|
|
394
368
|
* @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
|
|
395
369
|
* @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
|
|
396
370
|
* @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
|
|
@@ -415,65 +389,49 @@ function createDOMPurify() {
|
|
|
415
389
|
value: false
|
|
416
390
|
}
|
|
417
391
|
}));
|
|
418
|
-
|
|
419
392
|
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
|
|
420
393
|
let FORBID_TAGS = null;
|
|
421
|
-
|
|
422
394
|
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
|
|
423
395
|
let FORBID_ATTR = null;
|
|
424
|
-
|
|
425
396
|
/* Decide if ARIA attributes are okay */
|
|
426
397
|
let ALLOW_ARIA_ATTR = true;
|
|
427
|
-
|
|
428
398
|
/* Decide if custom data attributes are okay */
|
|
429
399
|
let ALLOW_DATA_ATTR = true;
|
|
430
|
-
|
|
431
400
|
/* Decide if unknown protocols are okay */
|
|
432
401
|
let ALLOW_UNKNOWN_PROTOCOLS = false;
|
|
433
|
-
|
|
434
402
|
/* Decide if self-closing tags in attributes are allowed.
|
|
435
403
|
* Usually removed due to a mXSS issue in jQuery 3.0 */
|
|
436
404
|
let ALLOW_SELF_CLOSE_IN_ATTR = true;
|
|
437
|
-
|
|
438
405
|
/* Output should be safe for common template engines.
|
|
439
406
|
* This means, DOMPurify removes data attributes, mustaches and ERB
|
|
440
407
|
*/
|
|
441
408
|
let SAFE_FOR_TEMPLATES = false;
|
|
442
|
-
|
|
443
409
|
/* Output should be safe even for XML used within HTML and alike.
|
|
444
410
|
* This means, DOMPurify removes comments when containing risky content.
|
|
445
411
|
*/
|
|
446
412
|
let SAFE_FOR_XML = true;
|
|
447
|
-
|
|
448
413
|
/* Decide if document with <html>... should be returned */
|
|
449
414
|
let WHOLE_DOCUMENT = false;
|
|
450
|
-
|
|
451
415
|
/* Track whether config is already set on this instance of DOMPurify. */
|
|
452
416
|
let SET_CONFIG = false;
|
|
453
|
-
|
|
454
417
|
/* Decide if all elements (e.g. style, script) must be children of
|
|
455
418
|
* document.body. By default, browsers might move them to document.head */
|
|
456
419
|
let FORCE_BODY = false;
|
|
457
|
-
|
|
458
420
|
/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
|
|
459
421
|
* string (or a TrustedHTML object if Trusted Types are supported).
|
|
460
422
|
* If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
|
|
461
423
|
*/
|
|
462
424
|
let RETURN_DOM = false;
|
|
463
|
-
|
|
464
425
|
/* Decide if a DOM `DocumentFragment` should be returned, instead of a html
|
|
465
426
|
* string (or a TrustedHTML object if Trusted Types are supported) */
|
|
466
427
|
let RETURN_DOM_FRAGMENT = false;
|
|
467
|
-
|
|
468
428
|
/* Try to return a Trusted Type object instead of a string, return a string in
|
|
469
429
|
* case Trusted Types are not supported */
|
|
470
430
|
let RETURN_TRUSTED_TYPE = false;
|
|
471
|
-
|
|
472
431
|
/* Output should be free from DOM clobbering attacks?
|
|
473
432
|
* This sanitizes markups named with colliding, clobberable built-in DOM APIs.
|
|
474
433
|
*/
|
|
475
434
|
let SANITIZE_DOM = true;
|
|
476
|
-
|
|
477
435
|
/* Achieve full DOM Clobbering protection by isolating the namespace of named
|
|
478
436
|
* properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
|
|
479
437
|
*
|
|
@@ -489,25 +447,19 @@ function createDOMPurify() {
|
|
|
489
447
|
*/
|
|
490
448
|
let SANITIZE_NAMED_PROPS = false;
|
|
491
449
|
const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
|
|
492
|
-
|
|
493
450
|
/* Keep element content when removing element? */
|
|
494
451
|
let KEEP_CONTENT = true;
|
|
495
|
-
|
|
496
452
|
/* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
|
|
497
453
|
* of importing it into a new Document and returning a sanitized copy */
|
|
498
454
|
let IN_PLACE = false;
|
|
499
|
-
|
|
500
455
|
/* Allow usage of profiles like html, svg and mathMl */
|
|
501
456
|
let USE_PROFILES = {};
|
|
502
|
-
|
|
503
457
|
/* Tags to ignore content of when KEEP_CONTENT is true */
|
|
504
458
|
let FORBID_CONTENTS = null;
|
|
505
459
|
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']);
|
|
506
|
-
|
|
507
460
|
/* Tags that are safe for data: URIs */
|
|
508
461
|
let DATA_URI_TAGS = null;
|
|
509
462
|
const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
|
|
510
|
-
|
|
511
463
|
/* Attributes safe for values like "javascript:" */
|
|
512
464
|
let URI_SAFE_ATTRIBUTES = null;
|
|
513
465
|
const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
|
|
@@ -517,32 +469,33 @@ function createDOMPurify() {
|
|
|
517
469
|
/* Document namespace */
|
|
518
470
|
let NAMESPACE = HTML_NAMESPACE;
|
|
519
471
|
let IS_EMPTY_INPUT = false;
|
|
520
|
-
|
|
521
472
|
/* Allowed XHTML+XML namespaces */
|
|
522
473
|
let ALLOWED_NAMESPACES = null;
|
|
523
474
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
524
|
-
|
|
475
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
476
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
|
|
477
|
+
// Certain elements are allowed in both SVG and HTML
|
|
478
|
+
// namespace. We need to specify them explicitly
|
|
479
|
+
// so that they don't get erroneously deleted from
|
|
480
|
+
// HTML namespace.
|
|
481
|
+
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
|
|
525
482
|
/* Parsing of strict XHTML documents */
|
|
526
483
|
let PARSER_MEDIA_TYPE = null;
|
|
527
484
|
const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
|
|
528
485
|
const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
|
|
529
486
|
let transformCaseFunc = null;
|
|
530
|
-
|
|
531
487
|
/* Keep a reference to config to pass to hooks */
|
|
532
488
|
let CONFIG = null;
|
|
533
|
-
|
|
534
489
|
/* Ideally, do not touch anything below this line */
|
|
535
490
|
/* ______________________________________________ */
|
|
536
|
-
|
|
537
491
|
const formElement = document.createElement('form');
|
|
538
492
|
const isRegexOrFunction = function isRegexOrFunction(testValue) {
|
|
539
493
|
return testValue instanceof RegExp || testValue instanceof Function;
|
|
540
494
|
};
|
|
541
|
-
|
|
542
495
|
/**
|
|
543
496
|
* _parseConfig
|
|
544
497
|
*
|
|
545
|
-
* @param
|
|
498
|
+
* @param cfg optional config literal
|
|
546
499
|
*/
|
|
547
500
|
// eslint-disable-next-line complexity
|
|
548
501
|
const _parseConfig = function _parseConfig() {
|
|
@@ -550,39 +503,23 @@ function createDOMPurify() {
|
|
|
550
503
|
if (CONFIG && CONFIG === cfg) {
|
|
551
504
|
return;
|
|
552
505
|
}
|
|
553
|
-
|
|
554
506
|
/* Shield configuration object from tampering */
|
|
555
507
|
if (!cfg || typeof cfg !== 'object') {
|
|
556
508
|
cfg = {};
|
|
557
509
|
}
|
|
558
|
-
|
|
559
510
|
/* Shield configuration object from prototype pollution */
|
|
560
511
|
cfg = clone(cfg);
|
|
561
512
|
PARSER_MEDIA_TYPE =
|
|
562
513
|
// eslint-disable-next-line unicorn/prefer-includes
|
|
563
514
|
SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
|
|
564
|
-
|
|
565
515
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
566
516
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
567
|
-
|
|
568
517
|
/* Set configuration parameters */
|
|
569
518
|
ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
|
|
570
519
|
ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
|
|
571
520
|
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
|
|
572
|
-
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES),
|
|
573
|
-
|
|
574
|
-
cfg.ADD_URI_SAFE_ATTR,
|
|
575
|
-
// eslint-disable-line indent
|
|
576
|
-
transformCaseFunc // eslint-disable-line indent
|
|
577
|
-
) // eslint-disable-line indent
|
|
578
|
-
: DEFAULT_URI_SAFE_ATTRIBUTES;
|
|
579
|
-
DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS),
|
|
580
|
-
// eslint-disable-line indent
|
|
581
|
-
cfg.ADD_DATA_URI_TAGS,
|
|
582
|
-
// eslint-disable-line indent
|
|
583
|
-
transformCaseFunc // eslint-disable-line indent
|
|
584
|
-
) // eslint-disable-line indent
|
|
585
|
-
: DEFAULT_DATA_URI_TAGS;
|
|
521
|
+
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
|
|
522
|
+
DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
|
|
586
523
|
FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
|
|
587
524
|
FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
|
|
588
525
|
FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
|
|
@@ -604,6 +541,8 @@ function createDOMPurify() {
|
|
|
604
541
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
605
542
|
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
|
|
606
543
|
NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
|
|
544
|
+
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
|
|
545
|
+
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
|
|
607
546
|
CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
|
|
608
547
|
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
|
|
609
548
|
CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
|
|
@@ -620,7 +559,6 @@ function createDOMPurify() {
|
|
|
620
559
|
if (RETURN_DOM_FRAGMENT) {
|
|
621
560
|
RETURN_DOM = true;
|
|
622
561
|
}
|
|
623
|
-
|
|
624
562
|
/* Parse profile info */
|
|
625
563
|
if (USE_PROFILES) {
|
|
626
564
|
ALLOWED_TAGS = addToSet({}, text);
|
|
@@ -645,7 +583,6 @@ function createDOMPurify() {
|
|
|
645
583
|
addToSet(ALLOWED_ATTR, xml);
|
|
646
584
|
}
|
|
647
585
|
}
|
|
648
|
-
|
|
649
586
|
/* Merge configuration parameters */
|
|
650
587
|
if (cfg.ADD_TAGS) {
|
|
651
588
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
@@ -668,17 +605,14 @@ function createDOMPurify() {
|
|
|
668
605
|
}
|
|
669
606
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
670
607
|
}
|
|
671
|
-
|
|
672
608
|
/* Add #text in case KEEP_CONTENT is set to true */
|
|
673
609
|
if (KEEP_CONTENT) {
|
|
674
610
|
ALLOWED_TAGS['#text'] = true;
|
|
675
611
|
}
|
|
676
|
-
|
|
677
612
|
/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
|
|
678
613
|
if (WHOLE_DOCUMENT) {
|
|
679
614
|
addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
|
|
680
615
|
}
|
|
681
|
-
|
|
682
616
|
/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
|
|
683
617
|
if (ALLOWED_TAGS.table) {
|
|
684
618
|
addToSet(ALLOWED_TAGS, ['tbody']);
|
|
@@ -691,10 +625,8 @@ function createDOMPurify() {
|
|
|
691
625
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
|
|
692
626
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
693
627
|
}
|
|
694
|
-
|
|
695
628
|
// Overwrite existing TrustedTypes policy.
|
|
696
629
|
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
697
|
-
|
|
698
630
|
// Sign local variables required by `sanitize`.
|
|
699
631
|
emptyHTML = trustedTypesPolicy.createHTML('');
|
|
700
632
|
} else {
|
|
@@ -702,13 +634,11 @@ function createDOMPurify() {
|
|
|
702
634
|
if (trustedTypesPolicy === undefined) {
|
|
703
635
|
trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
704
636
|
}
|
|
705
|
-
|
|
706
637
|
// If creating the internal policy succeeded sign internal variables.
|
|
707
638
|
if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
|
|
708
639
|
emptyHTML = trustedTypesPolicy.createHTML('');
|
|
709
640
|
}
|
|
710
641
|
}
|
|
711
|
-
|
|
712
642
|
// Prevent further manipulation of configuration.
|
|
713
643
|
// Not available in IE8, Safari 5, etc.
|
|
714
644
|
if (freeze) {
|
|
@@ -716,30 +646,19 @@ function createDOMPurify() {
|
|
|
716
646
|
}
|
|
717
647
|
CONFIG = cfg;
|
|
718
648
|
};
|
|
719
|
-
const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
720
|
-
const HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'annotation-xml']);
|
|
721
|
-
|
|
722
|
-
// Certain elements are allowed in both SVG and HTML
|
|
723
|
-
// namespace. We need to specify them explicitly
|
|
724
|
-
// so that they don't get erroneously deleted from
|
|
725
|
-
// HTML namespace.
|
|
726
|
-
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
|
|
727
|
-
|
|
728
649
|
/* Keep track of all possible SVG and MathML tags
|
|
729
650
|
* so that we can perform the namespace checks
|
|
730
651
|
* correctly. */
|
|
731
652
|
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
732
653
|
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
733
|
-
|
|
734
654
|
/**
|
|
735
|
-
* @param
|
|
736
|
-
* @returns
|
|
655
|
+
* @param element a DOM element whose namespace is being checked
|
|
656
|
+
* @returns Return false if the element has a
|
|
737
657
|
* namespace that a spec-compliant parser would never
|
|
738
658
|
* return. Return true otherwise.
|
|
739
659
|
*/
|
|
740
660
|
const _checkValidNamespace = function _checkValidNamespace(element) {
|
|
741
661
|
let parent = getParentNode(element);
|
|
742
|
-
|
|
743
662
|
// In JSDOM, if we're inside shadow DOM, then parentNode
|
|
744
663
|
// can be null. We just simulate parent in this case.
|
|
745
664
|
if (!parent || !parent.tagName) {
|
|
@@ -760,14 +679,12 @@ function createDOMPurify() {
|
|
|
760
679
|
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
761
680
|
return tagName === 'svg';
|
|
762
681
|
}
|
|
763
|
-
|
|
764
682
|
// The only way to switch from MathML to SVG is via`
|
|
765
683
|
// svg if parent is either <annotation-xml> or MathML
|
|
766
684
|
// text integration points.
|
|
767
685
|
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
768
686
|
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
769
687
|
}
|
|
770
|
-
|
|
771
688
|
// We only allow elements that are defined in SVG
|
|
772
689
|
// spec. All others are disallowed in SVG namespace.
|
|
773
690
|
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
@@ -779,13 +696,11 @@ function createDOMPurify() {
|
|
|
779
696
|
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
780
697
|
return tagName === 'math';
|
|
781
698
|
}
|
|
782
|
-
|
|
783
699
|
// The only way to switch from SVG to MathML is via
|
|
784
700
|
// <math> and HTML integration points
|
|
785
701
|
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
786
702
|
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
787
703
|
}
|
|
788
|
-
|
|
789
704
|
// We only allow elements that are defined in MathML
|
|
790
705
|
// spec. All others are disallowed in MathML namespace.
|
|
791
706
|
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
@@ -800,28 +715,24 @@ function createDOMPurify() {
|
|
|
800
715
|
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
801
716
|
return false;
|
|
802
717
|
}
|
|
803
|
-
|
|
804
718
|
// We disallow tags that are specific for MathML
|
|
805
719
|
// or SVG and should never appear in HTML namespace
|
|
806
720
|
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
807
721
|
}
|
|
808
|
-
|
|
809
722
|
// For XHTML and XML documents that support custom namespaces
|
|
810
723
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
811
724
|
return true;
|
|
812
725
|
}
|
|
813
|
-
|
|
814
726
|
// The code should never reach this place (this means
|
|
815
727
|
// that the element somehow got namespace that is not
|
|
816
728
|
// HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
|
|
817
729
|
// Return false just in case.
|
|
818
730
|
return false;
|
|
819
731
|
};
|
|
820
|
-
|
|
821
732
|
/**
|
|
822
733
|
* _forceRemove
|
|
823
734
|
*
|
|
824
|
-
* @param
|
|
735
|
+
* @param node a DOM node
|
|
825
736
|
*/
|
|
826
737
|
const _forceRemove = function _forceRemove(node) {
|
|
827
738
|
arrayPush(DOMPurify.removed, {
|
|
@@ -834,46 +745,43 @@ function createDOMPurify() {
|
|
|
834
745
|
remove(node);
|
|
835
746
|
}
|
|
836
747
|
};
|
|
837
|
-
|
|
838
748
|
/**
|
|
839
749
|
* _removeAttribute
|
|
840
750
|
*
|
|
841
|
-
* @param
|
|
842
|
-
* @param
|
|
751
|
+
* @param name an Attribute name
|
|
752
|
+
* @param element a DOM node
|
|
843
753
|
*/
|
|
844
|
-
const _removeAttribute = function _removeAttribute(name,
|
|
754
|
+
const _removeAttribute = function _removeAttribute(name, element) {
|
|
845
755
|
try {
|
|
846
756
|
arrayPush(DOMPurify.removed, {
|
|
847
|
-
attribute:
|
|
848
|
-
from:
|
|
757
|
+
attribute: element.getAttributeNode(name),
|
|
758
|
+
from: element
|
|
849
759
|
});
|
|
850
760
|
} catch (_) {
|
|
851
761
|
arrayPush(DOMPurify.removed, {
|
|
852
762
|
attribute: null,
|
|
853
|
-
from:
|
|
763
|
+
from: element
|
|
854
764
|
});
|
|
855
765
|
}
|
|
856
|
-
|
|
857
|
-
|
|
766
|
+
element.removeAttribute(name);
|
|
858
767
|
// We void attribute values for unremovable "is"" attributes
|
|
859
768
|
if (name === 'is' && !ALLOWED_ATTR[name]) {
|
|
860
769
|
if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
|
|
861
770
|
try {
|
|
862
|
-
_forceRemove(
|
|
771
|
+
_forceRemove(element);
|
|
863
772
|
} catch (_) {}
|
|
864
773
|
} else {
|
|
865
774
|
try {
|
|
866
|
-
|
|
775
|
+
element.setAttribute(name, '');
|
|
867
776
|
} catch (_) {}
|
|
868
777
|
}
|
|
869
778
|
}
|
|
870
779
|
};
|
|
871
|
-
|
|
872
780
|
/**
|
|
873
781
|
* _initDocument
|
|
874
782
|
*
|
|
875
|
-
* @param
|
|
876
|
-
* @return
|
|
783
|
+
* @param dirty - a string of dirty markup
|
|
784
|
+
* @return a DOM, filled with the dirty markup
|
|
877
785
|
*/
|
|
878
786
|
const _initDocument = function _initDocument(dirty) {
|
|
879
787
|
/* Create a HTML document */
|
|
@@ -900,7 +808,6 @@ function createDOMPurify() {
|
|
|
900
808
|
doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
|
|
901
809
|
} catch (_) {}
|
|
902
810
|
}
|
|
903
|
-
|
|
904
811
|
/* Use createHTMLDocument in case DOMParser is not available */
|
|
905
812
|
if (!doc || !doc.documentElement) {
|
|
906
813
|
doc = implementation.createDocument(NAMESPACE, 'template', null);
|
|
@@ -914,112 +821,89 @@ function createDOMPurify() {
|
|
|
914
821
|
if (dirty && leadingWhitespace) {
|
|
915
822
|
body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
|
|
916
823
|
}
|
|
917
|
-
|
|
918
824
|
/* Work on whole document or just its body */
|
|
919
825
|
if (NAMESPACE === HTML_NAMESPACE) {
|
|
920
826
|
return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
|
|
921
827
|
}
|
|
922
828
|
return WHOLE_DOCUMENT ? doc.documentElement : body;
|
|
923
829
|
};
|
|
924
|
-
|
|
925
830
|
/**
|
|
926
831
|
* Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
|
|
927
832
|
*
|
|
928
|
-
* @param
|
|
929
|
-
* @return
|
|
833
|
+
* @param root The root element or node to start traversing on.
|
|
834
|
+
* @return The created NodeIterator
|
|
930
835
|
*/
|
|
931
836
|
const _createNodeIterator = function _createNodeIterator(root) {
|
|
932
837
|
return createNodeIterator.call(root.ownerDocument || root, root,
|
|
933
838
|
// eslint-disable-next-line no-bitwise
|
|
934
839
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
935
840
|
};
|
|
936
|
-
|
|
937
841
|
/**
|
|
938
842
|
* _isClobbered
|
|
939
843
|
*
|
|
940
|
-
* @param
|
|
941
|
-
* @return
|
|
844
|
+
* @param element element to check for clobbering attacks
|
|
845
|
+
* @return true if clobbered, false if safe
|
|
942
846
|
*/
|
|
943
|
-
const _isClobbered = function _isClobbered(
|
|
944
|
-
return
|
|
847
|
+
const _isClobbered = function _isClobbered(element) {
|
|
848
|
+
return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
|
|
945
849
|
};
|
|
946
|
-
|
|
947
850
|
/**
|
|
948
851
|
* Checks whether the given object is a DOM node.
|
|
949
852
|
*
|
|
950
|
-
* @param
|
|
951
|
-
* @return
|
|
853
|
+
* @param value object to check whether it's a DOM node
|
|
854
|
+
* @return true is object is a DOM node
|
|
952
855
|
*/
|
|
953
|
-
const _isNode = function _isNode(
|
|
954
|
-
return typeof Node === 'function' &&
|
|
856
|
+
const _isNode = function _isNode(value) {
|
|
857
|
+
return typeof Node === 'function' && value instanceof Node;
|
|
955
858
|
};
|
|
956
|
-
|
|
957
|
-
/**
|
|
958
|
-
* _executeHook
|
|
959
|
-
* Execute user configurable hooks
|
|
960
|
-
*
|
|
961
|
-
* @param {String} entryPoint Name of the hook's entry point
|
|
962
|
-
* @param {Node} currentNode node to work on with the hook
|
|
963
|
-
* @param {Object} data additional hook parameters
|
|
964
|
-
*/
|
|
965
|
-
const _executeHook = function _executeHook(entryPoint, currentNode, data) {
|
|
859
|
+
function _executeHook(entryPoint, currentNode, data) {
|
|
966
860
|
if (!hooks[entryPoint]) {
|
|
967
861
|
return;
|
|
968
862
|
}
|
|
969
863
|
arrayForEach(hooks[entryPoint], hook => {
|
|
970
864
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
971
865
|
});
|
|
972
|
-
}
|
|
973
|
-
|
|
866
|
+
}
|
|
974
867
|
/**
|
|
975
868
|
* _sanitizeElements
|
|
976
869
|
*
|
|
977
870
|
* @protect nodeName
|
|
978
871
|
* @protect textContent
|
|
979
872
|
* @protect removeChild
|
|
980
|
-
*
|
|
981
|
-
* @
|
|
982
|
-
* @return {Boolean} true if node was killed, false if left alive
|
|
873
|
+
* @param currentNode to check for permission to exist
|
|
874
|
+
* @return true if node was killed, false if left alive
|
|
983
875
|
*/
|
|
984
876
|
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
985
877
|
let content = null;
|
|
986
|
-
|
|
987
878
|
/* Execute a hook if present */
|
|
988
879
|
_executeHook('beforeSanitizeElements', currentNode, null);
|
|
989
|
-
|
|
990
880
|
/* Check if element is clobbered or can clobber */
|
|
991
881
|
if (_isClobbered(currentNode)) {
|
|
992
882
|
_forceRemove(currentNode);
|
|
993
883
|
return true;
|
|
994
884
|
}
|
|
995
|
-
|
|
996
885
|
/* Now let's check the element's type and name */
|
|
997
886
|
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
998
|
-
|
|
999
887
|
/* Execute a hook if present */
|
|
1000
888
|
_executeHook('uponSanitizeElement', currentNode, {
|
|
1001
889
|
tagName,
|
|
1002
890
|
allowedTags: ALLOWED_TAGS
|
|
1003
891
|
});
|
|
1004
|
-
|
|
1005
892
|
/* Detect mXSS attempts abusing namespace confusion */
|
|
1006
893
|
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
|
|
1007
894
|
_forceRemove(currentNode);
|
|
1008
895
|
return true;
|
|
1009
896
|
}
|
|
1010
|
-
|
|
1011
897
|
/* Remove any occurrence of processing instructions */
|
|
1012
898
|
if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
|
|
1013
899
|
_forceRemove(currentNode);
|
|
1014
900
|
return true;
|
|
1015
901
|
}
|
|
1016
|
-
|
|
1017
902
|
/* Remove any kind of possibly harmful comments */
|
|
1018
903
|
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
|
|
1019
904
|
_forceRemove(currentNode);
|
|
1020
905
|
return true;
|
|
1021
906
|
}
|
|
1022
|
-
|
|
1023
907
|
/* Remove element if anything forbids its presence */
|
|
1024
908
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
1025
909
|
/* Check if we have a custom element to handle */
|
|
@@ -1031,7 +915,6 @@ function createDOMPurify() {
|
|
|
1031
915
|
return false;
|
|
1032
916
|
}
|
|
1033
917
|
}
|
|
1034
|
-
|
|
1035
918
|
/* Keep content except for bad-listed elements */
|
|
1036
919
|
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
1037
920
|
const parentNode = getParentNode(currentNode) || currentNode.parentNode;
|
|
@@ -1048,19 +931,16 @@ function createDOMPurify() {
|
|
|
1048
931
|
_forceRemove(currentNode);
|
|
1049
932
|
return true;
|
|
1050
933
|
}
|
|
1051
|
-
|
|
1052
934
|
/* Check whether element has a valid namespace */
|
|
1053
935
|
if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
|
|
1054
936
|
_forceRemove(currentNode);
|
|
1055
937
|
return true;
|
|
1056
938
|
}
|
|
1057
|
-
|
|
1058
939
|
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
1059
940
|
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
|
|
1060
941
|
_forceRemove(currentNode);
|
|
1061
942
|
return true;
|
|
1062
943
|
}
|
|
1063
|
-
|
|
1064
944
|
/* Sanitize element content to be template-safe */
|
|
1065
945
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1066
946
|
/* Get the element's text content */
|
|
@@ -1075,19 +955,17 @@ function createDOMPurify() {
|
|
|
1075
955
|
currentNode.textContent = content;
|
|
1076
956
|
}
|
|
1077
957
|
}
|
|
1078
|
-
|
|
1079
958
|
/* Execute a hook if present */
|
|
1080
959
|
_executeHook('afterSanitizeElements', currentNode, null);
|
|
1081
960
|
return false;
|
|
1082
961
|
};
|
|
1083
|
-
|
|
1084
962
|
/**
|
|
1085
963
|
* _isValidAttribute
|
|
1086
964
|
*
|
|
1087
|
-
* @param
|
|
1088
|
-
* @param
|
|
1089
|
-
* @param
|
|
1090
|
-
* @return
|
|
965
|
+
* @param lcTag Lowercase tag name of containing element.
|
|
966
|
+
* @param lcName Lowercase attribute name.
|
|
967
|
+
* @param value Attribute value.
|
|
968
|
+
* @return Returns true if `value` is valid, otherwise false.
|
|
1091
969
|
*/
|
|
1092
970
|
// eslint-disable-next-line complexity
|
|
1093
971
|
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
|
|
@@ -1095,7 +973,6 @@ function createDOMPurify() {
|
|
|
1095
973
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
1096
974
|
return false;
|
|
1097
975
|
}
|
|
1098
|
-
|
|
1099
976
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
1100
977
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1101
978
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
@@ -1117,19 +994,17 @@ function createDOMPurify() {
|
|
|
1117
994
|
} else ;
|
|
1118
995
|
return true;
|
|
1119
996
|
};
|
|
1120
|
-
|
|
1121
997
|
/**
|
|
1122
998
|
* _isBasicCustomElement
|
|
1123
999
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
1124
1000
|
* for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
|
|
1125
1001
|
*
|
|
1126
|
-
* @param
|
|
1127
|
-
* @returns
|
|
1002
|
+
* @param tagName name of the tag of the node to sanitize
|
|
1003
|
+
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
1128
1004
|
*/
|
|
1129
1005
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1130
1006
|
return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
|
|
1131
1007
|
};
|
|
1132
|
-
|
|
1133
1008
|
/**
|
|
1134
1009
|
* _sanitizeAttributes
|
|
1135
1010
|
*
|
|
@@ -1138,7 +1013,7 @@ function createDOMPurify() {
|
|
|
1138
1013
|
* @protect removeAttribute
|
|
1139
1014
|
* @protect setAttribute
|
|
1140
1015
|
*
|
|
1141
|
-
* @param
|
|
1016
|
+
* @param currentNode to sanitize
|
|
1142
1017
|
*/
|
|
1143
1018
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1144
1019
|
/* Execute a hook if present */
|
|
@@ -1146,7 +1021,6 @@ function createDOMPurify() {
|
|
|
1146
1021
|
const {
|
|
1147
1022
|
attributes
|
|
1148
1023
|
} = currentNode;
|
|
1149
|
-
|
|
1150
1024
|
/* Check if we have attributes; if not we might have a text node */
|
|
1151
1025
|
if (!attributes) {
|
|
1152
1026
|
return;
|
|
@@ -1155,10 +1029,10 @@ function createDOMPurify() {
|
|
|
1155
1029
|
attrName: '',
|
|
1156
1030
|
attrValue: '',
|
|
1157
1031
|
keepAttr: true,
|
|
1158
|
-
allowedAttributes: ALLOWED_ATTR
|
|
1032
|
+
allowedAttributes: ALLOWED_ATTR,
|
|
1033
|
+
forceKeepAttr: undefined
|
|
1159
1034
|
};
|
|
1160
1035
|
let l = attributes.length;
|
|
1161
|
-
|
|
1162
1036
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1163
1037
|
while (l--) {
|
|
1164
1038
|
const attr = attributes[l];
|
|
@@ -1169,7 +1043,6 @@ function createDOMPurify() {
|
|
|
1169
1043
|
} = attr;
|
|
1170
1044
|
const lcName = transformCaseFunc(name);
|
|
1171
1045
|
let value = name === 'value' ? attrValue : stringTrim(attrValue);
|
|
1172
|
-
|
|
1173
1046
|
/* Execute a hook if present */
|
|
1174
1047
|
hookEvent.attrName = lcName;
|
|
1175
1048
|
hookEvent.attrValue = value;
|
|
@@ -1177,56 +1050,46 @@ function createDOMPurify() {
|
|
|
1177
1050
|
hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
|
|
1178
1051
|
_executeHook('uponSanitizeAttribute', currentNode, hookEvent);
|
|
1179
1052
|
value = hookEvent.attrValue;
|
|
1180
|
-
|
|
1053
|
+
/* Full DOM Clobbering protection via namespace isolation,
|
|
1054
|
+
* Prefix id and name attributes with `user-content-`
|
|
1055
|
+
*/
|
|
1056
|
+
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
1057
|
+
// Remove the attribute with this value
|
|
1058
|
+
_removeAttribute(name, currentNode);
|
|
1059
|
+
// Prefix the value and later re-create the attribute with the sanitized value
|
|
1060
|
+
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
1061
|
+
}
|
|
1181
1062
|
/* Work around a security issue with comments inside attributes */
|
|
1182
1063
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
|
|
1183
1064
|
_removeAttribute(name, currentNode);
|
|
1184
1065
|
continue;
|
|
1185
1066
|
}
|
|
1186
|
-
|
|
1187
1067
|
/* Did the hooks approve of the attribute? */
|
|
1188
1068
|
if (hookEvent.forceKeepAttr) {
|
|
1189
1069
|
continue;
|
|
1190
1070
|
}
|
|
1191
|
-
|
|
1192
1071
|
/* Remove attribute */
|
|
1193
1072
|
_removeAttribute(name, currentNode);
|
|
1194
|
-
|
|
1195
1073
|
/* Did the hooks approve of the attribute? */
|
|
1196
1074
|
if (!hookEvent.keepAttr) {
|
|
1197
1075
|
continue;
|
|
1198
1076
|
}
|
|
1199
|
-
|
|
1200
1077
|
/* Work around a security issue in jQuery 3.0 */
|
|
1201
1078
|
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
|
|
1202
1079
|
_removeAttribute(name, currentNode);
|
|
1203
1080
|
continue;
|
|
1204
1081
|
}
|
|
1205
|
-
|
|
1206
1082
|
/* Sanitize attribute content to be template-safe */
|
|
1207
1083
|
if (SAFE_FOR_TEMPLATES) {
|
|
1208
1084
|
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1209
1085
|
value = stringReplace(value, expr, ' ');
|
|
1210
1086
|
});
|
|
1211
1087
|
}
|
|
1212
|
-
|
|
1213
1088
|
/* Is `value` valid for this attribute? */
|
|
1214
1089
|
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1215
1090
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
1216
1091
|
continue;
|
|
1217
1092
|
}
|
|
1218
|
-
|
|
1219
|
-
/* Full DOM Clobbering protection via namespace isolation,
|
|
1220
|
-
* Prefix id and name attributes with `user-content-`
|
|
1221
|
-
*/
|
|
1222
|
-
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
1223
|
-
// Remove the attribute with this value
|
|
1224
|
-
_removeAttribute(name, currentNode);
|
|
1225
|
-
|
|
1226
|
-
// Prefix the value and later re-create the attribute with the sanitized value
|
|
1227
|
-
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
1093
|
/* Handle attributes that require Trusted Types */
|
|
1231
1094
|
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
|
|
1232
1095
|
if (namespaceURI) ; else {
|
|
@@ -1244,7 +1107,6 @@ function createDOMPurify() {
|
|
|
1244
1107
|
}
|
|
1245
1108
|
}
|
|
1246
1109
|
}
|
|
1247
|
-
|
|
1248
1110
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
1249
1111
|
try {
|
|
1250
1112
|
if (namespaceURI) {
|
|
@@ -1260,51 +1122,36 @@ function createDOMPurify() {
|
|
|
1260
1122
|
}
|
|
1261
1123
|
} catch (_) {}
|
|
1262
1124
|
}
|
|
1263
|
-
|
|
1264
1125
|
/* Execute a hook if present */
|
|
1265
1126
|
_executeHook('afterSanitizeAttributes', currentNode, null);
|
|
1266
1127
|
};
|
|
1267
|
-
|
|
1268
1128
|
/**
|
|
1269
1129
|
* _sanitizeShadowDOM
|
|
1270
1130
|
*
|
|
1271
|
-
* @param
|
|
1131
|
+
* @param fragment to iterate over recursively
|
|
1272
1132
|
*/
|
|
1273
1133
|
const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
|
|
1274
1134
|
let shadowNode = null;
|
|
1275
1135
|
const shadowIterator = _createNodeIterator(fragment);
|
|
1276
|
-
|
|
1277
1136
|
/* Execute a hook if present */
|
|
1278
1137
|
_executeHook('beforeSanitizeShadowDOM', fragment, null);
|
|
1279
1138
|
while (shadowNode = shadowIterator.nextNode()) {
|
|
1280
1139
|
/* Execute a hook if present */
|
|
1281
1140
|
_executeHook('uponSanitizeShadowNode', shadowNode, null);
|
|
1282
|
-
|
|
1283
1141
|
/* Sanitize tags and elements */
|
|
1284
1142
|
if (_sanitizeElements(shadowNode)) {
|
|
1285
1143
|
continue;
|
|
1286
1144
|
}
|
|
1287
|
-
|
|
1288
1145
|
/* Deep shadow DOM detected */
|
|
1289
1146
|
if (shadowNode.content instanceof DocumentFragment) {
|
|
1290
1147
|
_sanitizeShadowDOM(shadowNode.content);
|
|
1291
1148
|
}
|
|
1292
|
-
|
|
1293
1149
|
/* Check attributes, sanitize if necessary */
|
|
1294
1150
|
_sanitizeAttributes(shadowNode);
|
|
1295
1151
|
}
|
|
1296
|
-
|
|
1297
1152
|
/* Execute a hook if present */
|
|
1298
1153
|
_executeHook('afterSanitizeShadowDOM', fragment, null);
|
|
1299
1154
|
};
|
|
1300
|
-
|
|
1301
|
-
/**
|
|
1302
|
-
* Sanitize
|
|
1303
|
-
* Public method providing core sanitation functionality
|
|
1304
|
-
*
|
|
1305
|
-
* @param {String|Node} dirty string or DOM node
|
|
1306
|
-
* @param {Object} cfg object
|
|
1307
|
-
*/
|
|
1308
1155
|
// eslint-disable-next-line complexity
|
|
1309
1156
|
DOMPurify.sanitize = function (dirty) {
|
|
1310
1157
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -1319,7 +1166,6 @@ function createDOMPurify() {
|
|
|
1319
1166
|
if (IS_EMPTY_INPUT) {
|
|
1320
1167
|
dirty = '<!-->';
|
|
1321
1168
|
}
|
|
1322
|
-
|
|
1323
1169
|
/* Stringify, in case dirty is an object */
|
|
1324
1170
|
if (typeof dirty !== 'string' && !_isNode(dirty)) {
|
|
1325
1171
|
if (typeof dirty.toString === 'function') {
|
|
@@ -1331,20 +1177,16 @@ function createDOMPurify() {
|
|
|
1331
1177
|
throw typeErrorCreate('toString is not a function');
|
|
1332
1178
|
}
|
|
1333
1179
|
}
|
|
1334
|
-
|
|
1335
1180
|
/* Return dirty HTML if DOMPurify cannot run */
|
|
1336
1181
|
if (!DOMPurify.isSupported) {
|
|
1337
1182
|
return dirty;
|
|
1338
1183
|
}
|
|
1339
|
-
|
|
1340
1184
|
/* Assign config vars */
|
|
1341
1185
|
if (!SET_CONFIG) {
|
|
1342
1186
|
_parseConfig(cfg);
|
|
1343
1187
|
}
|
|
1344
|
-
|
|
1345
1188
|
/* Clean up removed elements */
|
|
1346
1189
|
DOMPurify.removed = [];
|
|
1347
|
-
|
|
1348
1190
|
/* Check if dirty is correctly typed for IN_PLACE */
|
|
1349
1191
|
if (typeof dirty === 'string') {
|
|
1350
1192
|
IN_PLACE = false;
|
|
@@ -1378,45 +1220,36 @@ function createDOMPurify() {
|
|
|
1378
1220
|
dirty.indexOf('<') === -1) {
|
|
1379
1221
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
|
|
1380
1222
|
}
|
|
1381
|
-
|
|
1382
1223
|
/* Initialize the document to work on */
|
|
1383
1224
|
body = _initDocument(dirty);
|
|
1384
|
-
|
|
1385
1225
|
/* Check we have a DOM node from the data */
|
|
1386
1226
|
if (!body) {
|
|
1387
1227
|
return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
|
|
1388
1228
|
}
|
|
1389
1229
|
}
|
|
1390
|
-
|
|
1391
1230
|
/* Remove first element node (ours) if FORCE_BODY is set */
|
|
1392
1231
|
if (body && FORCE_BODY) {
|
|
1393
1232
|
_forceRemove(body.firstChild);
|
|
1394
1233
|
}
|
|
1395
|
-
|
|
1396
1234
|
/* Get node iterator */
|
|
1397
1235
|
const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
|
|
1398
|
-
|
|
1399
1236
|
/* Now start iterating over the created document */
|
|
1400
1237
|
while (currentNode = nodeIterator.nextNode()) {
|
|
1401
1238
|
/* Sanitize tags and elements */
|
|
1402
1239
|
if (_sanitizeElements(currentNode)) {
|
|
1403
1240
|
continue;
|
|
1404
1241
|
}
|
|
1405
|
-
|
|
1406
1242
|
/* Shadow DOM detected, sanitize it */
|
|
1407
1243
|
if (currentNode.content instanceof DocumentFragment) {
|
|
1408
1244
|
_sanitizeShadowDOM(currentNode.content);
|
|
1409
1245
|
}
|
|
1410
|
-
|
|
1411
1246
|
/* Check attributes, sanitize if necessary */
|
|
1412
1247
|
_sanitizeAttributes(currentNode);
|
|
1413
1248
|
}
|
|
1414
|
-
|
|
1415
1249
|
/* If we sanitized `dirty` in-place, return it. */
|
|
1416
1250
|
if (IN_PLACE) {
|
|
1417
1251
|
return dirty;
|
|
1418
1252
|
}
|
|
1419
|
-
|
|
1420
1253
|
/* Return sanitized string or DOM */
|
|
1421
1254
|
if (RETURN_DOM) {
|
|
1422
1255
|
if (RETURN_DOM_FRAGMENT) {
|
|
@@ -1441,12 +1274,10 @@ function createDOMPurify() {
|
|
|
1441
1274
|
return returnNode;
|
|
1442
1275
|
}
|
|
1443
1276
|
let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
|
|
1444
|
-
|
|
1445
1277
|
/* Serialize doctype if allowed */
|
|
1446
1278
|
if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
|
|
1447
1279
|
serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
|
|
1448
1280
|
}
|
|
1449
|
-
|
|
1450
1281
|
/* Sanitize final string template-safe */
|
|
1451
1282
|
if (SAFE_FOR_TEMPLATES) {
|
|
1452
1283
|
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
@@ -1455,39 +1286,15 @@ function createDOMPurify() {
|
|
|
1455
1286
|
}
|
|
1456
1287
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
|
|
1457
1288
|
};
|
|
1458
|
-
|
|
1459
|
-
/**
|
|
1460
|
-
* Public method to set the configuration once
|
|
1461
|
-
* setConfig
|
|
1462
|
-
*
|
|
1463
|
-
* @param {Object} cfg configuration object
|
|
1464
|
-
*/
|
|
1465
1289
|
DOMPurify.setConfig = function () {
|
|
1466
1290
|
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
1467
1291
|
_parseConfig(cfg);
|
|
1468
1292
|
SET_CONFIG = true;
|
|
1469
1293
|
};
|
|
1470
|
-
|
|
1471
|
-
/**
|
|
1472
|
-
* Public method to remove the configuration
|
|
1473
|
-
* clearConfig
|
|
1474
|
-
*
|
|
1475
|
-
*/
|
|
1476
1294
|
DOMPurify.clearConfig = function () {
|
|
1477
1295
|
CONFIG = null;
|
|
1478
1296
|
SET_CONFIG = false;
|
|
1479
1297
|
};
|
|
1480
|
-
|
|
1481
|
-
/**
|
|
1482
|
-
* Public method to check if an attribute value is valid.
|
|
1483
|
-
* Uses last set config, if any. Otherwise, uses config defaults.
|
|
1484
|
-
* isValidAttribute
|
|
1485
|
-
*
|
|
1486
|
-
* @param {String} tag Tag name of containing element.
|
|
1487
|
-
* @param {String} attr Attribute name.
|
|
1488
|
-
* @param {String} value Attribute value.
|
|
1489
|
-
* @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
|
|
1490
|
-
*/
|
|
1491
1298
|
DOMPurify.isValidAttribute = function (tag, attr, value) {
|
|
1492
1299
|
/* Initialize shared config vars if necessary. */
|
|
1493
1300
|
if (!CONFIG) {
|
|
@@ -1497,14 +1304,6 @@ function createDOMPurify() {
|
|
|
1497
1304
|
const lcName = transformCaseFunc(attr);
|
|
1498
1305
|
return _isValidAttribute(lcTag, lcName, value);
|
|
1499
1306
|
};
|
|
1500
|
-
|
|
1501
|
-
/**
|
|
1502
|
-
* AddHook
|
|
1503
|
-
* Public method to add DOMPurify hooks
|
|
1504
|
-
*
|
|
1505
|
-
* @param {String} entryPoint entry point for the hook to add
|
|
1506
|
-
* @param {Function} hookFunction function to execute
|
|
1507
|
-
*/
|
|
1508
1307
|
DOMPurify.addHook = function (entryPoint, hookFunction) {
|
|
1509
1308
|
if (typeof hookFunction !== 'function') {
|
|
1510
1309
|
return;
|
|
@@ -1512,37 +1311,16 @@ function createDOMPurify() {
|
|
|
1512
1311
|
hooks[entryPoint] = hooks[entryPoint] || [];
|
|
1513
1312
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
1514
1313
|
};
|
|
1515
|
-
|
|
1516
|
-
/**
|
|
1517
|
-
* RemoveHook
|
|
1518
|
-
* Public method to remove a DOMPurify hook at a given entryPoint
|
|
1519
|
-
* (pops it from the stack of hooks if more are present)
|
|
1520
|
-
*
|
|
1521
|
-
* @param {String} entryPoint entry point for the hook to remove
|
|
1522
|
-
* @return {Function} removed(popped) hook
|
|
1523
|
-
*/
|
|
1524
1314
|
DOMPurify.removeHook = function (entryPoint) {
|
|
1525
1315
|
if (hooks[entryPoint]) {
|
|
1526
1316
|
return arrayPop(hooks[entryPoint]);
|
|
1527
1317
|
}
|
|
1528
1318
|
};
|
|
1529
|
-
|
|
1530
|
-
/**
|
|
1531
|
-
* RemoveHooks
|
|
1532
|
-
* Public method to remove all DOMPurify hooks at a given entryPoint
|
|
1533
|
-
*
|
|
1534
|
-
* @param {String} entryPoint entry point for the hooks to remove
|
|
1535
|
-
*/
|
|
1536
1319
|
DOMPurify.removeHooks = function (entryPoint) {
|
|
1537
1320
|
if (hooks[entryPoint]) {
|
|
1538
1321
|
hooks[entryPoint] = [];
|
|
1539
1322
|
}
|
|
1540
1323
|
};
|
|
1541
|
-
|
|
1542
|
-
/**
|
|
1543
|
-
* RemoveAllHooks
|
|
1544
|
-
* Public method to remove all DOMPurify hooks
|
|
1545
|
-
*/
|
|
1546
1324
|
DOMPurify.removeAllHooks = function () {
|
|
1547
1325
|
hooks = {};
|
|
1548
1326
|
};
|