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