source: node_modules/dompurify/dist/purify.js@ b78c0c1

main
Last change on this file since b78c0c1 was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

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