2101 lines
68 KiB
JavaScript
2101 lines
68 KiB
JavaScript
//#region node_modules/dompurify/dist/purify.es.mjs
|
|
/*! @license DOMPurify 3.4.10 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.10/LICENSE */
|
|
function _arrayLikeToArray(r, a) {
|
|
(null == a || a > r.length) && (a = r.length);
|
|
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
return n;
|
|
}
|
|
function _arrayWithHoles(r) {
|
|
if (Array.isArray(r)) return r;
|
|
}
|
|
function _iterableToArrayLimit(r, l) {
|
|
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
if (null != t) {
|
|
var e, n, i, u, a = [], f = true, o = false;
|
|
try {
|
|
if (i = (t = t.call(r)).next, 0 === l);
|
|
else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
} catch (r) {
|
|
o = true, n = r;
|
|
} finally {
|
|
try {
|
|
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
} finally {
|
|
if (o) throw n;
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
}
|
|
function _nonIterableRest() {
|
|
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
}
|
|
function _slicedToArray(r, e) {
|
|
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
}
|
|
function _unsupportedIterableToArray(r, a) {
|
|
if (r) {
|
|
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
var t = {}.toString.call(r).slice(8, -1);
|
|
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
|
|
}
|
|
}
|
|
var entries = Object.entries, setPrototypeOf = Object.setPrototypeOf, isFrozen = Object.isFrozen, getPrototypeOf = Object.getPrototypeOf, getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
var freeze = Object.freeze, seal = Object.seal, create = Object.create;
|
|
var _ref = typeof Reflect !== "undefined" && Reflect, apply = _ref.apply, construct = _ref.construct;
|
|
if (!freeze) freeze = function freeze(x) {
|
|
return x;
|
|
};
|
|
if (!seal) seal = function seal(x) {
|
|
return x;
|
|
};
|
|
if (!apply) apply = function apply(func, thisArg) {
|
|
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) args[_key - 2] = arguments[_key];
|
|
return func.apply(thisArg, args);
|
|
};
|
|
if (!construct) construct = function construct(Func) {
|
|
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) args[_key2 - 1] = arguments[_key2];
|
|
return new Func(...args);
|
|
};
|
|
var arrayForEach = unapply(Array.prototype.forEach);
|
|
var arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
var arrayPop = unapply(Array.prototype.pop);
|
|
var arrayPush = unapply(Array.prototype.push);
|
|
var arraySplice = unapply(Array.prototype.splice);
|
|
var arrayIsArray = Array.isArray;
|
|
var stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
var stringToString = unapply(String.prototype.toString);
|
|
var stringMatch = unapply(String.prototype.match);
|
|
var stringReplace = unapply(String.prototype.replace);
|
|
var stringIndexOf = unapply(String.prototype.indexOf);
|
|
var stringTrim = unapply(String.prototype.trim);
|
|
var numberToString = unapply(Number.prototype.toString);
|
|
var booleanToString = unapply(Boolean.prototype.toString);
|
|
var bigintToString = typeof BigInt === "undefined" ? null : unapply(BigInt.prototype.toString);
|
|
var symbolToString = typeof Symbol === "undefined" ? null : unapply(Symbol.prototype.toString);
|
|
var objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
var objectToString = unapply(Object.prototype.toString);
|
|
var regExpTest = unapply(RegExp.prototype.test);
|
|
var typeErrorCreate = unconstruct(TypeError);
|
|
/**
|
|
* Creates a new function that calls the given function with a specified thisArg and arguments.
|
|
*
|
|
* @param func - The function to be wrapped and called.
|
|
* @returns A new function that calls the given function with a specified thisArg and arguments.
|
|
*/
|
|
function unapply(func) {
|
|
return function(thisArg) {
|
|
if (thisArg instanceof RegExp) thisArg.lastIndex = 0;
|
|
for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) args[_key3 - 1] = arguments[_key3];
|
|
return apply(func, thisArg, args);
|
|
};
|
|
}
|
|
/**
|
|
* Creates a new function that constructs an instance of the given constructor function with the provided arguments.
|
|
*
|
|
* @param func - The constructor function to be wrapped and called.
|
|
* @returns A new function that constructs an instance of the given constructor function with the provided arguments.
|
|
*/
|
|
function unconstruct(Func) {
|
|
return function() {
|
|
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) args[_key4] = arguments[_key4];
|
|
return construct(Func, args);
|
|
};
|
|
}
|
|
/**
|
|
* Add properties to a lookup table
|
|
*
|
|
* @param set - The set to which elements will be added.
|
|
* @param array - The array containing elements to be added to the set.
|
|
* @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
|
|
* @returns The modified set with added elements.
|
|
*/
|
|
function addToSet(set, array) {
|
|
let transformCaseFunc = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : stringToLowerCase;
|
|
if (setPrototypeOf) setPrototypeOf(set, null);
|
|
if (!arrayIsArray(array)) return set;
|
|
let l = array.length;
|
|
while (l--) {
|
|
let element = array[l];
|
|
if (typeof element === "string") {
|
|
const lcElement = transformCaseFunc(element);
|
|
if (lcElement !== element) {
|
|
if (!isFrozen(array)) array[l] = lcElement;
|
|
element = lcElement;
|
|
}
|
|
}
|
|
set[element] = true;
|
|
}
|
|
return set;
|
|
}
|
|
/**
|
|
* Clean up an array to harden against CSPP
|
|
*
|
|
* @param array - The array to be cleaned.
|
|
* @returns The cleaned version of the array
|
|
*/
|
|
function cleanArray(array) {
|
|
for (let index = 0; index < array.length; index++) if (!objectHasOwnProperty(array, index)) array[index] = null;
|
|
return array;
|
|
}
|
|
/**
|
|
* Shallow clone an object
|
|
*
|
|
* @param object - The object to be cloned.
|
|
* @returns A new object that copies the original.
|
|
*/
|
|
function clone(object) {
|
|
const newObject = create(null);
|
|
for (const _ref2 of entries(object)) {
|
|
var _ref3 = _slicedToArray(_ref2, 2);
|
|
const property = _ref3[0];
|
|
const value = _ref3[1];
|
|
if (objectHasOwnProperty(object, property)) if (arrayIsArray(value)) newObject[property] = cleanArray(value);
|
|
else if (value && typeof value === "object" && value.constructor === Object) newObject[property] = clone(value);
|
|
else newObject[property] = value;
|
|
}
|
|
return newObject;
|
|
}
|
|
/**
|
|
* Convert non-node values into strings without depending on direct property access.
|
|
*
|
|
* @param value - The value to stringify.
|
|
* @returns A string representation of the provided value.
|
|
*/
|
|
function stringifyValue(value) {
|
|
switch (typeof value) {
|
|
case "string": return value;
|
|
case "number": return numberToString(value);
|
|
case "boolean": return booleanToString(value);
|
|
case "bigint": return bigintToString ? bigintToString(value) : "0";
|
|
case "symbol": return symbolToString ? symbolToString(value) : "Symbol()";
|
|
case "undefined": return objectToString(value);
|
|
case "function":
|
|
case "object": {
|
|
if (value === null) return objectToString(value);
|
|
const valueAsRecord = value;
|
|
const valueToString = lookupGetter(valueAsRecord, "toString");
|
|
if (typeof valueToString === "function") {
|
|
const stringified = valueToString(valueAsRecord);
|
|
return typeof stringified === "string" ? stringified : objectToString(stringified);
|
|
}
|
|
return objectToString(value);
|
|
}
|
|
default: return objectToString(value);
|
|
}
|
|
}
|
|
/**
|
|
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
*
|
|
* @param object - The object to look up the getter function in its prototype chain.
|
|
* @param prop - The property name for which to find the getter function.
|
|
* @returns The getter function found in the prototype chain or a fallback function.
|
|
*/
|
|
function lookupGetter(object, prop) {
|
|
while (object !== null) {
|
|
const desc = getOwnPropertyDescriptor(object, prop);
|
|
if (desc) {
|
|
if (desc.get) return unapply(desc.get);
|
|
if (typeof desc.value === "function") return unapply(desc.value);
|
|
}
|
|
object = getPrototypeOf(object);
|
|
}
|
|
function fallbackValue() {
|
|
return null;
|
|
}
|
|
return fallbackValue;
|
|
}
|
|
function isRegex(value) {
|
|
try {
|
|
regExpTest(value, "");
|
|
return true;
|
|
} catch (_unused) {
|
|
return false;
|
|
}
|
|
}
|
|
var html$1 = freeze([
|
|
"a",
|
|
"abbr",
|
|
"acronym",
|
|
"address",
|
|
"area",
|
|
"article",
|
|
"aside",
|
|
"audio",
|
|
"b",
|
|
"bdi",
|
|
"bdo",
|
|
"big",
|
|
"blink",
|
|
"blockquote",
|
|
"body",
|
|
"br",
|
|
"button",
|
|
"canvas",
|
|
"caption",
|
|
"center",
|
|
"cite",
|
|
"code",
|
|
"col",
|
|
"colgroup",
|
|
"content",
|
|
"data",
|
|
"datalist",
|
|
"dd",
|
|
"decorator",
|
|
"del",
|
|
"details",
|
|
"dfn",
|
|
"dialog",
|
|
"dir",
|
|
"div",
|
|
"dl",
|
|
"dt",
|
|
"element",
|
|
"em",
|
|
"fieldset",
|
|
"figcaption",
|
|
"figure",
|
|
"font",
|
|
"footer",
|
|
"form",
|
|
"h1",
|
|
"h2",
|
|
"h3",
|
|
"h4",
|
|
"h5",
|
|
"h6",
|
|
"head",
|
|
"header",
|
|
"hgroup",
|
|
"hr",
|
|
"html",
|
|
"i",
|
|
"img",
|
|
"input",
|
|
"ins",
|
|
"kbd",
|
|
"label",
|
|
"legend",
|
|
"li",
|
|
"main",
|
|
"map",
|
|
"mark",
|
|
"marquee",
|
|
"menu",
|
|
"menuitem",
|
|
"meter",
|
|
"nav",
|
|
"nobr",
|
|
"ol",
|
|
"optgroup",
|
|
"option",
|
|
"output",
|
|
"p",
|
|
"picture",
|
|
"pre",
|
|
"progress",
|
|
"q",
|
|
"rp",
|
|
"rt",
|
|
"ruby",
|
|
"s",
|
|
"samp",
|
|
"search",
|
|
"section",
|
|
"select",
|
|
"shadow",
|
|
"slot",
|
|
"small",
|
|
"source",
|
|
"spacer",
|
|
"span",
|
|
"strike",
|
|
"strong",
|
|
"style",
|
|
"sub",
|
|
"summary",
|
|
"sup",
|
|
"table",
|
|
"tbody",
|
|
"td",
|
|
"template",
|
|
"textarea",
|
|
"tfoot",
|
|
"th",
|
|
"thead",
|
|
"time",
|
|
"tr",
|
|
"track",
|
|
"tt",
|
|
"u",
|
|
"ul",
|
|
"var",
|
|
"video",
|
|
"wbr"
|
|
]);
|
|
var svg$1 = freeze([
|
|
"svg",
|
|
"a",
|
|
"altglyph",
|
|
"altglyphdef",
|
|
"altglyphitem",
|
|
"animatecolor",
|
|
"animatemotion",
|
|
"animatetransform",
|
|
"circle",
|
|
"clippath",
|
|
"defs",
|
|
"desc",
|
|
"ellipse",
|
|
"enterkeyhint",
|
|
"exportparts",
|
|
"filter",
|
|
"font",
|
|
"g",
|
|
"glyph",
|
|
"glyphref",
|
|
"hkern",
|
|
"image",
|
|
"inputmode",
|
|
"line",
|
|
"lineargradient",
|
|
"marker",
|
|
"mask",
|
|
"metadata",
|
|
"mpath",
|
|
"part",
|
|
"path",
|
|
"pattern",
|
|
"polygon",
|
|
"polyline",
|
|
"radialgradient",
|
|
"rect",
|
|
"stop",
|
|
"style",
|
|
"switch",
|
|
"symbol",
|
|
"text",
|
|
"textpath",
|
|
"title",
|
|
"tref",
|
|
"tspan",
|
|
"view",
|
|
"vkern"
|
|
]);
|
|
var 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"
|
|
]);
|
|
var 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"
|
|
]);
|
|
var 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"
|
|
]);
|
|
var mathMlDisallowed = freeze([
|
|
"maction",
|
|
"maligngroup",
|
|
"malignmark",
|
|
"mlongdiv",
|
|
"mscarries",
|
|
"mscarry",
|
|
"msgroup",
|
|
"mstack",
|
|
"msline",
|
|
"msrow",
|
|
"semantics",
|
|
"annotation",
|
|
"annotation-xml",
|
|
"mprescripts",
|
|
"none"
|
|
]);
|
|
var text = freeze(["#text"]);
|
|
var html = freeze([
|
|
"accept",
|
|
"action",
|
|
"align",
|
|
"alt",
|
|
"autocapitalize",
|
|
"autocomplete",
|
|
"autopictureinpicture",
|
|
"autoplay",
|
|
"background",
|
|
"bgcolor",
|
|
"border",
|
|
"capture",
|
|
"cellpadding",
|
|
"cellspacing",
|
|
"checked",
|
|
"cite",
|
|
"class",
|
|
"clear",
|
|
"color",
|
|
"cols",
|
|
"colspan",
|
|
"command",
|
|
"commandfor",
|
|
"controls",
|
|
"controlslist",
|
|
"coords",
|
|
"crossorigin",
|
|
"datetime",
|
|
"decoding",
|
|
"default",
|
|
"dir",
|
|
"disabled",
|
|
"disablepictureinpicture",
|
|
"disableremoteplayback",
|
|
"download",
|
|
"draggable",
|
|
"enctype",
|
|
"enterkeyhint",
|
|
"exportparts",
|
|
"face",
|
|
"for",
|
|
"headers",
|
|
"height",
|
|
"hidden",
|
|
"high",
|
|
"href",
|
|
"hreflang",
|
|
"id",
|
|
"inert",
|
|
"inputmode",
|
|
"integrity",
|
|
"ismap",
|
|
"kind",
|
|
"label",
|
|
"lang",
|
|
"list",
|
|
"loading",
|
|
"loop",
|
|
"low",
|
|
"max",
|
|
"maxlength",
|
|
"media",
|
|
"method",
|
|
"min",
|
|
"minlength",
|
|
"multiple",
|
|
"muted",
|
|
"name",
|
|
"nonce",
|
|
"noshade",
|
|
"novalidate",
|
|
"nowrap",
|
|
"open",
|
|
"optimum",
|
|
"part",
|
|
"pattern",
|
|
"placeholder",
|
|
"playsinline",
|
|
"popover",
|
|
"popovertarget",
|
|
"popovertargetaction",
|
|
"poster",
|
|
"preload",
|
|
"pubdate",
|
|
"radiogroup",
|
|
"readonly",
|
|
"rel",
|
|
"required",
|
|
"rev",
|
|
"reversed",
|
|
"role",
|
|
"rows",
|
|
"rowspan",
|
|
"spellcheck",
|
|
"scope",
|
|
"selected",
|
|
"shape",
|
|
"size",
|
|
"sizes",
|
|
"slot",
|
|
"span",
|
|
"srclang",
|
|
"start",
|
|
"src",
|
|
"srcset",
|
|
"step",
|
|
"style",
|
|
"summary",
|
|
"tabindex",
|
|
"title",
|
|
"translate",
|
|
"type",
|
|
"usemap",
|
|
"valign",
|
|
"value",
|
|
"width",
|
|
"wrap",
|
|
"xmlns"
|
|
]);
|
|
var svg = freeze([
|
|
"accent-height",
|
|
"accumulate",
|
|
"additive",
|
|
"alignment-baseline",
|
|
"amplitude",
|
|
"ascent",
|
|
"attributename",
|
|
"attributetype",
|
|
"azimuth",
|
|
"basefrequency",
|
|
"baseline-shift",
|
|
"begin",
|
|
"bias",
|
|
"by",
|
|
"class",
|
|
"clip",
|
|
"clippathunits",
|
|
"clip-path",
|
|
"clip-rule",
|
|
"color",
|
|
"color-interpolation",
|
|
"color-interpolation-filters",
|
|
"color-profile",
|
|
"color-rendering",
|
|
"cx",
|
|
"cy",
|
|
"d",
|
|
"dx",
|
|
"dy",
|
|
"diffuseconstant",
|
|
"direction",
|
|
"display",
|
|
"divisor",
|
|
"dur",
|
|
"edgemode",
|
|
"elevation",
|
|
"end",
|
|
"exponent",
|
|
"fill",
|
|
"fill-opacity",
|
|
"fill-rule",
|
|
"filter",
|
|
"filterunits",
|
|
"flood-color",
|
|
"flood-opacity",
|
|
"font-family",
|
|
"font-size",
|
|
"font-size-adjust",
|
|
"font-stretch",
|
|
"font-style",
|
|
"font-variant",
|
|
"font-weight",
|
|
"fx",
|
|
"fy",
|
|
"g1",
|
|
"g2",
|
|
"glyph-name",
|
|
"glyphref",
|
|
"gradientunits",
|
|
"gradienttransform",
|
|
"height",
|
|
"href",
|
|
"id",
|
|
"image-rendering",
|
|
"in",
|
|
"in2",
|
|
"intercept",
|
|
"k",
|
|
"k1",
|
|
"k2",
|
|
"k3",
|
|
"k4",
|
|
"kerning",
|
|
"keypoints",
|
|
"keysplines",
|
|
"keytimes",
|
|
"lang",
|
|
"lengthadjust",
|
|
"letter-spacing",
|
|
"kernelmatrix",
|
|
"kernelunitlength",
|
|
"lighting-color",
|
|
"local",
|
|
"marker-end",
|
|
"marker-mid",
|
|
"marker-start",
|
|
"markerheight",
|
|
"markerunits",
|
|
"markerwidth",
|
|
"maskcontentunits",
|
|
"maskunits",
|
|
"max",
|
|
"mask",
|
|
"mask-type",
|
|
"media",
|
|
"method",
|
|
"mode",
|
|
"min",
|
|
"name",
|
|
"numoctaves",
|
|
"offset",
|
|
"operator",
|
|
"opacity",
|
|
"order",
|
|
"orient",
|
|
"orientation",
|
|
"origin",
|
|
"overflow",
|
|
"paint-order",
|
|
"path",
|
|
"pathlength",
|
|
"patterncontentunits",
|
|
"patterntransform",
|
|
"patternunits",
|
|
"points",
|
|
"preservealpha",
|
|
"preserveaspectratio",
|
|
"primitiveunits",
|
|
"r",
|
|
"rx",
|
|
"ry",
|
|
"radius",
|
|
"refx",
|
|
"refy",
|
|
"repeatcount",
|
|
"repeatdur",
|
|
"restart",
|
|
"result",
|
|
"rotate",
|
|
"scale",
|
|
"seed",
|
|
"shape-rendering",
|
|
"slope",
|
|
"specularconstant",
|
|
"specularexponent",
|
|
"spreadmethod",
|
|
"startoffset",
|
|
"stddeviation",
|
|
"stitchtiles",
|
|
"stop-color",
|
|
"stop-opacity",
|
|
"stroke-dasharray",
|
|
"stroke-dashoffset",
|
|
"stroke-linecap",
|
|
"stroke-linejoin",
|
|
"stroke-miterlimit",
|
|
"stroke-opacity",
|
|
"stroke",
|
|
"stroke-width",
|
|
"style",
|
|
"surfacescale",
|
|
"systemlanguage",
|
|
"tabindex",
|
|
"tablevalues",
|
|
"targetx",
|
|
"targety",
|
|
"transform",
|
|
"transform-origin",
|
|
"text-anchor",
|
|
"text-decoration",
|
|
"text-rendering",
|
|
"textlength",
|
|
"type",
|
|
"u1",
|
|
"u2",
|
|
"unicode",
|
|
"values",
|
|
"viewbox",
|
|
"visibility",
|
|
"version",
|
|
"vert-adv-y",
|
|
"vert-origin-x",
|
|
"vert-origin-y",
|
|
"width",
|
|
"word-spacing",
|
|
"wrap",
|
|
"writing-mode",
|
|
"xchannelselector",
|
|
"ychannelselector",
|
|
"x",
|
|
"x1",
|
|
"x2",
|
|
"xmlns",
|
|
"y",
|
|
"y1",
|
|
"y2",
|
|
"z",
|
|
"zoomandpan"
|
|
]);
|
|
var mathMl = freeze([
|
|
"accent",
|
|
"accentunder",
|
|
"align",
|
|
"bevelled",
|
|
"close",
|
|
"columnalign",
|
|
"columnlines",
|
|
"columnspacing",
|
|
"columnspan",
|
|
"denomalign",
|
|
"depth",
|
|
"dir",
|
|
"display",
|
|
"displaystyle",
|
|
"encoding",
|
|
"fence",
|
|
"frame",
|
|
"height",
|
|
"href",
|
|
"id",
|
|
"largeop",
|
|
"length",
|
|
"linethickness",
|
|
"lquote",
|
|
"lspace",
|
|
"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"
|
|
]);
|
|
var xml = freeze([
|
|
"xlink:href",
|
|
"xml:id",
|
|
"xlink:title",
|
|
"xml:space",
|
|
"xmlns:xlink"
|
|
]);
|
|
var MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
|
|
var ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
|
|
var TMPLIT_EXPR = seal(/\${[\w\W]*/g);
|
|
var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/);
|
|
var ARIA_ATTR = seal(/^aria-[\-\w]+$/);
|
|
var IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i);
|
|
var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
|
|
var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g);
|
|
var DOCTYPE_NAME = seal(/^html$/i);
|
|
var CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
var ELEMENT_MARKUP_PROBE = seal(/<[/\w!]/g);
|
|
var COMMENT_MARKUP_PROBE = seal(/<[/\w]/g);
|
|
var FALLBACK_TAG_CLOSE = seal(/<\/no(script|embed|frames)/i);
|
|
var SELF_CLOSING_TAG = seal(/\/>/i);
|
|
var NODE_TYPE = {
|
|
element: 1,
|
|
attribute: 2,
|
|
text: 3,
|
|
cdataSection: 4,
|
|
entityReference: 5,
|
|
entityNode: 6,
|
|
processingInstruction: 7,
|
|
comment: 8,
|
|
document: 9,
|
|
documentType: 10,
|
|
documentFragment: 11,
|
|
notation: 12
|
|
};
|
|
var getGlobal = function getGlobal() {
|
|
return typeof window === "undefined" ? null : window;
|
|
};
|
|
/**
|
|
* Creates a no-op policy for internal use only.
|
|
* Don't export this function outside this module!
|
|
* @param trustedTypes The policy factory.
|
|
* @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
|
|
* @return The policy created (or null, if Trusted Types
|
|
* are not supported or creating the policy failed).
|
|
*/
|
|
var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
|
|
if (typeof trustedTypes !== "object" || typeof trustedTypes.createPolicy !== "function") return null;
|
|
let suffix = null;
|
|
const ATTR_NAME = "data-tt-policy-suffix";
|
|
if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) suffix = purifyHostElement.getAttribute(ATTR_NAME);
|
|
const policyName = "dompurify" + (suffix ? "#" + suffix : "");
|
|
try {
|
|
return trustedTypes.createPolicy(policyName, {
|
|
createHTML(html) {
|
|
return html;
|
|
},
|
|
createScriptURL(scriptUrl) {
|
|
return scriptUrl;
|
|
}
|
|
});
|
|
} catch (_) {
|
|
console.warn("TrustedTypes policy " + policyName + " could not be created.");
|
|
return null;
|
|
}
|
|
};
|
|
var _createHooksMap = function _createHooksMap() {
|
|
return {
|
|
afterSanitizeAttributes: [],
|
|
afterSanitizeElements: [],
|
|
afterSanitizeShadowDOM: [],
|
|
beforeSanitizeAttributes: [],
|
|
beforeSanitizeElements: [],
|
|
beforeSanitizeShadowDOM: [],
|
|
uponSanitizeAttribute: [],
|
|
uponSanitizeElement: [],
|
|
uponSanitizeShadowNode: []
|
|
};
|
|
};
|
|
/**
|
|
* Resolve a set-valued configuration option: a fresh set built from
|
|
* cfg[key] when it is an own array property (seeded with a clone of
|
|
* options.base when given, case-normalized via options.transform),
|
|
* the fallback set otherwise.
|
|
*
|
|
* @param cfg the cloned, prototype-free configuration object
|
|
* @param key the configuration property to read
|
|
* @param fallback the set to use when the option is absent or not an array
|
|
* @param options transform and optional base set to merge into
|
|
* @returns the resolved set
|
|
*/
|
|
var _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
|
|
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
|
|
};
|
|
function createDOMPurify() {
|
|
let window = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();
|
|
const DOMPurify = (root) => createDOMPurify(root);
|
|
DOMPurify.version = "3.4.10";
|
|
DOMPurify.removed = [];
|
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
DOMPurify.isSupported = false;
|
|
return DOMPurify;
|
|
}
|
|
let document = window.document;
|
|
const originalDocument = document;
|
|
const currentScript = originalDocument.currentScript;
|
|
window.DocumentFragment;
|
|
const HTMLTemplateElement = window.HTMLTemplateElement, Node = window.Node, Element = window.Element, NodeFilter = window.NodeFilter;
|
|
window.NamedNodeMap === void 0 && (window.NamedNodeMap || window.MozNamedAttrMap);
|
|
window.HTMLFormElement;
|
|
const DOMParser = window.DOMParser, trustedTypes = window.trustedTypes;
|
|
const ElementPrototype = Element.prototype;
|
|
const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
|
|
const remove = lookupGetter(ElementPrototype, "remove");
|
|
const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
|
|
const getChildNodes = lookupGetter(ElementPrototype, "childNodes");
|
|
const getParentNode = lookupGetter(ElementPrototype, "parentNode");
|
|
const getShadowRoot = lookupGetter(ElementPrototype, "shadowRoot");
|
|
const getAttributes = lookupGetter(ElementPrototype, "attributes");
|
|
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeType") : null;
|
|
const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeName") : null;
|
|
if (typeof HTMLTemplateElement === "function") {
|
|
const template = document.createElement("template");
|
|
if (template.content && template.content.ownerDocument) document = template.content.ownerDocument;
|
|
}
|
|
let trustedTypesPolicy;
|
|
let emptyHTML = "";
|
|
let defaultTrustedTypesPolicy;
|
|
let defaultTrustedTypesPolicyResolved = false;
|
|
let IN_TRUSTED_TYPES_POLICY = 0;
|
|
const _assertNotInTrustedTypesPolicy = function _assertNotInTrustedTypesPolicy() {
|
|
if (IN_TRUSTED_TYPES_POLICY > 0) throw typeErrorCreate("A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the \"DOMPurify and Trusted Types\" section of the README.");
|
|
};
|
|
const _createTrustedHTML = function _createTrustedHTML(html) {
|
|
_assertNotInTrustedTypesPolicy();
|
|
IN_TRUSTED_TYPES_POLICY++;
|
|
try {
|
|
return trustedTypesPolicy.createHTML(html);
|
|
} finally {
|
|
IN_TRUSTED_TYPES_POLICY--;
|
|
}
|
|
};
|
|
const _createTrustedScriptURL = function _createTrustedScriptURL(scriptUrl) {
|
|
_assertNotInTrustedTypesPolicy();
|
|
IN_TRUSTED_TYPES_POLICY++;
|
|
try {
|
|
return trustedTypesPolicy.createScriptURL(scriptUrl);
|
|
} finally {
|
|
IN_TRUSTED_TYPES_POLICY--;
|
|
}
|
|
};
|
|
const _getDefaultTrustedTypesPolicy = function _getDefaultTrustedTypesPolicy() {
|
|
if (!defaultTrustedTypesPolicyResolved) {
|
|
defaultTrustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
defaultTrustedTypesPolicyResolved = true;
|
|
}
|
|
return defaultTrustedTypesPolicy;
|
|
};
|
|
const _document = document, implementation = _document.implementation, createNodeIterator = _document.createNodeIterator, createDocumentFragment = _document.createDocumentFragment, getElementsByTagName = _document.getElementsByTagName;
|
|
const importNode = originalDocument.importNode;
|
|
let hooks = _createHooksMap();
|
|
/**
|
|
* Expose whether this browser supports running the full DOMPurify.
|
|
*/
|
|
DOMPurify.isSupported = typeof entries === "function" && typeof getParentNode === "function" && implementation && implementation.createHTMLDocument !== void 0;
|
|
const MUSTACHE_EXPR$1 = MUSTACHE_EXPR, ERB_EXPR$1 = ERB_EXPR, TMPLIT_EXPR$1 = TMPLIT_EXPR, DATA_ATTR$1 = DATA_ATTR, ARIA_ATTR$1 = ARIA_ATTR, IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA, ATTR_WHITESPACE$1 = ATTR_WHITESPACE, CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
|
|
let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
|
|
/**
|
|
* We consider the elements and attributes below to be safe. Ideally
|
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
*/
|
|
let ALLOWED_TAGS = null;
|
|
const DEFAULT_ALLOWED_TAGS = addToSet({}, [
|
|
...html$1,
|
|
...svg$1,
|
|
...svgFilters,
|
|
...mathMl$1,
|
|
...text
|
|
]);
|
|
let ALLOWED_ATTR = null;
|
|
const DEFAULT_ALLOWED_ATTR = addToSet({}, [
|
|
...html,
|
|
...svg,
|
|
...mathMl,
|
|
...xml
|
|
]);
|
|
let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
|
|
tagNameCheck: {
|
|
writable: true,
|
|
configurable: false,
|
|
enumerable: true,
|
|
value: null
|
|
},
|
|
attributeNameCheck: {
|
|
writable: true,
|
|
configurable: false,
|
|
enumerable: true,
|
|
value: null
|
|
},
|
|
allowCustomizedBuiltInElements: {
|
|
writable: true,
|
|
configurable: false,
|
|
enumerable: true,
|
|
value: false
|
|
}
|
|
}));
|
|
let FORBID_TAGS = null;
|
|
let FORBID_ATTR = null;
|
|
const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {
|
|
tagCheck: {
|
|
writable: true,
|
|
configurable: false,
|
|
enumerable: true,
|
|
value: null
|
|
},
|
|
attributeCheck: {
|
|
writable: true,
|
|
configurable: false,
|
|
enumerable: true,
|
|
value: null
|
|
}
|
|
}));
|
|
let ALLOW_ARIA_ATTR = true;
|
|
let ALLOW_DATA_ATTR = true;
|
|
let ALLOW_UNKNOWN_PROTOCOLS = false;
|
|
let ALLOW_SELF_CLOSE_IN_ATTR = true;
|
|
let SAFE_FOR_TEMPLATES = false;
|
|
let SAFE_FOR_XML = true;
|
|
let WHOLE_DOCUMENT = false;
|
|
let SET_CONFIG = false;
|
|
let FORCE_BODY = false;
|
|
let RETURN_DOM = false;
|
|
let RETURN_DOM_FRAGMENT = false;
|
|
let RETURN_TRUSTED_TYPE = false;
|
|
let SANITIZE_DOM = true;
|
|
let SANITIZE_NAMED_PROPS = false;
|
|
const SANITIZE_NAMED_PROPS_PREFIX = "user-content-";
|
|
let KEEP_CONTENT = true;
|
|
let IN_PLACE = false;
|
|
let USE_PROFILES = {};
|
|
let FORBID_CONTENTS = null;
|
|
const DEFAULT_FORBID_CONTENTS = addToSet({}, [
|
|
"annotation-xml",
|
|
"audio",
|
|
"colgroup",
|
|
"desc",
|
|
"foreignobject",
|
|
"head",
|
|
"iframe",
|
|
"math",
|
|
"mi",
|
|
"mn",
|
|
"mo",
|
|
"ms",
|
|
"mtext",
|
|
"noembed",
|
|
"noframes",
|
|
"noscript",
|
|
"plaintext",
|
|
"script",
|
|
"selectedcontent",
|
|
"style",
|
|
"svg",
|
|
"template",
|
|
"thead",
|
|
"title",
|
|
"video",
|
|
"xmp"
|
|
]);
|
|
let DATA_URI_TAGS = null;
|
|
const DEFAULT_DATA_URI_TAGS = addToSet({}, [
|
|
"audio",
|
|
"video",
|
|
"img",
|
|
"source",
|
|
"image",
|
|
"track"
|
|
]);
|
|
let URI_SAFE_ATTRIBUTES = null;
|
|
const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [
|
|
"alt",
|
|
"class",
|
|
"for",
|
|
"id",
|
|
"label",
|
|
"name",
|
|
"pattern",
|
|
"placeholder",
|
|
"role",
|
|
"summary",
|
|
"title",
|
|
"value",
|
|
"style",
|
|
"xmlns"
|
|
]);
|
|
const MATHML_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
|
|
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
|
let NAMESPACE = HTML_NAMESPACE;
|
|
let IS_EMPTY_INPUT = false;
|
|
let ALLOWED_NAMESPACES = null;
|
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [
|
|
MATHML_NAMESPACE,
|
|
SVG_NAMESPACE,
|
|
HTML_NAMESPACE
|
|
], stringToString);
|
|
const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze([
|
|
"mi",
|
|
"mo",
|
|
"mn",
|
|
"ms",
|
|
"mtext"
|
|
]);
|
|
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
|
|
const DEFAULT_HTML_INTEGRATION_POINTS = freeze(["annotation-xml"]);
|
|
let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
|
|
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [
|
|
"title",
|
|
"style",
|
|
"font",
|
|
"a",
|
|
"script"
|
|
]);
|
|
let PARSER_MEDIA_TYPE = null;
|
|
const SUPPORTED_PARSER_MEDIA_TYPES = ["application/xhtml+xml", "text/html"];
|
|
const DEFAULT_PARSER_MEDIA_TYPE = "text/html";
|
|
let transformCaseFunc = null;
|
|
let CONFIG = null;
|
|
const formElement = document.createElement("form");
|
|
const isRegexOrFunction = function isRegexOrFunction(testValue) {
|
|
return testValue instanceof RegExp || testValue instanceof Function;
|
|
};
|
|
/**
|
|
* _parseConfig
|
|
*
|
|
* @param cfg optional config literal
|
|
*/
|
|
const _parseConfig = function _parseConfig() {
|
|
let cfg = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
|
|
if (CONFIG && CONFIG === cfg) return;
|
|
if (!cfg || typeof cfg !== "object") cfg = {};
|
|
cfg = clone(cfg);
|
|
PARSER_MEDIA_TYPE = SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
|
|
transformCaseFunc = PARSER_MEDIA_TYPE === "application/xhtml+xml" ? stringToString : stringToLowerCase;
|
|
ALLOWED_TAGS = _resolveSetOption(cfg, "ALLOWED_TAGS", DEFAULT_ALLOWED_TAGS, { transform: transformCaseFunc });
|
|
ALLOWED_ATTR = _resolveSetOption(cfg, "ALLOWED_ATTR", DEFAULT_ALLOWED_ATTR, { transform: transformCaseFunc });
|
|
ALLOWED_NAMESPACES = _resolveSetOption(cfg, "ALLOWED_NAMESPACES", DEFAULT_ALLOWED_NAMESPACES, { transform: stringToString });
|
|
URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, "ADD_URI_SAFE_ATTR", DEFAULT_URI_SAFE_ATTRIBUTES, {
|
|
transform: transformCaseFunc,
|
|
base: DEFAULT_URI_SAFE_ATTRIBUTES
|
|
});
|
|
DATA_URI_TAGS = _resolveSetOption(cfg, "ADD_DATA_URI_TAGS", DEFAULT_DATA_URI_TAGS, {
|
|
transform: transformCaseFunc,
|
|
base: DEFAULT_DATA_URI_TAGS
|
|
});
|
|
FORBID_CONTENTS = _resolveSetOption(cfg, "FORBID_CONTENTS", DEFAULT_FORBID_CONTENTS, { transform: transformCaseFunc });
|
|
FORBID_TAGS = _resolveSetOption(cfg, "FORBID_TAGS", clone({}), { transform: transformCaseFunc });
|
|
FORBID_ATTR = _resolveSetOption(cfg, "FORBID_ATTR", clone({}), { transform: transformCaseFunc });
|
|
USE_PROFILES = objectHasOwnProperty(cfg, "USE_PROFILES") ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === "object" ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
|
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false;
|
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false;
|
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false;
|
|
ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false;
|
|
SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false;
|
|
SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false;
|
|
WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false;
|
|
RETURN_DOM = cfg.RETURN_DOM || false;
|
|
RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false;
|
|
RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false;
|
|
FORCE_BODY = cfg.FORCE_BODY || false;
|
|
SANITIZE_DOM = cfg.SANITIZE_DOM !== false;
|
|
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false;
|
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false;
|
|
IN_PLACE = cfg.IN_PLACE || false;
|
|
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI;
|
|
NAMESPACE = typeof cfg.NAMESPACE === "string" ? cfg.NAMESPACE : HTML_NAMESPACE;
|
|
MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, "MATHML_TEXT_INTEGRATION_POINTS") && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === "object" ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
|
|
HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, "HTML_INTEGRATION_POINTS") && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === "object" ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
|
|
const customElementHandling = objectHasOwnProperty(cfg, "CUSTOM_ELEMENT_HANDLING") && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === "object" ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
|
|
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
if (objectHasOwnProperty(customElementHandling, "tagNameCheck") && isRegexOrFunction(customElementHandling.tagNameCheck)) CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck;
|
|
if (objectHasOwnProperty(customElementHandling, "attributeNameCheck") && isRegexOrFunction(customElementHandling.attributeNameCheck)) CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck;
|
|
if (objectHasOwnProperty(customElementHandling, "allowCustomizedBuiltInElements") && typeof customElementHandling.allowCustomizedBuiltInElements === "boolean") CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements;
|
|
seal(CUSTOM_ELEMENT_HANDLING);
|
|
if (SAFE_FOR_TEMPLATES) ALLOW_DATA_ATTR = false;
|
|
if (RETURN_DOM_FRAGMENT) RETURN_DOM = true;
|
|
if (USE_PROFILES) {
|
|
ALLOWED_TAGS = addToSet({}, text);
|
|
ALLOWED_ATTR = create(null);
|
|
if (USE_PROFILES.html === true) {
|
|
addToSet(ALLOWED_TAGS, html$1);
|
|
addToSet(ALLOWED_ATTR, html);
|
|
}
|
|
if (USE_PROFILES.svg === true) {
|
|
addToSet(ALLOWED_TAGS, svg$1);
|
|
addToSet(ALLOWED_ATTR, svg);
|
|
addToSet(ALLOWED_ATTR, xml);
|
|
}
|
|
if (USE_PROFILES.svgFilters === true) {
|
|
addToSet(ALLOWED_TAGS, svgFilters);
|
|
addToSet(ALLOWED_ATTR, svg);
|
|
addToSet(ALLOWED_ATTR, xml);
|
|
}
|
|
if (USE_PROFILES.mathMl === true) {
|
|
addToSet(ALLOWED_TAGS, mathMl$1);
|
|
addToSet(ALLOWED_ATTR, mathMl);
|
|
addToSet(ALLOWED_ATTR, xml);
|
|
}
|
|
}
|
|
EXTRA_ELEMENT_HANDLING.tagCheck = null;
|
|
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
|
|
if (objectHasOwnProperty(cfg, "ADD_TAGS")) {
|
|
if (typeof cfg.ADD_TAGS === "function") EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
|
|
else if (arrayIsArray(cfg.ADD_TAGS)) {
|
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
}
|
|
}
|
|
if (objectHasOwnProperty(cfg, "ADD_ATTR")) {
|
|
if (typeof cfg.ADD_ATTR === "function") EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
|
|
else if (arrayIsArray(cfg.ADD_ATTR)) {
|
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
}
|
|
}
|
|
if (objectHasOwnProperty(cfg, "ADD_URI_SAFE_ATTR") && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
if (objectHasOwnProperty(cfg, "FORBID_CONTENTS") && arrayIsArray(cfg.FORBID_CONTENTS)) {
|
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
}
|
|
if (objectHasOwnProperty(cfg, "ADD_FORBID_CONTENTS") && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
|
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
|
|
}
|
|
if (KEEP_CONTENT) ALLOWED_TAGS["#text"] = true;
|
|
if (WHOLE_DOCUMENT) addToSet(ALLOWED_TAGS, [
|
|
"html",
|
|
"head",
|
|
"body"
|
|
]);
|
|
if (ALLOWED_TAGS.table) {
|
|
addToSet(ALLOWED_TAGS, ["tbody"]);
|
|
delete FORBID_TAGS.tbody;
|
|
}
|
|
if (cfg.TRUSTED_TYPES_POLICY) {
|
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== "function") throw typeErrorCreate("TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook.");
|
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== "function") throw typeErrorCreate("TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook.");
|
|
const previousTrustedTypesPolicy = trustedTypesPolicy;
|
|
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
try {
|
|
emptyHTML = _createTrustedHTML("");
|
|
} catch (error) {
|
|
trustedTypesPolicy = previousTrustedTypesPolicy;
|
|
throw error;
|
|
}
|
|
} else if (cfg.TRUSTED_TYPES_POLICY === null) {
|
|
trustedTypesPolicy = void 0;
|
|
emptyHTML = "";
|
|
} else {
|
|
if (trustedTypesPolicy === void 0) trustedTypesPolicy = _getDefaultTrustedTypesPolicy();
|
|
if (trustedTypesPolicy && typeof emptyHTML === "string") emptyHTML = _createTrustedHTML("");
|
|
}
|
|
if ((hooks.uponSanitizeElement.length > 0 || hooks.uponSanitizeAttribute.length > 0) && ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
if (hooks.uponSanitizeAttribute.length > 0 && ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
if (freeze) freeze(cfg);
|
|
CONFIG = cfg;
|
|
};
|
|
const ALL_SVG_TAGS = addToSet({}, [
|
|
...svg$1,
|
|
...svgFilters,
|
|
...svgDisallowed
|
|
]);
|
|
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
/**
|
|
* Namespace rules for an element in the SVG namespace.
|
|
*
|
|
* @param tagName the element's lowercase tag name
|
|
* @param parent the (possibly simulated) parent node
|
|
* @param parentTagName the parent's lowercase tag name
|
|
* @returns true if a spec-compliant parser could produce this element
|
|
*/
|
|
const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) {
|
|
if (parent.namespaceURI === HTML_NAMESPACE) return tagName === "svg";
|
|
if (parent.namespaceURI === MATHML_NAMESPACE) return tagName === "svg" && (parentTagName === "annotation-xml" || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
};
|
|
/**
|
|
* Namespace rules for an element in the MathML namespace.
|
|
*
|
|
* @param tagName the element's lowercase tag name
|
|
* @param parent the (possibly simulated) parent node
|
|
* @param parentTagName the parent's lowercase tag name
|
|
* @returns true if a spec-compliant parser could produce this element
|
|
*/
|
|
const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) {
|
|
if (parent.namespaceURI === HTML_NAMESPACE) return tagName === "math";
|
|
if (parent.namespaceURI === SVG_NAMESPACE) return tagName === "math" && HTML_INTEGRATION_POINTS[parentTagName];
|
|
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
};
|
|
/**
|
|
* Namespace rules for an element in the HTML namespace.
|
|
*
|
|
* @param tagName the element's lowercase tag name
|
|
* @param parent the (possibly simulated) parent node
|
|
* @param parentTagName the parent's lowercase tag name
|
|
* @returns true if a spec-compliant parser could produce this element
|
|
*/
|
|
const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) {
|
|
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) return false;
|
|
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) return false;
|
|
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
};
|
|
/**
|
|
* @param element a DOM element whose namespace is being checked
|
|
* @returns Return false if the element has a
|
|
* namespace that a spec-compliant parser would never
|
|
* return. Return true otherwise.
|
|
*/
|
|
const _checkValidNamespace = function _checkValidNamespace(element) {
|
|
let parent = getParentNode(element);
|
|
if (!parent || !parent.tagName) parent = {
|
|
namespaceURI: NAMESPACE,
|
|
tagName: "template"
|
|
};
|
|
const tagName = stringToLowerCase(element.tagName);
|
|
const parentTagName = stringToLowerCase(parent.tagName);
|
|
if (!ALLOWED_NAMESPACES[element.namespaceURI]) return false;
|
|
if (element.namespaceURI === SVG_NAMESPACE) return _checkSvgNamespace(tagName, parent, parentTagName);
|
|
if (element.namespaceURI === MATHML_NAMESPACE) return _checkMathMlNamespace(tagName, parent, parentTagName);
|
|
if (element.namespaceURI === HTML_NAMESPACE) return _checkHtmlNamespace(tagName, parent, parentTagName);
|
|
if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && ALLOWED_NAMESPACES[element.namespaceURI]) return true;
|
|
return false;
|
|
};
|
|
/**
|
|
* _forceRemove
|
|
*
|
|
* @param node a DOM node
|
|
*/
|
|
const _forceRemove = function _forceRemove(node) {
|
|
arrayPush(DOMPurify.removed, { element: node });
|
|
try {
|
|
getParentNode(node).removeChild(node);
|
|
} catch (_) {
|
|
remove(node);
|
|
if (!getParentNode(node)) throw typeErrorCreate("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place");
|
|
}
|
|
};
|
|
/**
|
|
* _neutralizeRoot
|
|
*
|
|
* Fail-closed teardown of an in-place root after the sanitize walk aborts
|
|
* (campaign-3 F2). An internal throw mid-walk — e.g. a page-registered
|
|
* custom element's reaction detaches a node so `_forceRemove`'s deliberate
|
|
* parentless guard throws, or any other re-entrant engine mutation — would
|
|
* otherwise leave the caller's *live* tree half-sanitized, with everything
|
|
* after the abort point still carrying its handlers. There is no safe way
|
|
* to resume the walk (the tree mutated under us), so we strip the root bare:
|
|
* remove every child and every attribute, then let the caller's catch see
|
|
* the original error. Clobber-safe (cached `remove`/`childNodes`/`attributes`
|
|
* getters; the root was already clobber-pre-flighted at the IN_PLACE entry).
|
|
*
|
|
* @param root the in-place root to empty
|
|
*/
|
|
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
const childNodes = getChildNodes(root);
|
|
if (childNodes) {
|
|
const snapshot = [];
|
|
arrayForEach(childNodes, (child) => {
|
|
arrayPush(snapshot, child);
|
|
});
|
|
arrayForEach(snapshot, (child) => {
|
|
try {
|
|
remove(child);
|
|
} catch (_) {}
|
|
});
|
|
}
|
|
const attributes = getAttributes(root);
|
|
if (attributes) for (let i = attributes.length - 1; i >= 0; --i) {
|
|
const attribute = attributes[i];
|
|
const name = attribute && attribute.name;
|
|
if (typeof name === "string") try {
|
|
root.removeAttribute(name);
|
|
} catch (_) {}
|
|
}
|
|
};
|
|
/**
|
|
* _removeAttribute
|
|
*
|
|
* @param name an Attribute name
|
|
* @param element a DOM node
|
|
*/
|
|
const _removeAttribute = function _removeAttribute(name, element) {
|
|
try {
|
|
arrayPush(DOMPurify.removed, {
|
|
attribute: element.getAttributeNode(name),
|
|
from: element
|
|
});
|
|
} catch (_) {
|
|
arrayPush(DOMPurify.removed, {
|
|
attribute: null,
|
|
from: element
|
|
});
|
|
}
|
|
element.removeAttribute(name);
|
|
if (name === "is") if (RETURN_DOM || RETURN_DOM_FRAGMENT) try {
|
|
_forceRemove(element);
|
|
} catch (_) {}
|
|
else try {
|
|
element.setAttribute(name, "");
|
|
} catch (_) {}
|
|
};
|
|
/**
|
|
* _stripDisallowedAttributes
|
|
*
|
|
* Removes every attribute the active configuration does not allow from a
|
|
* single element, using the same allowlist as the main attribute pass (so
|
|
* `on*` handlers go, but no `/^on/` blocklist is introduced). Used only to
|
|
* neutralise nodes that are being discarded from an in-place tree.
|
|
*
|
|
* @param element the element to strip
|
|
*/
|
|
const _stripDisallowedAttributes = function _stripDisallowedAttributes(element) {
|
|
const attributes = getAttributes(element);
|
|
if (!attributes) return;
|
|
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
const attribute = attributes[i];
|
|
const name = attribute && attribute.name;
|
|
if (typeof name !== "string" || ALLOWED_ATTR[transformCaseFunc(name)]) continue;
|
|
try {
|
|
element.removeAttribute(name);
|
|
} catch (_) {}
|
|
}
|
|
};
|
|
/**
|
|
* _neutralizeSubtree
|
|
*
|
|
* Completes the audit-5 F1 fix across every removal path. The KEEP_CONTENT
|
|
* move-hoist neutralises only disallowed-tag removals; clobber, mXSS-canary,
|
|
* namespace, comment, processing-instruction and KEEP_CONTENT:false removals
|
|
* all drop their subtree wholesale via `_forceRemove`. On the IN_PLACE path
|
|
* those dropped nodes are detached from the caller's LIVE tree but a
|
|
* handler-bearing original among them (an `<img onerror>`/`<video>` that was
|
|
* loading) keeps its queued resource event, which fires in page scope after
|
|
* sanitize returns. This walks a removed subtree and strips every attribute
|
|
* the active configuration does not allow — so `on*` handlers are cancelled
|
|
* through the SAME allowlist that governs kept nodes, not a separate `/^on/`
|
|
* blocklist. Run synchronously before sanitize returns, i.e. before any
|
|
* queued event can fire. Hook-free by design: these nodes leave the output,
|
|
* so firing attribute hooks for them would be surprising. Clobber-safe reads;
|
|
* a doomed clobbered node may shadow `removeAttribute` (its own attributes are
|
|
* irrelevant — it is discarded — while its non-clobbered descendants, e.g.
|
|
* the `<img>`, are reached and scrubbed).
|
|
*
|
|
* @param root the root of a removed subtree to neutralise
|
|
*/
|
|
const _neutralizeSubtree = function _neutralizeSubtree(root) {
|
|
const stack = [root];
|
|
while (stack.length > 0) {
|
|
const node = stack.pop();
|
|
if ((getNodeType ? getNodeType(node) : node.nodeType) === NODE_TYPE.element) _stripDisallowedAttributes(node);
|
|
const childNodes = getChildNodes(node);
|
|
if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
|
|
}
|
|
};
|
|
/**
|
|
* _initDocument
|
|
*
|
|
* @param dirty - a string of dirty markup
|
|
* @return a DOM, filled with the dirty markup
|
|
*/
|
|
const _initDocument = function _initDocument(dirty) {
|
|
let doc = null;
|
|
let leadingWhitespace = null;
|
|
if (FORCE_BODY) dirty = "<remove></remove>" + dirty;
|
|
else {
|
|
const matches = stringMatch(dirty, /^[\r\n\t ]+/);
|
|
leadingWhitespace = matches && matches[0];
|
|
}
|
|
if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && NAMESPACE === HTML_NAMESPACE) dirty = "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head></head><body>" + dirty + "</body></html>";
|
|
const dirtyPayload = trustedTypesPolicy ? _createTrustedHTML(dirty) : dirty;
|
|
if (NAMESPACE === HTML_NAMESPACE) try {
|
|
doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
|
|
} catch (_) {}
|
|
if (!doc || !doc.documentElement) {
|
|
doc = implementation.createDocument(NAMESPACE, "template", null);
|
|
try {
|
|
doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
|
|
} catch (_) {}
|
|
}
|
|
const body = doc.body || doc.documentElement;
|
|
if (dirty && leadingWhitespace) body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
|
|
if (NAMESPACE === HTML_NAMESPACE) return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? "html" : "body")[0];
|
|
return WHOLE_DOCUMENT ? doc.documentElement : body;
|
|
};
|
|
/**
|
|
* Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
|
|
*
|
|
* @param root The root element or node to start traversing on.
|
|
* @return The created NodeIterator
|
|
*/
|
|
const _createNodeIterator = function _createNodeIterator(root) {
|
|
return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
};
|
|
/**
|
|
* Replace template expression syntax (mustache, ERB, template
|
|
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
|
|
* sites. Order matters: mustache, then ERB, then template literal.
|
|
*
|
|
* @param value the string to scrub
|
|
* @returns the scrubbed string
|
|
*/
|
|
const _stripTemplateExpressions = function _stripTemplateExpressions(value) {
|
|
value = stringReplace(value, MUSTACHE_EXPR$1, " ");
|
|
value = stringReplace(value, ERB_EXPR$1, " ");
|
|
value = stringReplace(value, TMPLIT_EXPR$1, " ");
|
|
return value;
|
|
};
|
|
/**
|
|
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
* character data of an element subtree. Used as the final safety net for
|
|
* SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
|
|
* which only form after text-node normalization (e.g. fragments split across
|
|
* stripped elements) cannot survive into a template-evaluating framework.
|
|
*
|
|
* Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
|
|
* in place rather than round-tripping through innerHTML. This preserves
|
|
* descendant node references (important for IN_PLACE callers), avoids a
|
|
* serialize/reparse cycle, and reads literal character data — which means
|
|
* `<%...%>` in text content matches the ERB regex against its real bytes
|
|
* instead of the HTML-entity-escaped form innerHTML would produce.
|
|
*
|
|
* Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
|
|
* attributes is performed during the per-node `_sanitizeAttributes` pass.
|
|
*
|
|
* @param node The root element whose character data should be scrubbed.
|
|
*/
|
|
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
var _node$querySelectorAl;
|
|
node.normalize();
|
|
const walker = createNodeIterator.call(node.ownerDocument || node, node, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
let currentNode = walker.nextNode();
|
|
while (currentNode) {
|
|
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
currentNode = walker.nextNode();
|
|
}
|
|
const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, "template");
|
|
if (templates) arrayForEach(templates, (tmpl) => {
|
|
if (_isDocumentFragment(tmpl.content)) _scrubTemplateExpressions2(tmpl.content);
|
|
});
|
|
};
|
|
/**
|
|
* _isClobbered
|
|
*
|
|
* Detect DOM-clobbering on HTMLFormElement nodes. Form is the only HTML
|
|
* interface with [LegacyOverrideBuiltIns]; a descendant element with a
|
|
* `name` attribute matching a prototype property shadows that property
|
|
* on direct reads. We use this check at the IN_PLACE entry-point and
|
|
* during attribute sanitization to refuse clobbered forms.
|
|
*
|
|
* @param element element to check for clobbering attacks
|
|
* @return true if clobbered, false if safe
|
|
*/
|
|
const _isClobbered = function _isClobbered(element) {
|
|
const realTagName = getNodeName ? getNodeName(element) : null;
|
|
if (typeof realTagName !== "string") return false;
|
|
if (transformCaseFunc(realTagName) !== "form") return false;
|
|
return typeof element.nodeName !== "string" || typeof element.textContent !== "string" || typeof element.removeChild !== "function" || element.attributes !== getAttributes(element) || typeof element.removeAttribute !== "function" || typeof element.setAttribute !== "function" || typeof element.namespaceURI !== "string" || typeof element.insertBefore !== "function" || typeof element.hasChildNodes !== "function" || element.nodeType !== getNodeType(element) || element.childNodes !== getChildNodes(element);
|
|
};
|
|
/**
|
|
* Checks whether the given value is a DocumentFragment from any realm.
|
|
*
|
|
* The realm-independent replacement reads `nodeType` through the cached
|
|
* Node.prototype getter and compares to the DOCUMENT_FRAGMENT_NODE
|
|
* constant (11). nodeType is a numeric value resolved from the node's
|
|
* internal slot, identical across realms for the same kind of node.
|
|
*
|
|
* @param value object to check
|
|
* @return true if value is a DocumentFragment-shaped node from any realm
|
|
*/
|
|
const _isDocumentFragment = function _isDocumentFragment(value) {
|
|
if (!getNodeType || typeof value !== "object" || value === null) return false;
|
|
try {
|
|
return getNodeType(value) === NODE_TYPE.documentFragment;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
};
|
|
/**
|
|
* Checks whether the given object is a DOM node, including nodes that
|
|
* originate from a different window/realm (e.g. an iframe's
|
|
* contentDocument). The previous `value instanceof Node` check was
|
|
* realm-bound: nodes from a different window failed it, causing
|
|
* sanitize() to silently stringify them and reset IN_PLACE to false,
|
|
* returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
|
|
*
|
|
* @param value object to check whether it's a DOM node
|
|
* @return true if value is a DOM node from any realm
|
|
*/
|
|
const _isNode = function _isNode(value) {
|
|
if (!getNodeType || typeof value !== "object" || value === null) return false;
|
|
try {
|
|
return typeof getNodeType(value) === "number";
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
};
|
|
function _executeHooks(hooks, currentNode, data) {
|
|
if (hooks.length === 0) return;
|
|
arrayForEach(hooks, (hook) => {
|
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
});
|
|
}
|
|
/**
|
|
* Structural-threat checks that condemn a node regardless of the
|
|
* allowlists: mXSS via namespace confusion, risky CSS construction,
|
|
* processing instructions, markup-bearing comments. Pure predicate;
|
|
* the caller removes. Check order is load-bearing.
|
|
*
|
|
* @param currentNode the node to inspect
|
|
* @param tagName the node's transformCaseFunc'd tag name
|
|
* @return true if the node must be removed
|
|
*/
|
|
const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
|
|
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) return true;
|
|
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === "style" && _isNode(currentNode.firstElementChild)) return true;
|
|
if (currentNode.nodeType === NODE_TYPE.processingInstruction) return true;
|
|
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) return true;
|
|
return false;
|
|
};
|
|
/**
|
|
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
* allowed custom elements (false return exits _sanitizeElements
|
|
* early - namespace/fallback checks and the afterSanitizeElements
|
|
* hook are intentionally skipped for kept custom elements), else
|
|
* hoist content per KEEP_CONTENT and remove.
|
|
*
|
|
* @param currentNode the disallowed node
|
|
* @param tagName the node's transformCaseFunc'd tag name
|
|
* @return true if the node was removed, false if kept
|
|
*/
|
|
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
|
|
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
|
|
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
|
|
}
|
|
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
const parentNode = getParentNode(currentNode);
|
|
const childNodes = getChildNodes(currentNode);
|
|
if (childNodes && parentNode) {
|
|
const childCount = childNodes.length;
|
|
for (let i = childCount - 1; i >= 0; --i) {
|
|
const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
|
|
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
}
|
|
}
|
|
}
|
|
_forceRemove(currentNode);
|
|
return true;
|
|
};
|
|
/**
|
|
* _sanitizeElements
|
|
*
|
|
* @protect nodeName
|
|
* @protect textContent
|
|
* @protect removeChild
|
|
* @param currentNode to check for permission to exist
|
|
* @return true if node was killed, false if left alive
|
|
*/
|
|
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
if (_isClobbered(currentNode)) {
|
|
_forceRemove(currentNode);
|
|
return true;
|
|
}
|
|
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
tagName,
|
|
allowedTags: ALLOWED_TAGS
|
|
});
|
|
if (_isUnsafeNode(currentNode, tagName)) {
|
|
_forceRemove(currentNode);
|
|
return true;
|
|
}
|
|
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) return _sanitizeDisallowedNode(currentNode, tagName);
|
|
if ((getNodeType ? getNodeType(currentNode) : currentNode.nodeType) === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
|
|
_forceRemove(currentNode);
|
|
return true;
|
|
}
|
|
if ((tagName === "noscript" || tagName === "noembed" || tagName === "noframes") && regExpTest(FALLBACK_TAG_CLOSE, currentNode.innerHTML)) {
|
|
_forceRemove(currentNode);
|
|
return true;
|
|
}
|
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
const content = _stripTemplateExpressions(currentNode.textContent);
|
|
if (currentNode.textContent !== content) {
|
|
arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });
|
|
currentNode.textContent = content;
|
|
}
|
|
}
|
|
_executeHooks(hooks.afterSanitizeElements, currentNode, null);
|
|
return false;
|
|
};
|
|
/**
|
|
* _isValidAttribute
|
|
*
|
|
* @param lcTag Lowercase tag name of containing element.
|
|
* @param lcName Lowercase attribute name.
|
|
* @param value Attribute value.
|
|
* @return Returns true if `value` is valid, otherwise false.
|
|
*/
|
|
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
|
|
if (FORBID_ATTR[lcName]) return false;
|
|
if (SANITIZE_DOM && (lcName === "id" || lcName === "name") && (value in document || value in formElement)) return false;
|
|
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName));
|
|
else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName));
|
|
else if (!nameIsPermitted) if (_isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName, lcTag)) || 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 return false;
|
|
else if (URI_SAFE_ATTRIBUTES[lcName]);
|
|
else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, "")));
|
|
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$1, stringReplace(value, ATTR_WHITESPACE$1, "")));
|
|
else if (value) return false;
|
|
return true;
|
|
};
|
|
const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, [
|
|
"annotation-xml",
|
|
"color-profile",
|
|
"font-face",
|
|
"font-face-format",
|
|
"font-face-name",
|
|
"font-face-src",
|
|
"font-face-uri",
|
|
"missing-glyph"
|
|
]);
|
|
/**
|
|
* _isBasicCustomElement
|
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
* for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
|
|
*
|
|
* @param tagName name of the tag of the node to sanitize
|
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
*/
|
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
};
|
|
/**
|
|
* Wrap an attribute value in the matching Trusted Types object when
|
|
* the active policy requires it. Namespaced attributes pass through
|
|
* unchanged (no TT support yet, see
|
|
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
|
|
*
|
|
* @param lcTag lowercase tag name of the containing element
|
|
* @param lcName lowercase attribute name
|
|
* @param namespaceURI the attribute's namespace, if any
|
|
* @param value the attribute value to wrap
|
|
* @return the value, wrapped when Trusted Types demand it
|
|
*/
|
|
const _applyTrustedTypesToAttribute = function _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value) {
|
|
if (trustedTypesPolicy && typeof trustedTypes === "object" && typeof trustedTypes.getAttributeType === "function" && !namespaceURI) switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
case "TrustedHTML": return _createTrustedHTML(value);
|
|
case "TrustedScriptURL": return _createTrustedScriptURL(value);
|
|
}
|
|
return value;
|
|
};
|
|
/**
|
|
* Write a modified attribute value back onto the element. On
|
|
* success, re-probe for clobbering introduced by the new value and
|
|
* remove the element when found; otherwise pop the removal entry
|
|
* recorded by the earlier _removeAttribute (long-standing pairing
|
|
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
|
|
* failure, remove the attribute instead.
|
|
*
|
|
* @param currentNode the element carrying the attribute
|
|
* @param name the attribute name as present on the element
|
|
* @param namespaceURI the attribute's namespace, if any
|
|
* @param value the new attribute value
|
|
*/
|
|
const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
|
|
try {
|
|
if (namespaceURI) currentNode.setAttributeNS(namespaceURI, name, value);
|
|
else currentNode.setAttribute(name, value);
|
|
if (_isClobbered(currentNode)) _forceRemove(currentNode);
|
|
else arrayPop(DOMPurify.removed);
|
|
} catch (_) {
|
|
_removeAttribute(name, currentNode);
|
|
}
|
|
};
|
|
/**
|
|
* _sanitizeAttributes
|
|
*
|
|
* @protect attributes
|
|
* @protect nodeName
|
|
* @protect removeAttribute
|
|
* @protect setAttribute
|
|
*
|
|
* @param currentNode to sanitize
|
|
*/
|
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
const attributes = currentNode.attributes;
|
|
if (!attributes || _isClobbered(currentNode)) return;
|
|
const hookEvent = {
|
|
attrName: "",
|
|
attrValue: "",
|
|
keepAttr: true,
|
|
allowedAttributes: ALLOWED_ATTR,
|
|
forceKeepAttr: void 0
|
|
};
|
|
let l = attributes.length;
|
|
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
while (l--) {
|
|
const attr = attributes[l];
|
|
const name = attr.name, namespaceURI = attr.namespaceURI, attrValue = attr.value;
|
|
const lcName = transformCaseFunc(name);
|
|
const initValue = attrValue;
|
|
let value = name === "value" ? initValue : stringTrim(initValue);
|
|
hookEvent.attrName = lcName;
|
|
hookEvent.attrValue = value;
|
|
hookEvent.keepAttr = true;
|
|
hookEvent.forceKeepAttr = void 0;
|
|
_executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
|
|
value = hookEvent.attrValue;
|
|
if (SANITIZE_NAMED_PROPS && (lcName === "id" || lcName === "name") && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
|
|
_removeAttribute(name, currentNode);
|
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
}
|
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
_removeAttribute(name, currentNode);
|
|
continue;
|
|
}
|
|
if (lcName === "attributename" && stringMatch(value, "href")) {
|
|
_removeAttribute(name, currentNode);
|
|
continue;
|
|
}
|
|
if (hookEvent.forceKeepAttr) continue;
|
|
if (!hookEvent.keepAttr) {
|
|
_removeAttribute(name, currentNode);
|
|
continue;
|
|
}
|
|
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
|
|
_removeAttribute(name, currentNode);
|
|
continue;
|
|
}
|
|
if (SAFE_FOR_TEMPLATES) value = _stripTemplateExpressions(value);
|
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
_removeAttribute(name, currentNode);
|
|
continue;
|
|
}
|
|
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
if (value !== initValue) _setAttributeValue(currentNode, name, namespaceURI, value);
|
|
}
|
|
_executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
|
|
};
|
|
/**
|
|
* _sanitizeShadowDOM
|
|
*
|
|
* @param fragment to iterate over recursively
|
|
*/
|
|
const _sanitizeShadowDOM2 = function _sanitizeShadowDOM(fragment) {
|
|
let shadowNode = null;
|
|
const shadowIterator = _createNodeIterator(fragment);
|
|
_executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
|
|
while (shadowNode = shadowIterator.nextNode()) {
|
|
_executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
|
|
_sanitizeElements(shadowNode);
|
|
_sanitizeAttributes(shadowNode);
|
|
if (_isDocumentFragment(shadowNode.content)) _sanitizeShadowDOM2(shadowNode.content);
|
|
if ((getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType) === NODE_TYPE.element) {
|
|
const innerSr = getShadowRoot(shadowNode);
|
|
if (_isDocumentFragment(innerSr)) {
|
|
_sanitizeAttachedShadowRoots(innerSr);
|
|
_sanitizeShadowDOM2(innerSr);
|
|
}
|
|
}
|
|
}
|
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
};
|
|
/**
|
|
* _sanitizeAttachedShadowRoots
|
|
*
|
|
* Walks `root` and feeds every attached shadow root we encounter into
|
|
* the existing _sanitizeShadowDOM pipeline. The default node iterator
|
|
* does not descend into shadow trees, so nodes inside an attached
|
|
* shadow root would otherwise be skipped entirely.
|
|
*
|
|
* Two real input paths put attached shadow roots in front of us:
|
|
* 1. IN_PLACE on a DOM node that already has shadow roots attached.
|
|
* 2. DOM-node input where importNode(dirty, true) deep-clones the
|
|
* shadow root because it was created with `clonable: true`.
|
|
*
|
|
* This pass runs once, up front, so the main iteration loop (and the
|
|
* existing _sanitizeShadowDOM template-content recursion) stay
|
|
* untouched — string-input paths are not affected.
|
|
*
|
|
* @param root the subtree root to walk for attached shadow roots
|
|
*/
|
|
const _sanitizeAttachedShadowRoots = function _sanitizeAttachedShadowRoots(root) {
|
|
const stack = [{
|
|
node: root,
|
|
shadow: null
|
|
}];
|
|
while (stack.length > 0) {
|
|
const item = stack.pop();
|
|
if (item.shadow) {
|
|
_sanitizeShadowDOM2(item.shadow);
|
|
continue;
|
|
}
|
|
const node = item.node;
|
|
const isElement = (getNodeType ? getNodeType(node) : node.nodeType) === NODE_TYPE.element;
|
|
const childNodes = getChildNodes(node);
|
|
if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push({
|
|
node: childNodes[i],
|
|
shadow: null
|
|
});
|
|
if (isElement) {
|
|
const rootName = getNodeName ? getNodeName(node) : null;
|
|
if (typeof rootName === "string" && transformCaseFunc(rootName) === "template") {
|
|
const content = node.content;
|
|
if (_isDocumentFragment(content)) stack.push({
|
|
node: content,
|
|
shadow: null
|
|
});
|
|
}
|
|
}
|
|
if (isElement) {
|
|
const sr = getShadowRoot(node);
|
|
if (_isDocumentFragment(sr)) stack.push({
|
|
node: null,
|
|
shadow: sr
|
|
}, {
|
|
node: sr,
|
|
shadow: null
|
|
});
|
|
}
|
|
}
|
|
};
|
|
DOMPurify.sanitize = function(dirty) {
|
|
let cfg = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
|
|
let body = null;
|
|
let importedNode = null;
|
|
let currentNode = null;
|
|
let returnNode = null;
|
|
IS_EMPTY_INPUT = !dirty;
|
|
if (IS_EMPTY_INPUT) dirty = "<!-->";
|
|
if (typeof dirty !== "string" && !_isNode(dirty)) {
|
|
dirty = stringifyValue(dirty);
|
|
if (typeof dirty !== "string") throw typeErrorCreate("dirty is not a string, aborting");
|
|
}
|
|
if (!DOMPurify.isSupported) return dirty;
|
|
if (!SET_CONFIG) _parseConfig(cfg);
|
|
DOMPurify.removed = [];
|
|
const inPlace = IN_PLACE && typeof dirty !== "string" && _isNode(dirty);
|
|
if (inPlace) {
|
|
const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
|
|
if (typeof nn === "string") {
|
|
const tagName = transformCaseFunc(nn);
|
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) throw typeErrorCreate("root node is forbidden and cannot be sanitized in-place");
|
|
}
|
|
if (_isClobbered(dirty)) throw typeErrorCreate("root node is clobbered and cannot be sanitized in-place");
|
|
try {
|
|
_sanitizeAttachedShadowRoots(dirty);
|
|
} catch (error) {
|
|
_neutralizeRoot(dirty);
|
|
throw error;
|
|
}
|
|
} else if (_isNode(dirty)) {
|
|
body = _initDocument("<!---->");
|
|
importedNode = body.ownerDocument.importNode(dirty, true);
|
|
if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === "BODY") body = importedNode;
|
|
else if (importedNode.nodeName === "HTML") body = importedNode;
|
|
else body.appendChild(importedNode);
|
|
_sanitizeAttachedShadowRoots(importedNode);
|
|
} else {
|
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && dirty.indexOf("<") === -1) return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
|
|
body = _initDocument(dirty);
|
|
if (!body) return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : "";
|
|
}
|
|
if (body && FORCE_BODY) _forceRemove(body.firstChild);
|
|
const nodeIterator = _createNodeIterator(inPlace ? dirty : body);
|
|
try {
|
|
while (currentNode = nodeIterator.nextNode()) {
|
|
_sanitizeElements(currentNode);
|
|
_sanitizeAttributes(currentNode);
|
|
if (_isDocumentFragment(currentNode.content)) _sanitizeShadowDOM2(currentNode.content);
|
|
}
|
|
} catch (error) {
|
|
if (inPlace) _neutralizeRoot(dirty);
|
|
throw error;
|
|
}
|
|
if (inPlace) {
|
|
arrayForEach(DOMPurify.removed, (entry) => {
|
|
if (entry.element) _neutralizeSubtree(entry.element);
|
|
});
|
|
if (SAFE_FOR_TEMPLATES) _scrubTemplateExpressions2(dirty);
|
|
return dirty;
|
|
}
|
|
if (RETURN_DOM) {
|
|
if (SAFE_FOR_TEMPLATES) _scrubTemplateExpressions2(body);
|
|
if (RETURN_DOM_FRAGMENT) {
|
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
while (body.firstChild) returnNode.appendChild(body.firstChild);
|
|
} else returnNode = body;
|
|
if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) returnNode = importNode.call(originalDocument, returnNode, true);
|
|
return returnNode;
|
|
}
|
|
let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
|
|
if (WHOLE_DOCUMENT && ALLOWED_TAGS["!doctype"] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) serializedHTML = "<!DOCTYPE " + body.ownerDocument.doctype.name + ">\n" + serializedHTML;
|
|
if (SAFE_FOR_TEMPLATES) serializedHTML = _stripTemplateExpressions(serializedHTML);
|
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
|
|
};
|
|
DOMPurify.setConfig = function() {
|
|
_parseConfig(arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {});
|
|
SET_CONFIG = true;
|
|
};
|
|
DOMPurify.clearConfig = function() {
|
|
CONFIG = null;
|
|
SET_CONFIG = false;
|
|
trustedTypesPolicy = defaultTrustedTypesPolicy;
|
|
emptyHTML = "";
|
|
};
|
|
DOMPurify.isValidAttribute = function(tag, attr, value) {
|
|
if (!CONFIG) _parseConfig({});
|
|
return _isValidAttribute(transformCaseFunc(tag), transformCaseFunc(attr), value);
|
|
};
|
|
DOMPurify.addHook = function(entryPoint, hookFunction) {
|
|
if (typeof hookFunction !== "function") return;
|
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
};
|
|
DOMPurify.removeHook = function(entryPoint, hookFunction) {
|
|
if (hookFunction !== void 0) {
|
|
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
|
|
return index === -1 ? void 0 : arraySplice(hooks[entryPoint], index, 1)[0];
|
|
}
|
|
return arrayPop(hooks[entryPoint]);
|
|
};
|
|
DOMPurify.removeHooks = function(entryPoint) {
|
|
hooks[entryPoint] = [];
|
|
};
|
|
DOMPurify.removeAllHooks = function() {
|
|
hooks = _createHooksMap();
|
|
};
|
|
return DOMPurify;
|
|
}
|
|
var purify = createDOMPurify();
|
|
//#endregion
|
|
export { purify as default };
|
|
|
|
//# sourceMappingURL=purify.es-erUxcz-o.js.map
|