From ece5ce1cdf6bad1058e212bf3a7480c88294e028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Diot?= Date: Thu, 28 Sep 2023 11:30:45 +0100 Subject: [PATCH] Add HTML sanitization when injecting code in pages in the UI --- .github/codeql.yml | 5 +- .prettierignore | 1 + src/ui/static/js/global.js | 13 +- src/ui/static/js/plugins.js | 46 +- src/ui/static/js/utils/form.js | 15 +- src/ui/static/js/utils/purify/purify.min.js | 3 + .../static/js/utils/purify/purify.min.js.map | 1 + src/ui/static/js/utils/purify/src/attrs.js | 362 ++++ src/ui/static/js/utils/purify/src/purify.js | 1637 +++++++++++++++++ src/ui/static/js/utils/purify/src/regexp.js | 16 + src/ui/static/js/utils/purify/src/tags.js | 280 +++ src/ui/static/js/utils/purify/src/utils.js | 193 ++ src/ui/templates/head.html | 5 +- 13 files changed, 2538 insertions(+), 39 deletions(-) create mode 100644 src/ui/static/js/utils/purify/purify.min.js create mode 100644 src/ui/static/js/utils/purify/purify.min.js.map create mode 100644 src/ui/static/js/utils/purify/src/attrs.js create mode 100644 src/ui/static/js/utils/purify/src/purify.js create mode 100644 src/ui/static/js/utils/purify/src/regexp.js create mode 100644 src/ui/static/js/utils/purify/src/tags.js create mode 100644 src/ui/static/js/utils/purify/src/utils.js diff --git a/.github/codeql.yml b/.github/codeql.yml index 8e9947f9..b21ddec0 100644 --- a/.github/codeql.yml +++ b/.github/codeql.yml @@ -6,7 +6,8 @@ paths: - src/ui - src/common paths-ignore: - - src/ui/static/tsparticles.bundle.min.js - - src/ui/static/js/utils/flatpickr.js + - src/ui/static/js/tsparticles.bundle.min.js - src/ui/static/js/editor + - src/ui/static/js/utils/flatpickr.js + - src/ui/static/js/utils/purify - src/common/core/modsecurity/files diff --git a/.prettierignore b/.prettierignore index d1cb8cbd..743f971f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -13,6 +13,7 @@ SECURITY.md tsparticles.bundle.min.js flatpickr.* src/ui/static/js/editor/* +src/ui/static/js/utils/purify/* src/ui/templates/* datepicker-foundation.css examples/* diff --git a/src/ui/static/js/global.js b/src/ui/static/js/global.js index bed2646b..1d8effff 100644 --- a/src/ui/static/js/global.js +++ b/src/ui/static/js/global.js @@ -72,12 +72,13 @@ class News { excerpt, tags, date, - lastUpdate, + lastUpdate ); + let cleanHTML = DOMPurify.sanitize(cardHTML); //add to DOM document .querySelector("[data-news-container]") - .insertAdjacentHTML("afterbegin", cardHTML); + .insertAdjacentHTML("afterbegin", cleanHTML); }); } @@ -190,7 +191,7 @@ class darkMode { }; const send = await fetch( `${location.href.split("/").slice(0, -1).join("/")}/darkmode`, - data, + data ); } } @@ -230,7 +231,7 @@ class FlashMsg { flashEl.remove(); //update count this.flashCount.textContent = document.querySelectorAll( - "[data-flash-message]", + "[data-flash-message]" ).length; } } catch (err) {} @@ -299,7 +300,7 @@ const setMenu = new Menu(); const setNewsSidebar = new Sidebar( "[data-sidebar-info]", "[data-sidebar-info-open]", - "[data-sidebar-info-close]", + "[data-sidebar-info-close]" ); const setCheckbox = new Checkbox(); @@ -310,7 +311,7 @@ const setDisabledPop = new DisabledPop(); const setFlashSidebar = new Sidebar( "[data-flash-sidebar]", "[data-flash-sidebar-open]", - "[data-flash-sidebar-close]", + "[data-flash-sidebar-close]" ); const setNews = new News(); const setDarkM = new darkMode(); diff --git a/src/ui/static/js/plugins.js b/src/ui/static/js/plugins.js index b498223b..3cbaa44a 100644 --- a/src/ui/static/js/plugins.js +++ b/src/ui/static/js/plugins.js @@ -36,7 +36,7 @@ class Dropdown { const btn = e.target.closest("button"); const btnValue = btn.getAttribute("value"); const btnSetting = btn.getAttribute( - `data-${this.prefix}-setting-select-dropdown-btn`, + `data-${this.prefix}-setting-select-dropdown-btn` ); //stop if same value to avoid new fetching const isSameVal = this.isSameValue(btnSetting, btnValue); @@ -57,7 +57,7 @@ class Dropdown { closeAllDrop() { const drops = document.querySelectorAll( - `[data-${this.prefix}-setting-select-dropdown]`, + `[data-${this.prefix}-setting-select-dropdown]` ); drops.forEach((drop) => { drop.classList.add("hidden"); @@ -65,8 +65,8 @@ class Dropdown { document .querySelector( `svg[data-${this.prefix}-setting-select="${drop.getAttribute( - `data-${this.prefix}-setting-select-dropdown`, - )}"]`, + `data-${this.prefix}-setting-select-dropdown` + )}"]` ) .classList.remove("rotate-180"); }); @@ -74,7 +74,7 @@ class Dropdown { isSameValue(btnSetting, value) { const selectCustom = document.querySelector( - `[data-${this.prefix}-setting-select-text="${btnSetting}"]`, + `[data-${this.prefix}-setting-select-text="${btnSetting}"]` ); const currVal = selectCustom.textContent; return currVal === value ? true : false; @@ -82,30 +82,30 @@ class Dropdown { setSelectNewValue(btnSetting, value) { const selectCustom = document.querySelector( - `[data-${this.prefix}-setting-select="${btnSetting}"]`, + `[data-${this.prefix}-setting-select="${btnSetting}"]` ); selectCustom.querySelector( - `[data-${this.prefix}-setting-select-text]`, + `[data-${this.prefix}-setting-select-text]` ).textContent = value; } hideDropdown(btnSetting) { //hide dropdown const dropdownEl = document.querySelector( - `[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`, + `[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]` ); dropdownEl.classList.add("hidden"); dropdownEl.classList.remove("flex"); //svg effect const dropdownChevron = document.querySelector( - `svg[data-${this.prefix}-setting-select="${btnSetting}"]`, + `svg[data-${this.prefix}-setting-select="${btnSetting}"]` ); dropdownChevron.classList.remove("rotate-180"); } changeDropBtnStyle(btnSetting, selectedBtn) { const dropdownEl = document.querySelector( - `[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`, + `[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]` ); //reset dropdown btns const btnEls = dropdownEl.querySelectorAll("button"); @@ -116,7 +116,7 @@ class Dropdown { "bg-primary", "bg-primary", "text-gray-300", - "text-gray-300", + "text-gray-300" ); btn.classList.add("bg-white", "dark:bg-slate-700", "text-gray-700"); }); @@ -124,7 +124,7 @@ class Dropdown { selectedBtn.classList.remove( "bg-white", "dark:bg-slate-700", - "text-gray-700", + "text-gray-700" ); selectedBtn.classList.add("dark:bg-primary", "bg-primary", "text-gray-300"); } @@ -135,10 +135,10 @@ class Dropdown { .getAttribute(`data-${this.prefix}-setting-select`); //toggle dropdown const dropdownEl = document.querySelector( - `[data-${this.prefix}-setting-select-dropdown="${attribut}"]`, + `[data-${this.prefix}-setting-select-dropdown="${attribut}"]` ); const dropdownChevron = document.querySelector( - `svg[data-${this.prefix}-setting-select="${attribut}"]`, + `svg[data-${this.prefix}-setting-select="${attribut}"]` ); dropdownEl.classList.toggle("hidden"); dropdownEl.classList.toggle("flex"); @@ -300,7 +300,7 @@ class Upload { this.dropZoneElement.classList.remove( "border-solid", "bg-gray-100", - "dark:bg-slate-700/50", + "dark:bg-slate-700/50" ); this.dropZoneElement.classList.add("border-dashed"); } @@ -309,7 +309,7 @@ class Upload { this.dropZoneElement.classList.add( "border-solid", "bg-gray-100", - "dark:bg-slate-700/50", + "dark:bg-slate-700/50" ); this.dropZoneElement.classList.remove("border-dashed"); } @@ -345,13 +345,13 @@ class Upload { if (xhr.status == 201) { this.uploadedArea.insertAdjacentHTML( "afterbegin", - this.fileSuccess(name, fileSize), + this.fileSuccess(name, fileSize) ); this.allowReload(); } else { this.uploadedArea.insertAdjacentHTML( "afterbegin", - this.fileFail(name, fileSize), + this.fileFail(name, fileSize) ); } } @@ -408,7 +408,8 @@ class Upload { `; - return str; + let cleanHTML = DOMPurify.sanitize(str); + return cleanHTML; } fileFail(name, fileSize) { @@ -435,7 +436,8 @@ class Upload { `; - return str; + let cleanHTML = DOMPurify.sanitize(str); + return cleanHTML; } } @@ -448,10 +450,10 @@ class Modal { this.modalExtInp = this.modal.querySelector("input#external"); this.modalTitle = this.modal.querySelector( - `[data-${this.prefix}-modal-title]`, + `[data-${this.prefix}-modal-title]` ); this.modalTxt = this.modal.querySelector( - `[data-${this.prefix}-modal-text]`, + `[data-${this.prefix}-modal-text]` ); this.init(); } diff --git a/src/ui/static/js/utils/form.js b/src/ui/static/js/utils/form.js index b4034634..1c50da35 100644 --- a/src/ui/static/js/utils/form.js +++ b/src/ui/static/js/utils/form.js @@ -52,18 +52,18 @@ class Select { try { if (!e.target.closest("button")) { const selectEls = document.querySelectorAll( - "div[data-setting-select-dropdown]", + "div[data-setting-select-dropdown]" ); selectEls.forEach((select) => { select.classList.add("hidden"); select.classList.remove("flex"); }); const btnEls = document.querySelectorAll( - "button[data-setting-select]", + "button[data-setting-select]" ); btnEls.forEach((btn) => { const dropdownChevron = btn.querySelector( - `svg[data-setting-select]`, + `svg[data-setting-select]` ); dropdownChevron.classList.remove("rotate-180"); }); @@ -87,7 +87,7 @@ class Select { .hasAttribute(`data-setting-select-dropdown-btn`) ) { const btn = e.target.closest( - `button[data-setting-select-dropdown-btn]`, + `button[data-setting-select-dropdown-btn]` ); const btnValue = btn.getAttribute("value"); @@ -116,7 +116,7 @@ class Select { //close dropdown const dropdownChevron = selectCustom.querySelector( - `svg[data-setting-select]`, + `svg[data-setting-select]` ); dropdownChevron.classList.remove("rotate-180"); @@ -139,7 +139,7 @@ class Select { }); //select new one const newOption = selectEl.querySelector( - `option[value="${selectedValue}"]`, + `option[value="${selectedValue}"]` ); newOption.selected = true; newOption.setAttribute("selected", ""); @@ -255,7 +255,8 @@ class DisabledPop { } bg-blue-500 absolute right-2 rounded-lg px-2 py-1 z-20 dark:brightness-90">

disabled by ${method}

`; - el.insertAdjacentHTML("beforebegin", popupHTML); + let cleanHTML = DOMPurify.sanitize(popupHTML); + el.insertAdjacentHTML("beforebegin", cleanHTML); } } diff --git a/src/ui/static/js/utils/purify/purify.min.js b/src/ui/static/js/utils/purify/purify.min.js new file mode 100644 index 00000000..7a4da768 --- /dev/null +++ b/src/ui/static/js/utils/purify/purify.min.js @@ -0,0 +1,3 @@ +/*! @license DOMPurify 3.0.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.0.6/LICENSE */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,(function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:o,getOwnPropertyDescriptor:r}=Object;let{freeze:i,seal:a,create:l}=Object,{apply:c,construct:s}="undefined"!=typeof Reflect&&Reflect;i||(i=function(e){return e}),a||(a=function(e){return e}),c||(c=function(e,t,n){return e.apply(t,n)}),s||(s=function(e,t){return new e(...t)});const u=N(Array.prototype.forEach),m=N(Array.prototype.pop),f=N(Array.prototype.push),p=N(String.prototype.toLowerCase),d=N(String.prototype.toString),h=N(String.prototype.match),g=N(String.prototype.replace),T=N(String.prototype.indexOf),y=N(String.prototype.trim),E=N(RegExp.prototype.test),A=(_=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n1?n-1:0),r=1;r2&&void 0!==arguments[2]?arguments[2]:p;t&&t(e,null);let i=o.length;for(;i--;){let t=o[i];if("string"==typeof t){const e=r(t);e!==t&&(n(o)||(o[i]=e),t=e)}e[t]=!0}return e}function S(t){const n=l(null);for(const[o,i]of e(t))void 0!==r(t,o)&&(n[o]=i);return n}function R(e,t){for(;null!==e;){const n=r(e,t);if(n){if(n.get)return N(n.get);if("function"==typeof n.value)return N(n.value)}e=o(e)}return function(e){return console.warn("fallback value for",e),null}}const w=i(["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"]),D=i(["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"]),L=i(["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"]),v=i(["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"]),x=i(["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"]),k=i(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),C=i(["#text"]),O=i(["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","xmlns","slot"]),I=i(["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"]),M=i(["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"]),U=i(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),P=a(/\{\{[\w\W]*|[\w\W]*\}\}/gm),F=a(/<%[\w\W]*|[\w\W]*%>/gm),H=a(/\${[\w\W]*}/gm),z=a(/^data-[\-\w.\u00B7-\uFFFF]/),B=a(/^aria-[\-\w]+$/),W=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),G=a(/^(?:\w+script|data):/i),Y=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),j=a(/^html$/i);var q=Object.freeze({__proto__:null,MUSTACHE_EXPR:P,ERB_EXPR:F,TMPLIT_EXPR:H,DATA_ATTR:z,ARIA_ATTR:B,IS_ALLOWED_URI:W,IS_SCRIPT_OR_DATA:G,ATTR_WHITESPACE:Y,DOCTYPE_NAME:j});const X=function(){return"undefined"==typeof window?null:window},K=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const o="data-tt-policy-suffix";t&&t.hasAttribute(o)&&(n=t.getAttribute(o));const r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}};var V=function t(){let n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:X();const o=e=>t(e);if(o.version="3.0.6",o.removed=[],!n||!n.document||9!==n.document.nodeType)return o.isSupported=!1,o;let{document:r}=n;const a=r,c=a.currentScript,{DocumentFragment:s,HTMLTemplateElement:_,Node:N,Element:P,NodeFilter:F,NamedNodeMap:H=n.NamedNodeMap||n.MozNamedAttrMap,HTMLFormElement:z,DOMParser:B,trustedTypes:G}=n,Y=P.prototype,V=R(Y,"cloneNode"),$=R(Y,"nextSibling"),Z=R(Y,"childNodes"),J=R(Y,"parentNode");if("function"==typeof _){const e=r.createElement("template");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let Q,ee="";const{implementation:te,createNodeIterator:ne,createDocumentFragment:oe,getElementsByTagName:re}=r,{importNode:ie}=a;let ae={};o.isSupported="function"==typeof e&&"function"==typeof J&&te&&void 0!==te.createHTMLDocument;const{MUSTACHE_EXPR:le,ERB_EXPR:ce,TMPLIT_EXPR:se,DATA_ATTR:ue,ARIA_ATTR:me,IS_SCRIPT_OR_DATA:fe,ATTR_WHITESPACE:pe}=q;let{IS_ALLOWED_URI:de}=q,he=null;const ge=b({},[...w,...D,...L,...x,...C]);let Te=null;const ye=b({},[...O,...I,...M,...U]);let Ee=Object.seal(l(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Ae=null,_e=null,Ne=!0,be=!0,Se=!1,Re=!0,we=!1,De=!1,Le=!1,ve=!1,xe=!1,ke=!1,Ce=!1,Oe=!0,Ie=!1;const Me="user-content-";let Ue=!0,Pe=!1,Fe={},He=null;const ze=b({},["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"]);let Be=null;const We=b({},["audio","video","img","source","image","track"]);let Ge=null;const Ye=b({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),je="http://www.w3.org/1998/Math/MathML",qe="http://www.w3.org/2000/svg",Xe="http://www.w3.org/1999/xhtml";let Ke=Xe,Ve=!1,$e=null;const Ze=b({},[je,qe,Xe],d);let Je=null;const Qe=["application/xhtml+xml","text/html"],et="text/html";let tt=null,nt=null;const ot=r.createElement("form"),rt=function(e){return e instanceof RegExp||e instanceof Function},it=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!nt||nt!==e){if(e&&"object"==typeof e||(e={}),e=S(e),Je=Je=-1===Qe.indexOf(e.PARSER_MEDIA_TYPE)?et:e.PARSER_MEDIA_TYPE,tt="application/xhtml+xml"===Je?d:p,he="ALLOWED_TAGS"in e?b({},e.ALLOWED_TAGS,tt):ge,Te="ALLOWED_ATTR"in e?b({},e.ALLOWED_ATTR,tt):ye,$e="ALLOWED_NAMESPACES"in e?b({},e.ALLOWED_NAMESPACES,d):Ze,Ge="ADD_URI_SAFE_ATTR"in e?b(S(Ye),e.ADD_URI_SAFE_ATTR,tt):Ye,Be="ADD_DATA_URI_TAGS"in e?b(S(We),e.ADD_DATA_URI_TAGS,tt):We,He="FORBID_CONTENTS"in e?b({},e.FORBID_CONTENTS,tt):ze,Ae="FORBID_TAGS"in e?b({},e.FORBID_TAGS,tt):{},_e="FORBID_ATTR"in e?b({},e.FORBID_ATTR,tt):{},Fe="USE_PROFILES"in e&&e.USE_PROFILES,Ne=!1!==e.ALLOW_ARIA_ATTR,be=!1!==e.ALLOW_DATA_ATTR,Se=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Re=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,we=e.SAFE_FOR_TEMPLATES||!1,De=e.WHOLE_DOCUMENT||!1,xe=e.RETURN_DOM||!1,ke=e.RETURN_DOM_FRAGMENT||!1,Ce=e.RETURN_TRUSTED_TYPE||!1,ve=e.FORCE_BODY||!1,Oe=!1!==e.SANITIZE_DOM,Ie=e.SANITIZE_NAMED_PROPS||!1,Ue=!1!==e.KEEP_CONTENT,Pe=e.IN_PLACE||!1,de=e.ALLOWED_URI_REGEXP||W,Ke=e.NAMESPACE||Xe,Ee=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&rt(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Ee.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&rt(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Ee.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Ee.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),we&&(be=!1),ke&&(xe=!0),Fe&&(he=b({},[...C]),Te=[],!0===Fe.html&&(b(he,w),b(Te,O)),!0===Fe.svg&&(b(he,D),b(Te,I),b(Te,U)),!0===Fe.svgFilters&&(b(he,L),b(Te,I),b(Te,U)),!0===Fe.mathMl&&(b(he,x),b(Te,M),b(Te,U))),e.ADD_TAGS&&(he===ge&&(he=S(he)),b(he,e.ADD_TAGS,tt)),e.ADD_ATTR&&(Te===ye&&(Te=S(Te)),b(Te,e.ADD_ATTR,tt)),e.ADD_URI_SAFE_ATTR&&b(Ge,e.ADD_URI_SAFE_ATTR,tt),e.FORBID_CONTENTS&&(He===ze&&(He=S(He)),b(He,e.FORBID_CONTENTS,tt)),Ue&&(he["#text"]=!0),De&&b(he,["html","head","body"]),he.table&&(b(he,["tbody"]),delete Ae.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');Q=e.TRUSTED_TYPES_POLICY,ee=Q.createHTML("")}else void 0===Q&&(Q=K(G,c)),null!==Q&&"string"==typeof ee&&(ee=Q.createHTML(""));i&&i(e),nt=e}},at=b({},["mi","mo","mn","ms","mtext"]),lt=b({},["foreignobject","desc","title","annotation-xml"]),ct=b({},["title","style","font","a","script"]),st=b({},D);b(st,L),b(st,v);const ut=b({},x);b(ut,k);const mt=function(e){let t=J(e);t&&t.tagName||(t={namespaceURI:Ke,tagName:"template"});const n=p(e.tagName),o=p(t.tagName);return!!$e[e.namespaceURI]&&(e.namespaceURI===qe?t.namespaceURI===Xe?"svg"===n:t.namespaceURI===je?"svg"===n&&("annotation-xml"===o||at[o]):Boolean(st[n]):e.namespaceURI===je?t.namespaceURI===Xe?"math"===n:t.namespaceURI===qe?"math"===n&<[o]:Boolean(ut[n]):e.namespaceURI===Xe?!(t.namespaceURI===qe&&!lt[o])&&(!(t.namespaceURI===je&&!at[o])&&(!ut[n]&&(ct[n]||!st[n]))):!("application/xhtml+xml"!==Je||!$e[e.namespaceURI]))},ft=function(e){f(o.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.remove()}},pt=function(e,t){try{f(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){f(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Te[e])if(xe||ke)try{ft(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},dt=function(e){let t=null,n=null;if(ve)e=""+e;else{const t=h(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===Je&&Ke===Xe&&(e=''+e+"");const o=Q?Q.createHTML(e):e;if(Ke===Xe)try{t=(new B).parseFromString(o,Je)}catch(e){}if(!t||!t.documentElement){t=te.createDocument(Ke,"template",null);try{t.documentElement.innerHTML=Ve?ee:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),Ke===Xe?re.call(t,De?"html":"body")[0]:De?t.documentElement:i},ht=function(e){return ne.call(e.ownerDocument||e,e,F.SHOW_ELEMENT|F.SHOW_COMMENT|F.SHOW_TEXT,null)},gt=function(e){return e instanceof z&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof H)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},Tt=function(e){return"function"==typeof N&&e instanceof N},yt=function(e,t,n){ae[e]&&u(ae[e],(e=>{e.call(o,t,n,nt)}))},Et=function(e){let t=null;if(yt("beforeSanitizeElements",e,null),gt(e))return ft(e),!0;const n=tt(e.nodeName);if(yt("uponSanitizeElement",e,{tagName:n,allowedTags:he}),e.hasChildNodes()&&!Tt(e.firstElementChild)&&E(/<[/\w]/g,e.innerHTML)&&E(/<[/\w]/g,e.textContent))return ft(e),!0;if(!he[n]||Ae[n]){if(!Ae[n]&&_t(n)){if(Ee.tagNameCheck instanceof RegExp&&E(Ee.tagNameCheck,n))return!1;if(Ee.tagNameCheck instanceof Function&&Ee.tagNameCheck(n))return!1}if(Ue&&!He[n]){const t=J(e)||e.parentNode,n=Z(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o)t.insertBefore(V(n[o],!0),$(e))}}return ft(e),!0}return e instanceof P&&!mt(e)?(ft(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!E(/<\/no(script|embed|frames)/i,e.innerHTML)?(we&&3===e.nodeType&&(t=e.textContent,u([le,ce,se],(e=>{t=g(t,e," ")})),e.textContent!==t&&(f(o.removed,{element:e.cloneNode()}),e.textContent=t)),yt("afterSanitizeElements",e,null),!1):(ft(e),!0)},At=function(e,t,n){if(Oe&&("id"===t||"name"===t)&&(n in r||n in ot))return!1;if(be&&!_e[t]&&E(ue,t));else if(Ne&&E(me,t));else if(!Te[t]||_e[t]){if(!(_t(e)&&(Ee.tagNameCheck instanceof RegExp&&E(Ee.tagNameCheck,e)||Ee.tagNameCheck instanceof Function&&Ee.tagNameCheck(e))&&(Ee.attributeNameCheck instanceof RegExp&&E(Ee.attributeNameCheck,t)||Ee.attributeNameCheck instanceof Function&&Ee.attributeNameCheck(t))||"is"===t&&Ee.allowCustomizedBuiltInElements&&(Ee.tagNameCheck instanceof RegExp&&E(Ee.tagNameCheck,n)||Ee.tagNameCheck instanceof Function&&Ee.tagNameCheck(n))))return!1}else if(Ge[t]);else if(E(de,g(n,pe,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==T(n,"data:")||!Be[e]){if(Se&&!E(fe,g(n,pe,"")));else if(n)return!1}else;return!0},_t=function(e){return e.indexOf("-")>0},Nt=function(e){yt("beforeSanitizeAttributes",e,null);const{attributes:t}=e;if(!t)return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Te};let r=t.length;for(;r--;){const i=t[r],{name:a,namespaceURI:l,value:c}=i,s=tt(a);let f="value"===a?c:y(c);if(n.attrName=s,n.attrValue=f,n.keepAttr=!0,n.forceKeepAttr=void 0,yt("uponSanitizeAttribute",e,n),f=n.attrValue,n.forceKeepAttr)continue;if(pt(a,e),!n.keepAttr)continue;if(!Re&&E(/\/>/i,f)){pt(a,e);continue}we&&u([le,ce,se],(e=>{f=g(f,e," ")}));const p=tt(e.nodeName);if(At(p,s,f)){if(!Ie||"id"!==s&&"name"!==s||(pt(a,e),f=Me+f),Q&&"object"==typeof G&&"function"==typeof G.getAttributeType)if(l);else switch(G.getAttributeType(p,s)){case"TrustedHTML":f=Q.createHTML(f);break;case"TrustedScriptURL":f=Q.createScriptURL(f)}try{l?e.setAttributeNS(l,a,f):e.setAttribute(a,f),m(o.removed)}catch(e){}}}yt("afterSanitizeAttributes",e,null)},bt=function e(t){let n=null;const o=ht(t);for(yt("beforeSanitizeShadowDOM",t,null);n=o.nextNode();)yt("uponSanitizeShadowNode",n,null),Et(n)||(n.content instanceof s&&e(n.content),Nt(n));yt("afterSanitizeShadowDOM",t,null)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,i=null,l=null;if(Ve=!e,Ve&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Tt(e)){if("function"!=typeof e.toString)throw A("toString is not a function");if("string"!=typeof(e=e.toString()))throw A("dirty is not a string, aborting")}if(!o.isSupported)return e;if(Le||it(t),o.removed=[],"string"==typeof e&&(Pe=!1),Pe){if(e.nodeName){const t=tt(e.nodeName);if(!he[t]||Ae[t])throw A("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof N)n=dt("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),1===r.nodeType&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!xe&&!we&&!De&&-1===e.indexOf("<"))return Q&&Ce?Q.createHTML(e):e;if(n=dt(e),!n)return xe?null:Ce?ee:""}n&&ve&&ft(n.firstChild);const c=ht(Pe?e:n);for(;i=c.nextNode();)Et(i)||(i.content instanceof s&&bt(i.content),Nt(i));if(Pe)return e;if(xe){if(ke)for(l=oe.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(Te.shadowroot||Te.shadowrootmode)&&(l=ie.call(a,l,!0)),l}let m=De?n.outerHTML:n.innerHTML;return De&&he["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&E(j,n.ownerDocument.doctype.name)&&(m="\n"+m),we&&u([le,ce,se],(e=>{m=g(m,e," ")})),Q&&Ce?Q.createHTML(m):m},o.setConfig=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};it(e),Le=!0},o.clearConfig=function(){nt=null,Le=!1},o.isValidAttribute=function(e,t,n){nt||it({});const o=tt(e),r=tt(t);return At(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&(ae[e]=ae[e]||[],f(ae[e],t))},o.removeHook=function(e){if(ae[e])return m(ae[e])},o.removeHooks=function(e){ae[e]&&(ae[e]=[])},o.removeAllHooks=function(){ae={}},o}();return V})); +//# sourceMappingURL=purify.min.js.map diff --git a/src/ui/static/js/utils/purify/purify.min.js.map b/src/ui/static/js/utils/purify/purify.min.js.map new file mode 100644 index 00000000..c0e565f5 --- /dev/null +++ b/src/ui/static/js/utils/purify/purify.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"purify.min.js","sources":["src/utils.js","src/tags.js","src/attrs.js","src/regexp.js","src/purify.js"],"sourcesContent":["const {\n entries,\n setPrototypeOf,\n isFrozen,\n getPrototypeOf,\n getOwnPropertyDescriptor,\n} = Object;\n\nlet { freeze, seal, create } = Object; // eslint-disable-line import/no-mutable-exports\nlet { apply, construct } = typeof Reflect !== 'undefined' && Reflect;\n\nif (!freeze) {\n freeze = function (x) {\n return x;\n };\n}\n\nif (!seal) {\n seal = function (x) {\n return x;\n };\n}\n\nif (!apply) {\n apply = function (fun, thisValue, args) {\n return fun.apply(thisValue, args);\n };\n}\n\nif (!construct) {\n construct = function (Func, args) {\n return new Func(...args);\n };\n}\n\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayIndexOf = unapply(Array.prototype.indexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySlice = unapply(Array.prototype.slice);\n\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\n\nconst regExpTest = unapply(RegExp.prototype.test);\n\nconst typeErrorCreate = unconstruct(TypeError);\n\n/**\n * Creates a new function that calls the given function with a specified thisArg and arguments.\n *\n * @param {Function} func - The function to be wrapped and called.\n * @returns {Function} A new function that calls the given function with a specified thisArg and arguments.\n */\nfunction unapply(func) {\n return (thisArg, ...args) => apply(func, thisArg, args);\n}\n\n/**\n * Creates a new function that constructs an instance of the given constructor function with the provided arguments.\n *\n * @param {Function} func - The constructor function to be wrapped and called.\n * @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments.\n */\nfunction unconstruct(func) {\n return (...args) => construct(func, args);\n}\n\n/**\n * Add properties to a lookup table\n *\n * @param {Object} set - The set to which elements will be added.\n * @param {Array} array - The array containing elements to be added to the set.\n * @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set.\n * @returns {Object} The modified set with added elements.\n */\nfunction addToSet(set, array, transformCaseFunc = stringToLowerCase) {\n if (setPrototypeOf) {\n // Make 'in' and truthy checks like Boolean(set.constructor)\n // independent of any properties defined on Object.prototype.\n // Prevent prototype setters from intercepting set as a this value.\n setPrototypeOf(set, null);\n }\n\n let l = array.length;\n while (l--) {\n let element = array[l];\n if (typeof element === 'string') {\n const lcElement = transformCaseFunc(element);\n if (lcElement !== element) {\n // Config presets (e.g. tags.js, attrs.js) are immutable.\n if (!isFrozen(array)) {\n array[l] = lcElement;\n }\n\n element = lcElement;\n }\n }\n\n set[element] = true;\n }\n\n return set;\n}\n\n/**\n * Shallow clone an object\n *\n * @param {Object} object - The object to be cloned.\n * @returns {Object} A new object that copies the original.\n */\nexport function clone(object) {\n const newObject = create(null);\n\n for (const [property, value] of entries(object)) {\n if (getOwnPropertyDescriptor(object, property) !== undefined) {\n newObject[property] = value;\n }\n }\n\n return newObject;\n}\n\n/**\n * This method automatically checks if the prop is function or getter and behaves accordingly.\n *\n * @param {Object} object - The object to look up the getter function in its prototype chain.\n * @param {String} prop - The property name for which to find the getter function.\n * @returns {Function} The getter function found in the prototype chain or a fallback function.\n */\nfunction lookupGetter(object, prop) {\n while (object !== null) {\n const desc = getOwnPropertyDescriptor(object, prop);\n\n if (desc) {\n if (desc.get) {\n return unapply(desc.get);\n }\n\n if (typeof desc.value === 'function') {\n return unapply(desc.value);\n }\n }\n\n object = getPrototypeOf(object);\n }\n\n function fallbackValue(element) {\n console.warn('fallback value for', element);\n return null;\n }\n\n return fallbackValue;\n}\n\nexport {\n // Array\n arrayForEach,\n arrayIndexOf,\n arrayPop,\n arrayPush,\n arraySlice,\n // Object\n entries,\n freeze,\n getPrototypeOf,\n getOwnPropertyDescriptor,\n isFrozen,\n setPrototypeOf,\n seal,\n create,\n // RegExp\n regExpTest,\n // String\n stringIndexOf,\n stringMatch,\n stringReplace,\n stringToLowerCase,\n stringToString,\n stringTrim,\n // Errors\n typeErrorCreate,\n // Other\n lookupGetter,\n addToSet,\n // Reflect\n unapply,\n unconstruct,\n};\n","import { freeze } from './utils.js';\n\nexport const html = freeze([\n 'a',\n 'abbr',\n 'acronym',\n 'address',\n 'area',\n 'article',\n 'aside',\n 'audio',\n 'b',\n 'bdi',\n 'bdo',\n 'big',\n 'blink',\n 'blockquote',\n 'body',\n 'br',\n 'button',\n 'canvas',\n 'caption',\n 'center',\n 'cite',\n 'code',\n 'col',\n 'colgroup',\n 'content',\n 'data',\n 'datalist',\n 'dd',\n 'decorator',\n 'del',\n 'details',\n 'dfn',\n 'dialog',\n 'dir',\n 'div',\n 'dl',\n 'dt',\n 'element',\n 'em',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'font',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'head',\n 'header',\n 'hgroup',\n 'hr',\n 'html',\n 'i',\n 'img',\n 'input',\n 'ins',\n 'kbd',\n 'label',\n 'legend',\n 'li',\n 'main',\n 'map',\n 'mark',\n 'marquee',\n 'menu',\n 'menuitem',\n 'meter',\n 'nav',\n 'nobr',\n 'ol',\n 'optgroup',\n 'option',\n 'output',\n 'p',\n 'picture',\n 'pre',\n 'progress',\n 'q',\n 'rp',\n 'rt',\n 'ruby',\n 's',\n 'samp',\n 'section',\n 'select',\n 'shadow',\n 'small',\n 'source',\n 'spacer',\n 'span',\n 'strike',\n 'strong',\n 'style',\n 'sub',\n 'summary',\n 'sup',\n 'table',\n 'tbody',\n 'td',\n 'template',\n 'textarea',\n 'tfoot',\n 'th',\n 'thead',\n 'time',\n 'tr',\n 'track',\n 'tt',\n 'u',\n 'ul',\n 'var',\n 'video',\n 'wbr',\n]);\n\n// SVG\nexport const svg = freeze([\n 'svg',\n 'a',\n 'altglyph',\n 'altglyphdef',\n 'altglyphitem',\n 'animatecolor',\n 'animatemotion',\n 'animatetransform',\n 'circle',\n 'clippath',\n 'defs',\n 'desc',\n 'ellipse',\n 'filter',\n 'font',\n 'g',\n 'glyph',\n 'glyphref',\n 'hkern',\n 'image',\n 'line',\n 'lineargradient',\n 'marker',\n 'mask',\n 'metadata',\n 'mpath',\n 'path',\n 'pattern',\n 'polygon',\n 'polyline',\n 'radialgradient',\n 'rect',\n 'stop',\n 'style',\n 'switch',\n 'symbol',\n 'text',\n 'textpath',\n 'title',\n 'tref',\n 'tspan',\n 'view',\n 'vkern',\n]);\n\nexport const svgFilters = freeze([\n 'feBlend',\n 'feColorMatrix',\n 'feComponentTransfer',\n 'feComposite',\n 'feConvolveMatrix',\n 'feDiffuseLighting',\n 'feDisplacementMap',\n 'feDistantLight',\n 'feDropShadow',\n 'feFlood',\n 'feFuncA',\n 'feFuncB',\n 'feFuncG',\n 'feFuncR',\n 'feGaussianBlur',\n 'feImage',\n 'feMerge',\n 'feMergeNode',\n 'feMorphology',\n 'feOffset',\n 'fePointLight',\n 'feSpecularLighting',\n 'feSpotLight',\n 'feTile',\n 'feTurbulence',\n]);\n\n// List of SVG elements that are disallowed by default.\n// We still need to know them so that we can do namespace\n// checks properly in case one wants to add them to\n// allow-list.\nexport const svgDisallowed = freeze([\n 'animate',\n 'color-profile',\n 'cursor',\n 'discard',\n 'font-face',\n 'font-face-format',\n 'font-face-name',\n 'font-face-src',\n 'font-face-uri',\n 'foreignobject',\n 'hatch',\n 'hatchpath',\n 'mesh',\n 'meshgradient',\n 'meshpatch',\n 'meshrow',\n 'missing-glyph',\n 'script',\n 'set',\n 'solidcolor',\n 'unknown',\n 'use',\n]);\n\nexport const mathMl = freeze([\n 'math',\n 'menclose',\n 'merror',\n 'mfenced',\n 'mfrac',\n 'mglyph',\n 'mi',\n 'mlabeledtr',\n 'mmultiscripts',\n 'mn',\n 'mo',\n 'mover',\n 'mpadded',\n 'mphantom',\n 'mroot',\n 'mrow',\n 'ms',\n 'mspace',\n 'msqrt',\n 'mstyle',\n 'msub',\n 'msup',\n 'msubsup',\n 'mtable',\n 'mtd',\n 'mtext',\n 'mtr',\n 'munder',\n 'munderover',\n 'mprescripts',\n]);\n\n// Similarly to SVG, we want to know all MathML elements,\n// even those that we disallow by default.\nexport const mathMlDisallowed = freeze([\n 'maction',\n 'maligngroup',\n 'malignmark',\n 'mlongdiv',\n 'mscarries',\n 'mscarry',\n 'msgroup',\n 'mstack',\n 'msline',\n 'msrow',\n 'semantics',\n 'annotation',\n 'annotation-xml',\n 'mprescripts',\n 'none',\n]);\n\nexport const text = freeze(['#text']);\n","import { freeze } from './utils.js';\n\nexport const html = freeze([\n 'accept',\n 'action',\n 'align',\n 'alt',\n 'autocapitalize',\n 'autocomplete',\n 'autopictureinpicture',\n 'autoplay',\n 'background',\n 'bgcolor',\n 'border',\n 'capture',\n 'cellpadding',\n 'cellspacing',\n 'checked',\n 'cite',\n 'class',\n 'clear',\n 'color',\n 'cols',\n 'colspan',\n 'controls',\n 'controlslist',\n 'coords',\n 'crossorigin',\n 'datetime',\n 'decoding',\n 'default',\n 'dir',\n 'disabled',\n 'disablepictureinpicture',\n 'disableremoteplayback',\n 'download',\n 'draggable',\n 'enctype',\n 'enterkeyhint',\n 'face',\n 'for',\n 'headers',\n 'height',\n 'hidden',\n 'high',\n 'href',\n 'hreflang',\n 'id',\n 'inputmode',\n 'integrity',\n 'ismap',\n 'kind',\n 'label',\n 'lang',\n 'list',\n 'loading',\n 'loop',\n 'low',\n 'max',\n 'maxlength',\n 'media',\n 'method',\n 'min',\n 'minlength',\n 'multiple',\n 'muted',\n 'name',\n 'nonce',\n 'noshade',\n 'novalidate',\n 'nowrap',\n 'open',\n 'optimum',\n 'pattern',\n 'placeholder',\n 'playsinline',\n 'poster',\n 'preload',\n 'pubdate',\n 'radiogroup',\n 'readonly',\n 'rel',\n 'required',\n 'rev',\n 'reversed',\n 'role',\n 'rows',\n 'rowspan',\n 'spellcheck',\n 'scope',\n 'selected',\n 'shape',\n 'size',\n 'sizes',\n 'span',\n 'srclang',\n 'start',\n 'src',\n 'srcset',\n 'step',\n 'style',\n 'summary',\n 'tabindex',\n 'title',\n 'translate',\n 'type',\n 'usemap',\n 'valign',\n 'value',\n 'width',\n 'xmlns',\n 'slot',\n]);\n\nexport const svg = freeze([\n 'accent-height',\n 'accumulate',\n 'additive',\n 'alignment-baseline',\n 'ascent',\n 'attributename',\n 'attributetype',\n 'azimuth',\n 'basefrequency',\n 'baseline-shift',\n 'begin',\n 'bias',\n 'by',\n 'class',\n 'clip',\n 'clippathunits',\n 'clip-path',\n 'clip-rule',\n 'color',\n 'color-interpolation',\n 'color-interpolation-filters',\n 'color-profile',\n 'color-rendering',\n 'cx',\n 'cy',\n 'd',\n 'dx',\n 'dy',\n 'diffuseconstant',\n 'direction',\n 'display',\n 'divisor',\n 'dur',\n 'edgemode',\n 'elevation',\n 'end',\n 'fill',\n 'fill-opacity',\n 'fill-rule',\n 'filter',\n 'filterunits',\n 'flood-color',\n 'flood-opacity',\n 'font-family',\n 'font-size',\n 'font-size-adjust',\n 'font-stretch',\n 'font-style',\n 'font-variant',\n 'font-weight',\n 'fx',\n 'fy',\n 'g1',\n 'g2',\n 'glyph-name',\n 'glyphref',\n 'gradientunits',\n 'gradienttransform',\n 'height',\n 'href',\n 'id',\n 'image-rendering',\n 'in',\n 'in2',\n 'k',\n 'k1',\n 'k2',\n 'k3',\n 'k4',\n 'kerning',\n 'keypoints',\n 'keysplines',\n 'keytimes',\n 'lang',\n 'lengthadjust',\n 'letter-spacing',\n 'kernelmatrix',\n 'kernelunitlength',\n 'lighting-color',\n 'local',\n 'marker-end',\n 'marker-mid',\n 'marker-start',\n 'markerheight',\n 'markerunits',\n 'markerwidth',\n 'maskcontentunits',\n 'maskunits',\n 'max',\n 'mask',\n 'media',\n 'method',\n 'mode',\n 'min',\n 'name',\n 'numoctaves',\n 'offset',\n 'operator',\n 'opacity',\n 'order',\n 'orient',\n 'orientation',\n 'origin',\n 'overflow',\n 'paint-order',\n 'path',\n 'pathlength',\n 'patterncontentunits',\n 'patterntransform',\n 'patternunits',\n 'points',\n 'preservealpha',\n 'preserveaspectratio',\n 'primitiveunits',\n 'r',\n 'rx',\n 'ry',\n 'radius',\n 'refx',\n 'refy',\n 'repeatcount',\n 'repeatdur',\n 'restart',\n 'result',\n 'rotate',\n 'scale',\n 'seed',\n 'shape-rendering',\n 'specularconstant',\n 'specularexponent',\n 'spreadmethod',\n 'startoffset',\n 'stddeviation',\n 'stitchtiles',\n 'stop-color',\n 'stop-opacity',\n 'stroke-dasharray',\n 'stroke-dashoffset',\n 'stroke-linecap',\n 'stroke-linejoin',\n 'stroke-miterlimit',\n 'stroke-opacity',\n 'stroke',\n 'stroke-width',\n 'style',\n 'surfacescale',\n 'systemlanguage',\n 'tabindex',\n 'targetx',\n 'targety',\n 'transform',\n 'transform-origin',\n 'text-anchor',\n 'text-decoration',\n 'text-rendering',\n 'textlength',\n 'type',\n 'u1',\n 'u2',\n 'unicode',\n 'values',\n 'viewbox',\n 'visibility',\n 'version',\n 'vert-adv-y',\n 'vert-origin-x',\n 'vert-origin-y',\n 'width',\n 'word-spacing',\n 'wrap',\n 'writing-mode',\n 'xchannelselector',\n 'ychannelselector',\n 'x',\n 'x1',\n 'x2',\n 'xmlns',\n 'y',\n 'y1',\n 'y2',\n 'z',\n 'zoomandpan',\n]);\n\nexport const mathMl = freeze([\n 'accent',\n 'accentunder',\n 'align',\n 'bevelled',\n 'close',\n 'columnsalign',\n 'columnlines',\n 'columnspan',\n 'denomalign',\n 'depth',\n 'dir',\n 'display',\n 'displaystyle',\n 'encoding',\n 'fence',\n 'frame',\n 'height',\n 'href',\n 'id',\n 'largeop',\n 'length',\n 'linethickness',\n 'lspace',\n 'lquote',\n 'mathbackground',\n 'mathcolor',\n 'mathsize',\n 'mathvariant',\n 'maxsize',\n 'minsize',\n 'movablelimits',\n 'notation',\n 'numalign',\n 'open',\n 'rowalign',\n 'rowlines',\n 'rowspacing',\n 'rowspan',\n 'rspace',\n 'rquote',\n 'scriptlevel',\n 'scriptminsize',\n 'scriptsizemultiplier',\n 'selection',\n 'separator',\n 'separators',\n 'stretchy',\n 'subscriptshift',\n 'supscriptshift',\n 'symmetric',\n 'voffset',\n 'width',\n 'xmlns',\n]);\n\nexport const xml = freeze([\n 'xlink:href',\n 'xml:id',\n 'xlink:title',\n 'xml:space',\n 'xmlns:xlink',\n]);\n","import { seal } from './utils.js';\n\n// eslint-disable-next-line unicorn/better-regex\nexport const MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nexport const ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nexport const TMPLIT_EXPR = seal(/\\${[\\w\\W]*}/gm);\nexport const DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]/); // eslint-disable-line no-useless-escape\nexport const ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nexport const IS_ALLOWED_URI = seal(\n /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i // eslint-disable-line no-useless-escape\n);\nexport const IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nexport const ATTR_WHITESPACE = seal(\n /[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nexport const DOCTYPE_NAME = seal(/^html$/i);\n","import * as TAGS from './tags.js';\nimport * as ATTRS from './attrs.js';\nimport * as EXPRESSIONS from './regexp.js';\nimport {\n addToSet,\n clone,\n entries,\n freeze,\n arrayForEach,\n arrayPop,\n arrayPush,\n stringMatch,\n stringReplace,\n stringToLowerCase,\n stringToString,\n stringIndexOf,\n stringTrim,\n regExpTest,\n typeErrorCreate,\n lookupGetter,\n create,\n} from './utils.js';\n\nconst getGlobal = function () {\n return typeof window === 'undefined' ? null : window;\n};\n\n/**\n * Creates a no-op policy for internal use only.\n * Don't export this function outside this module!\n * @param {?TrustedTypePolicyFactory} trustedTypes The policy factory.\n * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).\n * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types\n * are not supported or creating the policy failed).\n */\nconst _createTrustedTypesPolicy = function (trustedTypes, purifyHostElement) {\n if (\n typeof trustedTypes !== 'object' ||\n typeof trustedTypes.createPolicy !== 'function'\n ) {\n return null;\n }\n\n // Allow the callers to control the unique policy name\n // by adding a data-tt-policy-suffix to the script element with the DOMPurify.\n // Policy creation with duplicate names throws in Trusted Types.\n let suffix = null;\n const ATTR_NAME = 'data-tt-policy-suffix';\n if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {\n suffix = purifyHostElement.getAttribute(ATTR_NAME);\n }\n\n const policyName = 'dompurify' + (suffix ? '#' + suffix : '');\n\n try {\n return trustedTypes.createPolicy(policyName, {\n createHTML(html) {\n return html;\n },\n createScriptURL(scriptUrl) {\n return scriptUrl;\n },\n });\n } catch (_) {\n // Policy creation failed (most likely another DOMPurify script has\n // already run). Skip creating the policy, as this will only cause errors\n // if TT are enforced.\n console.warn(\n 'TrustedTypes policy ' + policyName + ' could not be created.'\n );\n return null;\n }\n};\n\nfunction createDOMPurify(window = getGlobal()) {\n const DOMPurify = (root) => createDOMPurify(root);\n\n /**\n * Version label, exposed for easier checks\n * if DOMPurify is up to date or not\n */\n DOMPurify.version = VERSION;\n\n /**\n * Array of elements that DOMPurify removed during sanitation.\n * Empty if nothing was removed.\n */\n DOMPurify.removed = [];\n\n if (!window || !window.document || window.document.nodeType !== 9) {\n // Not running in a browser, provide a factory function\n // so that you can pass your own Window\n DOMPurify.isSupported = false;\n\n return DOMPurify;\n }\n\n let { document } = window;\n\n const originalDocument = document;\n const currentScript = originalDocument.currentScript;\n const {\n DocumentFragment,\n HTMLTemplateElement,\n Node,\n Element,\n NodeFilter,\n NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,\n HTMLFormElement,\n DOMParser,\n trustedTypes,\n } = window;\n\n const ElementPrototype = Element.prototype;\n\n const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');\n const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');\n const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');\n const getParentNode = lookupGetter(ElementPrototype, 'parentNode');\n\n // As per issue #47, the web-components registry is inherited by a\n // new document created via createHTMLDocument. As per the spec\n // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)\n // a new empty registry is used when creating a template contents owner\n // document, so we use that as our parent document to ensure nothing\n // is inherited.\n if (typeof HTMLTemplateElement === 'function') {\n const template = document.createElement('template');\n if (template.content && template.content.ownerDocument) {\n document = template.content.ownerDocument;\n }\n }\n\n let trustedTypesPolicy;\n let emptyHTML = '';\n\n const {\n implementation,\n createNodeIterator,\n createDocumentFragment,\n getElementsByTagName,\n } = document;\n const { importNode } = originalDocument;\n\n let hooks = {};\n\n /**\n * Expose whether this browser supports running the full DOMPurify.\n */\n DOMPurify.isSupported =\n typeof entries === 'function' &&\n typeof getParentNode === 'function' &&\n implementation &&\n implementation.createHTMLDocument !== undefined;\n\n const {\n MUSTACHE_EXPR,\n ERB_EXPR,\n TMPLIT_EXPR,\n DATA_ATTR,\n ARIA_ATTR,\n IS_SCRIPT_OR_DATA,\n ATTR_WHITESPACE,\n } = EXPRESSIONS;\n\n let { IS_ALLOWED_URI } = EXPRESSIONS;\n\n /**\n * We consider the elements and attributes below to be safe. Ideally\n * don't add any new ones but feel free to remove unwanted ones.\n */\n\n /* allowed element names */\n let ALLOWED_TAGS = null;\n const DEFAULT_ALLOWED_TAGS = addToSet({}, [\n ...TAGS.html,\n ...TAGS.svg,\n ...TAGS.svgFilters,\n ...TAGS.mathMl,\n ...TAGS.text,\n ]);\n\n /* Allowed attribute names */\n let ALLOWED_ATTR = null;\n const DEFAULT_ALLOWED_ATTR = addToSet({}, [\n ...ATTRS.html,\n ...ATTRS.svg,\n ...ATTRS.mathMl,\n ...ATTRS.xml,\n ]);\n\n /*\n * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.\n * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)\n * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)\n * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.\n */\n let CUSTOM_ELEMENT_HANDLING = Object.seal(\n create(null, {\n tagNameCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null,\n },\n attributeNameCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null,\n },\n allowCustomizedBuiltInElements: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: false,\n },\n })\n );\n\n /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */\n let FORBID_TAGS = null;\n\n /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */\n let FORBID_ATTR = null;\n\n /* Decide if ARIA attributes are okay */\n let ALLOW_ARIA_ATTR = true;\n\n /* Decide if custom data attributes are okay */\n let ALLOW_DATA_ATTR = true;\n\n /* Decide if unknown protocols are okay */\n let ALLOW_UNKNOWN_PROTOCOLS = false;\n\n /* Decide if self-closing tags in attributes are allowed.\n * Usually removed due to a mXSS issue in jQuery 3.0 */\n let ALLOW_SELF_CLOSE_IN_ATTR = true;\n\n /* Output should be safe for common template engines.\n * This means, DOMPurify removes data attributes, mustaches and ERB\n */\n let SAFE_FOR_TEMPLATES = false;\n\n /* Decide if document with ... should be returned */\n let WHOLE_DOCUMENT = false;\n\n /* Track whether config is already set on this instance of DOMPurify. */\n let SET_CONFIG = false;\n\n /* Decide if all elements (e.g. style, script) must be children of\n * document.body. By default, browsers might move them to document.head */\n let FORCE_BODY = false;\n\n /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html\n * string (or a TrustedHTML object if Trusted Types are supported).\n * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead\n */\n let RETURN_DOM = false;\n\n /* Decide if a DOM `DocumentFragment` should be returned, instead of a html\n * string (or a TrustedHTML object if Trusted Types are supported) */\n let RETURN_DOM_FRAGMENT = false;\n\n /* Try to return a Trusted Type object instead of a string, return a string in\n * case Trusted Types are not supported */\n let RETURN_TRUSTED_TYPE = false;\n\n /* Output should be free from DOM clobbering attacks?\n * This sanitizes markups named with colliding, clobberable built-in DOM APIs.\n */\n let SANITIZE_DOM = true;\n\n /* Achieve full DOM Clobbering protection by isolating the namespace of named\n * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.\n *\n * HTML/DOM spec rules that enable DOM Clobbering:\n * - Named Access on Window (§7.3.3)\n * - DOM Tree Accessors (§3.1.5)\n * - Form Element Parent-Child Relations (§4.10.3)\n * - Iframe srcdoc / Nested WindowProxies (§4.8.5)\n * - HTMLCollection (§4.2.10.2)\n *\n * Namespace isolation is implemented by prefixing `id` and `name` attributes\n * with a constant string, i.e., `user-content-`\n */\n let SANITIZE_NAMED_PROPS = false;\n const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';\n\n /* Keep element content when removing element? */\n let KEEP_CONTENT = true;\n\n /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead\n * of importing it into a new Document and returning a sanitized copy */\n let IN_PLACE = false;\n\n /* Allow usage of profiles like html, svg and mathMl */\n let USE_PROFILES = {};\n\n /* Tags to ignore content of when KEEP_CONTENT is true */\n let FORBID_CONTENTS = null;\n const DEFAULT_FORBID_CONTENTS = addToSet({}, [\n 'annotation-xml',\n 'audio',\n 'colgroup',\n 'desc',\n 'foreignobject',\n 'head',\n 'iframe',\n 'math',\n 'mi',\n 'mn',\n 'mo',\n 'ms',\n 'mtext',\n 'noembed',\n 'noframes',\n 'noscript',\n 'plaintext',\n 'script',\n 'style',\n 'svg',\n 'template',\n 'thead',\n 'title',\n 'video',\n 'xmp',\n ]);\n\n /* Tags that are safe for data: URIs */\n let DATA_URI_TAGS = null;\n const DEFAULT_DATA_URI_TAGS = addToSet({}, [\n 'audio',\n 'video',\n 'img',\n 'source',\n 'image',\n 'track',\n ]);\n\n /* Attributes safe for values like \"javascript:\" */\n let URI_SAFE_ATTRIBUTES = null;\n const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [\n 'alt',\n 'class',\n 'for',\n 'id',\n 'label',\n 'name',\n 'pattern',\n 'placeholder',\n 'role',\n 'summary',\n 'title',\n 'value',\n 'style',\n 'xmlns',\n ]);\n\n const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\n /* Document namespace */\n let NAMESPACE = HTML_NAMESPACE;\n let IS_EMPTY_INPUT = false;\n\n /* Allowed XHTML+XML namespaces */\n let ALLOWED_NAMESPACES = null;\n const DEFAULT_ALLOWED_NAMESPACES = addToSet(\n {},\n [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE],\n stringToString\n );\n\n /* Parsing of strict XHTML documents */\n let PARSER_MEDIA_TYPE = null;\n const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];\n const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';\n let transformCaseFunc = null;\n\n /* Keep a reference to config to pass to hooks */\n let CONFIG = null;\n\n /* Ideally, do not touch anything below this line */\n /* ______________________________________________ */\n\n const formElement = document.createElement('form');\n\n const isRegexOrFunction = function (testValue) {\n return testValue instanceof RegExp || testValue instanceof Function;\n };\n\n /**\n * _parseConfig\n *\n * @param {Object} cfg optional config literal\n */\n // eslint-disable-next-line complexity\n const _parseConfig = function (cfg = {}) {\n if (CONFIG && CONFIG === cfg) {\n return;\n }\n\n /* Shield configuration object from tampering */\n if (!cfg || typeof cfg !== 'object') {\n cfg = {};\n }\n\n /* Shield configuration object from prototype pollution */\n cfg = clone(cfg);\n\n PARSER_MEDIA_TYPE =\n // eslint-disable-next-line unicorn/prefer-includes\n SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1\n ? (PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE)\n : (PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE);\n\n // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.\n transformCaseFunc =\n PARSER_MEDIA_TYPE === 'application/xhtml+xml'\n ? stringToString\n : stringToLowerCase;\n\n /* Set configuration parameters */\n ALLOWED_TAGS =\n 'ALLOWED_TAGS' in cfg\n ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc)\n : DEFAULT_ALLOWED_TAGS;\n ALLOWED_ATTR =\n 'ALLOWED_ATTR' in cfg\n ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc)\n : DEFAULT_ALLOWED_ATTR;\n ALLOWED_NAMESPACES =\n 'ALLOWED_NAMESPACES' in cfg\n ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString)\n : DEFAULT_ALLOWED_NAMESPACES;\n URI_SAFE_ATTRIBUTES =\n 'ADD_URI_SAFE_ATTR' in cfg\n ? addToSet(\n clone(DEFAULT_URI_SAFE_ATTRIBUTES), // eslint-disable-line indent\n cfg.ADD_URI_SAFE_ATTR, // eslint-disable-line indent\n transformCaseFunc // eslint-disable-line indent\n ) // eslint-disable-line indent\n : DEFAULT_URI_SAFE_ATTRIBUTES;\n DATA_URI_TAGS =\n 'ADD_DATA_URI_TAGS' in cfg\n ? addToSet(\n clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent\n cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent\n transformCaseFunc // eslint-disable-line indent\n ) // eslint-disable-line indent\n : DEFAULT_DATA_URI_TAGS;\n FORBID_CONTENTS =\n 'FORBID_CONTENTS' in cfg\n ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc)\n : DEFAULT_FORBID_CONTENTS;\n FORBID_TAGS =\n 'FORBID_TAGS' in cfg\n ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc)\n : {};\n FORBID_ATTR =\n 'FORBID_ATTR' in cfg\n ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc)\n : {};\n USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;\n ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true\n ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true\n ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false\n ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true\n SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false\n WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false\n RETURN_DOM = cfg.RETURN_DOM || false; // Default false\n RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false\n RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false\n FORCE_BODY = cfg.FORCE_BODY || false; // Default false\n SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true\n SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false\n KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true\n IN_PLACE = cfg.IN_PLACE || false; // Default false\n IS_ALLOWED_URI = cfg.ALLOWED_URI_REGEXP || EXPRESSIONS.IS_ALLOWED_URI;\n NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;\n CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};\n if (\n cfg.CUSTOM_ELEMENT_HANDLING &&\n isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)\n ) {\n CUSTOM_ELEMENT_HANDLING.tagNameCheck =\n cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;\n }\n\n if (\n cfg.CUSTOM_ELEMENT_HANDLING &&\n isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)\n ) {\n CUSTOM_ELEMENT_HANDLING.attributeNameCheck =\n cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;\n }\n\n if (\n cfg.CUSTOM_ELEMENT_HANDLING &&\n typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements ===\n 'boolean'\n ) {\n CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements =\n cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;\n }\n\n if (SAFE_FOR_TEMPLATES) {\n ALLOW_DATA_ATTR = false;\n }\n\n if (RETURN_DOM_FRAGMENT) {\n RETURN_DOM = true;\n }\n\n /* Parse profile info */\n if (USE_PROFILES) {\n ALLOWED_TAGS = addToSet({}, [...TAGS.text]);\n ALLOWED_ATTR = [];\n if (USE_PROFILES.html === true) {\n addToSet(ALLOWED_TAGS, TAGS.html);\n addToSet(ALLOWED_ATTR, ATTRS.html);\n }\n\n if (USE_PROFILES.svg === true) {\n addToSet(ALLOWED_TAGS, TAGS.svg);\n addToSet(ALLOWED_ATTR, ATTRS.svg);\n addToSet(ALLOWED_ATTR, ATTRS.xml);\n }\n\n if (USE_PROFILES.svgFilters === true) {\n addToSet(ALLOWED_TAGS, TAGS.svgFilters);\n addToSet(ALLOWED_ATTR, ATTRS.svg);\n addToSet(ALLOWED_ATTR, ATTRS.xml);\n }\n\n if (USE_PROFILES.mathMl === true) {\n addToSet(ALLOWED_TAGS, TAGS.mathMl);\n addToSet(ALLOWED_ATTR, ATTRS.mathMl);\n addToSet(ALLOWED_ATTR, ATTRS.xml);\n }\n }\n\n /* Merge configuration parameters */\n if (cfg.ADD_TAGS) {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n\n if (cfg.ADD_ATTR) {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\n\n if (cfg.ADD_URI_SAFE_ATTR) {\n addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);\n }\n\n if (cfg.FORBID_CONTENTS) {\n if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {\n FORBID_CONTENTS = clone(FORBID_CONTENTS);\n }\n\n addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);\n }\n\n /* Add #text in case KEEP_CONTENT is set to true */\n if (KEEP_CONTENT) {\n ALLOWED_TAGS['#text'] = true;\n }\n\n /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */\n if (WHOLE_DOCUMENT) {\n addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);\n }\n\n /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */\n if (ALLOWED_TAGS.table) {\n addToSet(ALLOWED_TAGS, ['tbody']);\n delete FORBID_TAGS.tbody;\n }\n\n if (cfg.TRUSTED_TYPES_POLICY) {\n if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {\n throw typeErrorCreate(\n 'TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook.'\n );\n }\n\n if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {\n throw typeErrorCreate(\n 'TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook.'\n );\n }\n\n // Overwrite existing TrustedTypes policy.\n trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;\n\n // Sign local variables required by `sanitize`.\n emptyHTML = trustedTypesPolicy.createHTML('');\n } else {\n // Uninitialized policy, attempt to initialize the internal dompurify policy.\n if (trustedTypesPolicy === undefined) {\n trustedTypesPolicy = _createTrustedTypesPolicy(\n trustedTypes,\n currentScript\n );\n }\n\n // If creating the internal policy succeeded sign internal variables.\n if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {\n emptyHTML = trustedTypesPolicy.createHTML('');\n }\n }\n\n // Prevent further manipulation of configuration.\n // Not available in IE8, Safari 5, etc.\n if (freeze) {\n freeze(cfg);\n }\n\n CONFIG = cfg;\n };\n\n const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, [\n 'mi',\n 'mo',\n 'mn',\n 'ms',\n 'mtext',\n ]);\n\n const HTML_INTEGRATION_POINTS = addToSet({}, [\n 'foreignobject',\n 'desc',\n 'title',\n 'annotation-xml',\n ]);\n\n // Certain elements are allowed in both SVG and HTML\n // namespace. We need to specify them explicitly\n // so that they don't get erroneously deleted from\n // HTML namespace.\n const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [\n 'title',\n 'style',\n 'font',\n 'a',\n 'script',\n ]);\n\n /* Keep track of all possible SVG and MathML tags\n * so that we can perform the namespace checks\n * correctly. */\n const ALL_SVG_TAGS = addToSet({}, TAGS.svg);\n addToSet(ALL_SVG_TAGS, TAGS.svgFilters);\n addToSet(ALL_SVG_TAGS, TAGS.svgDisallowed);\n\n const ALL_MATHML_TAGS = addToSet({}, TAGS.mathMl);\n addToSet(ALL_MATHML_TAGS, TAGS.mathMlDisallowed);\n\n /**\n * @param {Element} element a DOM element whose namespace is being checked\n * @returns {boolean} Return false if the element has a\n * namespace that a spec-compliant parser would never\n * return. Return true otherwise.\n */\n const _checkValidNamespace = function (element) {\n let parent = getParentNode(element);\n\n // In JSDOM, if we're inside shadow DOM, then parentNode\n // can be null. We just simulate parent in this case.\n if (!parent || !parent.tagName) {\n parent = {\n namespaceURI: NAMESPACE,\n tagName: 'template',\n };\n }\n\n const tagName = stringToLowerCase(element.tagName);\n const parentTagName = stringToLowerCase(parent.tagName);\n\n if (!ALLOWED_NAMESPACES[element.namespaceURI]) {\n return false;\n }\n\n if (element.namespaceURI === SVG_NAMESPACE) {\n // The only way to switch from HTML namespace to SVG\n // is via . If it happens via any other tag, then\n // it should be killed.\n if (parent.namespaceURI === HTML_NAMESPACE) {\n return tagName === 'svg';\n }\n\n // The only way to switch from MathML to SVG is via`\n // svg if parent is either or MathML\n // text integration points.\n if (parent.namespaceURI === MATHML_NAMESPACE) {\n return (\n tagName === 'svg' &&\n (parentTagName === 'annotation-xml' ||\n MATHML_TEXT_INTEGRATION_POINTS[parentTagName])\n );\n }\n\n // We only allow elements that are defined in SVG\n // spec. All others are disallowed in SVG namespace.\n return Boolean(ALL_SVG_TAGS[tagName]);\n }\n\n if (element.namespaceURI === MATHML_NAMESPACE) {\n // The only way to switch from HTML namespace to MathML\n // is via . If it happens via any other tag, then\n // it should be killed.\n if (parent.namespaceURI === HTML_NAMESPACE) {\n return tagName === 'math';\n }\n\n // The only way to switch from SVG to MathML is via\n // and HTML integration points\n if (parent.namespaceURI === SVG_NAMESPACE) {\n return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];\n }\n\n // We only allow elements that are defined in MathML\n // spec. All others are disallowed in MathML namespace.\n return Boolean(ALL_MATHML_TAGS[tagName]);\n }\n\n if (element.namespaceURI === HTML_NAMESPACE) {\n // The only way to switch from SVG to HTML is via\n // HTML integration points, and from MathML to HTML\n // is via MathML text integration points\n if (\n parent.namespaceURI === SVG_NAMESPACE &&\n !HTML_INTEGRATION_POINTS[parentTagName]\n ) {\n return false;\n }\n\n if (\n parent.namespaceURI === MATHML_NAMESPACE &&\n !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]\n ) {\n return false;\n }\n\n // We disallow tags that are specific for MathML\n // or SVG and should never appear in HTML namespace\n return (\n !ALL_MATHML_TAGS[tagName] &&\n (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName])\n );\n }\n\n // For XHTML and XML documents that support custom namespaces\n if (\n PARSER_MEDIA_TYPE === 'application/xhtml+xml' &&\n ALLOWED_NAMESPACES[element.namespaceURI]\n ) {\n return true;\n }\n\n // The code should never reach this place (this means\n // that the element somehow got namespace that is not\n // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).\n // Return false just in case.\n return false;\n };\n\n /**\n * _forceRemove\n *\n * @param {Node} node a DOM node\n */\n const _forceRemove = function (node) {\n arrayPush(DOMPurify.removed, { element: node });\n try {\n // eslint-disable-next-line unicorn/prefer-dom-node-remove\n node.parentNode.removeChild(node);\n } catch (_) {\n node.remove();\n }\n };\n\n /**\n * _removeAttribute\n *\n * @param {String} name an Attribute name\n * @param {Node} node a DOM node\n */\n const _removeAttribute = function (name, node) {\n try {\n arrayPush(DOMPurify.removed, {\n attribute: node.getAttributeNode(name),\n from: node,\n });\n } catch (_) {\n arrayPush(DOMPurify.removed, {\n attribute: null,\n from: node,\n });\n }\n\n node.removeAttribute(name);\n\n // We void attribute values for unremovable \"is\"\" attributes\n if (name === 'is' && !ALLOWED_ATTR[name]) {\n if (RETURN_DOM || RETURN_DOM_FRAGMENT) {\n try {\n _forceRemove(node);\n } catch (_) {}\n } else {\n try {\n node.setAttribute(name, '');\n } catch (_) {}\n }\n }\n };\n\n /**\n * _initDocument\n *\n * @param {String} dirty a string of dirty markup\n * @return {Document} a DOM, filled with the dirty markup\n */\n const _initDocument = function (dirty) {\n /* Create a HTML document */\n let doc = null;\n let leadingWhitespace = null;\n\n if (FORCE_BODY) {\n dirty = '' + dirty;\n } else {\n /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */\n const matches = stringMatch(dirty, /^[\\r\\n\\t ]+/);\n leadingWhitespace = matches && matches[0];\n }\n\n if (\n PARSER_MEDIA_TYPE === 'application/xhtml+xml' &&\n NAMESPACE === HTML_NAMESPACE\n ) {\n // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)\n dirty =\n '' +\n dirty +\n '';\n }\n\n const dirtyPayload = trustedTypesPolicy\n ? trustedTypesPolicy.createHTML(dirty)\n : dirty;\n /*\n * Use the DOMParser API by default, fallback later if needs be\n * DOMParser not work for svg when has multiple root element.\n */\n if (NAMESPACE === HTML_NAMESPACE) {\n try {\n doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);\n } catch (_) {}\n }\n\n /* Use createHTMLDocument in case DOMParser is not available */\n if (!doc || !doc.documentElement) {\n doc = implementation.createDocument(NAMESPACE, 'template', null);\n try {\n doc.documentElement.innerHTML = IS_EMPTY_INPUT\n ? emptyHTML\n : dirtyPayload;\n } catch (_) {\n // Syntax error if dirtyPayload is invalid xml\n }\n }\n\n const body = doc.body || doc.documentElement;\n\n if (dirty && leadingWhitespace) {\n body.insertBefore(\n document.createTextNode(leadingWhitespace),\n body.childNodes[0] || null\n );\n }\n\n /* Work on whole document or just its body */\n if (NAMESPACE === HTML_NAMESPACE) {\n return getElementsByTagName.call(\n doc,\n WHOLE_DOCUMENT ? 'html' : 'body'\n )[0];\n }\n\n return WHOLE_DOCUMENT ? doc.documentElement : body;\n };\n\n /**\n * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.\n *\n * @param {Node} root The root element or node to start traversing on.\n * @return {NodeIterator} The created NodeIterator\n */\n const _createNodeIterator = function (root) {\n return createNodeIterator.call(\n root.ownerDocument || root,\n root,\n // eslint-disable-next-line no-bitwise\n NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT,\n null\n );\n };\n\n /**\n * _isClobbered\n *\n * @param {Node} elm element to check for clobbering attacks\n * @return {Boolean} true if clobbered, false if safe\n */\n const _isClobbered = function (elm) {\n return (\n elm instanceof HTMLFormElement &&\n (typeof elm.nodeName !== 'string' ||\n typeof elm.textContent !== 'string' ||\n typeof elm.removeChild !== 'function' ||\n !(elm.attributes instanceof NamedNodeMap) ||\n typeof elm.removeAttribute !== 'function' ||\n typeof elm.setAttribute !== 'function' ||\n typeof elm.namespaceURI !== 'string' ||\n typeof elm.insertBefore !== 'function' ||\n typeof elm.hasChildNodes !== 'function')\n );\n };\n\n /**\n * Checks whether the given object is a DOM node.\n *\n * @param {Node} object object to check whether it's a DOM node\n * @return {Boolean} true is object is a DOM node\n */\n const _isNode = function (object) {\n return typeof Node === 'function' && object instanceof Node;\n };\n\n /**\n * _executeHook\n * Execute user configurable hooks\n *\n * @param {String} entryPoint Name of the hook's entry point\n * @param {Node} currentNode node to work on with the hook\n * @param {Object} data additional hook parameters\n */\n const _executeHook = function (entryPoint, currentNode, data) {\n if (!hooks[entryPoint]) {\n return;\n }\n\n arrayForEach(hooks[entryPoint], (hook) => {\n hook.call(DOMPurify, currentNode, data, CONFIG);\n });\n };\n\n /**\n * _sanitizeElements\n *\n * @protect nodeName\n * @protect textContent\n * @protect removeChild\n *\n * @param {Node} currentNode to check for permission to exist\n * @return {Boolean} true if node was killed, false if left alive\n */\n const _sanitizeElements = function (currentNode) {\n let content = null;\n\n /* Execute a hook if present */\n _executeHook('beforeSanitizeElements', currentNode, null);\n\n /* Check if element is clobbered or can clobber */\n if (_isClobbered(currentNode)) {\n _forceRemove(currentNode);\n return true;\n }\n\n /* Now let's check the element's type and name */\n const tagName = transformCaseFunc(currentNode.nodeName);\n\n /* Execute a hook if present */\n _executeHook('uponSanitizeElement', currentNode, {\n tagName,\n allowedTags: ALLOWED_TAGS,\n });\n\n /* Detect mXSS attempts abusing namespace confusion */\n if (\n currentNode.hasChildNodes() &&\n !_isNode(currentNode.firstElementChild) &&\n regExpTest(/<[/\\w]/g, currentNode.innerHTML) &&\n regExpTest(/<[/\\w]/g, currentNode.textContent)\n ) {\n _forceRemove(currentNode);\n return true;\n }\n\n /* Remove element if anything forbids its presence */\n if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {\n /* Check if we have a custom element to handle */\n if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {\n if (\n CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&\n regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)\n ) {\n return false;\n }\n\n if (\n CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&\n CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)\n ) {\n return false;\n }\n }\n\n /* Keep content except for bad-listed elements */\n if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {\n const parentNode = getParentNode(currentNode) || currentNode.parentNode;\n const childNodes = getChildNodes(currentNode) || currentNode.childNodes;\n\n if (childNodes && parentNode) {\n const childCount = childNodes.length;\n\n for (let i = childCount - 1; i >= 0; --i) {\n parentNode.insertBefore(\n cloneNode(childNodes[i], true),\n getNextSibling(currentNode)\n );\n }\n }\n }\n\n _forceRemove(currentNode);\n return true;\n }\n\n /* Check whether element has a valid namespace */\n if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {\n _forceRemove(currentNode);\n return true;\n }\n\n /* Make sure that older browsers don't get fallback-tag mXSS */\n if (\n (tagName === 'noscript' ||\n tagName === 'noembed' ||\n tagName === 'noframes') &&\n regExpTest(/<\\/no(script|embed|frames)/i, currentNode.innerHTML)\n ) {\n _forceRemove(currentNode);\n return true;\n }\n\n /* Sanitize element content to be template-safe */\n if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {\n /* Get the element's text content */\n content = currentNode.textContent;\n\n arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => {\n content = stringReplace(content, expr, ' ');\n });\n\n if (currentNode.textContent !== content) {\n arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });\n currentNode.textContent = content;\n }\n }\n\n /* Execute a hook if present */\n _executeHook('afterSanitizeElements', currentNode, null);\n\n return false;\n };\n\n /**\n * _isValidAttribute\n *\n * @param {string} lcTag Lowercase tag name of containing element.\n * @param {string} lcName Lowercase attribute name.\n * @param {string} value Attribute value.\n * @return {Boolean} Returns true if `value` is valid, otherwise false.\n */\n // eslint-disable-next-line complexity\n const _isValidAttribute = function (lcTag, lcName, value) {\n /* Make sure attribute cannot clobber */\n if (\n SANITIZE_DOM &&\n (lcName === 'id' || lcName === 'name') &&\n (value in document || value in formElement)\n ) {\n return false;\n }\n\n /* Allow valid data-* attributes: At least one character after \"-\"\n (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)\n XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)\n We don't need to check the value; it's always URI safe. */\n if (\n ALLOW_DATA_ATTR &&\n !FORBID_ATTR[lcName] &&\n regExpTest(DATA_ATTR, lcName)\n ) {\n // This attribute is safe\n } else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) {\n // This attribute is safe\n /* Otherwise, check the name is permitted */\n } else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {\n if (\n // First condition does a very basic check if a) it's basically a valid custom element tagname AND\n // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck\n // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck\n (_isBasicCustomElement(lcTag) &&\n ((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&\n regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag)) ||\n (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&\n CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag))) &&\n ((CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp &&\n regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName)) ||\n (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function &&\n CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)))) ||\n // Alternative, second condition checks if it's an `is`-attribute, AND\n // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck\n (lcName === 'is' &&\n CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements &&\n ((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&\n regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value)) ||\n (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&\n CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))))\n ) {\n // If user has supplied a regexp or function in CUSTOM_ELEMENT_HANDLING.tagNameCheck, we need to also allow derived custom elements using the same tagName test.\n // Additionally, we need to allow attributes passing the CUSTOM_ELEMENT_HANDLING.attributeNameCheck user has configured, as custom elements can define these at their own discretion.\n } else {\n return false;\n }\n /* Check value is safe. First, is attr inert? If so, is safe */\n } else if (URI_SAFE_ATTRIBUTES[lcName]) {\n // This attribute is safe\n /* Check no script, data or unknown possibly unsafe URI\n unless we know URI values are safe for that attribute */\n } else if (\n regExpTest(IS_ALLOWED_URI, stringReplace(value, ATTR_WHITESPACE, ''))\n ) {\n // This attribute is safe\n /* Keep image data URIs alive if src/xlink:href is allowed */\n /* Further prevent gadget XSS for dynamically built script tags */\n } else if (\n (lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') &&\n lcTag !== 'script' &&\n stringIndexOf(value, 'data:') === 0 &&\n DATA_URI_TAGS[lcTag]\n ) {\n // This attribute is safe\n /* Allow unknown protocols: This provides support for links that\n are handled by protocol handlers which may be unknown ahead of\n time, e.g. fb:, spotify: */\n } else if (\n ALLOW_UNKNOWN_PROTOCOLS &&\n !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))\n ) {\n // This attribute is safe\n /* Check for binary attributes */\n } else if (value) {\n return false;\n } else {\n // Binary attributes are safe at this point\n /* Anything else, presume unsafe, do not add it back */\n }\n\n return true;\n };\n\n /**\n * _isBasicCustomElement\n * checks if at least one dash is included in tagName, and it's not the first char\n * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name\n *\n * @param {string} tagName name of the tag of the node to sanitize\n * @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false.\n */\n const _isBasicCustomElement = function (tagName) {\n return tagName.indexOf('-') > 0;\n };\n\n /**\n * _sanitizeAttributes\n *\n * @protect attributes\n * @protect nodeName\n * @protect removeAttribute\n * @protect setAttribute\n *\n * @param {Node} currentNode to sanitize\n */\n const _sanitizeAttributes = function (currentNode) {\n /* Execute a hook if present */\n _executeHook('beforeSanitizeAttributes', currentNode, null);\n\n const { attributes } = currentNode;\n\n /* Check if we have attributes; if not we might have a text node */\n if (!attributes) {\n return;\n }\n\n const hookEvent = {\n attrName: '',\n attrValue: '',\n keepAttr: true,\n allowedAttributes: ALLOWED_ATTR,\n };\n let l = attributes.length;\n\n /* Go backwards over all attributes; safely remove bad ones */\n while (l--) {\n const attr = attributes[l];\n const { name, namespaceURI, value: attrValue } = attr;\n const lcName = transformCaseFunc(name);\n\n let value = name === 'value' ? attrValue : stringTrim(attrValue);\n\n /* Execute a hook if present */\n hookEvent.attrName = lcName;\n hookEvent.attrValue = value;\n hookEvent.keepAttr = true;\n hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set\n _executeHook('uponSanitizeAttribute', currentNode, hookEvent);\n value = hookEvent.attrValue;\n /* Did the hooks approve of the attribute? */\n if (hookEvent.forceKeepAttr) {\n continue;\n }\n\n /* Remove attribute */\n _removeAttribute(name, currentNode);\n\n /* Did the hooks approve of the attribute? */\n if (!hookEvent.keepAttr) {\n continue;\n }\n\n /* Work around a security issue in jQuery 3.0 */\n if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\\/>/i, value)) {\n _removeAttribute(name, currentNode);\n continue;\n }\n\n /* Sanitize attribute content to be template-safe */\n if (SAFE_FOR_TEMPLATES) {\n arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => {\n value = stringReplace(value, expr, ' ');\n });\n }\n\n /* Is `value` valid for this attribute? */\n const lcTag = transformCaseFunc(currentNode.nodeName);\n if (!_isValidAttribute(lcTag, lcName, value)) {\n continue;\n }\n\n /* Full DOM Clobbering protection via namespace isolation,\n * Prefix id and name attributes with `user-content-`\n */\n if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {\n // Remove the attribute with this value\n _removeAttribute(name, currentNode);\n\n // Prefix the value and later re-create the attribute with the sanitized value\n value = SANITIZE_NAMED_PROPS_PREFIX + value;\n }\n\n /* Handle attributes that require Trusted Types */\n if (\n trustedTypesPolicy &&\n typeof trustedTypes === 'object' &&\n typeof trustedTypes.getAttributeType === 'function'\n ) {\n if (namespaceURI) {\n /* Namespaces are not yet supported, see https://bugs.chromium.org/p/chromium/issues/detail?id=1305293 */\n } else {\n switch (trustedTypes.getAttributeType(lcTag, lcName)) {\n case 'TrustedHTML': {\n value = trustedTypesPolicy.createHTML(value);\n break;\n }\n\n case 'TrustedScriptURL': {\n value = trustedTypesPolicy.createScriptURL(value);\n break;\n }\n\n default: {\n break;\n }\n }\n }\n }\n\n /* Handle invalid data-* attribute set by try-catching it */\n try {\n if (namespaceURI) {\n currentNode.setAttributeNS(namespaceURI, name, value);\n } else {\n /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. \"x-schema\". */\n currentNode.setAttribute(name, value);\n }\n\n arrayPop(DOMPurify.removed);\n } catch (_) {}\n }\n\n /* Execute a hook if present */\n _executeHook('afterSanitizeAttributes', currentNode, null);\n };\n\n /**\n * _sanitizeShadowDOM\n *\n * @param {DocumentFragment} fragment to iterate over recursively\n */\n const _sanitizeShadowDOM = function (fragment) {\n let shadowNode = null;\n const shadowIterator = _createNodeIterator(fragment);\n\n /* Execute a hook if present */\n _executeHook('beforeSanitizeShadowDOM', fragment, null);\n\n while ((shadowNode = shadowIterator.nextNode())) {\n /* Execute a hook if present */\n _executeHook('uponSanitizeShadowNode', shadowNode, null);\n\n /* Sanitize tags and elements */\n if (_sanitizeElements(shadowNode)) {\n continue;\n }\n\n /* Deep shadow DOM detected */\n if (shadowNode.content instanceof DocumentFragment) {\n _sanitizeShadowDOM(shadowNode.content);\n }\n\n /* Check attributes, sanitize if necessary */\n _sanitizeAttributes(shadowNode);\n }\n\n /* Execute a hook if present */\n _executeHook('afterSanitizeShadowDOM', fragment, null);\n };\n\n /**\n * Sanitize\n * Public method providing core sanitation functionality\n *\n * @param {String|Node} dirty string or DOM node\n * @param {Object} cfg object\n */\n // eslint-disable-next-line complexity\n DOMPurify.sanitize = function (dirty, cfg = {}) {\n let body = null;\n let importedNode = null;\n let currentNode = null;\n let returnNode = null;\n /* Make sure we have a string to sanitize.\n DO NOT return early, as this will return the wrong type if\n the user has requested a DOM object rather than a string */\n IS_EMPTY_INPUT = !dirty;\n if (IS_EMPTY_INPUT) {\n dirty = '';\n }\n\n /* Stringify, in case dirty is an object */\n if (typeof dirty !== 'string' && !_isNode(dirty)) {\n if (typeof dirty.toString === 'function') {\n dirty = dirty.toString();\n if (typeof dirty !== 'string') {\n throw typeErrorCreate('dirty is not a string, aborting');\n }\n } else {\n throw typeErrorCreate('toString is not a function');\n }\n }\n\n /* Return dirty HTML if DOMPurify cannot run */\n if (!DOMPurify.isSupported) {\n return dirty;\n }\n\n /* Assign config vars */\n if (!SET_CONFIG) {\n _parseConfig(cfg);\n }\n\n /* Clean up removed elements */\n DOMPurify.removed = [];\n\n /* Check if dirty is correctly typed for IN_PLACE */\n if (typeof dirty === 'string') {\n IN_PLACE = false;\n }\n\n if (IN_PLACE) {\n /* Do some early pre-sanitization to avoid unsafe root nodes */\n if (dirty.nodeName) {\n const tagName = transformCaseFunc(dirty.nodeName);\n if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {\n throw typeErrorCreate(\n 'root node is forbidden and cannot be sanitized in-place'\n );\n }\n }\n } else if (dirty instanceof Node) {\n /* If dirty is a DOM element, append to an empty document to avoid\n elements being stripped by the parser */\n body = _initDocument('');\n importedNode = body.ownerDocument.importNode(dirty, true);\n if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {\n /* Node is already a body, use as is */\n body = importedNode;\n } else if (importedNode.nodeName === 'HTML') {\n body = importedNode;\n } else {\n // eslint-disable-next-line unicorn/prefer-dom-node-append\n body.appendChild(importedNode);\n }\n } else {\n /* Exit directly if we have nothing to do */\n if (\n !RETURN_DOM &&\n !SAFE_FOR_TEMPLATES &&\n !WHOLE_DOCUMENT &&\n // eslint-disable-next-line unicorn/prefer-includes\n dirty.indexOf('<') === -1\n ) {\n return trustedTypesPolicy && RETURN_TRUSTED_TYPE\n ? trustedTypesPolicy.createHTML(dirty)\n : dirty;\n }\n\n /* Initialize the document to work on */\n body = _initDocument(dirty);\n\n /* Check we have a DOM node from the data */\n if (!body) {\n return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';\n }\n }\n\n /* Remove first element node (ours) if FORCE_BODY is set */\n if (body && FORCE_BODY) {\n _forceRemove(body.firstChild);\n }\n\n /* Get node iterator */\n const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);\n\n /* Now start iterating over the created document */\n while ((currentNode = nodeIterator.nextNode())) {\n /* Sanitize tags and elements */\n if (_sanitizeElements(currentNode)) {\n continue;\n }\n\n /* Shadow DOM detected, sanitize it */\n if (currentNode.content instanceof DocumentFragment) {\n _sanitizeShadowDOM(currentNode.content);\n }\n\n /* Check attributes, sanitize if necessary */\n _sanitizeAttributes(currentNode);\n }\n\n /* If we sanitized `dirty` in-place, return it. */\n if (IN_PLACE) {\n return dirty;\n }\n\n /* Return sanitized string or DOM */\n if (RETURN_DOM) {\n if (RETURN_DOM_FRAGMENT) {\n returnNode = createDocumentFragment.call(body.ownerDocument);\n\n while (body.firstChild) {\n // eslint-disable-next-line unicorn/prefer-dom-node-append\n returnNode.appendChild(body.firstChild);\n }\n } else {\n returnNode = body;\n }\n\n if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {\n /*\n AdoptNode() is not used because internal state is not reset\n (e.g. the past names map of a HTMLFormElement), this is safe\n in theory but we would rather not risk another attack vector.\n The state that is cloned by importNode() is explicitly defined\n by the specs.\n */\n returnNode = importNode.call(originalDocument, returnNode, true);\n }\n\n return returnNode;\n }\n\n let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;\n\n /* Serialize doctype if allowed */\n if (\n WHOLE_DOCUMENT &&\n ALLOWED_TAGS['!doctype'] &&\n body.ownerDocument &&\n body.ownerDocument.doctype &&\n body.ownerDocument.doctype.name &&\n regExpTest(EXPRESSIONS.DOCTYPE_NAME, body.ownerDocument.doctype.name)\n ) {\n serializedHTML =\n '\\n' + serializedHTML;\n }\n\n /* Sanitize final string template-safe */\n if (SAFE_FOR_TEMPLATES) {\n arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => {\n serializedHTML = stringReplace(serializedHTML, expr, ' ');\n });\n }\n\n return trustedTypesPolicy && RETURN_TRUSTED_TYPE\n ? trustedTypesPolicy.createHTML(serializedHTML)\n : serializedHTML;\n };\n\n /**\n * Public method to set the configuration once\n * setConfig\n *\n * @param {Object} cfg configuration object\n */\n DOMPurify.setConfig = function (cfg = {}) {\n _parseConfig(cfg);\n SET_CONFIG = true;\n };\n\n /**\n * Public method to remove the configuration\n * clearConfig\n *\n */\n DOMPurify.clearConfig = function () {\n CONFIG = null;\n SET_CONFIG = false;\n };\n\n /**\n * Public method to check if an attribute value is valid.\n * Uses last set config, if any. Otherwise, uses config defaults.\n * isValidAttribute\n *\n * @param {String} tag Tag name of containing element.\n * @param {String} attr Attribute name.\n * @param {String} value Attribute value.\n * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.\n */\n DOMPurify.isValidAttribute = function (tag, attr, value) {\n /* Initialize shared config vars if necessary. */\n if (!CONFIG) {\n _parseConfig({});\n }\n\n const lcTag = transformCaseFunc(tag);\n const lcName = transformCaseFunc(attr);\n return _isValidAttribute(lcTag, lcName, value);\n };\n\n /**\n * AddHook\n * Public method to add DOMPurify hooks\n *\n * @param {String} entryPoint entry point for the hook to add\n * @param {Function} hookFunction function to execute\n */\n DOMPurify.addHook = function (entryPoint, hookFunction) {\n if (typeof hookFunction !== 'function') {\n return;\n }\n\n hooks[entryPoint] = hooks[entryPoint] || [];\n arrayPush(hooks[entryPoint], hookFunction);\n };\n\n /**\n * RemoveHook\n * Public method to remove a DOMPurify hook at a given entryPoint\n * (pops it from the stack of hooks if more are present)\n *\n * @param {String} entryPoint entry point for the hook to remove\n * @return {Function} removed(popped) hook\n */\n DOMPurify.removeHook = function (entryPoint) {\n if (hooks[entryPoint]) {\n return arrayPop(hooks[entryPoint]);\n }\n };\n\n /**\n * RemoveHooks\n * Public method to remove all DOMPurify hooks at a given entryPoint\n *\n * @param {String} entryPoint entry point for the hooks to remove\n */\n DOMPurify.removeHooks = function (entryPoint) {\n if (hooks[entryPoint]) {\n hooks[entryPoint] = [];\n }\n };\n\n /**\n * RemoveAllHooks\n * Public method to remove all DOMPurify hooks\n */\n DOMPurify.removeAllHooks = function () {\n hooks = {};\n };\n\n return DOMPurify;\n}\n\nexport default createDOMPurify();\n"],"names":["entries","setPrototypeOf","isFrozen","getPrototypeOf","getOwnPropertyDescriptor","Object","freeze","seal","create","apply","construct","Reflect","x","fun","thisValue","args","Func","arrayForEach","unapply","Array","prototype","forEach","arrayPop","pop","arrayPush","push","stringToLowerCase","String","toLowerCase","stringToString","toString","stringMatch","match","stringReplace","replace","stringIndexOf","indexOf","stringTrim","trim","regExpTest","RegExp","test","typeErrorCreate","func","TypeError","_len2","arguments","length","_key2","thisArg","_len","_key","addToSet","set","array","transformCaseFunc","l","element","lcElement","clone","object","newObject","property","value","undefined","lookupGetter","prop","desc","get","fallbackValue","console","warn","html","svg","svgFilters","svgDisallowed","mathMl","mathMlDisallowed","text","xml","MUSTACHE_EXPR","ERB_EXPR","TMPLIT_EXPR","DATA_ATTR","ARIA_ATTR","IS_ALLOWED_URI","IS_SCRIPT_OR_DATA","ATTR_WHITESPACE","DOCTYPE_NAME","getGlobal","window","_createTrustedTypesPolicy","trustedTypes","purifyHostElement","createPolicy","suffix","ATTR_NAME","hasAttribute","getAttribute","policyName","createHTML","createScriptURL","scriptUrl","_","purify","createDOMPurify","DOMPurify","root","version","VERSION","removed","document","nodeType","isSupported","originalDocument","currentScript","DocumentFragment","HTMLTemplateElement","Node","Element","NodeFilter","NamedNodeMap","MozNamedAttrMap","HTMLFormElement","DOMParser","ElementPrototype","cloneNode","getNextSibling","getChildNodes","getParentNode","template","createElement","content","ownerDocument","trustedTypesPolicy","emptyHTML","implementation","createNodeIterator","createDocumentFragment","getElementsByTagName","importNode","hooks","createHTMLDocument","EXPRESSIONS","ALLOWED_TAGS","DEFAULT_ALLOWED_TAGS","TAGS","ALLOWED_ATTR","DEFAULT_ALLOWED_ATTR","ATTRS","CUSTOM_ELEMENT_HANDLING","tagNameCheck","writable","configurable","enumerable","attributeNameCheck","allowCustomizedBuiltInElements","FORBID_TAGS","FORBID_ATTR","ALLOW_ARIA_ATTR","ALLOW_DATA_ATTR","ALLOW_UNKNOWN_PROTOCOLS","ALLOW_SELF_CLOSE_IN_ATTR","SAFE_FOR_TEMPLATES","WHOLE_DOCUMENT","SET_CONFIG","FORCE_BODY","RETURN_DOM","RETURN_DOM_FRAGMENT","RETURN_TRUSTED_TYPE","SANITIZE_DOM","SANITIZE_NAMED_PROPS","SANITIZE_NAMED_PROPS_PREFIX","KEEP_CONTENT","IN_PLACE","USE_PROFILES","FORBID_CONTENTS","DEFAULT_FORBID_CONTENTS","DATA_URI_TAGS","DEFAULT_DATA_URI_TAGS","URI_SAFE_ATTRIBUTES","DEFAULT_URI_SAFE_ATTRIBUTES","MATHML_NAMESPACE","SVG_NAMESPACE","HTML_NAMESPACE","NAMESPACE","IS_EMPTY_INPUT","ALLOWED_NAMESPACES","DEFAULT_ALLOWED_NAMESPACES","PARSER_MEDIA_TYPE","SUPPORTED_PARSER_MEDIA_TYPES","DEFAULT_PARSER_MEDIA_TYPE","CONFIG","formElement","isRegexOrFunction","testValue","Function","_parseConfig","cfg","ADD_URI_SAFE_ATTR","ADD_DATA_URI_TAGS","ALLOWED_URI_REGEXP","ADD_TAGS","ADD_ATTR","table","tbody","TRUSTED_TYPES_POLICY","MATHML_TEXT_INTEGRATION_POINTS","HTML_INTEGRATION_POINTS","COMMON_SVG_AND_HTML_ELEMENTS","ALL_SVG_TAGS","ALL_MATHML_TAGS","_checkValidNamespace","parent","tagName","namespaceURI","parentTagName","Boolean","_forceRemove","node","parentNode","removeChild","remove","_removeAttribute","name","attribute","getAttributeNode","from","removeAttribute","setAttribute","_initDocument","dirty","doc","leadingWhitespace","matches","dirtyPayload","parseFromString","documentElement","createDocument","innerHTML","body","insertBefore","createTextNode","childNodes","call","_createNodeIterator","SHOW_ELEMENT","SHOW_COMMENT","SHOW_TEXT","_isClobbered","elm","nodeName","textContent","attributes","hasChildNodes","_isNode","_executeHook","entryPoint","currentNode","data","hook","_sanitizeElements","allowedTags","firstElementChild","_isBasicCustomElement","i","expr","_isValidAttribute","lcTag","lcName","_sanitizeAttributes","hookEvent","attrName","attrValue","keepAttr","allowedAttributes","attr","forceKeepAttr","getAttributeType","setAttributeNS","_sanitizeShadowDOM","fragment","shadowNode","shadowIterator","nextNode","sanitize","importedNode","returnNode","appendChild","firstChild","nodeIterator","shadowroot","shadowrootmode","serializedHTML","outerHTML","doctype","setConfig","clearConfig","isValidAttribute","tag","addHook","hookFunction","removeHook","removeHooks","removeAllHooks"],"mappings":";0OAAA,MAAMA,QACJA,EADIC,eAEJA,EAFIC,SAGJA,EAHIC,eAIJA,EAJIC,yBAKJA,GACEC,OAEJ,IAAIC,OAAEA,EAAFC,KAAUA,EAAVC,OAAgBA,GAAWH,QAC3BI,MAAEA,EAAFC,UAASA,GAAiC,oBAAZC,SAA2BA,QAExDL,IACHA,EAAS,SAAUM,GACjB,OAAOA,CACR,GAGEL,IACHA,EAAO,SAAUK,GACf,OAAOA,CACR,GAGEH,IACHA,EAAQ,SAAUI,EAAKC,EAAWC,GAChC,OAAOF,EAAIJ,MAAMK,EAAWC,EAC7B,GAGEL,IACHA,EAAY,SAAUM,EAAMD,GAC1B,OAAO,IAAIC,KAAQD,EACpB,GAGH,MAAME,EAAeC,EAAQC,MAAMC,UAAUC,SAEvCC,EAAWJ,EAAQC,MAAMC,UAAUG,KACnCC,EAAYN,EAAQC,MAAMC,UAAUK,MAGpCC,EAAoBR,EAAQS,OAAOP,UAAUQ,aAC7CC,EAAiBX,EAAQS,OAAOP,UAAUU,UAC1CC,EAAcb,EAAQS,OAAOP,UAAUY,OACvCC,EAAgBf,EAAQS,OAAOP,UAAUc,SACzCC,EAAgBjB,EAAQS,OAAOP,UAAUgB,SACzCC,EAAanB,EAAQS,OAAOP,UAAUkB,MAEtCC,EAAarB,EAAQsB,OAAOpB,UAAUqB,MAEtCC,GAkBeC,EAlBeC,UAmB3B,WAAA,IAAA,IAAAC,EAAAC,UAAAC,OAAIhC,EAAJ,IAAAI,MAAA0B,GAAAG,EAAA,EAAAA,EAAAH,EAAAG,IAAIjC,EAAJiC,GAAAF,UAAAE,GAAA,OAAatC,EAAUiC,EAAM5B,EAA7B,GADT,IAAqB4B,EAVrB,SAASzB,EAAQyB,GACf,OAAO,SAACM,GAAD,IAAA,IAAAC,EAAAJ,UAAAC,OAAahC,EAAb,IAAAI,MAAA+B,EAAA,EAAAA,EAAA,EAAA,GAAAC,EAAA,EAAAA,EAAAD,EAAAC,IAAapC,EAAboC,EAAA,GAAAL,UAAAK,GAAA,OAAsB1C,EAAMkC,EAAMM,EAASlC,EAA3C,CACR,CAoBD,SAASqC,EAASC,EAAKC,GAAOC,IAAAA,yDAAoB7B,EAC5CzB,GAIFA,EAAeoD,EAAK,MAGtB,IAAIG,EAAIF,EAAMP,OACPS,KAAAA,KAAK,CACV,IAAIC,EAAUH,EAAME,GACpB,GAAuB,iBAAZC,EAAsB,CAC/B,MAAMC,EAAYH,EAAkBE,GAChCC,IAAcD,IAEXvD,EAASoD,KACZA,EAAME,GAAKE,GAGbD,EAAUC,EAEb,CAEDL,EAAII,IAAW,CAChB,CAED,OAAOJ,CACR,CAQM,SAASM,EAAMC,GACpB,MAAMC,EAAYrD,EAAO,MAEpB,IAAA,MAAOsD,EAAUC,KAAU/D,EAAQ4D,QACaI,IAA/C5D,EAAyBwD,EAAQE,KACnCD,EAAUC,GAAYC,GAI1B,OAAOF,CACR,CASD,SAASI,EAAaL,EAAQM,GACrBN,KAAW,OAAXA,GAAiB,CACtB,MAAMO,EAAO/D,EAAyBwD,EAAQM,GAE9C,GAAIC,EAAM,CACJA,GAAAA,EAAKC,IACP,OAAOlD,EAAQiD,EAAKC,KAGtB,GAA0B,mBAAfD,EAAKJ,MACd,OAAO7C,EAAQiD,EAAKJ,MAEvB,CAEDH,EAASzD,EAAeyD,EACzB,CAOD,OALSS,SAAcZ,GAErB,OADAa,QAAQC,KAAK,qBAAsBd,GAC5B,IACR,CAGF,CC3JM,MAAMe,EAAOlE,EAAO,CACzB,IACA,OACA,UACA,UACA,OACA,UACA,QACA,QACA,IACA,MACA,MACA,MACA,QACA,aACA,OACA,KACA,SACA,SACA,UACA,SACA,OACA,OACA,MACA,WACA,UACA,OACA,WACA,KACA,YACA,MACA,UACA,MACA,SACA,MACA,MACA,KACA,KACA,UACA,KACA,WACA,aACA,SACA,OACA,SACA,OACA,KACA,KACA,KACA,KACA,KACA,KACA,OACA,SACA,SACA,KACA,OACA,IACA,MACA,QACA,MACA,MACA,QACA,SACA,KACA,OACA,MACA,OACA,UACA,OACA,WACA,QACA,MACA,OACA,KACA,WACA,SACA,SACA,IACA,UACA,MACA,WACA,IACA,KACA,KACA,OACA,IACA,OACA,UACA,SACA,SACA,QACA,SACA,SACA,OACA,SACA,SACA,QACA,MACA,UACA,MACA,QACA,QACA,KACA,WACA,WACA,QACA,KACA,QACA,OACA,KACA,QACA,KACA,IACA,KACA,MACA,QACA,QAIWmE,EAAMnE,EAAO,CACxB,MACA,IACA,WACA,cACA,eACA,eACA,gBACA,mBACA,SACA,WACA,OACA,OACA,UACA,SACA,OACA,IACA,QACA,WACA,QACA,QACA,OACA,iBACA,SACA,OACA,WACA,QACA,OACA,UACA,UACA,WACA,iBACA,OACA,OACA,QACA,SACA,SACA,OACA,WACA,QACA,OACA,QACA,OACA,UAGWoE,EAAapE,EAAO,CAC/B,UACA,gBACA,sBACA,cACA,mBACA,oBACA,oBACA,iBACA,eACA,UACA,UACA,UACA,UACA,UACA,iBACA,UACA,UACA,cACA,eACA,WACA,eACA,qBACA,cACA,SACA,iBAOWqE,EAAgBrE,EAAO,CAClC,UACA,gBACA,SACA,UACA,YACA,mBACA,iBACA,gBACA,gBACA,gBACA,QACA,YACA,OACA,eACA,YACA,UACA,gBACA,SACA,MACA,aACA,UACA,QAGWsE,EAAStE,EAAO,CAC3B,OACA,WACA,SACA,UACA,QACA,SACA,KACA,aACA,gBACA,KACA,KACA,QACA,UACA,WACA,QACA,OACA,KACA,SACA,QACA,SACA,OACA,OACA,UACA,SACA,MACA,QACA,MACA,SACA,aACA,gBAKWuE,EAAmBvE,EAAO,CACrC,UACA,cACA,aACA,WACA,YACA,UACA,UACA,SACA,SACA,QACA,YACA,aACA,iBACA,cACA,SAGWwE,EAAOxE,EAAO,CAAC,UCrRfkE,EAAOlE,EAAO,CACzB,SACA,SACA,QACA,MACA,iBACA,eACA,uBACA,WACA,aACA,UACA,SACA,UACA,cACA,cACA,UACA,OACA,QACA,QACA,QACA,OACA,UACA,WACA,eACA,SACA,cACA,WACA,WACA,UACA,MACA,WACA,0BACA,wBACA,WACA,YACA,UACA,eACA,OACA,MACA,UACA,SACA,SACA,OACA,OACA,WACA,KACA,YACA,YACA,QACA,OACA,QACA,OACA,OACA,UACA,OACA,MACA,MACA,YACA,QACA,SACA,MACA,YACA,WACA,QACA,OACA,QACA,UACA,aACA,SACA,OACA,UACA,UACA,cACA,cACA,SACA,UACA,UACA,aACA,WACA,MACA,WACA,MACA,WACA,OACA,OACA,UACA,aACA,QACA,WACA,QACA,OACA,QACA,OACA,UACA,QACA,MACA,SACA,OACA,QACA,UACA,WACA,QACA,YACA,OACA,SACA,SACA,QACA,QACA,QACA,SAGWmE,EAAMnE,EAAO,CACxB,gBACA,aACA,WACA,qBACA,SACA,gBACA,gBACA,UACA,gBACA,iBACA,QACA,OACA,KACA,QACA,OACA,gBACA,YACA,YACA,QACA,sBACA,8BACA,gBACA,kBACA,KACA,KACA,IACA,KACA,KACA,kBACA,YACA,UACA,UACA,MACA,WACA,YACA,MACA,OACA,eACA,YACA,SACA,cACA,cACA,gBACA,cACA,YACA,mBACA,eACA,aACA,eACA,cACA,KACA,KACA,KACA,KACA,aACA,WACA,gBACA,oBACA,SACA,OACA,KACA,kBACA,KACA,MACA,IACA,KACA,KACA,KACA,KACA,UACA,YACA,aACA,WACA,OACA,eACA,iBACA,eACA,mBACA,iBACA,QACA,aACA,aACA,eACA,eACA,cACA,cACA,mBACA,YACA,MACA,OACA,QACA,SACA,OACA,MACA,OACA,aACA,SACA,WACA,UACA,QACA,SACA,cACA,SACA,WACA,cACA,OACA,aACA,sBACA,mBACA,eACA,SACA,gBACA,sBACA,iBACA,IACA,KACA,KACA,SACA,OACA,OACA,cACA,YACA,UACA,SACA,SACA,QACA,OACA,kBACA,mBACA,mBACA,eACA,cACA,eACA,cACA,aACA,eACA,mBACA,oBACA,iBACA,kBACA,oBACA,iBACA,SACA,eACA,QACA,eACA,iBACA,WACA,UACA,UACA,YACA,mBACA,cACA,kBACA,iBACA,aACA,OACA,KACA,KACA,UACA,SACA,UACA,aACA,UACA,aACA,gBACA,gBACA,QACA,eACA,OACA,eACA,mBACA,mBACA,IACA,KACA,KACA,QACA,IACA,KACA,KACA,IACA,eAGWsE,EAA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"} \ No newline at end of file diff --git a/src/ui/static/js/utils/purify/src/attrs.js b/src/ui/static/js/utils/purify/src/attrs.js new file mode 100644 index 00000000..09f2a0fb --- /dev/null +++ b/src/ui/static/js/utils/purify/src/attrs.js @@ -0,0 +1,362 @@ +import { freeze } from './utils.js'; + +export 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', + 'xmlns', + 'slot', +]); + +export 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', +]); + +export 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', +]); + +export const xml = freeze([ + 'xlink:href', + 'xml:id', + 'xlink:title', + 'xml:space', + 'xmlns:xlink', +]); diff --git a/src/ui/static/js/utils/purify/src/purify.js b/src/ui/static/js/utils/purify/src/purify.js new file mode 100644 index 00000000..f6511db6 --- /dev/null +++ b/src/ui/static/js/utils/purify/src/purify.js @@ -0,0 +1,1637 @@ +import * as TAGS from './tags.js'; +import * as ATTRS from './attrs.js'; +import * as EXPRESSIONS from './regexp.js'; +import { + addToSet, + clone, + entries, + freeze, + arrayForEach, + arrayPop, + arrayPush, + stringMatch, + stringReplace, + stringToLowerCase, + stringToString, + stringIndexOf, + stringTrim, + regExpTest, + typeErrorCreate, + lookupGetter, + create, +} from './utils.js'; + +const getGlobal = function () { + return typeof window === 'undefined' ? null : window; +}; + +/** + * Creates a no-op policy for internal use only. + * Don't export this function outside this module! + * @param {?TrustedTypePolicyFactory} trustedTypes The policy factory. + * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix). + * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types + * are not supported or creating the policy failed). + */ +const _createTrustedTypesPolicy = function (trustedTypes, purifyHostElement) { + if ( + typeof trustedTypes !== 'object' || + typeof trustedTypes.createPolicy !== 'function' + ) { + return null; + } + + // Allow the callers to control the unique policy name + // by adding a data-tt-policy-suffix to the script element with the DOMPurify. + // Policy creation with duplicate names throws in Trusted Types. + 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 (_) { + // Policy creation failed (most likely another DOMPurify script has + // already run). Skip creating the policy, as this will only cause errors + // if TT are enforced. + console.warn( + 'TrustedTypes policy ' + policyName + ' could not be created.' + ); + return null; + } +}; + +function createDOMPurify(window = getGlobal()) { + const DOMPurify = (root) => createDOMPurify(root); + + /** + * Version label, exposed for easier checks + * if DOMPurify is up to date or not + */ + DOMPurify.version = VERSION; + + /** + * Array of elements that DOMPurify removed during sanitation. + * Empty if nothing was removed. + */ + DOMPurify.removed = []; + + if (!window || !window.document || window.document.nodeType !== 9) { + // Not running in a browser, provide a factory function + // so that you can pass your own Window + DOMPurify.isSupported = false; + + return DOMPurify; + } + + let { document } = window; + + const originalDocument = document; + const currentScript = originalDocument.currentScript; + const { + DocumentFragment, + HTMLTemplateElement, + Node, + Element, + NodeFilter, + NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap, + HTMLFormElement, + DOMParser, + trustedTypes, + } = window; + + const ElementPrototype = Element.prototype; + + const cloneNode = lookupGetter(ElementPrototype, 'cloneNode'); + const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling'); + const getChildNodes = lookupGetter(ElementPrototype, 'childNodes'); + const getParentNode = lookupGetter(ElementPrototype, 'parentNode'); + + // As per issue #47, the web-components registry is inherited by a + // new document created via createHTMLDocument. As per the spec + // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) + // a new empty registry is used when creating a template contents owner + // document, so we use that as our parent document to ensure nothing + // is inherited. + if (typeof HTMLTemplateElement === 'function') { + const template = document.createElement('template'); + if (template.content && template.content.ownerDocument) { + document = template.content.ownerDocument; + } + } + + let trustedTypesPolicy; + let emptyHTML = ''; + + const { + implementation, + createNodeIterator, + createDocumentFragment, + getElementsByTagName, + } = document; + const { importNode } = originalDocument; + + let hooks = {}; + + /** + * Expose whether this browser supports running the full DOMPurify. + */ + DOMPurify.isSupported = + typeof entries === 'function' && + typeof getParentNode === 'function' && + implementation && + implementation.createHTMLDocument !== undefined; + + const { + MUSTACHE_EXPR, + ERB_EXPR, + TMPLIT_EXPR, + DATA_ATTR, + ARIA_ATTR, + IS_SCRIPT_OR_DATA, + ATTR_WHITESPACE, + } = EXPRESSIONS; + + let { IS_ALLOWED_URI } = EXPRESSIONS; + + /** + * We consider the elements and attributes below to be safe. Ideally + * don't add any new ones but feel free to remove unwanted ones. + */ + + /* allowed element names */ + let ALLOWED_TAGS = null; + const DEFAULT_ALLOWED_TAGS = addToSet({}, [ + ...TAGS.html, + ...TAGS.svg, + ...TAGS.svgFilters, + ...TAGS.mathMl, + ...TAGS.text, + ]); + + /* Allowed attribute names */ + let ALLOWED_ATTR = null; + const DEFAULT_ALLOWED_ATTR = addToSet({}, [ + ...ATTRS.html, + ...ATTRS.svg, + ...ATTRS.mathMl, + ...ATTRS.xml, + ]); + + /* + * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements. + * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements) + * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list) + * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`. + */ + 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, + }, + }) + ); + + /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ + let FORBID_TAGS = null; + + /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ + let FORBID_ATTR = null; + + /* Decide if ARIA attributes are okay */ + let ALLOW_ARIA_ATTR = true; + + /* Decide if custom data attributes are okay */ + let ALLOW_DATA_ATTR = true; + + /* Decide if unknown protocols are okay */ + let ALLOW_UNKNOWN_PROTOCOLS = false; + + /* Decide if self-closing tags in attributes are allowed. + * Usually removed due to a mXSS issue in jQuery 3.0 */ + let ALLOW_SELF_CLOSE_IN_ATTR = true; + + /* Output should be safe for common template engines. + * This means, DOMPurify removes data attributes, mustaches and ERB + */ + let SAFE_FOR_TEMPLATES = false; + + /* Decide if document with ... should be returned */ + let WHOLE_DOCUMENT = false; + + /* Track whether config is already set on this instance of DOMPurify. */ + let SET_CONFIG = false; + + /* Decide if all elements (e.g. style, script) must be children of + * document.body. By default, browsers might move them to document.head */ + let FORCE_BODY = false; + + /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html + * string (or a TrustedHTML object if Trusted Types are supported). + * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead + */ + let RETURN_DOM = false; + + /* Decide if a DOM `DocumentFragment` should be returned, instead of a html + * string (or a TrustedHTML object if Trusted Types are supported) */ + let RETURN_DOM_FRAGMENT = false; + + /* Try to return a Trusted Type object instead of a string, return a string in + * case Trusted Types are not supported */ + let RETURN_TRUSTED_TYPE = false; + + /* Output should be free from DOM clobbering attacks? + * This sanitizes markups named with colliding, clobberable built-in DOM APIs. + */ + let SANITIZE_DOM = true; + + /* Achieve full DOM Clobbering protection by isolating the namespace of named + * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules. + * + * HTML/DOM spec rules that enable DOM Clobbering: + * - Named Access on Window (§7.3.3) + * - DOM Tree Accessors (§3.1.5) + * - Form Element Parent-Child Relations (§4.10.3) + * - Iframe srcdoc / Nested WindowProxies (§4.8.5) + * - HTMLCollection (§4.2.10.2) + * + * Namespace isolation is implemented by prefixing `id` and `name` attributes + * with a constant string, i.e., `user-content-` + */ + let SANITIZE_NAMED_PROPS = false; + const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-'; + + /* Keep element content when removing element? */ + let KEEP_CONTENT = true; + + /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead + * of importing it into a new Document and returning a sanitized copy */ + let IN_PLACE = false; + + /* Allow usage of profiles like html, svg and mathMl */ + let USE_PROFILES = {}; + + /* Tags to ignore content of when KEEP_CONTENT is true */ + 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', + 'style', + 'svg', + 'template', + 'thead', + 'title', + 'video', + 'xmp', + ]); + + /* Tags that are safe for data: URIs */ + let DATA_URI_TAGS = null; + const DEFAULT_DATA_URI_TAGS = addToSet({}, [ + 'audio', + 'video', + 'img', + 'source', + 'image', + 'track', + ]); + + /* Attributes safe for values like "javascript:" */ + 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'; + /* Document namespace */ + let NAMESPACE = HTML_NAMESPACE; + let IS_EMPTY_INPUT = false; + + /* Allowed XHTML+XML namespaces */ + let ALLOWED_NAMESPACES = null; + const DEFAULT_ALLOWED_NAMESPACES = addToSet( + {}, + [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], + stringToString + ); + + /* Parsing of strict XHTML documents */ + 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; + + /* Keep a reference to config to pass to hooks */ + let CONFIG = null; + + /* Ideally, do not touch anything below this line */ + /* ______________________________________________ */ + + const formElement = document.createElement('form'); + + const isRegexOrFunction = function (testValue) { + return testValue instanceof RegExp || testValue instanceof Function; + }; + + /** + * _parseConfig + * + * @param {Object} cfg optional config literal + */ + // eslint-disable-next-line complexity + const _parseConfig = function (cfg = {}) { + if (CONFIG && CONFIG === cfg) { + return; + } + + /* Shield configuration object from tampering */ + if (!cfg || typeof cfg !== 'object') { + cfg = {}; + } + + /* Shield configuration object from prototype pollution */ + cfg = clone(cfg); + + PARSER_MEDIA_TYPE = + // eslint-disable-next-line unicorn/prefer-includes + SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 + ? (PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE) + : (PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE); + + // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. + transformCaseFunc = + PARSER_MEDIA_TYPE === 'application/xhtml+xml' + ? stringToString + : stringToLowerCase; + + /* Set configuration parameters */ + ALLOWED_TAGS = + 'ALLOWED_TAGS' in cfg + ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) + : DEFAULT_ALLOWED_TAGS; + ALLOWED_ATTR = + 'ALLOWED_ATTR' in cfg + ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) + : DEFAULT_ALLOWED_ATTR; + ALLOWED_NAMESPACES = + 'ALLOWED_NAMESPACES' in cfg + ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) + : DEFAULT_ALLOWED_NAMESPACES; + URI_SAFE_ATTRIBUTES = + 'ADD_URI_SAFE_ATTR' in cfg + ? addToSet( + clone(DEFAULT_URI_SAFE_ATTRIBUTES), // eslint-disable-line indent + cfg.ADD_URI_SAFE_ATTR, // eslint-disable-line indent + transformCaseFunc // eslint-disable-line indent + ) // eslint-disable-line indent + : DEFAULT_URI_SAFE_ATTRIBUTES; + DATA_URI_TAGS = + 'ADD_DATA_URI_TAGS' in cfg + ? addToSet( + clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent + cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent + transformCaseFunc // eslint-disable-line indent + ) // eslint-disable-line indent + : DEFAULT_DATA_URI_TAGS; + FORBID_CONTENTS = + 'FORBID_CONTENTS' in cfg + ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) + : DEFAULT_FORBID_CONTENTS; + FORBID_TAGS = + 'FORBID_TAGS' in cfg + ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) + : {}; + FORBID_ATTR = + 'FORBID_ATTR' in cfg + ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) + : {}; + USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false; + ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true + ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true + ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false + ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true + SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false + WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false + RETURN_DOM = cfg.RETURN_DOM || false; // Default false + RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false + RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false + FORCE_BODY = cfg.FORCE_BODY || false; // Default false + SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true + SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false + KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true + IN_PLACE = cfg.IN_PLACE || false; // Default false + IS_ALLOWED_URI = cfg.ALLOWED_URI_REGEXP || EXPRESSIONS.IS_ALLOWED_URI; + NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE; + CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {}; + if ( + cfg.CUSTOM_ELEMENT_HANDLING && + isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck) + ) { + CUSTOM_ELEMENT_HANDLING.tagNameCheck = + cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck; + } + + if ( + cfg.CUSTOM_ELEMENT_HANDLING && + isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck) + ) { + CUSTOM_ELEMENT_HANDLING.attributeNameCheck = + cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck; + } + + if ( + cfg.CUSTOM_ELEMENT_HANDLING && + typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === + 'boolean' + ) { + CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = + cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements; + } + + if (SAFE_FOR_TEMPLATES) { + ALLOW_DATA_ATTR = false; + } + + if (RETURN_DOM_FRAGMENT) { + RETURN_DOM = true; + } + + /* Parse profile info */ + if (USE_PROFILES) { + ALLOWED_TAGS = addToSet({}, [...TAGS.text]); + ALLOWED_ATTR = []; + if (USE_PROFILES.html === true) { + addToSet(ALLOWED_TAGS, TAGS.html); + addToSet(ALLOWED_ATTR, ATTRS.html); + } + + if (USE_PROFILES.svg === true) { + addToSet(ALLOWED_TAGS, TAGS.svg); + addToSet(ALLOWED_ATTR, ATTRS.svg); + addToSet(ALLOWED_ATTR, ATTRS.xml); + } + + if (USE_PROFILES.svgFilters === true) { + addToSet(ALLOWED_TAGS, TAGS.svgFilters); + addToSet(ALLOWED_ATTR, ATTRS.svg); + addToSet(ALLOWED_ATTR, ATTRS.xml); + } + + if (USE_PROFILES.mathMl === true) { + addToSet(ALLOWED_TAGS, TAGS.mathMl); + addToSet(ALLOWED_ATTR, ATTRS.mathMl); + addToSet(ALLOWED_ATTR, ATTRS.xml); + } + } + + /* Merge configuration parameters */ + if (cfg.ADD_TAGS) { + if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { + ALLOWED_TAGS = clone(ALLOWED_TAGS); + } + + addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc); + } + + if (cfg.ADD_ATTR) { + if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { + ALLOWED_ATTR = clone(ALLOWED_ATTR); + } + + addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc); + } + + if (cfg.ADD_URI_SAFE_ATTR) { + addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc); + } + + if (cfg.FORBID_CONTENTS) { + if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) { + FORBID_CONTENTS = clone(FORBID_CONTENTS); + } + + addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc); + } + + /* Add #text in case KEEP_CONTENT is set to true */ + if (KEEP_CONTENT) { + ALLOWED_TAGS['#text'] = true; + } + + /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ + if (WHOLE_DOCUMENT) { + addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); + } + + /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */ + 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.' + ); + } + + // Overwrite existing TrustedTypes policy. + trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY; + + // Sign local variables required by `sanitize`. + emptyHTML = trustedTypesPolicy.createHTML(''); + } else { + // Uninitialized policy, attempt to initialize the internal dompurify policy. + if (trustedTypesPolicy === undefined) { + trustedTypesPolicy = _createTrustedTypesPolicy( + trustedTypes, + currentScript + ); + } + + // If creating the internal policy succeeded sign internal variables. + if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') { + emptyHTML = trustedTypesPolicy.createHTML(''); + } + } + + // Prevent further manipulation of configuration. + // Not available in IE8, Safari 5, etc. + if (freeze) { + freeze(cfg); + } + + CONFIG = cfg; + }; + + const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, [ + 'mi', + 'mo', + 'mn', + 'ms', + 'mtext', + ]); + + const HTML_INTEGRATION_POINTS = addToSet({}, [ + 'foreignobject', + 'desc', + 'title', + 'annotation-xml', + ]); + + // Certain elements are allowed in both SVG and HTML + // namespace. We need to specify them explicitly + // so that they don't get erroneously deleted from + // HTML namespace. + const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [ + 'title', + 'style', + 'font', + 'a', + 'script', + ]); + + /* Keep track of all possible SVG and MathML tags + * so that we can perform the namespace checks + * correctly. */ + const ALL_SVG_TAGS = addToSet({}, TAGS.svg); + addToSet(ALL_SVG_TAGS, TAGS.svgFilters); + addToSet(ALL_SVG_TAGS, TAGS.svgDisallowed); + + const ALL_MATHML_TAGS = addToSet({}, TAGS.mathMl); + addToSet(ALL_MATHML_TAGS, TAGS.mathMlDisallowed); + + /** + * @param {Element} element a DOM element whose namespace is being checked + * @returns {boolean} Return false if the element has a + * namespace that a spec-compliant parser would never + * return. Return true otherwise. + */ + const _checkValidNamespace = function (element) { + let parent = getParentNode(element); + + // In JSDOM, if we're inside shadow DOM, then parentNode + // can be null. We just simulate parent in this case. + 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) { + // The only way to switch from HTML namespace to SVG + // is via . If it happens via any other tag, then + // it should be killed. + if (parent.namespaceURI === HTML_NAMESPACE) { + return tagName === 'svg'; + } + + // The only way to switch from MathML to SVG is via` + // svg if parent is either or MathML + // text integration points. + if (parent.namespaceURI === MATHML_NAMESPACE) { + return ( + tagName === 'svg' && + (parentTagName === 'annotation-xml' || + MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) + ); + } + + // We only allow elements that are defined in SVG + // spec. All others are disallowed in SVG namespace. + return Boolean(ALL_SVG_TAGS[tagName]); + } + + if (element.namespaceURI === MATHML_NAMESPACE) { + // The only way to switch from HTML namespace to MathML + // is via . If it happens via any other tag, then + // it should be killed. + if (parent.namespaceURI === HTML_NAMESPACE) { + return tagName === 'math'; + } + + // The only way to switch from SVG to MathML is via + // and HTML integration points + if (parent.namespaceURI === SVG_NAMESPACE) { + return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName]; + } + + // We only allow elements that are defined in MathML + // spec. All others are disallowed in MathML namespace. + return Boolean(ALL_MATHML_TAGS[tagName]); + } + + if (element.namespaceURI === HTML_NAMESPACE) { + // The only way to switch from SVG to HTML is via + // HTML integration points, and from MathML to HTML + // is via MathML text integration points + if ( + parent.namespaceURI === SVG_NAMESPACE && + !HTML_INTEGRATION_POINTS[parentTagName] + ) { + return false; + } + + if ( + parent.namespaceURI === MATHML_NAMESPACE && + !MATHML_TEXT_INTEGRATION_POINTS[parentTagName] + ) { + return false; + } + + // We disallow tags that are specific for MathML + // or SVG and should never appear in HTML namespace + return ( + !ALL_MATHML_TAGS[tagName] && + (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]) + ); + } + + // For XHTML and XML documents that support custom namespaces + if ( + PARSER_MEDIA_TYPE === 'application/xhtml+xml' && + ALLOWED_NAMESPACES[element.namespaceURI] + ) { + return true; + } + + // The code should never reach this place (this means + // that the element somehow got namespace that is not + // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES). + // Return false just in case. + return false; + }; + + /** + * _forceRemove + * + * @param {Node} node a DOM node + */ + const _forceRemove = function (node) { + arrayPush(DOMPurify.removed, { element: node }); + try { + // eslint-disable-next-line unicorn/prefer-dom-node-remove + node.parentNode.removeChild(node); + } catch (_) { + node.remove(); + } + }; + + /** + * _removeAttribute + * + * @param {String} name an Attribute name + * @param {Node} node a DOM node + */ + const _removeAttribute = function (name, node) { + try { + arrayPush(DOMPurify.removed, { + attribute: node.getAttributeNode(name), + from: node, + }); + } catch (_) { + arrayPush(DOMPurify.removed, { + attribute: null, + from: node, + }); + } + + node.removeAttribute(name); + + // We void attribute values for unremovable "is"" attributes + if (name === 'is' && !ALLOWED_ATTR[name]) { + if (RETURN_DOM || RETURN_DOM_FRAGMENT) { + try { + _forceRemove(node); + } catch (_) {} + } else { + try { + node.setAttribute(name, ''); + } catch (_) {} + } + } + }; + + /** + * _initDocument + * + * @param {String} dirty a string of dirty markup + * @return {Document} a DOM, filled with the dirty markup + */ + const _initDocument = function (dirty) { + /* Create a HTML document */ + let doc = null; + let leadingWhitespace = null; + + if (FORCE_BODY) { + dirty = '' + dirty; + } else { + /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */ + const matches = stringMatch(dirty, /^[\r\n\t ]+/); + leadingWhitespace = matches && matches[0]; + } + + if ( + PARSER_MEDIA_TYPE === 'application/xhtml+xml' && + NAMESPACE === HTML_NAMESPACE + ) { + // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict) + dirty = + '' + + dirty + + ''; + } + + const dirtyPayload = trustedTypesPolicy + ? trustedTypesPolicy.createHTML(dirty) + : dirty; + /* + * Use the DOMParser API by default, fallback later if needs be + * DOMParser not work for svg when has multiple root element. + */ + if (NAMESPACE === HTML_NAMESPACE) { + try { + doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE); + } catch (_) {} + } + + /* Use createHTMLDocument in case DOMParser is not available */ + if (!doc || !doc.documentElement) { + doc = implementation.createDocument(NAMESPACE, 'template', null); + try { + doc.documentElement.innerHTML = IS_EMPTY_INPUT + ? emptyHTML + : dirtyPayload; + } catch (_) { + // Syntax error if dirtyPayload is invalid xml + } + } + + const body = doc.body || doc.documentElement; + + if (dirty && leadingWhitespace) { + body.insertBefore( + document.createTextNode(leadingWhitespace), + body.childNodes[0] || null + ); + } + + /* Work on whole document or just its body */ + 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 {Node} root The root element or node to start traversing on. + * @return {NodeIterator} The created NodeIterator + */ + const _createNodeIterator = function (root) { + return createNodeIterator.call( + root.ownerDocument || root, + root, + // eslint-disable-next-line no-bitwise + NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, + null + ); + }; + + /** + * _isClobbered + * + * @param {Node} elm element to check for clobbering attacks + * @return {Boolean} true if clobbered, false if safe + */ + const _isClobbered = function (elm) { + return ( + elm instanceof HTMLFormElement && + (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') + ); + }; + + /** + * Checks whether the given object is a DOM node. + * + * @param {Node} object object to check whether it's a DOM node + * @return {Boolean} true is object is a DOM node + */ + const _isNode = function (object) { + return typeof Node === 'function' && object instanceof Node; + }; + + /** + * _executeHook + * Execute user configurable hooks + * + * @param {String} entryPoint Name of the hook's entry point + * @param {Node} currentNode node to work on with the hook + * @param {Object} data additional hook parameters + */ + const _executeHook = function (entryPoint, currentNode, data) { + if (!hooks[entryPoint]) { + return; + } + + arrayForEach(hooks[entryPoint], (hook) => { + hook.call(DOMPurify, currentNode, data, CONFIG); + }); + }; + + /** + * _sanitizeElements + * + * @protect nodeName + * @protect textContent + * @protect removeChild + * + * @param {Node} currentNode to check for permission to exist + * @return {Boolean} true if node was killed, false if left alive + */ + const _sanitizeElements = function (currentNode) { + let content = null; + + /* Execute a hook if present */ + _executeHook('beforeSanitizeElements', currentNode, null); + + /* Check if element is clobbered or can clobber */ + if (_isClobbered(currentNode)) { + _forceRemove(currentNode); + return true; + } + + /* Now let's check the element's type and name */ + const tagName = transformCaseFunc(currentNode.nodeName); + + /* Execute a hook if present */ + _executeHook('uponSanitizeElement', currentNode, { + tagName, + allowedTags: ALLOWED_TAGS, + }); + + /* Detect mXSS attempts abusing namespace confusion */ + if ( + currentNode.hasChildNodes() && + !_isNode(currentNode.firstElementChild) && + regExpTest(/<[/\w]/g, currentNode.innerHTML) && + regExpTest(/<[/\w]/g, currentNode.textContent) + ) { + _forceRemove(currentNode); + return true; + } + + /* Remove element if anything forbids its presence */ + if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { + /* Check if we have a custom element to handle */ + 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; + } + } + + /* Keep content except for bad-listed elements */ + if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) { + const parentNode = getParentNode(currentNode) || currentNode.parentNode; + const childNodes = getChildNodes(currentNode) || currentNode.childNodes; + + if (childNodes && parentNode) { + const childCount = childNodes.length; + + for (let i = childCount - 1; i >= 0; --i) { + parentNode.insertBefore( + cloneNode(childNodes[i], true), + getNextSibling(currentNode) + ); + } + } + } + + _forceRemove(currentNode); + return true; + } + + /* Check whether element has a valid namespace */ + if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) { + _forceRemove(currentNode); + return true; + } + + /* Make sure that older browsers don't get fallback-tag mXSS */ + if ( + (tagName === 'noscript' || + tagName === 'noembed' || + tagName === 'noframes') && + regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML) + ) { + _forceRemove(currentNode); + return true; + } + + /* Sanitize element content to be template-safe */ + if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) { + /* Get the element's text content */ + content = currentNode.textContent; + + arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => { + content = stringReplace(content, expr, ' '); + }); + + if (currentNode.textContent !== content) { + arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() }); + currentNode.textContent = content; + } + } + + /* Execute a hook if present */ + _executeHook('afterSanitizeElements', currentNode, null); + + return false; + }; + + /** + * _isValidAttribute + * + * @param {string} lcTag Lowercase tag name of containing element. + * @param {string} lcName Lowercase attribute name. + * @param {string} value Attribute value. + * @return {Boolean} Returns true if `value` is valid, otherwise false. + */ + // eslint-disable-next-line complexity + const _isValidAttribute = function (lcTag, lcName, value) { + /* Make sure attribute cannot clobber */ + if ( + SANITIZE_DOM && + (lcName === 'id' || lcName === 'name') && + (value in document || value in formElement) + ) { + return false; + } + + /* Allow valid data-* attributes: At least one character after "-" + (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) + XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) + We don't need to check the value; it's always URI safe. */ + if ( + ALLOW_DATA_ATTR && + !FORBID_ATTR[lcName] && + regExpTest(DATA_ATTR, lcName) + ) { + // This attribute is safe + } else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) { + // This attribute is safe + /* Otherwise, check the name is permitted */ + } else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) { + if ( + // First condition does a very basic check if a) it's basically a valid custom element tagname AND + // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck + // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck + (_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)))) || + // Alternative, second condition checks if it's an `is`-attribute, AND + // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck + (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)))) + ) { + // If user has supplied a regexp or function in CUSTOM_ELEMENT_HANDLING.tagNameCheck, we need to also allow derived custom elements using the same tagName test. + // Additionally, we need to allow attributes passing the CUSTOM_ELEMENT_HANDLING.attributeNameCheck user has configured, as custom elements can define these at their own discretion. + } else { + return false; + } + /* Check value is safe. First, is attr inert? If so, is safe */ + } else if (URI_SAFE_ATTRIBUTES[lcName]) { + // This attribute is safe + /* Check no script, data or unknown possibly unsafe URI + unless we know URI values are safe for that attribute */ + } else if ( + regExpTest(IS_ALLOWED_URI, stringReplace(value, ATTR_WHITESPACE, '')) + ) { + // This attribute is safe + /* Keep image data URIs alive if src/xlink:href is allowed */ + /* Further prevent gadget XSS for dynamically built script tags */ + } else if ( + (lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && + lcTag !== 'script' && + stringIndexOf(value, 'data:') === 0 && + DATA_URI_TAGS[lcTag] + ) { + // This attribute is safe + /* Allow unknown protocols: This provides support for links that + are handled by protocol handlers which may be unknown ahead of + time, e.g. fb:, spotify: */ + } else if ( + ALLOW_UNKNOWN_PROTOCOLS && + !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, '')) + ) { + // This attribute is safe + /* Check for binary attributes */ + } else if (value) { + return false; + } else { + // Binary attributes are safe at this point + /* Anything else, presume unsafe, do not add it back */ + } + + return true; + }; + + /** + * _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 {string} tagName name of the tag of the node to sanitize + * @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false. + */ + const _isBasicCustomElement = function (tagName) { + return tagName.indexOf('-') > 0; + }; + + /** + * _sanitizeAttributes + * + * @protect attributes + * @protect nodeName + * @protect removeAttribute + * @protect setAttribute + * + * @param {Node} currentNode to sanitize + */ + const _sanitizeAttributes = function (currentNode) { + /* Execute a hook if present */ + _executeHook('beforeSanitizeAttributes', currentNode, null); + + const { attributes } = currentNode; + + /* Check if we have attributes; if not we might have a text node */ + if (!attributes) { + return; + } + + const hookEvent = { + attrName: '', + attrValue: '', + keepAttr: true, + allowedAttributes: ALLOWED_ATTR, + }; + let l = attributes.length; + + /* Go backwards over all attributes; safely remove bad ones */ + while (l--) { + const attr = attributes[l]; + const { name, namespaceURI, value: attrValue } = attr; + const lcName = transformCaseFunc(name); + + let value = name === 'value' ? attrValue : stringTrim(attrValue); + + /* Execute a hook if present */ + hookEvent.attrName = lcName; + hookEvent.attrValue = value; + hookEvent.keepAttr = true; + hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set + _executeHook('uponSanitizeAttribute', currentNode, hookEvent); + value = hookEvent.attrValue; + /* Did the hooks approve of the attribute? */ + if (hookEvent.forceKeepAttr) { + continue; + } + + /* Remove attribute */ + _removeAttribute(name, currentNode); + + /* Did the hooks approve of the attribute? */ + if (!hookEvent.keepAttr) { + continue; + } + + /* Work around a security issue in jQuery 3.0 */ + if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) { + _removeAttribute(name, currentNode); + continue; + } + + /* Sanitize attribute content to be template-safe */ + if (SAFE_FOR_TEMPLATES) { + arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => { + value = stringReplace(value, expr, ' '); + }); + } + + /* Is `value` valid for this attribute? */ + const lcTag = transformCaseFunc(currentNode.nodeName); + if (!_isValidAttribute(lcTag, lcName, value)) { + continue; + } + + /* Full DOM Clobbering protection via namespace isolation, + * Prefix id and name attributes with `user-content-` + */ + if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) { + // Remove the attribute with this value + _removeAttribute(name, currentNode); + + // Prefix the value and later re-create the attribute with the sanitized value + value = SANITIZE_NAMED_PROPS_PREFIX + value; + } + + /* Handle attributes that require Trusted Types */ + if ( + trustedTypesPolicy && + typeof trustedTypes === 'object' && + typeof trustedTypes.getAttributeType === 'function' + ) { + if (namespaceURI) { + /* Namespaces are not yet supported, see https://bugs.chromium.org/p/chromium/issues/detail?id=1305293 */ + } else { + switch (trustedTypes.getAttributeType(lcTag, lcName)) { + case 'TrustedHTML': { + value = trustedTypesPolicy.createHTML(value); + break; + } + + case 'TrustedScriptURL': { + value = trustedTypesPolicy.createScriptURL(value); + break; + } + + default: { + break; + } + } + } + } + + /* Handle invalid data-* attribute set by try-catching it */ + try { + if (namespaceURI) { + currentNode.setAttributeNS(namespaceURI, name, value); + } else { + /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ + currentNode.setAttribute(name, value); + } + + arrayPop(DOMPurify.removed); + } catch (_) {} + } + + /* Execute a hook if present */ + _executeHook('afterSanitizeAttributes', currentNode, null); + }; + + /** + * _sanitizeShadowDOM + * + * @param {DocumentFragment} fragment to iterate over recursively + */ + const _sanitizeShadowDOM = function (fragment) { + let shadowNode = null; + const shadowIterator = _createNodeIterator(fragment); + + /* Execute a hook if present */ + _executeHook('beforeSanitizeShadowDOM', fragment, null); + + while ((shadowNode = shadowIterator.nextNode())) { + /* Execute a hook if present */ + _executeHook('uponSanitizeShadowNode', shadowNode, null); + + /* Sanitize tags and elements */ + if (_sanitizeElements(shadowNode)) { + continue; + } + + /* Deep shadow DOM detected */ + if (shadowNode.content instanceof DocumentFragment) { + _sanitizeShadowDOM(shadowNode.content); + } + + /* Check attributes, sanitize if necessary */ + _sanitizeAttributes(shadowNode); + } + + /* Execute a hook if present */ + _executeHook('afterSanitizeShadowDOM', fragment, null); + }; + + /** + * Sanitize + * Public method providing core sanitation functionality + * + * @param {String|Node} dirty string or DOM node + * @param {Object} cfg object + */ + // eslint-disable-next-line complexity + DOMPurify.sanitize = function (dirty, cfg = {}) { + let body = null; + let importedNode = null; + let currentNode = null; + let returnNode = null; + /* Make sure we have a string to sanitize. + DO NOT return early, as this will return the wrong type if + the user has requested a DOM object rather than a string */ + IS_EMPTY_INPUT = !dirty; + if (IS_EMPTY_INPUT) { + dirty = ''; + } + + /* Stringify, in case dirty is an object */ + if (typeof dirty !== 'string' && !_isNode(dirty)) { + if (typeof dirty.toString === 'function') { + dirty = dirty.toString(); + if (typeof dirty !== 'string') { + throw typeErrorCreate('dirty is not a string, aborting'); + } + } else { + throw typeErrorCreate('toString is not a function'); + } + } + + /* Return dirty HTML if DOMPurify cannot run */ + if (!DOMPurify.isSupported) { + return dirty; + } + + /* Assign config vars */ + if (!SET_CONFIG) { + _parseConfig(cfg); + } + + /* Clean up removed elements */ + DOMPurify.removed = []; + + /* Check if dirty is correctly typed for IN_PLACE */ + if (typeof dirty === 'string') { + IN_PLACE = false; + } + + if (IN_PLACE) { + /* Do some early pre-sanitization to avoid unsafe root nodes */ + if (dirty.nodeName) { + const tagName = transformCaseFunc(dirty.nodeName); + if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { + throw typeErrorCreate( + 'root node is forbidden and cannot be sanitized in-place' + ); + } + } + } else if (dirty instanceof Node) { + /* If dirty is a DOM element, append to an empty document to avoid + elements being stripped by the parser */ + body = _initDocument(''); + importedNode = body.ownerDocument.importNode(dirty, true); + if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') { + /* Node is already a body, use as is */ + body = importedNode; + } else if (importedNode.nodeName === 'HTML') { + body = importedNode; + } else { + // eslint-disable-next-line unicorn/prefer-dom-node-append + body.appendChild(importedNode); + } + } else { + /* Exit directly if we have nothing to do */ + if ( + !RETURN_DOM && + !SAFE_FOR_TEMPLATES && + !WHOLE_DOCUMENT && + // eslint-disable-next-line unicorn/prefer-includes + dirty.indexOf('<') === -1 + ) { + return trustedTypesPolicy && RETURN_TRUSTED_TYPE + ? trustedTypesPolicy.createHTML(dirty) + : dirty; + } + + /* Initialize the document to work on */ + body = _initDocument(dirty); + + /* Check we have a DOM node from the data */ + if (!body) { + return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : ''; + } + } + + /* Remove first element node (ours) if FORCE_BODY is set */ + if (body && FORCE_BODY) { + _forceRemove(body.firstChild); + } + + /* Get node iterator */ + const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body); + + /* Now start iterating over the created document */ + while ((currentNode = nodeIterator.nextNode())) { + /* Sanitize tags and elements */ + if (_sanitizeElements(currentNode)) { + continue; + } + + /* Shadow DOM detected, sanitize it */ + if (currentNode.content instanceof DocumentFragment) { + _sanitizeShadowDOM(currentNode.content); + } + + /* Check attributes, sanitize if necessary */ + _sanitizeAttributes(currentNode); + } + + /* If we sanitized `dirty` in-place, return it. */ + if (IN_PLACE) { + return dirty; + } + + /* Return sanitized string or DOM */ + if (RETURN_DOM) { + if (RETURN_DOM_FRAGMENT) { + returnNode = createDocumentFragment.call(body.ownerDocument); + + while (body.firstChild) { + // eslint-disable-next-line unicorn/prefer-dom-node-append + returnNode.appendChild(body.firstChild); + } + } else { + returnNode = body; + } + + if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) { + /* + AdoptNode() is not used because internal state is not reset + (e.g. the past names map of a HTMLFormElement), this is safe + in theory but we would rather not risk another attack vector. + The state that is cloned by importNode() is explicitly defined + by the specs. + */ + returnNode = importNode.call(originalDocument, returnNode, true); + } + + return returnNode; + } + + let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; + + /* Serialize doctype if allowed */ + if ( + WHOLE_DOCUMENT && + ALLOWED_TAGS['!doctype'] && + body.ownerDocument && + body.ownerDocument.doctype && + body.ownerDocument.doctype.name && + regExpTest(EXPRESSIONS.DOCTYPE_NAME, body.ownerDocument.doctype.name) + ) { + serializedHTML = + '\n' + serializedHTML; + } + + /* Sanitize final string template-safe */ + if (SAFE_FOR_TEMPLATES) { + arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr) => { + serializedHTML = stringReplace(serializedHTML, expr, ' '); + }); + } + + return trustedTypesPolicy && RETURN_TRUSTED_TYPE + ? trustedTypesPolicy.createHTML(serializedHTML) + : serializedHTML; + }; + + /** + * Public method to set the configuration once + * setConfig + * + * @param {Object} cfg configuration object + */ + DOMPurify.setConfig = function (cfg = {}) { + _parseConfig(cfg); + SET_CONFIG = true; + }; + + /** + * Public method to remove the configuration + * clearConfig + * + */ + DOMPurify.clearConfig = function () { + CONFIG = null; + SET_CONFIG = false; + }; + + /** + * Public method to check if an attribute value is valid. + * Uses last set config, if any. Otherwise, uses config defaults. + * isValidAttribute + * + * @param {String} tag Tag name of containing element. + * @param {String} attr Attribute name. + * @param {String} value Attribute value. + * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false. + */ + DOMPurify.isValidAttribute = function (tag, attr, value) { + /* Initialize shared config vars if necessary. */ + if (!CONFIG) { + _parseConfig({}); + } + + const lcTag = transformCaseFunc(tag); + const lcName = transformCaseFunc(attr); + return _isValidAttribute(lcTag, lcName, value); + }; + + /** + * AddHook + * Public method to add DOMPurify hooks + * + * @param {String} entryPoint entry point for the hook to add + * @param {Function} hookFunction function to execute + */ + DOMPurify.addHook = function (entryPoint, hookFunction) { + if (typeof hookFunction !== 'function') { + return; + } + + hooks[entryPoint] = hooks[entryPoint] || []; + arrayPush(hooks[entryPoint], hookFunction); + }; + + /** + * RemoveHook + * Public method to remove a DOMPurify hook at a given entryPoint + * (pops it from the stack of hooks if more are present) + * + * @param {String} entryPoint entry point for the hook to remove + * @return {Function} removed(popped) hook + */ + DOMPurify.removeHook = function (entryPoint) { + if (hooks[entryPoint]) { + return arrayPop(hooks[entryPoint]); + } + }; + + /** + * RemoveHooks + * Public method to remove all DOMPurify hooks at a given entryPoint + * + * @param {String} entryPoint entry point for the hooks to remove + */ + DOMPurify.removeHooks = function (entryPoint) { + if (hooks[entryPoint]) { + hooks[entryPoint] = []; + } + }; + + /** + * RemoveAllHooks + * Public method to remove all DOMPurify hooks + */ + DOMPurify.removeAllHooks = function () { + hooks = {}; + }; + + return DOMPurify; +} + +export default createDOMPurify(); diff --git a/src/ui/static/js/utils/purify/src/regexp.js b/src/ui/static/js/utils/purify/src/regexp.js new file mode 100644 index 00000000..3c9f75fe --- /dev/null +++ b/src/ui/static/js/utils/purify/src/regexp.js @@ -0,0 +1,16 @@ +import { seal } from './utils.js'; + +// eslint-disable-next-line unicorn/better-regex +export const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode +export const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm); +export const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm); +export const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape +export const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape +export 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 +); +export const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i); +export const ATTR_WHITESPACE = seal( + /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex +); +export const DOCTYPE_NAME = seal(/^html$/i); diff --git a/src/ui/static/js/utils/purify/src/tags.js b/src/ui/static/js/utils/purify/src/tags.js new file mode 100644 index 00000000..853c7308 --- /dev/null +++ b/src/ui/static/js/utils/purify/src/tags.js @@ -0,0 +1,280 @@ +import { freeze } from './utils.js'; + +export const html = 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', +]); + +// SVG +export const svg = 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', +]); + +export 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', +]); + +// List of SVG elements that are disallowed by default. +// We still need to know them so that we can do namespace +// checks properly in case one wants to add them to +// allow-list. +export 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', +]); + +export const mathMl = 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', +]); + +// Similarly to SVG, we want to know all MathML elements, +// even those that we disallow by default. +export const mathMlDisallowed = freeze([ + 'maction', + 'maligngroup', + 'malignmark', + 'mlongdiv', + 'mscarries', + 'mscarry', + 'msgroup', + 'mstack', + 'msline', + 'msrow', + 'semantics', + 'annotation', + 'annotation-xml', + 'mprescripts', + 'none', +]); + +export const text = freeze(['#text']); diff --git a/src/ui/static/js/utils/purify/src/utils.js b/src/ui/static/js/utils/purify/src/utils.js new file mode 100644 index 00000000..a658514f --- /dev/null +++ b/src/ui/static/js/utils/purify/src/utils.js @@ -0,0 +1,193 @@ +const { + entries, + setPrototypeOf, + isFrozen, + getPrototypeOf, + getOwnPropertyDescriptor, +} = Object; + +let { freeze, seal, create } = Object; // eslint-disable-line import/no-mutable-exports +let { apply, construct } = typeof Reflect !== 'undefined' && Reflect; + +if (!freeze) { + freeze = function (x) { + return x; + }; +} + +if (!seal) { + seal = function (x) { + return x; + }; +} + +if (!apply) { + apply = function (fun, thisValue, args) { + return fun.apply(thisValue, args); + }; +} + +if (!construct) { + construct = function (Func, args) { + return new Func(...args); + }; +} + +const arrayForEach = unapply(Array.prototype.forEach); +const arrayIndexOf = unapply(Array.prototype.indexOf); +const arrayPop = unapply(Array.prototype.pop); +const arrayPush = unapply(Array.prototype.push); +const arraySlice = unapply(Array.prototype.slice); + +const stringToLowerCase = unapply(String.prototype.toLowerCase); +const stringToString = unapply(String.prototype.toString); +const stringMatch = unapply(String.prototype.match); +const stringReplace = unapply(String.prototype.replace); +const stringIndexOf = unapply(String.prototype.indexOf); +const stringTrim = unapply(String.prototype.trim); + +const regExpTest = unapply(RegExp.prototype.test); + +const typeErrorCreate = unconstruct(TypeError); + +/** + * Creates a new function that calls the given function with a specified thisArg and arguments. + * + * @param {Function} func - The function to be wrapped and called. + * @returns {Function} A new function that calls the given function with a specified thisArg and arguments. + */ +function unapply(func) { + return (thisArg, ...args) => apply(func, thisArg, args); +} + +/** + * Creates a new function that constructs an instance of the given constructor function with the provided arguments. + * + * @param {Function} func - The constructor function to be wrapped and called. + * @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments. + */ +function unconstruct(func) { + return (...args) => construct(func, args); +} + +/** + * Add properties to a lookup table + * + * @param {Object} set - The set to which elements will be added. + * @param {Array} array - The array containing elements to be added to the set. + * @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set. + * @returns {Object} The modified set with added elements. + */ +function addToSet(set, array, transformCaseFunc = stringToLowerCase) { + if (setPrototypeOf) { + // Make 'in' and truthy checks like Boolean(set.constructor) + // independent of any properties defined on Object.prototype. + // Prevent prototype setters from intercepting set as a this value. + setPrototypeOf(set, null); + } + + let l = array.length; + while (l--) { + let element = array[l]; + if (typeof element === 'string') { + const lcElement = transformCaseFunc(element); + if (lcElement !== element) { + // Config presets (e.g. tags.js, attrs.js) are immutable. + if (!isFrozen(array)) { + array[l] = lcElement; + } + + element = lcElement; + } + } + + set[element] = true; + } + + return set; +} + +/** + * Shallow clone an object + * + * @param {Object} object - The object to be cloned. + * @returns {Object} A new object that copies the original. + */ +export function clone(object) { + const newObject = create(null); + + for (const [property, value] of entries(object)) { + if (getOwnPropertyDescriptor(object, property) !== undefined) { + newObject[property] = value; + } + } + + return newObject; +} + +/** + * This method automatically checks if the prop is function or getter and behaves accordingly. + * + * @param {Object} object - The object to look up the getter function in its prototype chain. + * @param {String} prop - The property name for which to find the getter function. + * @returns {Function} 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(element) { + console.warn('fallback value for', element); + return null; + } + + return fallbackValue; +} + +export { + // Array + arrayForEach, + arrayIndexOf, + arrayPop, + arrayPush, + arraySlice, + // Object + entries, + freeze, + getPrototypeOf, + getOwnPropertyDescriptor, + isFrozen, + setPrototypeOf, + seal, + create, + // RegExp + regExpTest, + // String + stringIndexOf, + stringMatch, + stringReplace, + stringToLowerCase, + stringToString, + stringTrim, + // Errors + typeErrorCreate, + // Other + lookupGetter, + addToSet, + // Reflect + unapply, + unconstruct, +}; diff --git a/src/ui/templates/head.html b/src/ui/templates/head.html index c9a4ed86..90f58f9e 100644 --- a/src/ui/templates/head.html +++ b/src/ui/templates/head.html @@ -18,7 +18,8 @@ - + + {% if current_endpoint == "global_config" %} @@ -33,7 +34,7 @@ {% elif current_endpoint == "logs" %} - +