main
mjs 1,397 lines 63.1 KB
Raw
1 /*! @license DOMPurify 3.3.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.3/LICENSE */
2
3 const {
4 entries,
5 setPrototypeOf,
6 isFrozen,
7 getPrototypeOf,
8 getOwnPropertyDescriptor
9 } = Object;
10 let {
11 freeze,
12 seal,
13 create
14 } = Object; // eslint-disable-line import/no-mutable-exports
15 let {
16 apply,
17 construct
18 } = typeof Reflect !== 'undefined' && Reflect;
19 if (!freeze) {
20 freeze = function freeze(x) {
21 return x;
22 };
23 }
24 if (!seal) {
25 seal = function seal(x) {
26 return x;
27 };
28 }
29 if (!apply) {
30 apply = function apply(func, thisArg) {
31 for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
32 args[_key - 2] = arguments[_key];
33 }
34 return func.apply(thisArg, args);
35 };
36 }
37 if (!construct) {
38 construct = function construct(Func) {
39 for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
40 args[_key2 - 1] = arguments[_key2];
41 }
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 _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
71 args[_key3 - 1] = arguments[_key3];
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 _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
85 args[_key4] = arguments[_key4];
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', 'search', 'section', 'select', 'shadow', 'slot', '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', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', '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', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', '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', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', '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', 'mask-type', '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.3.3';
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 /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */
420 const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {
421 tagCheck: {
422 writable: true,
423 configurable: false,
424 enumerable: true,
425 value: null
426 },
427 attributeCheck: {
428 writable: true,
429 configurable: false,
430 enumerable: true,
431 value: null
432 }
433 }));
434 /* Decide if ARIA attributes are okay */
435 let ALLOW_ARIA_ATTR = true;
436 /* Decide if custom data attributes are okay */
437 let ALLOW_DATA_ATTR = true;
438 /* Decide if unknown protocols are okay */
439 let ALLOW_UNKNOWN_PROTOCOLS = false;
440 /* Decide if self-closing tags in attributes are allowed.
441 * Usually removed due to a mXSS issue in jQuery 3.0 */
442 let ALLOW_SELF_CLOSE_IN_ATTR = true;
443 /* Output should be safe for common template engines.
444 * This means, DOMPurify removes data attributes, mustaches and ERB
445 */
446 let SAFE_FOR_TEMPLATES = false;
447 /* Output should be safe even for XML used within HTML and alike.
448 * This means, DOMPurify removes comments when containing risky content.
449 */
450 let SAFE_FOR_XML = true;
451 /* Decide if document with <html>... should be returned */
452 let WHOLE_DOCUMENT = false;
453 /* Track whether config is already set on this instance of DOMPurify. */
454 let SET_CONFIG = false;
455 /* Decide if all elements (e.g. style, script) must be children of
456 * document.body. By default, browsers might move them to document.head */
457 let FORCE_BODY = false;
458 /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
459 * string (or a TrustedHTML object if Trusted Types are supported).
460 * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
461 */
462 let RETURN_DOM = false;
463 /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
464 * string (or a TrustedHTML object if Trusted Types are supported) */
465 let RETURN_DOM_FRAGMENT = false;
466 /* Try to return a Trusted Type object instead of a string, return a string in
467 * case Trusted Types are not supported */
468 let RETURN_TRUSTED_TYPE = false;
469 /* Output should be free from DOM clobbering attacks?
470 * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
471 */
472 let SANITIZE_DOM = true;
473 /* Achieve full DOM Clobbering protection by isolating the namespace of named
474 * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
475 *
476 * HTML/DOM spec rules that enable DOM Clobbering:
477 * - Named Access on Window (§7.3.3)
478 * - DOM Tree Accessors (§3.1.5)
479 * - Form Element Parent-Child Relations (§4.10.3)
480 * - Iframe srcdoc / Nested WindowProxies (§4.8.5)
481 * - HTMLCollection (§4.2.10.2)
482 *
483 * Namespace isolation is implemented by prefixing `id` and `name` attributes
484 * with a constant string, i.e., `user-content-`
485 */
486 let SANITIZE_NAMED_PROPS = false;
487 const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
488 /* Keep element content when removing element? */
489 let KEEP_CONTENT = true;
490 /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
491 * of importing it into a new Document and returning a sanitized copy */
492 let IN_PLACE = false;
493 /* Allow usage of profiles like html, svg and mathMl */
494 let USE_PROFILES = {};
495 /* Tags to ignore content of when KEEP_CONTENT is true */
496 let FORBID_CONTENTS = null;
497 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']);
498 /* Tags that are safe for data: URIs */
499 let DATA_URI_TAGS = null;
500 const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
501 /* Attributes safe for values like "javascript:" */
502 let URI_SAFE_ATTRIBUTES = null;
503 const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
504 const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
505 const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
506 const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
507 /* Document namespace */
508 let NAMESPACE = HTML_NAMESPACE;
509 let IS_EMPTY_INPUT = false;
510 /* Allowed XHTML+XML namespaces */
511 let ALLOWED_NAMESPACES = null;
512 const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
513 let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
514 let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
515 // Certain elements are allowed in both SVG and HTML
516 // namespace. We need to specify them explicitly
517 // so that they don't get erroneously deleted from
518 // HTML namespace.
519 const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
520 /* Parsing of strict XHTML documents */
521 let PARSER_MEDIA_TYPE = null;
522 const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
523 const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
524 let transformCaseFunc = null;
525 /* Keep a reference to config to pass to hooks */
526 let CONFIG = null;
527 /* Ideally, do not touch anything below this line */
528 /* ______________________________________________ */
529 const formElement = document.createElement('form');
530 const isRegexOrFunction = function isRegexOrFunction(testValue) {
531 return testValue instanceof RegExp || testValue instanceof Function;
532 };
533 /**
534 * _parseConfig
535 *
536 * @param cfg optional config literal
537 */
538 // eslint-disable-next-line complexity
539 const _parseConfig = function _parseConfig() {
540 let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
541 if (CONFIG && CONFIG === cfg) {
542 return;
543 }
544 /* Shield configuration object from tampering */
545 if (!cfg || typeof cfg !== 'object') {
546 cfg = {};
547 }
548 /* Shield configuration object from prototype pollution */
549 cfg = clone(cfg);
550 PARSER_MEDIA_TYPE =
551 // eslint-disable-next-line unicorn/prefer-includes
552 SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
553 // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
554 transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
555 /* Set configuration parameters */
556 ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
557 ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
558 ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
559 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;
560 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;
561 FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
562 FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
563 FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
564 USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
565 ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
566 ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
567 ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
568 ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
569 SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
570 SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
571 WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
572 RETURN_DOM = cfg.RETURN_DOM || false; // Default false
573 RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
574 RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
575 FORCE_BODY = cfg.FORCE_BODY || false; // Default false
576 SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
577 SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
578 KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
579 IN_PLACE = cfg.IN_PLACE || false; // Default false
580 IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
581 NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
582 MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
583 HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
584 CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
585 if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
586 CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
587 }
588 if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
589 CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
590 }
591 if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
592 CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
593 }
594 if (SAFE_FOR_TEMPLATES) {
595 ALLOW_DATA_ATTR = false;
596 }
597 if (RETURN_DOM_FRAGMENT) {
598 RETURN_DOM = true;
599 }
600 /* Parse profile info */
601 if (USE_PROFILES) {
602 ALLOWED_TAGS = addToSet({}, text);
603 ALLOWED_ATTR = create(null);
604 if (USE_PROFILES.html === true) {
605 addToSet(ALLOWED_TAGS, html$1);
606 addToSet(ALLOWED_ATTR, html);
607 }
608 if (USE_PROFILES.svg === true) {
609 addToSet(ALLOWED_TAGS, svg$1);
610 addToSet(ALLOWED_ATTR, svg);
611 addToSet(ALLOWED_ATTR, xml);
612 }
613 if (USE_PROFILES.svgFilters === true) {
614 addToSet(ALLOWED_TAGS, svgFilters);
615 addToSet(ALLOWED_ATTR, svg);
616 addToSet(ALLOWED_ATTR, xml);
617 }
618 if (USE_PROFILES.mathMl === true) {
619 addToSet(ALLOWED_TAGS, mathMl$1);
620 addToSet(ALLOWED_ATTR, mathMl);
621 addToSet(ALLOWED_ATTR, xml);
622 }
623 }
624 /* Prevent function-based ADD_ATTR / ADD_TAGS from leaking across calls */
625 if (!objectHasOwnProperty(cfg, 'ADD_TAGS')) {
626 EXTRA_ELEMENT_HANDLING.tagCheck = null;
627 }
628 if (!objectHasOwnProperty(cfg, 'ADD_ATTR')) {
629 EXTRA_ELEMENT_HANDLING.attributeCheck = null;
630 }
631 /* Merge configuration parameters */
632 if (cfg.ADD_TAGS) {
633 if (typeof cfg.ADD_TAGS === 'function') {
634 EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
635 } else {
636 if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
637 ALLOWED_TAGS = clone(ALLOWED_TAGS);
638 }
639 addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
640 }
641 }
642 if (cfg.ADD_ATTR) {
643 if (typeof cfg.ADD_ATTR === 'function') {
644 EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
645 } else {
646 if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
647 ALLOWED_ATTR = clone(ALLOWED_ATTR);
648 }
649 addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
650 }
651 }
652 if (cfg.ADD_URI_SAFE_ATTR) {
653 addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
654 }
655 if (cfg.FORBID_CONTENTS) {
656 if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
657 FORBID_CONTENTS = clone(FORBID_CONTENTS);
658 }
659 addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
660 }
661 if (cfg.ADD_FORBID_CONTENTS) {
662 if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
663 FORBID_CONTENTS = clone(FORBID_CONTENTS);
664 }
665 addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
666 }
667 /* Add #text in case KEEP_CONTENT is set to true */
668 if (KEEP_CONTENT) {
669 ALLOWED_TAGS['#text'] = true;
670 }
671 /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
672 if (WHOLE_DOCUMENT) {
673 addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
674 }
675 /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
676 if (ALLOWED_TAGS.table) {
677 addToSet(ALLOWED_TAGS, ['tbody']);
678 delete FORBID_TAGS.tbody;
679 }
680 if (cfg.TRUSTED_TYPES_POLICY) {
681 if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
682 throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
683 }
684 if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
685 throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
686 }
687 // Overwrite existing TrustedTypes policy.
688 trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
689 // Sign local variables required by `sanitize`.
690 emptyHTML = trustedTypesPolicy.createHTML('');
691 } else {
692 // Uninitialized policy, attempt to initialize the internal dompurify policy.
693 if (trustedTypesPolicy === undefined) {
694 trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
695 }
696 // If creating the internal policy succeeded sign internal variables.
697 if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
698 emptyHTML = trustedTypesPolicy.createHTML('');
699 }
700 }
701 // Prevent further manipulation of configuration.
702 // Not available in IE8, Safari 5, etc.
703 if (freeze) {
704 freeze(cfg);
705 }
706 CONFIG = cfg;
707 };
708 /* Keep track of all possible SVG and MathML tags
709 * so that we can perform the namespace checks
710 * correctly. */
711 const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
712 const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
713 /**
714 * @param element a DOM element whose namespace is being checked
715 * @returns 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 // In JSDOM, if we're inside shadow DOM, then parentNode
722 // can be null. We just simulate parent in this case.
723 if (!parent || !parent.tagName) {
724 parent = {
725 namespaceURI: NAMESPACE,
726 tagName: 'template'
727 };
728 }
729 const tagName = stringToLowerCase(element.tagName);
730 const parentTagName = stringToLowerCase(parent.tagName);
731 if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
732 return false;
733 }
734 if (element.namespaceURI === SVG_NAMESPACE) {
735 // The only way to switch from HTML namespace to SVG
736 // is via <svg>. If it happens via any other tag, then
737 // it should be killed.
738 if (parent.namespaceURI === HTML_NAMESPACE) {
739 return tagName === 'svg';
740 }
741 // The only way to switch from MathML to SVG is via`
742 // svg if parent is either <annotation-xml> or MathML
743 // text integration points.
744 if (parent.namespaceURI === MATHML_NAMESPACE) {
745 return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
746 }
747 // We only allow elements that are defined in SVG
748 // spec. All others are disallowed in SVG namespace.
749 return Boolean(ALL_SVG_TAGS[tagName]);
750 }
751 if (element.namespaceURI === MATHML_NAMESPACE) {
752 // The only way to switch from HTML namespace to MathML
753 // is via <math>. If it happens via any other tag, then
754 // it should be killed.
755 if (parent.namespaceURI === HTML_NAMESPACE) {
756 return tagName === 'math';
757 }
758 // The only way to switch from SVG to MathML is via
759 // <math> and HTML integration points
760 if (parent.namespaceURI === SVG_NAMESPACE) {
761 return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
762 }
763 // We only allow elements that are defined in MathML
764 // spec. All others are disallowed in MathML namespace.
765 return Boolean(ALL_MATHML_TAGS[tagName]);
766 }
767 if (element.namespaceURI === HTML_NAMESPACE) {
768 // The only way to switch from SVG to HTML is via
769 // HTML integration points, and from MathML to HTML
770 // is via MathML text integration points
771 if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
772 return false;
773 }
774 if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
775 return false;
776 }
777 // We disallow tags that are specific for MathML
778 // or SVG and should never appear in HTML namespace
779 return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
780 }
781 // For XHTML and XML documents that support custom namespaces
782 if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
783 return true;
784 }
785 // The code should never reach this place (this means
786 // that the element somehow got namespace that is not
787 // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
788 // Return false just in case.
789 return false;
790 };
791 /**
792 * _forceRemove
793 *
794 * @param node a DOM node
795 */
796 const _forceRemove = function _forceRemove(node) {
797 arrayPush(DOMPurify.removed, {
798 element: node
799 });
800 try {
801 // eslint-disable-next-line unicorn/prefer-dom-node-remove
802 getParentNode(node).removeChild(node);
803 } catch (_) {
804 remove(node);
805 }
806 };
807 /**
808 * _removeAttribute
809 *
810 * @param name an Attribute name
811 * @param element a DOM node
812 */
813 const _removeAttribute = function _removeAttribute(name, element) {
814 try {
815 arrayPush(DOMPurify.removed, {
816 attribute: element.getAttributeNode(name),
817 from: element
818 });
819 } catch (_) {
820 arrayPush(DOMPurify.removed, {
821 attribute: null,
822 from: element
823 });
824 }
825 element.removeAttribute(name);
826 // We void attribute values for unremovable "is" attributes
827 if (name === 'is') {
828 if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
829 try {
830 _forceRemove(element);
831 } catch (_) {}
832 } else {
833 try {
834 element.setAttribute(name, '');
835 } catch (_) {}
836 }
837 }
838 };
839 /**
840 * _initDocument
841 *
842 * @param dirty - a string of dirty markup
843 * @return a DOM, filled with the dirty markup
844 */
845 const _initDocument = function _initDocument(dirty) {
846 /* Create a HTML document */
847 let doc = null;
848 let leadingWhitespace = null;
849 if (FORCE_BODY) {
850 dirty = '<remove></remove>' + dirty;
851 } else {
852 /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
853 const matches = stringMatch(dirty, /^[\r\n\t ]+/);
854 leadingWhitespace = matches && matches[0];
855 }
856 if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
857 // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
858 dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
859 }
860 const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
861 /*
862 * Use the DOMParser API by default, fallback later if needs be
863 * DOMParser not work for svg when has multiple root element.
864 */
865 if (NAMESPACE === HTML_NAMESPACE) {
866 try {
867 doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
868 } catch (_) {}
869 }
870 /* Use createHTMLDocument in case DOMParser is not available */
871 if (!doc || !doc.documentElement) {
872 doc = implementation.createDocument(NAMESPACE, 'template', null);
873 try {
874 doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
875 } catch (_) {
876 // Syntax error if dirtyPayload is invalid xml
877 }
878 }
879 const body = doc.body || doc.documentElement;
880 if (dirty && leadingWhitespace) {
881 body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
882 }
883 /* Work on whole document or just its body */
884 if (NAMESPACE === HTML_NAMESPACE) {
885 return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
886 }
887 return WHOLE_DOCUMENT ? doc.documentElement : body;
888 };
889 /**
890 * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
891 *
892 * @param root The root element or node to start traversing on.
893 * @return The created NodeIterator
894 */
895 const _createNodeIterator = function _createNodeIterator(root) {
896 return createNodeIterator.call(root.ownerDocument || root, root,
897 // eslint-disable-next-line no-bitwise
898 NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
899 };
900 /**
901 * _isClobbered
902 *
903 * @param element element to check for clobbering attacks
904 * @return true if clobbered, false if safe
905 */
906 const _isClobbered = function _isClobbered(element) {
907 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');
908 };
909 /**
910 * Checks whether the given object is a DOM node.
911 *
912 * @param value object to check whether it's a DOM node
913 * @return true is object is a DOM node
914 */
915 const _isNode = function _isNode(value) {
916 return typeof Node === 'function' && value instanceof Node;
917 };
918 function _executeHooks(hooks, currentNode, data) {
919 arrayForEach(hooks, hook => {
920 hook.call(DOMPurify, currentNode, data, CONFIG);
921 });
922 }
923 /**
924 * _sanitizeElements
925 *
926 * @protect nodeName
927 * @protect textContent
928 * @protect removeChild
929 * @param currentNode to check for permission to exist
930 * @return true if node was killed, false if left alive
931 */
932 const _sanitizeElements = function _sanitizeElements(currentNode) {
933 let content = null;
934 /* Execute a hook if present */
935 _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
936 /* Check if element is clobbered or can clobber */
937 if (_isClobbered(currentNode)) {
938 _forceRemove(currentNode);
939 return true;
940 }
941 /* Now let's check the element's type and name */
942 const tagName = transformCaseFunc(currentNode.nodeName);
943 /* Execute a hook if present */
944 _executeHooks(hooks.uponSanitizeElement, currentNode, {
945 tagName,
946 allowedTags: ALLOWED_TAGS
947 });
948 /* Detect mXSS attempts abusing namespace confusion */
949 if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
950 _forceRemove(currentNode);
951 return true;
952 }
953 /* Remove any occurrence of processing instructions */
954 if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
955 _forceRemove(currentNode);
956 return true;
957 }
958 /* Remove any kind of possibly harmful comments */
959 if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
960 _forceRemove(currentNode);
961 return true;
962 }
963 /* Remove element if anything forbids its presence */
964 if (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName])) {
965 /* Check if we have a custom element to handle */
966 if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
967 if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
968 return false;
969 }
970 if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
971 return false;
972 }
973 }
974 /* Keep content except for bad-listed elements */
975 if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
976 const parentNode = getParentNode(currentNode) || currentNode.parentNode;
977 const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
978 if (childNodes && parentNode) {
979 const childCount = childNodes.length;
980 for (let i = childCount - 1; i >= 0; --i) {
981 const childClone = cloneNode(childNodes[i], true);
982 childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
983 parentNode.insertBefore(childClone, getNextSibling(currentNode));
984 }
985 }
986 }
987 _forceRemove(currentNode);
988 return true;
989 }
990 /* Check whether element has a valid namespace */
991 if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
992 _forceRemove(currentNode);
993 return true;
994 }
995 /* Make sure that older browsers don't get fallback-tag mXSS */
996 if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
997 _forceRemove(currentNode);
998 return true;
999 }
1000 /* Sanitize element content to be template-safe */
1001 if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
1002 /* Get the element's text content */
1003 content = currentNode.textContent;
1004 arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1005 content = stringReplace(content, expr, ' ');
1006 });
1007 if (currentNode.textContent !== content) {
1008 arrayPush(DOMPurify.removed, {
1009 element: currentNode.cloneNode()
1010 });
1011 currentNode.textContent = content;
1012 }
1013 }
1014 /* Execute a hook if present */
1015 _executeHooks(hooks.afterSanitizeElements, currentNode, null);
1016 return false;
1017 };
1018 /**
1019 * _isValidAttribute
1020 *
1021 * @param lcTag Lowercase tag name of containing element.
1022 * @param lcName Lowercase attribute name.
1023 * @param value Attribute value.
1024 * @return Returns true if `value` is valid, otherwise false.
1025 */
1026 // eslint-disable-next-line complexity
1027 const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
1028 /* FORBID_ATTR must always win, even if ADD_ATTR predicate would allow it */
1029 if (FORBID_ATTR[lcName]) {
1030 return false;
1031 }
1032 /* Make sure attribute cannot clobber */
1033 if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
1034 return false;
1035 }
1036 /* Allow valid data-* attributes: At least one character after "-"
1037 (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
1038 XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
1039 We don't need to check the value; it's always URI safe. */
1040 if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
1041 if (
1042 // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1043 // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1044 // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1045 _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, lcTag)) ||
1046 // Alternative, second condition checks if it's an `is`-attribute, AND
1047 // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1048 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 {
1049 return false;
1050 }
1051 /* Check value is safe. First, is attr inert? If so, is safe */
1052 } 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) {
1053 return false;
1054 } else ;
1055 return true;
1056 };
1057 /**
1058 * _isBasicCustomElement
1059 * checks if at least one dash is included in tagName, and it's not the first char
1060 * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1061 *
1062 * @param tagName name of the tag of the node to sanitize
1063 * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
1064 */
1065 const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
1066 return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
1067 };
1068 /**
1069 * _sanitizeAttributes
1070 *
1071 * @protect attributes
1072 * @protect nodeName
1073 * @protect removeAttribute
1074 * @protect setAttribute
1075 *
1076 * @param currentNode to sanitize
1077 */
1078 const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
1079 /* Execute a hook if present */
1080 _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
1081 const {
1082 attributes
1083 } = currentNode;
1084 /* Check if we have attributes; if not we might have a text node */
1085 if (!attributes || _isClobbered(currentNode)) {
1086 return;
1087 }
1088 const hookEvent = {
1089 attrName: '',
1090 attrValue: '',
1091 keepAttr: true,
1092 allowedAttributes: ALLOWED_ATTR,
1093 forceKeepAttr: undefined
1094 };
1095 let l = attributes.length;
1096 /* Go backwards over all attributes; safely remove bad ones */
1097 while (l--) {
1098 const attr = attributes[l];
1099 const {
1100 name,
1101 namespaceURI,
1102 value: attrValue
1103 } = attr;
1104 const lcName = transformCaseFunc(name);
1105 const initValue = attrValue;
1106 let value = name === 'value' ? initValue : stringTrim(initValue);
1107 /* Execute a hook if present */
1108 hookEvent.attrName = lcName;
1109 hookEvent.attrValue = value;
1110 hookEvent.keepAttr = true;
1111 hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
1112 _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
1113 value = hookEvent.attrValue;
1114 /* Full DOM Clobbering protection via namespace isolation,
1115 * Prefix id and name attributes with `user-content-`
1116 */
1117 if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
1118 // Remove the attribute with this value
1119 _removeAttribute(name, currentNode);
1120 // Prefix the value and later re-create the attribute with the sanitized value
1121 value = SANITIZE_NAMED_PROPS_PREFIX + value;
1122 }
1123 /* Work around a security issue with comments inside attributes */
1124 if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
1125 _removeAttribute(name, currentNode);
1126 continue;
1127 }
1128 /* Make sure we cannot easily use animated hrefs, even if animations are allowed */
1129 if (lcName === 'attributename' && stringMatch(value, 'href')) {
1130 _removeAttribute(name, currentNode);
1131 continue;
1132 }
1133 /* Did the hooks approve of the attribute? */
1134 if (hookEvent.forceKeepAttr) {
1135 continue;
1136 }
1137 /* Did the hooks approve of the attribute? */
1138 if (!hookEvent.keepAttr) {
1139 _removeAttribute(name, currentNode);
1140 continue;
1141 }
1142 /* Work around a security issue in jQuery 3.0 */
1143 if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
1144 _removeAttribute(name, currentNode);
1145 continue;
1146 }
1147 /* Sanitize attribute content to be template-safe */
1148 if (SAFE_FOR_TEMPLATES) {
1149 arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1150 value = stringReplace(value, expr, ' ');
1151 });
1152 }
1153 /* Is `value` valid for this attribute? */
1154 const lcTag = transformCaseFunc(currentNode.nodeName);
1155 if (!_isValidAttribute(lcTag, lcName, value)) {
1156 _removeAttribute(name, currentNode);
1157 continue;
1158 }
1159 /* Handle attributes that require Trusted Types */
1160 if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
1161 if (namespaceURI) ; else {
1162 switch (trustedTypes.getAttributeType(lcTag, lcName)) {
1163 case 'TrustedHTML':
1164 {
1165 value = trustedTypesPolicy.createHTML(value);
1166 break;
1167 }
1168 case 'TrustedScriptURL':
1169 {
1170 value = trustedTypesPolicy.createScriptURL(value);
1171 break;
1172 }
1173 }
1174 }
1175 }
1176 /* Handle invalid data-* attribute set by try-catching it */
1177 if (value !== initValue) {
1178 try {
1179 if (namespaceURI) {
1180 currentNode.setAttributeNS(namespaceURI, name, value);
1181 } else {
1182 /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1183 currentNode.setAttribute(name, value);
1184 }
1185 if (_isClobbered(currentNode)) {
1186 _forceRemove(currentNode);
1187 } else {
1188 arrayPop(DOMPurify.removed);
1189 }
1190 } catch (_) {
1191 _removeAttribute(name, currentNode);
1192 }
1193 }
1194 }
1195 /* Execute a hook if present */
1196 _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
1197 };
1198 /**
1199 * _sanitizeShadowDOM
1200 *
1201 * @param fragment to iterate over recursively
1202 */
1203 const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
1204 let shadowNode = null;
1205 const shadowIterator = _createNodeIterator(fragment);
1206 /* Execute a hook if present */
1207 _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
1208 while (shadowNode = shadowIterator.nextNode()) {
1209 /* Execute a hook if present */
1210 _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
1211 /* Sanitize tags and elements */
1212 _sanitizeElements(shadowNode);
1213 /* Check attributes next */
1214 _sanitizeAttributes(shadowNode);
1215 /* Deep shadow DOM detected */
1216 if (shadowNode.content instanceof DocumentFragment) {
1217 _sanitizeShadowDOM(shadowNode.content);
1218 }
1219 }
1220 /* Execute a hook if present */
1221 _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
1222 };
1223 // eslint-disable-next-line complexity
1224 DOMPurify.sanitize = function (dirty) {
1225 let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1226 let body = null;
1227 let importedNode = null;
1228 let currentNode = null;
1229 let returnNode = null;
1230 /* Make sure we have a string to sanitize.
1231 DO NOT return early, as this will return the wrong type if
1232 the user has requested a DOM object rather than a string */
1233 IS_EMPTY_INPUT = !dirty;
1234 if (IS_EMPTY_INPUT) {
1235 dirty = '<!-->';
1236 }
1237 /* Stringify, in case dirty is an object */
1238 if (typeof dirty !== 'string' && !_isNode(dirty)) {
1239 if (typeof dirty.toString === 'function') {
1240 dirty = dirty.toString();
1241 if (typeof dirty !== 'string') {
1242 throw typeErrorCreate('dirty is not a string, aborting');
1243 }
1244 } else {
1245 throw typeErrorCreate('toString is not a function');
1246 }
1247 }
1248 /* Return dirty HTML if DOMPurify cannot run */
1249 if (!DOMPurify.isSupported) {
1250 return dirty;
1251 }
1252 /* Assign config vars */
1253 if (!SET_CONFIG) {
1254 _parseConfig(cfg);
1255 }
1256 /* Clean up removed elements */
1257 DOMPurify.removed = [];
1258 /* Check if dirty is correctly typed for IN_PLACE */
1259 if (typeof dirty === 'string') {
1260 IN_PLACE = false;
1261 }
1262 if (IN_PLACE) {
1263 /* Do some early pre-sanitization to avoid unsafe root nodes */
1264 if (dirty.nodeName) {
1265 const tagName = transformCaseFunc(dirty.nodeName);
1266 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1267 throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1268 }
1269 }
1270 } else if (dirty instanceof Node) {
1271 /* If dirty is a DOM element, append to an empty document to avoid
1272 elements being stripped by the parser */
1273 body = _initDocument('<!---->');
1274 importedNode = body.ownerDocument.importNode(dirty, true);
1275 if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
1276 /* Node is already a body, use as is */
1277 body = importedNode;
1278 } else if (importedNode.nodeName === 'HTML') {
1279 body = importedNode;
1280 } else {
1281 // eslint-disable-next-line unicorn/prefer-dom-node-append
1282 body.appendChild(importedNode);
1283 }
1284 } else {
1285 /* Exit directly if we have nothing to do */
1286 if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
1287 // eslint-disable-next-line unicorn/prefer-includes
1288 dirty.indexOf('<') === -1) {
1289 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
1290 }
1291 /* Initialize the document to work on */
1292 body = _initDocument(dirty);
1293 /* Check we have a DOM node from the data */
1294 if (!body) {
1295 return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
1296 }
1297 }
1298 /* Remove first element node (ours) if FORCE_BODY is set */
1299 if (body && FORCE_BODY) {
1300 _forceRemove(body.firstChild);
1301 }
1302 /* Get node iterator */
1303 const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
1304 /* Now start iterating over the created document */
1305 while (currentNode = nodeIterator.nextNode()) {
1306 /* Sanitize tags and elements */
1307 _sanitizeElements(currentNode);
1308 /* Check attributes next */
1309 _sanitizeAttributes(currentNode);
1310 /* Shadow DOM detected, sanitize it */
1311 if (currentNode.content instanceof DocumentFragment) {
1312 _sanitizeShadowDOM(currentNode.content);
1313 }
1314 }
1315 /* If we sanitized `dirty` in-place, return it. */
1316 if (IN_PLACE) {
1317 return dirty;
1318 }
1319 /* Return sanitized string or DOM */
1320 if (RETURN_DOM) {
1321 if (RETURN_DOM_FRAGMENT) {
1322 returnNode = createDocumentFragment.call(body.ownerDocument);
1323 while (body.firstChild) {
1324 // eslint-disable-next-line unicorn/prefer-dom-node-append
1325 returnNode.appendChild(body.firstChild);
1326 }
1327 } else {
1328 returnNode = body;
1329 }
1330 if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
1331 /*
1332 AdoptNode() is not used because internal state is not reset
1333 (e.g. the past names map of a HTMLFormElement), this is safe
1334 in theory but we would rather not risk another attack vector.
1335 The state that is cloned by importNode() is explicitly defined
1336 by the specs.
1337 */
1338 returnNode = importNode.call(originalDocument, returnNode, true);
1339 }
1340 return returnNode;
1341 }
1342 let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1343 /* Serialize doctype if allowed */
1344 if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
1345 serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
1346 }
1347 /* Sanitize final string template-safe */
1348 if (SAFE_FOR_TEMPLATES) {
1349 arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1350 serializedHTML = stringReplace(serializedHTML, expr, ' ');
1351 });
1352 }
1353 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
1354 };
1355 DOMPurify.setConfig = function () {
1356 let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1357 _parseConfig(cfg);
1358 SET_CONFIG = true;
1359 };
1360 DOMPurify.clearConfig = function () {
1361 CONFIG = null;
1362 SET_CONFIG = false;
1363 };
1364 DOMPurify.isValidAttribute = function (tag, attr, value) {
1365 /* Initialize shared config vars if necessary. */
1366 if (!CONFIG) {
1367 _parseConfig({});
1368 }
1369 const lcTag = transformCaseFunc(tag);
1370 const lcName = transformCaseFunc(attr);
1371 return _isValidAttribute(lcTag, lcName, value);
1372 };
1373 DOMPurify.addHook = function (entryPoint, hookFunction) {
1374 if (typeof hookFunction !== 'function') {
1375 return;
1376 }
1377 arrayPush(hooks[entryPoint], hookFunction);
1378 };
1379 DOMPurify.removeHook = function (entryPoint, hookFunction) {
1380 if (hookFunction !== undefined) {
1381 const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
1382 return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
1383 }
1384 return arrayPop(hooks[entryPoint]);
1385 };
1386 DOMPurify.removeHooks = function (entryPoint) {
1387 hooks[entryPoint] = [];
1388 };
1389 DOMPurify.removeAllHooks = function () {
1390 hooks = _createHooksMap();
1391 };
1392 return DOMPurify;
1393 }
1394 var purify = createDOMPurify();
1395
1396 export { purify as default };
1397 //# sourceMappingURL=purify.es.mjs.map