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