PluginProbe
Elementor Website Builder – more than just a page builder / 3.30.0-beta1
Elementor Website Builder – more than just a page builder v3.30.0-beta1
4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 All 457 releases
elementor / assets / js / text-path.795be0048f5240994e8b.bundle.js

text-path.795be0048f5240994e8b.bundle.js in Elementor Website Builder – more than just a page builder 3.30.0-beta1, at assets/js/text-path.795be0048f5240994e8b.bundle.js

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