PluginProbe
Elementor Website Builder – more than just a page builder / 3.26.1
Elementor Website Builder – more than just a page builder v3.26.1
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.12d8f0d07bb4893759c1.bundle.js

text-path.12d8f0d07bb4893759c1.bundle.js in Elementor Website Builder – more than just a page builder 3.26.1, at assets/js/text-path.12d8f0d07bb4893759c1.bundle.js

1,911 lines 76.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*! elementor - v3.26.0 - 19-12-2024 */
2 (self["webpackChunkelementor"] = self["webpackChunkelementor"] || []).push([["text-path"],{
3
4 /***/ "../modules/shapes/assets/js/frontend/handlers/text-path.js":
5 /*!******************************************************************!*\
6 !*** ../modules/shapes/assets/js/frontend/handlers/text-path.js ***!
7 \******************************************************************/
8 /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
9
10 "use strict";
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.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.js":
285 /*!************************************************!*\
286 !*** ../node_modules/dompurify/dist/purify.js ***!
287 \************************************************/
288 /***/ (function(module) {
289
290 /*! @license DOMPurify 3.1.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.3/LICENSE */
291
292 (function (global, factory) {
293 true ? module.exports = factory() :
294 0;
295 })(this, (function () { 'use strict';
296
297 const {
298 entries,
299 setPrototypeOf,
300 isFrozen,
301 getPrototypeOf,
302 getOwnPropertyDescriptor
303 } = Object;
304 let {
305 freeze,
306 seal,
307 create
308 } = Object; // eslint-disable-line import/no-mutable-exports
309 let {
310 apply,
311 construct
312 } = typeof Reflect !== 'undefined' && Reflect;
313 if (!freeze) {
314 freeze = function freeze(x) {
315 return x;
316 };
317 }
318 if (!seal) {
319 seal = function seal(x) {
320 return x;
321 };
322 }
323 if (!apply) {
324 apply = function apply(fun, thisValue, args) {
325 return fun.apply(thisValue, args);
326 };
327 }
328 if (!construct) {
329 construct = function construct(Func, args) {
330 return new Func(...args);
331 };
332 }
333 const arrayForEach = unapply(Array.prototype.forEach);
334 const arrayPop = unapply(Array.prototype.pop);
335 const arrayPush = unapply(Array.prototype.push);
336 const stringToLowerCase = unapply(String.prototype.toLowerCase);
337 const stringToString = unapply(String.prototype.toString);
338 const stringMatch = unapply(String.prototype.match);
339 const stringReplace = unapply(String.prototype.replace);
340 const stringIndexOf = unapply(String.prototype.indexOf);
341 const stringTrim = unapply(String.prototype.trim);
342 const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
343 const regExpTest = unapply(RegExp.prototype.test);
344 const typeErrorCreate = unconstruct(TypeError);
345 const numberIsNaN = unapply(Number.isNaN);
346
347 /**
348 * Creates a new function that calls the given function with a specified thisArg and arguments.
349 *
350 * @param {Function} func - The function to be wrapped and called.
351 * @returns {Function} A new function that calls the given function with a specified thisArg and arguments.
352 */
353 function unapply(func) {
354 return function (thisArg) {
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 /**
363 * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
364 *
365 * @param {Function} func - The constructor function to be wrapped and called.
366 * @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments.
367 */
368 function unconstruct(func) {
369 return function () {
370 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
371 args[_key2] = arguments[_key2];
372 }
373 return construct(func, args);
374 };
375 }
376
377 /**
378 * Add properties to a lookup table
379 *
380 * @param {Object} set - The set to which elements will be added.
381 * @param {Array} array - The array containing elements to be added to the set.
382 * @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set.
383 * @returns {Object} The modified set with added elements.
384 */
385 function addToSet(set, array) {
386 let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
387 if (setPrototypeOf) {
388 // Make 'in' and truthy checks like Boolean(set.constructor)
389 // independent of any properties defined on Object.prototype.
390 // Prevent prototype setters from intercepting set as a this value.
391 setPrototypeOf(set, null);
392 }
393 let l = array.length;
394 while (l--) {
395 let element = array[l];
396 if (typeof element === 'string') {
397 const lcElement = transformCaseFunc(element);
398 if (lcElement !== element) {
399 // Config presets (e.g. tags.js, attrs.js) are immutable.
400 if (!isFrozen(array)) {
401 array[l] = lcElement;
402 }
403 element = lcElement;
404 }
405 }
406 set[element] = true;
407 }
408 return set;
409 }
410
411 /**
412 * Clean up an array to harden against CSPP
413 *
414 * @param {Array} array - The array to be cleaned.
415 * @returns {Array} The cleaned version of the array
416 */
417 function cleanArray(array) {
418 for (let index = 0; index < array.length; index++) {
419 const isPropertyExist = objectHasOwnProperty(array, index);
420 if (!isPropertyExist) {
421 array[index] = null;
422 }
423 }
424 return array;
425 }
426
427 /**
428 * Shallow clone an object
429 *
430 * @param {Object} object - The object to be cloned.
431 * @returns {Object} A new object that copies the original.
432 */
433 function clone(object) {
434 const newObject = create(null);
435 for (const [property, value] of entries(object)) {
436 const isPropertyExist = objectHasOwnProperty(object, property);
437 if (isPropertyExist) {
438 if (Array.isArray(value)) {
439 newObject[property] = cleanArray(value);
440 } else if (value && typeof value === 'object' && value.constructor === Object) {
441 newObject[property] = clone(value);
442 } else {
443 newObject[property] = value;
444 }
445 }
446 }
447 return newObject;
448 }
449
450 /**
451 * This method automatically checks if the prop is function or getter and behaves accordingly.
452 *
453 * @param {Object} object - The object to look up the getter function in its prototype chain.
454 * @param {String} prop - The property name for which to find the getter function.
455 * @returns {Function} The getter function found in the prototype chain or a fallback function.
456 */
457 function lookupGetter(object, prop) {
458 while (object !== null) {
459 const desc = getOwnPropertyDescriptor(object, prop);
460 if (desc) {
461 if (desc.get) {
462 return unapply(desc.get);
463 }
464 if (typeof desc.value === 'function') {
465 return unapply(desc.value);
466 }
467 }
468 object = getPrototypeOf(object);
469 }
470 function fallbackValue() {
471 return null;
472 }
473 return fallbackValue;
474 }
475
476 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']);
477
478 // SVG
479 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']);
480 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']);
481
482 // List of SVG elements that are disallowed by default.
483 // We still need to know them so that we can do namespace
484 // checks properly in case one wants to add them to
485 // allow-list.
486 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']);
487 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']);
488
489 // Similarly to SVG, we want to know all MathML elements,
490 // even those that we disallow by default.
491 const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
492 const text = freeze(['#text']);
493
494 const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
495 const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
496 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']);
497 const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
498
499 // eslint-disable-next-line unicorn/better-regex
500 const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
501 const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
502 const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
503 const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
504 const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
505 const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
506 );
507
508 const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
509 const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
510 );
511
512 const DOCTYPE_NAME = seal(/^html$/i);
513 const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
514
515 var EXPRESSIONS = /*#__PURE__*/Object.freeze({
516 __proto__: null,
517 MUSTACHE_EXPR: MUSTACHE_EXPR,
518 ERB_EXPR: ERB_EXPR,
519 TMPLIT_EXPR: TMPLIT_EXPR,
520 DATA_ATTR: DATA_ATTR,
521 ARIA_ATTR: ARIA_ATTR,
522 IS_ALLOWED_URI: IS_ALLOWED_URI,
523 IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
524 ATTR_WHITESPACE: ATTR_WHITESPACE,
525 DOCTYPE_NAME: DOCTYPE_NAME,
526 CUSTOM_ELEMENT: CUSTOM_ELEMENT
527 });
528
529 // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
530 const NODE_TYPE = {
531 element: 1,
532 attribute: 2,
533 text: 3,
534 cdataSection: 4,
535 entityReference: 5,
536 // Deprecated
537 entityNode: 6,
538 // Deprecated
539 progressingInstruction: 7,
540 comment: 8,
541 document: 9,
542 documentType: 10,
543 documentFragment: 11,
544 notation: 12 // Deprecated
545 };
546
547 const getGlobal = function getGlobal() {
548 return typeof window === 'undefined' ? null : window;
549 };
550
551 /**
552 * Creates a no-op policy for internal use only.
553 * Don't export this function outside this module!
554 * @param {TrustedTypePolicyFactory} trustedTypes The policy factory.
555 * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
556 * @return {TrustedTypePolicy} The policy created (or null, if Trusted Types
557 * are not supported or creating the policy failed).
558 */
559 const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
560 if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
561 return null;
562 }
563
564 // Allow the callers to control the unique policy name
565 // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
566 // Policy creation with duplicate names throws in Trusted Types.
567 let suffix = null;
568 const ATTR_NAME = 'data-tt-policy-suffix';
569 if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
570 suffix = purifyHostElement.getAttribute(ATTR_NAME);
571 }
572 const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
573 try {
574 return trustedTypes.createPolicy(policyName, {
575 createHTML(html) {
576 return html;
577 },
578 createScriptURL(scriptUrl) {
579 return scriptUrl;
580 }
581 });
582 } catch (_) {
583 // Policy creation failed (most likely another DOMPurify script has
584 // already run). Skip creating the policy, as this will only cause errors
585 // if TT are enforced.
586 console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
587 return null;
588 }
589 };
590 function createDOMPurify() {
591 let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
592 const DOMPurify = root => createDOMPurify(root);
593
594 /**
595 * Version label, exposed for easier checks
596 * if DOMPurify is up to date or not
597 */
598 DOMPurify.version = '3.1.3';
599
600 /**
601 * Array of elements that DOMPurify removed during sanitation.
602 * Empty if nothing was removed.
603 */
604 DOMPurify.removed = [];
605 if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {
606 // Not running in a browser, provide a factory function
607 // so that you can pass your own Window
608 DOMPurify.isSupported = false;
609 return DOMPurify;
610 }
611 let {
612 document
613 } = window;
614 const originalDocument = document;
615 const currentScript = originalDocument.currentScript;
616 const {
617 DocumentFragment,
618 HTMLTemplateElement,
619 Node,
620 Element,
621 NodeFilter,
622 NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
623 HTMLFormElement,
624 DOMParser,
625 trustedTypes
626 } = window;
627 const ElementPrototype = Element.prototype;
628 const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
629 const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
630 const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
631 const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
632
633 // As per issue #47, the web-components registry is inherited by a
634 // new document created via createHTMLDocument. As per the spec
635 // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
636 // a new empty registry is used when creating a template contents owner
637 // document, so we use that as our parent document to ensure nothing
638 // is inherited.
639 if (typeof HTMLTemplateElement === 'function') {
640 const template = document.createElement('template');
641 if (template.content && template.content.ownerDocument) {
642 document = template.content.ownerDocument;
643 }
644 }
645 let trustedTypesPolicy;
646 let emptyHTML = '';
647 const {
648 implementation,
649 createNodeIterator,
650 createDocumentFragment,
651 getElementsByTagName
652 } = document;
653 const {
654 importNode
655 } = originalDocument;
656 let hooks = {};
657
658 /**
659 * Expose whether this browser supports running the full DOMPurify.
660 */
661 DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
662 const {
663 MUSTACHE_EXPR,
664 ERB_EXPR,
665 TMPLIT_EXPR,
666 DATA_ATTR,
667 ARIA_ATTR,
668 IS_SCRIPT_OR_DATA,
669 ATTR_WHITESPACE,
670 CUSTOM_ELEMENT
671 } = EXPRESSIONS;
672 let {
673 IS_ALLOWED_URI: IS_ALLOWED_URI$1
674 } = EXPRESSIONS;
675
676 /**
677 * We consider the elements and attributes below to be safe. Ideally
678 * don't add any new ones but feel free to remove unwanted ones.
679 */
680
681 /* allowed element names */
682 let ALLOWED_TAGS = null;
683 const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
684
685 /* Allowed attribute names */
686 let ALLOWED_ATTR = null;
687 const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
688
689 /*
690 * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
691 * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
692 * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
693 * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
694 */
695 let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
696 tagNameCheck: {
697 writable: true,
698 configurable: false,
699 enumerable: true,
700 value: null
701 },
702 attributeNameCheck: {
703 writable: true,
704 configurable: false,
705 enumerable: true,
706 value: null
707 },
708 allowCustomizedBuiltInElements: {
709 writable: true,
710 configurable: false,
711 enumerable: true,
712 value: false
713 }
714 }));
715
716 /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
717 let FORBID_TAGS = null;
718
719 /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
720 let FORBID_ATTR = null;
721
722 /* Decide if ARIA attributes are okay */
723 let ALLOW_ARIA_ATTR = true;
724
725 /* Decide if custom data attributes are okay */
726 let ALLOW_DATA_ATTR = true;
727
728 /* Decide if unknown protocols are okay */
729 let ALLOW_UNKNOWN_PROTOCOLS = false;
730
731 /* Decide if self-closing tags in attributes are allowed.
732 * Usually removed due to a mXSS issue in jQuery 3.0 */
733 let ALLOW_SELF_CLOSE_IN_ATTR = true;
734
735 /* Output should be safe for common template engines.
736 * This means, DOMPurify removes data attributes, mustaches and ERB
737 */
738 let SAFE_FOR_TEMPLATES = false;
739
740 /* Output should be safe even for XML used within HTML and alike.
741 * This means, DOMPurify removes comments when containing risky content.
742 */
743 let SAFE_FOR_XML = true;
744
745 /* Decide if document with <html>... should be returned */
746 let WHOLE_DOCUMENT = false;
747
748 /* Track whether config is already set on this instance of DOMPurify. */
749 let SET_CONFIG = false;
750
751 /* Decide if all elements (e.g. style, script) must be children of
752 * document.body. By default, browsers might move them to document.head */
753 let FORCE_BODY = false;
754
755 /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
756 * string (or a TrustedHTML object if Trusted Types are supported).
757 * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
758 */
759 let RETURN_DOM = false;
760
761 /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
762 * string (or a TrustedHTML object if Trusted Types are supported) */
763 let RETURN_DOM_FRAGMENT = false;
764
765 /* Try to return a Trusted Type object instead of a string, return a string in
766 * case Trusted Types are not supported */
767 let RETURN_TRUSTED_TYPE = false;
768
769 /* Output should be free from DOM clobbering attacks?
770 * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
771 */
772 let SANITIZE_DOM = true;
773
774 /* Achieve full DOM Clobbering protection by isolating the namespace of named
775 * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
776 *
777 * HTML/DOM spec rules that enable DOM Clobbering:
778 * - Named Access on Window (§7.3.3)
779 * - DOM Tree Accessors (§3.1.5)
780 * - Form Element Parent-Child Relations (§4.10.3)
781 * - Iframe srcdoc / Nested WindowProxies (§4.8.5)
782 * - HTMLCollection (§4.2.10.2)
783 *
784 * Namespace isolation is implemented by prefixing `id` and `name` attributes
785 * with a constant string, i.e., `user-content-`
786 */
787 let SANITIZE_NAMED_PROPS = false;
788 const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
789
790 /* Keep element content when removing element? */
791 let KEEP_CONTENT = true;
792
793 /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
794 * of importing it into a new Document and returning a sanitized copy */
795 let IN_PLACE = false;
796
797 /* Allow usage of profiles like html, svg and mathMl */
798 let USE_PROFILES = {};
799
800 /* Tags to ignore content of when KEEP_CONTENT is true */
801 let FORBID_CONTENTS = null;
802 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']);
803
804 /* Tags that are safe for data: URIs */
805 let DATA_URI_TAGS = null;
806 const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
807
808 /* Attributes safe for values like "javascript:" */
809 let URI_SAFE_ATTRIBUTES = null;
810 const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
811 const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
812 const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
813 const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
814 /* Document namespace */
815 let NAMESPACE = HTML_NAMESPACE;
816 let IS_EMPTY_INPUT = false;
817
818 /* Allowed XHTML+XML namespaces */
819 let ALLOWED_NAMESPACES = null;
820 const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
821
822 /* Parsing of strict XHTML documents */
823 let PARSER_MEDIA_TYPE = null;
824 const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
825 const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
826 let transformCaseFunc = null;
827
828 /* Keep a reference to config to pass to hooks */
829 let CONFIG = null;
830
831 /* Specify the maximum element nesting depth to prevent mXSS */
832 const MAX_NESTING_DEPTH = 255;
833
834 /* Ideally, do not touch anything below this line */
835 /* ______________________________________________ */
836
837 const formElement = document.createElement('form');
838 const isRegexOrFunction = function isRegexOrFunction(testValue) {
839 return testValue instanceof RegExp || testValue instanceof Function;
840 };
841
842 /**
843 * _parseConfig
844 *
845 * @param {Object} cfg optional config literal
846 */
847 // eslint-disable-next-line complexity
848 const _parseConfig = function _parseConfig() {
849 let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
850 if (CONFIG && CONFIG === cfg) {
851 return;
852 }
853
854 /* Shield configuration object from tampering */
855 if (!cfg || typeof cfg !== 'object') {
856 cfg = {};
857 }
858
859 /* Shield configuration object from prototype pollution */
860 cfg = clone(cfg);
861 PARSER_MEDIA_TYPE =
862 // eslint-disable-next-line unicorn/prefer-includes
863 SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
864
865 // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
866 transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
867
868 /* Set configuration parameters */
869 ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
870 ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
871 ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
872 URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES),
873 // eslint-disable-line indent
874 cfg.ADD_URI_SAFE_ATTR,
875 // eslint-disable-line indent
876 transformCaseFunc // eslint-disable-line indent
877 ) // eslint-disable-line indent
878 : DEFAULT_URI_SAFE_ATTRIBUTES;
879 DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS),
880 // eslint-disable-line indent
881 cfg.ADD_DATA_URI_TAGS,
882 // eslint-disable-line indent
883 transformCaseFunc // eslint-disable-line indent
884 ) // eslint-disable-line indent
885 : DEFAULT_DATA_URI_TAGS;
886 FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
887 FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
888 FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
889 USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
890 ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
891 ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
892 ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
893 ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
894 SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
895 SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
896 WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
897 RETURN_DOM = cfg.RETURN_DOM || false; // Default false
898 RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
899 RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
900 FORCE_BODY = cfg.FORCE_BODY || false; // Default false
901 SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
902 SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
903 KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
904 IN_PLACE = cfg.IN_PLACE || false; // Default false
905 IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
906 NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
907 CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
908 if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
909 CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
910 }
911 if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
912 CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
913 }
914 if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
915 CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
916 }
917 if (SAFE_FOR_TEMPLATES) {
918 ALLOW_DATA_ATTR = false;
919 }
920 if (RETURN_DOM_FRAGMENT) {
921 RETURN_DOM = true;
922 }
923
924 /* Parse profile info */
925 if (USE_PROFILES) {
926 ALLOWED_TAGS = addToSet({}, text);
927 ALLOWED_ATTR = [];
928 if (USE_PROFILES.html === true) {
929 addToSet(ALLOWED_TAGS, html$1);
930 addToSet(ALLOWED_ATTR, html);
931 }
932 if (USE_PROFILES.svg === true) {
933 addToSet(ALLOWED_TAGS, svg$1);
934 addToSet(ALLOWED_ATTR, svg);
935 addToSet(ALLOWED_ATTR, xml);
936 }
937 if (USE_PROFILES.svgFilters === true) {
938 addToSet(ALLOWED_TAGS, svgFilters);
939 addToSet(ALLOWED_ATTR, svg);
940 addToSet(ALLOWED_ATTR, xml);
941 }
942 if (USE_PROFILES.mathMl === true) {
943 addToSet(ALLOWED_TAGS, mathMl$1);
944 addToSet(ALLOWED_ATTR, mathMl);
945 addToSet(ALLOWED_ATTR, xml);
946 }
947 }
948
949 /* Merge configuration parameters */
950 if (cfg.ADD_TAGS) {
951 if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
952 ALLOWED_TAGS = clone(ALLOWED_TAGS);
953 }
954 addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
955 }
956 if (cfg.ADD_ATTR) {
957 if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
958 ALLOWED_ATTR = clone(ALLOWED_ATTR);
959 }
960 addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
961 }
962 if (cfg.ADD_URI_SAFE_ATTR) {
963 addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
964 }
965 if (cfg.FORBID_CONTENTS) {
966 if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
967 FORBID_CONTENTS = clone(FORBID_CONTENTS);
968 }
969 addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
970 }
971
972 /* Add #text in case KEEP_CONTENT is set to true */
973 if (KEEP_CONTENT) {
974 ALLOWED_TAGS['#text'] = true;
975 }
976
977 /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
978 if (WHOLE_DOCUMENT) {
979 addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
980 }
981
982 /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
983 if (ALLOWED_TAGS.table) {
984 addToSet(ALLOWED_TAGS, ['tbody']);
985 delete FORBID_TAGS.tbody;
986 }
987 if (cfg.TRUSTED_TYPES_POLICY) {
988 if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
989 throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
990 }
991 if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
992 throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
993 }
994
995 // Overwrite existing TrustedTypes policy.
996 trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
997
998 // Sign local variables required by `sanitize`.
999 emptyHTML = trustedTypesPolicy.createHTML('');
1000 } else {
1001 // Uninitialized policy, attempt to initialize the internal dompurify policy.
1002 if (trustedTypesPolicy === undefined) {
1003 trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
1004 }
1005
1006 // If creating the internal policy succeeded sign internal variables.
1007 if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
1008 emptyHTML = trustedTypesPolicy.createHTML('');
1009 }
1010 }
1011
1012 // Prevent further manipulation of configuration.
1013 // Not available in IE8, Safari 5, etc.
1014 if (freeze) {
1015 freeze(cfg);
1016 }
1017 CONFIG = cfg;
1018 };
1019 const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
1020 const HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'annotation-xml']);
1021
1022 // Certain elements are allowed in both SVG and HTML
1023 // namespace. We need to specify them explicitly
1024 // so that they don't get erroneously deleted from
1025 // HTML namespace.
1026 const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
1027
1028 /* Keep track of all possible SVG and MathML tags
1029 * so that we can perform the namespace checks
1030 * correctly. */
1031 const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
1032 const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
1033
1034 /**
1035 * @param {Element} element a DOM element whose namespace is being checked
1036 * @returns {boolean} Return false if the element has a
1037 * namespace that a spec-compliant parser would never
1038 * return. Return true otherwise.
1039 */
1040 const _checkValidNamespace = function _checkValidNamespace(element) {
1041 let parent = getParentNode(element);
1042
1043 // In JSDOM, if we're inside shadow DOM, then parentNode
1044 // can be null. We just simulate parent in this case.
1045 if (!parent || !parent.tagName) {
1046 parent = {
1047 namespaceURI: NAMESPACE,
1048 tagName: 'template'
1049 };
1050 }
1051 const tagName = stringToLowerCase(element.tagName);
1052 const parentTagName = stringToLowerCase(parent.tagName);
1053 if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
1054 return false;
1055 }
1056 if (element.namespaceURI === SVG_NAMESPACE) {
1057 // The only way to switch from HTML namespace to SVG
1058 // is via <svg>. If it happens via any other tag, then
1059 // it should be killed.
1060 if (parent.namespaceURI === HTML_NAMESPACE) {
1061 return tagName === 'svg';
1062 }
1063
1064 // The only way to switch from MathML to SVG is via`
1065 // svg if parent is either <annotation-xml> or MathML
1066 // text integration points.
1067 if (parent.namespaceURI === MATHML_NAMESPACE) {
1068 return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
1069 }
1070
1071 // We only allow elements that are defined in SVG
1072 // spec. All others are disallowed in SVG namespace.
1073 return Boolean(ALL_SVG_TAGS[tagName]);
1074 }
1075 if (element.namespaceURI === MATHML_NAMESPACE) {
1076 // The only way to switch from HTML namespace to MathML
1077 // is via <math>. If it happens via any other tag, then
1078 // it should be killed.
1079 if (parent.namespaceURI === HTML_NAMESPACE) {
1080 return tagName === 'math';
1081 }
1082
1083 // The only way to switch from SVG to MathML is via
1084 // <math> and HTML integration points
1085 if (parent.namespaceURI === SVG_NAMESPACE) {
1086 return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
1087 }
1088
1089 // We only allow elements that are defined in MathML
1090 // spec. All others are disallowed in MathML namespace.
1091 return Boolean(ALL_MATHML_TAGS[tagName]);
1092 }
1093 if (element.namespaceURI === HTML_NAMESPACE) {
1094 // The only way to switch from SVG to HTML is via
1095 // HTML integration points, and from MathML to HTML
1096 // is via MathML text integration points
1097 if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
1098 return false;
1099 }
1100 if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
1101 return false;
1102 }
1103
1104 // We disallow tags that are specific for MathML
1105 // or SVG and should never appear in HTML namespace
1106 return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
1107 }
1108
1109 // For XHTML and XML documents that support custom namespaces
1110 if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
1111 return true;
1112 }
1113
1114 // The code should never reach this place (this means
1115 // that the element somehow got namespace that is not
1116 // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
1117 // Return false just in case.
1118 return false;
1119 };
1120
1121 /**
1122 * _forceRemove
1123 *
1124 * @param {Node} node a DOM node
1125 */
1126 const _forceRemove = function _forceRemove(node) {
1127 arrayPush(DOMPurify.removed, {
1128 element: node
1129 });
1130 try {
1131 // eslint-disable-next-line unicorn/prefer-dom-node-remove
1132 node.parentNode.removeChild(node);
1133 } catch (_) {
1134 node.remove();
1135 }
1136 };
1137
1138 /**
1139 * _removeAttribute
1140 *
1141 * @param {String} name an Attribute name
1142 * @param {Node} node a DOM node
1143 */
1144 const _removeAttribute = function _removeAttribute(name, node) {
1145 try {
1146 arrayPush(DOMPurify.removed, {
1147 attribute: node.getAttributeNode(name),
1148 from: node
1149 });
1150 } catch (_) {
1151 arrayPush(DOMPurify.removed, {
1152 attribute: null,
1153 from: node
1154 });
1155 }
1156 node.removeAttribute(name);
1157
1158 // We void attribute values for unremovable "is"" attributes
1159 if (name === 'is' && !ALLOWED_ATTR[name]) {
1160 if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
1161 try {
1162 _forceRemove(node);
1163 } catch (_) {}
1164 } else {
1165 try {
1166 node.setAttribute(name, '');
1167 } catch (_) {}
1168 }
1169 }
1170 };
1171
1172 /**
1173 * _initDocument
1174 *
1175 * @param {String} dirty a string of dirty markup
1176 * @return {Document} a DOM, filled with the dirty markup
1177 */
1178 const _initDocument = function _initDocument(dirty) {
1179 /* Create a HTML document */
1180 let doc = null;
1181 let leadingWhitespace = null;
1182 if (FORCE_BODY) {
1183 dirty = '<remove></remove>' + dirty;
1184 } else {
1185 /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
1186 const matches = stringMatch(dirty, /^[\r\n\t ]+/);
1187 leadingWhitespace = matches && matches[0];
1188 }
1189 if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
1190 // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
1191 dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
1192 }
1193 const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
1194 /*
1195 * Use the DOMParser API by default, fallback later if needs be
1196 * DOMParser not work for svg when has multiple root element.
1197 */
1198 if (NAMESPACE === HTML_NAMESPACE) {
1199 try {
1200 doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
1201 } catch (_) {}
1202 }
1203
1204 /* Use createHTMLDocument in case DOMParser is not available */
1205 if (!doc || !doc.documentElement) {
1206 doc = implementation.createDocument(NAMESPACE, 'template', null);
1207 try {
1208 doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
1209 } catch (_) {
1210 // Syntax error if dirtyPayload is invalid xml
1211 }
1212 }
1213 const body = doc.body || doc.documentElement;
1214 if (dirty && leadingWhitespace) {
1215 body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
1216 }
1217
1218 /* Work on whole document or just its body */
1219 if (NAMESPACE === HTML_NAMESPACE) {
1220 return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
1221 }
1222 return WHOLE_DOCUMENT ? doc.documentElement : body;
1223 };
1224
1225 /**
1226 * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
1227 *
1228 * @param {Node} root The root element or node to start traversing on.
1229 * @return {NodeIterator} The created NodeIterator
1230 */
1231 const _createNodeIterator = function _createNodeIterator(root) {
1232 return createNodeIterator.call(root.ownerDocument || root, root,
1233 // eslint-disable-next-line no-bitwise
1234 NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
1235 };
1236
1237 /**
1238 * _isClobbered
1239 *
1240 * @param {Node} elm element to check for clobbering attacks
1241 * @return {Boolean} true if clobbered, false if safe
1242 */
1243 const _isClobbered = function _isClobbered(elm) {
1244 return elm instanceof HTMLFormElement && (
1245 // eslint-disable-next-line unicorn/no-typeof-undefined
1246 typeof elm.__depth !== 'undefined' && typeof elm.__depth !== 'number' ||
1247 // eslint-disable-next-line unicorn/no-typeof-undefined
1248 typeof elm.__removalCount !== 'undefined' && typeof elm.__removalCount !== 'number' || typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function');
1249 };
1250
1251 /**
1252 * Checks whether the given object is a DOM node.
1253 *
1254 * @param {Node} object object to check whether it's a DOM node
1255 * @return {Boolean} true is object is a DOM node
1256 */
1257 const _isNode = function _isNode(object) {
1258 return typeof Node === 'function' && object instanceof Node;
1259 };
1260
1261 /**
1262 * _executeHook
1263 * Execute user configurable hooks
1264 *
1265 * @param {String} entryPoint Name of the hook's entry point
1266 * @param {Node} currentNode node to work on with the hook
1267 * @param {Object} data additional hook parameters
1268 */
1269 const _executeHook = function _executeHook(entryPoint, currentNode, data) {
1270 if (!hooks[entryPoint]) {
1271 return;
1272 }
1273 arrayForEach(hooks[entryPoint], hook => {
1274 hook.call(DOMPurify, currentNode, data, CONFIG);
1275 });
1276 };
1277
1278 /**
1279 * _sanitizeElements
1280 *
1281 * @protect nodeName
1282 * @protect textContent
1283 * @protect removeChild
1284 *
1285 * @param {Node} currentNode to check for permission to exist
1286 * @return {Boolean} true if node was killed, false if left alive
1287 */
1288 const _sanitizeElements = function _sanitizeElements(currentNode) {
1289 let content = null;
1290
1291 /* Execute a hook if present */
1292 _executeHook('beforeSanitizeElements', currentNode, null);
1293
1294 /* Check if element is clobbered or can clobber */
1295 if (_isClobbered(currentNode)) {
1296 _forceRemove(currentNode);
1297 return true;
1298 }
1299
1300 /* Now let's check the element's type and name */
1301 const tagName = transformCaseFunc(currentNode.nodeName);
1302
1303 /* Execute a hook if present */
1304 _executeHook('uponSanitizeElement', currentNode, {
1305 tagName,
1306 allowedTags: ALLOWED_TAGS
1307 });
1308
1309 /* Detect mXSS attempts abusing namespace confusion */
1310 if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
1311 _forceRemove(currentNode);
1312 return true;
1313 }
1314
1315 /* Remove any ocurrence of processing instructions */
1316 if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
1317 _forceRemove(currentNode);
1318 return true;
1319 }
1320
1321 /* Remove any kind of possibly harmful comments */
1322 if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
1323 _forceRemove(currentNode);
1324 return true;
1325 }
1326
1327 /* Remove element if anything forbids its presence */
1328 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1329 /* Check if we have a custom element to handle */
1330 if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
1331 if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
1332 return false;
1333 }
1334 if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
1335 return false;
1336 }
1337 }
1338
1339 /* Keep content except for bad-listed elements */
1340 if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
1341 const parentNode = getParentNode(currentNode) || currentNode.parentNode;
1342 const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
1343 if (childNodes && parentNode) {
1344 const childCount = childNodes.length;
1345 for (let i = childCount - 1; i >= 0; --i) {
1346 const childClone = cloneNode(childNodes[i], true);
1347 childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
1348 parentNode.insertBefore(childClone, getNextSibling(currentNode));
1349 }
1350 }
1351 }
1352 _forceRemove(currentNode);
1353 return true;
1354 }
1355
1356 /* Check whether element has a valid namespace */
1357 if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
1358 _forceRemove(currentNode);
1359 return true;
1360 }
1361
1362 /* Make sure that older browsers don't get fallback-tag mXSS */
1363 if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
1364 _forceRemove(currentNode);
1365 return true;
1366 }
1367
1368 /* Sanitize element content to be template-safe */
1369 if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
1370 /* Get the element's text content */
1371 content = currentNode.textContent;
1372 arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1373 content = stringReplace(content, expr, ' ');
1374 });
1375 if (currentNode.textContent !== content) {
1376 arrayPush(DOMPurify.removed, {
1377 element: currentNode.cloneNode()
1378 });
1379 currentNode.textContent = content;
1380 }
1381 }
1382
1383 /* Execute a hook if present */
1384 _executeHook('afterSanitizeElements', currentNode, null);
1385 return false;
1386 };
1387
1388 /**
1389 * _isValidAttribute
1390 *
1391 * @param {string} lcTag Lowercase tag name of containing element.
1392 * @param {string} lcName Lowercase attribute name.
1393 * @param {string} value Attribute value.
1394 * @return {Boolean} Returns true if `value` is valid, otherwise false.
1395 */
1396 // eslint-disable-next-line complexity
1397 const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
1398 /* Make sure attribute cannot clobber */
1399 if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement || value === '__depth' || value === '__removalCount')) {
1400 return false;
1401 }
1402
1403 /* Allow valid data-* attributes: At least one character after "-"
1404 (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
1405 XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
1406 We don't need to check the value; it's always URI safe. */
1407 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]) {
1408 if (
1409 // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1410 // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1411 // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1412 _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)) ||
1413 // Alternative, second condition checks if it's an `is`-attribute, AND
1414 // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1415 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 {
1416 return false;
1417 }
1418 /* Check value is safe. First, is attr inert? If so, is safe */
1419 } 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) {
1420 return false;
1421 } else ;
1422 return true;
1423 };
1424
1425 /**
1426 * _isBasicCustomElement
1427 * checks if at least one dash is included in tagName, and it's not the first char
1428 * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1429 *
1430 * @param {string} tagName name of the tag of the node to sanitize
1431 * @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
1432 */
1433 const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
1434 return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
1435 };
1436
1437 /**
1438 * _sanitizeAttributes
1439 *
1440 * @protect attributes
1441 * @protect nodeName
1442 * @protect removeAttribute
1443 * @protect setAttribute
1444 *
1445 * @param {Node} currentNode to sanitize
1446 */
1447 const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
1448 /* Execute a hook if present */
1449 _executeHook('beforeSanitizeAttributes', currentNode, null);
1450 const {
1451 attributes
1452 } = currentNode;
1453
1454 /* Check if we have attributes; if not we might have a text node */
1455 if (!attributes) {
1456 return;
1457 }
1458 const hookEvent = {
1459 attrName: '',
1460 attrValue: '',
1461 keepAttr: true,
1462 allowedAttributes: ALLOWED_ATTR
1463 };
1464 let l = attributes.length;
1465
1466 /* Go backwards over all attributes; safely remove bad ones */
1467 while (l--) {
1468 const attr = attributes[l];
1469 const {
1470 name,
1471 namespaceURI,
1472 value: attrValue
1473 } = attr;
1474 const lcName = transformCaseFunc(name);
1475 let value = name === 'value' ? attrValue : stringTrim(attrValue);
1476
1477 /* Execute a hook if present */
1478 hookEvent.attrName = lcName;
1479 hookEvent.attrValue = value;
1480 hookEvent.keepAttr = true;
1481 hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
1482 _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
1483 value = hookEvent.attrValue;
1484 /* Did the hooks approve of the attribute? */
1485 if (hookEvent.forceKeepAttr) {
1486 continue;
1487 }
1488
1489 /* Remove attribute */
1490 _removeAttribute(name, currentNode);
1491
1492 /* Did the hooks approve of the attribute? */
1493 if (!hookEvent.keepAttr) {
1494 continue;
1495 }
1496
1497 /* Work around a security issue in jQuery 3.0 */
1498 if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
1499 _removeAttribute(name, currentNode);
1500 continue;
1501 }
1502
1503 /* Work around a security issue with comments inside attributes */
1504 if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
1505 _removeAttribute(name, currentNode);
1506 continue;
1507 }
1508
1509 /* Sanitize attribute content to be template-safe */
1510 if (SAFE_FOR_TEMPLATES) {
1511 arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1512 value = stringReplace(value, expr, ' ');
1513 });
1514 }
1515
1516 /* Is `value` valid for this attribute? */
1517 const lcTag = transformCaseFunc(currentNode.nodeName);
1518 if (!_isValidAttribute(lcTag, lcName, value)) {
1519 continue;
1520 }
1521
1522 /* Full DOM Clobbering protection via namespace isolation,
1523 * Prefix id and name attributes with `user-content-`
1524 */
1525 if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
1526 // Remove the attribute with this value
1527 _removeAttribute(name, currentNode);
1528
1529 // Prefix the value and later re-create the attribute with the sanitized value
1530 value = SANITIZE_NAMED_PROPS_PREFIX + value;
1531 }
1532
1533 /* Handle attributes that require Trusted Types */
1534 if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
1535 if (namespaceURI) ; else {
1536 switch (trustedTypes.getAttributeType(lcTag, lcName)) {
1537 case 'TrustedHTML':
1538 {
1539 value = trustedTypesPolicy.createHTML(value);
1540 break;
1541 }
1542 case 'TrustedScriptURL':
1543 {
1544 value = trustedTypesPolicy.createScriptURL(value);
1545 break;
1546 }
1547 }
1548 }
1549 }
1550
1551 /* Handle invalid data-* attribute set by try-catching it */
1552 try {
1553 if (namespaceURI) {
1554 currentNode.setAttributeNS(namespaceURI, name, value);
1555 } else {
1556 /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1557 currentNode.setAttribute(name, value);
1558 }
1559 if (_isClobbered(currentNode)) {
1560 _forceRemove(currentNode);
1561 } else {
1562 arrayPop(DOMPurify.removed);
1563 }
1564 } catch (_) {}
1565 }
1566
1567 /* Execute a hook if present */
1568 _executeHook('afterSanitizeAttributes', currentNode, null);
1569 };
1570
1571 /**
1572 * _sanitizeShadowDOM
1573 *
1574 * @param {DocumentFragment} fragment to iterate over recursively
1575 */
1576 const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
1577 let shadowNode = null;
1578 const shadowIterator = _createNodeIterator(fragment);
1579
1580 /* Execute a hook if present */
1581 _executeHook('beforeSanitizeShadowDOM', fragment, null);
1582 while (shadowNode = shadowIterator.nextNode()) {
1583 /* Execute a hook if present */
1584 _executeHook('uponSanitizeShadowNode', shadowNode, null);
1585
1586 /* Sanitize tags and elements */
1587 if (_sanitizeElements(shadowNode)) {
1588 continue;
1589 }
1590 const parentNode = getParentNode(shadowNode);
1591
1592 /* Set the nesting depth of an element */
1593 if (shadowNode.nodeType === NODE_TYPE.element) {
1594 if (parentNode && parentNode.__depth) {
1595 /*
1596 We want the depth of the node in the original tree, which can
1597 change when it's removed from its parent.
1598 */
1599 shadowNode.__depth = (shadowNode.__removalCount || 0) + parentNode.__depth + 1;
1600 } else {
1601 shadowNode.__depth = 1;
1602 }
1603 }
1604
1605 /*
1606 * Remove an element if nested too deeply to avoid mXSS
1607 * or if the __depth might have been tampered with
1608 */
1609 if (shadowNode.__depth >= MAX_NESTING_DEPTH || shadowNode.__depth < 0 || numberIsNaN(shadowNode.__depth)) {
1610 _forceRemove(shadowNode);
1611 }
1612
1613 /* Deep shadow DOM detected */
1614 if (shadowNode.content instanceof DocumentFragment) {
1615 shadowNode.content.__depth = shadowNode.__depth;
1616 _sanitizeShadowDOM(shadowNode.content);
1617 }
1618
1619 /* Check attributes, sanitize if necessary */
1620 _sanitizeAttributes(shadowNode);
1621 }
1622
1623 /* Execute a hook if present */
1624 _executeHook('afterSanitizeShadowDOM', fragment, null);
1625 };
1626
1627 /**
1628 * Sanitize
1629 * Public method providing core sanitation functionality
1630 *
1631 * @param {String|Node} dirty string or DOM node
1632 * @param {Object} cfg object
1633 */
1634 // eslint-disable-next-line complexity
1635 DOMPurify.sanitize = function (dirty) {
1636 let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1637 let body = null;
1638 let importedNode = null;
1639 let currentNode = null;
1640 let returnNode = null;
1641 /* Make sure we have a string to sanitize.
1642 DO NOT return early, as this will return the wrong type if
1643 the user has requested a DOM object rather than a string */
1644 IS_EMPTY_INPUT = !dirty;
1645 if (IS_EMPTY_INPUT) {
1646 dirty = '<!-->';
1647 }
1648
1649 /* Stringify, in case dirty is an object */
1650 if (typeof dirty !== 'string' && !_isNode(dirty)) {
1651 if (typeof dirty.toString === 'function') {
1652 dirty = dirty.toString();
1653 if (typeof dirty !== 'string') {
1654 throw typeErrorCreate('dirty is not a string, aborting');
1655 }
1656 } else {
1657 throw typeErrorCreate('toString is not a function');
1658 }
1659 }
1660
1661 /* Return dirty HTML if DOMPurify cannot run */
1662 if (!DOMPurify.isSupported) {
1663 return dirty;
1664 }
1665
1666 /* Assign config vars */
1667 if (!SET_CONFIG) {
1668 _parseConfig(cfg);
1669 }
1670
1671 /* Clean up removed elements */
1672 DOMPurify.removed = [];
1673
1674 /* Check if dirty is correctly typed for IN_PLACE */
1675 if (typeof dirty === 'string') {
1676 IN_PLACE = false;
1677 }
1678 if (IN_PLACE) {
1679 /* Do some early pre-sanitization to avoid unsafe root nodes */
1680 if (dirty.nodeName) {
1681 const tagName = transformCaseFunc(dirty.nodeName);
1682 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1683 throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1684 }
1685 }
1686 } else if (dirty instanceof Node) {
1687 /* If dirty is a DOM element, append to an empty document to avoid
1688 elements being stripped by the parser */
1689 body = _initDocument('<!---->');
1690 importedNode = body.ownerDocument.importNode(dirty, true);
1691 if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
1692 /* Node is already a body, use as is */
1693 body = importedNode;
1694 } else if (importedNode.nodeName === 'HTML') {
1695 body = importedNode;
1696 } else {
1697 // eslint-disable-next-line unicorn/prefer-dom-node-append
1698 body.appendChild(importedNode);
1699 }
1700 } else {
1701 /* Exit directly if we have nothing to do */
1702 if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
1703 // eslint-disable-next-line unicorn/prefer-includes
1704 dirty.indexOf('<') === -1) {
1705 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
1706 }
1707
1708 /* Initialize the document to work on */
1709 body = _initDocument(dirty);
1710
1711 /* Check we have a DOM node from the data */
1712 if (!body) {
1713 return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
1714 }
1715 }
1716
1717 /* Remove first element node (ours) if FORCE_BODY is set */
1718 if (body && FORCE_BODY) {
1719 _forceRemove(body.firstChild);
1720 }
1721
1722 /* Get node iterator */
1723 const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
1724
1725 /* Now start iterating over the created document */
1726 while (currentNode = nodeIterator.nextNode()) {
1727 /* Sanitize tags and elements */
1728 if (_sanitizeElements(currentNode)) {
1729 continue;
1730 }
1731 const parentNode = getParentNode(currentNode);
1732
1733 /* Set the nesting depth of an element */
1734 if (currentNode.nodeType === NODE_TYPE.element) {
1735 if (parentNode && parentNode.__depth) {
1736 /*
1737 We want the depth of the node in the original tree, which can
1738 change when it's removed from its parent.
1739 */
1740 currentNode.__depth = (currentNode.__removalCount || 0) + parentNode.__depth + 1;
1741 } else {
1742 currentNode.__depth = 1;
1743 }
1744 }
1745
1746 /*
1747 * Remove an element if nested too deeply to avoid mXSS
1748 * or if the __depth might have been tampered with
1749 */
1750 if (currentNode.__depth >= MAX_NESTING_DEPTH || currentNode.__depth < 0 || numberIsNaN(currentNode.__depth)) {
1751 _forceRemove(currentNode);
1752 }
1753
1754 /* Shadow DOM detected, sanitize it */
1755 if (currentNode.content instanceof DocumentFragment) {
1756 currentNode.content.__depth = currentNode.__depth;
1757 _sanitizeShadowDOM(currentNode.content);
1758 }
1759
1760 /* Check attributes, sanitize if necessary */
1761 _sanitizeAttributes(currentNode);
1762 }
1763
1764 /* If we sanitized `dirty` in-place, return it. */
1765 if (IN_PLACE) {
1766 return dirty;
1767 }
1768
1769 /* Return sanitized string or DOM */
1770 if (RETURN_DOM) {
1771 if (RETURN_DOM_FRAGMENT) {
1772 returnNode = createDocumentFragment.call(body.ownerDocument);
1773 while (body.firstChild) {
1774 // eslint-disable-next-line unicorn/prefer-dom-node-append
1775 returnNode.appendChild(body.firstChild);
1776 }
1777 } else {
1778 returnNode = body;
1779 }
1780 if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
1781 /*
1782 AdoptNode() is not used because internal state is not reset
1783 (e.g. the past names map of a HTMLFormElement), this is safe
1784 in theory but we would rather not risk another attack vector.
1785 The state that is cloned by importNode() is explicitly defined
1786 by the specs.
1787 */
1788 returnNode = importNode.call(originalDocument, returnNode, true);
1789 }
1790 return returnNode;
1791 }
1792 let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1793
1794 /* Serialize doctype if allowed */
1795 if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
1796 serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
1797 }
1798
1799 /* Sanitize final string template-safe */
1800 if (SAFE_FOR_TEMPLATES) {
1801 arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1802 serializedHTML = stringReplace(serializedHTML, expr, ' ');
1803 });
1804 }
1805 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
1806 };
1807
1808 /**
1809 * Public method to set the configuration once
1810 * setConfig
1811 *
1812 * @param {Object} cfg configuration object
1813 */
1814 DOMPurify.setConfig = function () {
1815 let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1816 _parseConfig(cfg);
1817 SET_CONFIG = true;
1818 };
1819
1820 /**
1821 * Public method to remove the configuration
1822 * clearConfig
1823 *
1824 */
1825 DOMPurify.clearConfig = function () {
1826 CONFIG = null;
1827 SET_CONFIG = false;
1828 };
1829
1830 /**
1831 * Public method to check if an attribute value is valid.
1832 * Uses last set config, if any. Otherwise, uses config defaults.
1833 * isValidAttribute
1834 *
1835 * @param {String} tag Tag name of containing element.
1836 * @param {String} attr Attribute name.
1837 * @param {String} value Attribute value.
1838 * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
1839 */
1840 DOMPurify.isValidAttribute = function (tag, attr, value) {
1841 /* Initialize shared config vars if necessary. */
1842 if (!CONFIG) {
1843 _parseConfig({});
1844 }
1845 const lcTag = transformCaseFunc(tag);
1846 const lcName = transformCaseFunc(attr);
1847 return _isValidAttribute(lcTag, lcName, value);
1848 };
1849
1850 /**
1851 * AddHook
1852 * Public method to add DOMPurify hooks
1853 *
1854 * @param {String} entryPoint entry point for the hook to add
1855 * @param {Function} hookFunction function to execute
1856 */
1857 DOMPurify.addHook = function (entryPoint, hookFunction) {
1858 if (typeof hookFunction !== 'function') {
1859 return;
1860 }
1861 hooks[entryPoint] = hooks[entryPoint] || [];
1862 arrayPush(hooks[entryPoint], hookFunction);
1863 };
1864
1865 /**
1866 * RemoveHook
1867 * Public method to remove a DOMPurify hook at a given entryPoint
1868 * (pops it from the stack of hooks if more are present)
1869 *
1870 * @param {String} entryPoint entry point for the hook to remove
1871 * @return {Function} removed(popped) hook
1872 */
1873 DOMPurify.removeHook = function (entryPoint) {
1874 if (hooks[entryPoint]) {
1875 return arrayPop(hooks[entryPoint]);
1876 }
1877 };
1878
1879 /**
1880 * RemoveHooks
1881 * Public method to remove all DOMPurify hooks at a given entryPoint
1882 *
1883 * @param {String} entryPoint entry point for the hooks to remove
1884 */
1885 DOMPurify.removeHooks = function (entryPoint) {
1886 if (hooks[entryPoint]) {
1887 hooks[entryPoint] = [];
1888 }
1889 };
1890
1891 /**
1892 * RemoveAllHooks
1893 * Public method to remove all DOMPurify hooks
1894 */
1895 DOMPurify.removeAllHooks = function () {
1896 hooks = {};
1897 };
1898 return DOMPurify;
1899 }
1900 var purify = createDOMPurify();
1901
1902 return purify;
1903
1904 }));
1905 //# sourceMappingURL=purify.js.map
1906
1907
1908 /***/ })
1909
1910 }]);
1911 //# sourceMappingURL=text-path.12d8f0d07bb4893759c1.bundle.js.map