PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 1.10.2
Fluent Support – Helpdesk & Customer Support Ticket System v1.10.2
2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 All 68 releases
fluent-support / assets / libs / purify / purify.js

purify.js in Fluent Support – Helpdesk & Customer Support Ticket System 1.10.2, at assets/libs/purify/purify.js

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