dompurify
Advanced tools
Comparing version 0.9.0 to 1.0.0
@@ -1,2 +0,2 @@ | ||
(function(e){"use strict";var t=typeof window==="undefined"?null:window;if(typeof define==="function"&&define.amd){define(function(){return e(t)})}else if(typeof module!=="undefined"){module.exports=e(t)}else{t.DOMPurify=e(t)}})(function e(t){"use strict";var r=function(t){return e(t)};r.version="0.9.0";r.removed=[];if(!t||!t.document||t.document.nodeType!==9){r.isSupported=false;return r}var n=t.document;var a=n;var i=t.DocumentFragment;var o=t.HTMLTemplateElement;var l=t.Node;var s=t.NodeFilter;var f=t.NamedNodeMap||t.MozNamedAttrMap;var c=t.Text;var u=t.Comment;var d=t.DOMParser;var m=t.XMLHttpRequest;var p=t.encodeURI;var v=false;var h=false;if(typeof o==="function"){var g=n.createElement("template");if(g.content&&g.content.ownerDocument){n=g.content.ownerDocument}}var y=n.implementation;var T=n.createNodeIterator;var b=n.getElementsByTagName;var A=n.createDocumentFragment;var x=a.importNode;var k={};r.isSupported=typeof y.createHTMLDocument!=="undefined"&&n.documentMode!==9;var w=function(e,t){var r=t.length;while(r--){if(typeof t[r]==="string"){t[r]=t[r].toLowerCase()}e[t[r]]=true}return e};var S=function(e){var t={};var r;for(r in e){if(e.hasOwnProperty(r)){t[r]=e[r]}}return t};var E=null;var N=w({},["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","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","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","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","switch","symbol","text","textpath","title","tref","tspan","view","vkern","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feMerge","feMergeNode","feMorphology","feOffset","feSpecularLighting","feTile","feTurbulence","math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmuliscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mpspace","msqrt","mystyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","#text"]);var O=null;var D=w({},["accept","action","align","alt","autocomplete","background","bgcolor","border","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","coords","datetime","default","dir","disabled","download","enctype","face","for","headers","height","hidden","high","href","hreflang","id","ismap","label","lang","list","loop","low","max","maxlength","media","method","min","multiple","name","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","span","srclang","start","src","step","style","summary","tabindex","title","type","usemap","valign","value","width","xmlns","accent-height","accumulate","additivive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","clip","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","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","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","mode","min","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","surfacescale","targetx","targety","transform","text-anchor","text-decoration","text-rendering","textlength","u1","u2","unicode","values","viewbox","visibility","vert-adv-y","vert-origin-x","vert-origin-y","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","y","y1","y2","z","zoomandpan","accent","accentunder","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","display","displaystyle","fence","frame","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","xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]);var M=null;var L=null;var _=true;var C=true;var R=false;var z=false;var F=false;var H=/\{\{[\s\S]*|[\s\S]*\}\}/gm;var I=/<%[\s\S]*|[\s\S]*%>/gm;var j=false;var W=false;var q=false;var B=false;var G=false;var U=false;var P=true;var V=true;var Y=w({},["audio","head","math","script","style","template","svg","video"]);var K=w({},["audio","video","img","source","image"]);var X=w({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]);var $=null;var J=n.createElement("form");var Q=function(e){if(typeof e!=="object"){e={}}E="ALLOWED_TAGS"in e?w({},e.ALLOWED_TAGS):N;O="ALLOWED_ATTR"in e?w({},e.ALLOWED_ATTR):D;M="FORBID_TAGS"in e?w({},e.FORBID_TAGS):{};L="FORBID_ATTR"in e?w({},e.FORBID_ATTR):{};_=e.ALLOW_ARIA_ATTR!==false;C=e.ALLOW_DATA_ATTR!==false;R=e.ALLOW_UNKNOWN_PROTOCOLS||false;z=e.SAFE_FOR_JQUERY||false;F=e.SAFE_FOR_TEMPLATES||false;j=e.WHOLE_DOCUMENT||false;B=e.RETURN_DOM||false;G=e.RETURN_DOM_FRAGMENT||false;U=e.RETURN_DOM_IMPORT||false;q=e.FORCE_BODY||false;P=e.SANITIZE_DOM!==false;V=e.KEEP_CONTENT!==false;if(F){C=false}if(G){B=true}if(e.ADD_TAGS){if(E===N){E=S(E)}w(E,e.ADD_TAGS)}if(e.ADD_ATTR){if(O===D){O=S(O)}w(O,e.ADD_ATTR)}if(e.ADD_URI_SAFE_ATTR){w(X,e.ADD_URI_SAFE_ATTR)}if(V){E["#text"]=true}if(Object&&"freeze"in Object){Object.freeze(e)}$=e};var Z=function(e){r.removed.push({element:e});try{e.parentNode.removeChild(e)}catch(t){e.outerHTML=""}};var ee=function(e,t){r.removed.push({attribute:t.getAttributeNode(e),from:t});t.removeAttribute(e)};var te=function(e){var t,r;if(q){e="<remove></remove>"+e}if(v){try{e=p(e)}catch(n){}var a=new m;a.responseType="document";a.open("GET","data:text/html;charset=utf-8,"+e,false);a.send(null);t=a.response}if(h){try{t=(new d).parseFromString(e,"text/html")}catch(n){}}if(!t||!t.documentElement){t=y.createHTMLDocument("");r=t.body;r.parentNode.removeChild(r.parentNode.firstElementChild);r.outerHTML=e}return b.call(t,j?"html":"body")[0]};if(r.isSupported){(function(){var e=te('<svg><g onload="this.parentNode.remove()"></g></svg>');if(!e.querySelector("svg")){v=true}e=te('<svg><p><style><img src="</style><img src=x onerror=alert(1)//">');if(e.querySelector("svg img")){h=true}})()}var re=function(e){return T.call(e.ownerDocument||e,e,s.SHOW_ELEMENT|s.SHOW_COMMENT|s.SHOW_TEXT,function(){return s.FILTER_ACCEPT},false)};var ne=function(e){if(e instanceof c||e instanceof u){return false}if(typeof e.nodeName!=="string"||typeof e.textContent!=="string"||typeof e.removeChild!=="function"||!(e.attributes instanceof f)||typeof e.removeAttribute!=="function"||typeof e.setAttribute!=="function"){return true}return false};var ae=function(e){return typeof l==="object"?e instanceof l:e&&typeof e==="object"&&typeof e.nodeType==="number"&&typeof e.nodeName==="string"};var ie=function(e){var t,n;me("beforeSanitizeElements",e,null);if(ne(e)){Z(e);return true}t=e.nodeName.toLowerCase();me("uponSanitizeElement",e,{tagName:t,allowedTags:E});if(!E[t]||M[t]){if(V&&!Y[t]&&typeof e.insertAdjacentHTML==="function"){try{e.insertAdjacentHTML("AfterEnd",e.innerHTML)}catch(a){}}Z(e);return true}if(z&&!e.firstElementChild&&(!e.content||!e.content.firstElementChild)&&/</g.test(e.textContent)){r.removed.push({element:e.cloneNode()});e.innerHTML=e.textContent.replace(/</g,"<")}if(F&&e.nodeType===3){n=e.textContent;n=n.replace(H," ");n=n.replace(I," ");if(e.textContent!==n){r.removed.push({element:e.cloneNode()});e.textContent=n}}me("afterSanitizeElements",e,null);return false};var oe=/^data-[\-\w.\u00B7-\uFFFF]/;var le=/^aria-[\-\w]+$/;var se=/^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;var fe=/^(?:\w+script|data):/i;var ce=/[\x00-\x20\xA0\u1680\u180E\u2000-\u2029\u205f\u3000]/g;var ue=function(e){var a,i,o,l,s,f,c,u;me("beforeSanitizeAttributes",e,null);f=e.attributes;if(!f){return}c={attrName:"",attrValue:"",keepAttr:true,allowedAttributes:O};u=f.length;while(u--){a=f[u];i=a.name;o=a.value.trim();l=i.toLowerCase();c.attrName=l;c.attrValue=o;c.keepAttr=true;me("uponSanitizeAttribute",e,c);o=c.attrValue;if(l==="name"&&e.nodeName==="IMG"&&f.id){s=f.id;f=Array.prototype.slice.apply(f);ee("id",e);ee(i,e);if(f.indexOf(s)>u){e.setAttribute("id",s.value)}}else if(e.nodeName==="INPUT"&&l==="type"&&o==="file"&&(O[l]||!L[l])){continue}else{if(i==="id"){e.setAttribute(i,"")}ee(i,e)}if(!c.keepAttr){continue}if(P&&(l==="id"||l==="name")&&(o in t||o in n||o in J)){continue}if(F){o=o.replace(H," ");o=o.replace(I," ")}if(C&&oe.test(l)){}else if(_&&le.test(l)){}else if(!O[l]||L[l]){continue}else if(X[l]){}else if(se.test(o.replace(ce,""))){}else if((l==="src"||l==="xlink:href")&&o.indexOf("data:")===0&&K[e.nodeName.toLowerCase()]){}else if(R&&!fe.test(o.replace(ce,""))){}else if(!o){}else{continue}try{e.setAttribute(i,o);r.removed.pop()}catch(d){}}me("afterSanitizeAttributes",e,null)};var de=function(e){var t;var r=re(e);me("beforeSanitizeShadowDOM",e,null);while(t=r.nextNode()){me("uponSanitizeShadowNode",t,null);if(ie(t)){continue}if(t.content instanceof i){de(t.content)}ue(t)}me("afterSanitizeShadowDOM",e,null)};var me=function(e,t,n){if(!k[e]){return}k[e].forEach(function(e){e.call(r,t,n,$)})};r.sanitize=function(e,n){var o,s,f,c,u,d;if(!e){e="<!-->"}if(typeof e!=="string"&&!ae(e)){if(typeof e.toString!=="function"){throw new TypeError("toString is not a function")}else{e=e.toString()}}if(!r.isSupported){if(typeof t.toStaticHTML==="object"||typeof t.toStaticHTML==="function"){if(typeof e==="string"){return t.toStaticHTML(e)}else if(ae(e)){return t.toStaticHTML(e.outerHTML)}}return e}if(!W){Q(n)}r.removed=[];if(e instanceof l){o=te("<!-->");s=o.ownerDocument.importNode(e,true);if(s.nodeType===1&&s.nodeName==="BODY"){o=s}else{o.appendChild(s)}}else{if(!B&&!j&&e.indexOf("<")===-1){return e}o=te(e);if(!o){return B?null:""}}if(q){Z(o.firstChild)}u=re(o);while(f=u.nextNode()){if(f.nodeType===3&&f===c){continue}if(ie(f)){continue}if(f.content instanceof i){de(f.content)}ue(f);c=f}if(B){if(G){d=A.call(o.ownerDocument);while(o.firstChild){d.appendChild(o.firstChild)}}else{d=o}if(U){d=x.call(a,d,true)}return d}return j?o.outerHTML:o.innerHTML};r.setConfig=function(e){Q(e);W=true};r.clearConfig=function(){$=null;W=false};r.addHook=function(e,t){if(typeof t!=="function"){return}k[e]=k[e]||[];k[e].push(t)};r.removeHook=function(e){if(k[e]){k[e].pop()}};r.removeHooks=function(e){if(k[e]){k[e]=[]}};r.removeAllHooks=function(){k={}};return r}); | ||
//# sourceMappingURL=./dist/purify.min.js.map | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.DOMPurify=t()}(this,function(){"use strict";function e(e,t){for(var n=t.length;n--;)"string"==typeof t[n]&&(t[n]=t[n].toLowerCase()),e[t[n]]=!0;return e}function t(e){var t={},n=void 0;for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t}function n(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function o(){return Function("return this")()}function r(){var h=arguments.length>0&&void 0!==arguments[0]?arguments[0]:o(),g=function(e){return r(e)};if(g.version="1.0.0",g.removed=[],!h||!h.document||9!==h.document.nodeType)return g.isSupported=!1,g;var y=h.document,v=!1,b=!1,T=h.document,A=h.DocumentFragment,x=h.HTMLTemplateElement,S=h.Node,k=h.NodeFilter,E=h.NamedNodeMap,w=void 0===E?h.NamedNodeMap||h.MozNamedAttrMap:E,O=h.Text,M=h.Comment,N=h.DOMParser,L=h.XMLHttpRequest,D=void 0===L?h.XMLHttpRequest:L,_=h.encodeURI,R=void 0===_?h.encodeURI:_;if("function"==typeof x){var C=T.createElement("template");C.content&&C.content.ownerDocument&&(T=C.content.ownerDocument)}var F=T,z=F.implementation,H=F.createNodeIterator,I=F.getElementsByTagName,j=F.createDocumentFragment,U=y.importNode,q={};g.isSupported=z&&void 0!==z.createHTMLDocument&&9!==T.documentMode;var W=null,B=e({},[].concat(n(i),n(a),n(l),n(s),n(c))),G=null,P=e({},[].concat(n(d),n(u),n(m),n(f))),V=null,X=null,Y=!0,K=!0,$=!1,J=!1,Q=!1,Z=/\{\{[\s\S]*|[\s\S]*\}\}/gm,ee=/<%[\s\S]*|[\s\S]*%>/gm,te=!1,ne=!1,oe=!1,re=!1,ie=!1,ae=!1,le=!0,se=!0,ce={},de=e({},["audio","head","math","script","style","template","svg","video"]),ue=e({},["audio","video","img","source","image"]),me=e({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]),fe=null,pe=T.createElement("form"),he=function(o){"object"!==(void 0===o?"undefined":p(o))&&(o={}),W="ALLOWED_TAGS"in o?e({},o.ALLOWED_TAGS):B,G="ALLOWED_ATTR"in o?e({},o.ALLOWED_ATTR):P,V="FORBID_TAGS"in o?e({},o.FORBID_TAGS):{},X="FORBID_ATTR"in o?e({},o.FORBID_ATTR):{},ce="USE_PROFILES"in o&&o.USE_PROFILES,Y=!1!==o.ALLOW_ARIA_ATTR,K=!1!==o.ALLOW_DATA_ATTR,$=o.ALLOW_UNKNOWN_PROTOCOLS||!1,J=o.SAFE_FOR_JQUERY||!1,Q=o.SAFE_FOR_TEMPLATES||!1,te=o.WHOLE_DOCUMENT||!1,re=o.RETURN_DOM||!1,ie=o.RETURN_DOM_FRAGMENT||!1,ae=o.RETURN_DOM_IMPORT||!1,oe=o.FORCE_BODY||!1,le=!1!==o.SANITIZE_DOM,se=!1!==o.KEEP_CONTENT,Q&&(K=!1),ie&&(re=!0),ce&&(W=e({},[].concat(n(c))),G=[],!0===ce.html&&(e(W,i),e(G,d)),!0===ce.svg&&(e(W,a),e(G,u),e(G,f)),!0===ce.svgFilters&&(e(W,l),e(G,u),e(G,f)),!0===ce.mathMl&&(e(W,s),e(G,m),e(G,f))),o.ADD_TAGS&&(W===B&&(W=t(W)),e(W,o.ADD_TAGS)),o.ADD_ATTR&&(G===P&&(G=t(G)),e(G,o.ADD_ATTR)),o.ADD_URI_SAFE_ATTR&&e(me,o.ADD_URI_SAFE_ATTR),se&&(W["#text"]=!0),Object&&"freeze"in Object&&Object.freeze(o),fe=o},ge=function(e){g.removed.push({element:e});try{e.parentNode.removeChild(e)}catch(t){e.outerHTML=""}},ye=function(e,t){g.removed.push({attribute:t.getAttributeNode(e),from:t}),t.removeAttribute(e)},ve=function(e){var t=void 0,n=void 0;if(oe&&(e="<remove></remove>"+e),b){try{e=R(e)}catch(e){}var o=new D;o.responseType="document",o.open("GET","data:text/html;charset=utf-8,"+e,!1),o.send(null),t=o.response}if(v)try{t=(new N).parseFromString(e,"text/html")}catch(e){}return t&&t.documentElement||((n=(t=z.createHTMLDocument("")).body).parentNode.removeChild(n.parentNode.firstElementChild),n.outerHTML=e),I.call(t,te?"html":"body")[0]};g.isSupported&&function(){var e=ve('<svg><g onload="this.parentNode.remove()"></g></svg>');e.querySelector("svg")||(b=!0);try{(e=ve('<svg><p><style><img src="</style><img src=x onerror=alert(1)//">')).querySelector("svg img")&&(v=!0)}catch(e){}}();var be=function(e){return H.call(e.ownerDocument||e,e,k.SHOW_ELEMENT|k.SHOW_COMMENT|k.SHOW_TEXT,function(){return k.FILTER_ACCEPT},!1)},Te=function(e){return!(e instanceof O||e instanceof M)&&!("string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof w&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute)},Ae=function(e){return"object"===(void 0===S?"undefined":p(S))?e instanceof S:e&&"object"===(void 0===e?"undefined":p(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},xe=function(e,t,n){q[e]&&q[e].forEach(function(e){e.call(g,t,n,fe)})},Se=function(e){var t=void 0;if(xe("beforeSanitizeElements",e,null),Te(e))return ge(e),!0;var n=e.nodeName.toLowerCase();if(xe("uponSanitizeElement",e,{tagName:n,allowedTags:W}),!W[n]||V[n]){if(se&&!de[n]&&"function"==typeof e.insertAdjacentHTML)try{e.insertAdjacentHTML("AfterEnd",e.innerHTML)}catch(e){}return ge(e),!0}return!J||e.firstElementChild||e.content&&e.content.firstElementChild||!/</g.test(e.textContent)||(g.removed.push({element:e.cloneNode()}),e.innerHTML=e.textContent.replace(/</g,"<")),Q&&3===e.nodeType&&(t=(t=(t=e.textContent).replace(Z," ")).replace(ee," "),e.textContent!==t&&(g.removed.push({element:e.cloneNode()}),e.textContent=t)),xe("afterSanitizeElements",e,null),!1},ke=/^data-[\-\w.\u00B7-\uFFFF]/,Ee=/^aria-[\-\w]+$/,we=/^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,Oe=/^(?:\w+script|data):/i,Me=/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g,Ne=function(e){var t=void 0,n=void 0,o=void 0,r=void 0,i=void 0,a=void 0,l=void 0;if(xe("beforeSanitizeAttributes",e,null),a=e.attributes){var s={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:G};for(l=a.length;l--;){if(t=a[l],n=t.name,o=t.value.trim(),r=n.toLowerCase(),s.attrName=r,s.attrValue=o,s.keepAttr=!0,xe("uponSanitizeAttribute",e,s),o=s.attrValue,"name"===r&&"IMG"===e.nodeName&&a.id)i=a.id,a=Array.prototype.slice.apply(a),ye("id",e),ye(n,e),a.indexOf(i)>l&&e.setAttribute("id",i.value);else{if("INPUT"===e.nodeName&&"type"===r&&"file"===o&&(G[r]||!X[r]))continue;"id"===n&&e.setAttribute(n,""),ye(n,e)}if(s.keepAttr&&(!le||"id"!==r&&"name"!==r||!(o in h||o in T||o in pe))){if(Q&&(o=(o=o.replace(Z," ")).replace(ee," ")),K&&ke.test(r));else if(Y&&Ee.test(r));else{if(!G[r]||X[r])continue;if(me[r]);else if(we.test(o.replace(Me,"")));else if("src"!==r&&"xlink:href"!==r||0!==o.indexOf("data:")||!ue[e.nodeName.toLowerCase()]){if($&&!Oe.test(o.replace(Me,"")));else if(o)continue}else;}try{e.setAttribute(n,o),g.removed.pop()}catch(e){}}}xe("afterSanitizeAttributes",e,null)}},Le=function e(t){var n=void 0,o=be(t);for(xe("beforeSanitizeShadowDOM",t,null);n=o.nextNode();)xe("uponSanitizeShadowNode",n,null),Se(n)||(n.content instanceof A&&e(n.content),Ne(n));xe("afterSanitizeShadowDOM",t,null)};return g.sanitize=function(e,t){var n=void 0,o=void 0,r=void 0,i=void 0,a=void 0;if(e||(e="\x3c!--\x3e"),"string"!=typeof e&&!Ae(e)){if("function"!=typeof e.toString)throw new TypeError("toString is not a function");e=e.toString()}if(!g.isSupported){if("object"===p(h.toStaticHTML)||"function"==typeof h.toStaticHTML){if("string"==typeof e)return h.toStaticHTML(e);if(Ae(e))return h.toStaticHTML(e.outerHTML)}return e}if(ne||he(t),g.removed=[],e instanceof S)1===(o=(n=ve("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===o.nodeName?n=o:n.appendChild(o);else{if(!re&&!te&&-1===e.indexOf("<"))return e;if(!(n=ve(e)))return re?null:""}oe&&ge(n.firstChild);for(var l=be(n);r=l.nextNode();)3===r.nodeType&&r===i||Se(r)||(r.content instanceof A&&Le(r.content),Ne(r),i=r);if(re){if(ie)for(a=j.call(n.ownerDocument);n.firstChild;)a.appendChild(n.firstChild);else a=n;return ae&&(a=U.call(y,a,!0)),a}return te?n.outerHTML:n.innerHTML},g.setConfig=function(e){he(e),ne=!0},g.clearConfig=function(){fe=null,ne=!1},g.addHook=function(e,t){"function"==typeof t&&(q[e]=q[e]||[],q[e].push(t))},g.removeHook=function(e){q[e]&&q[e].pop()},g.removeHooks=function(e){q[e]&&(q[e]=[])},g.removeAllHooks=function(){q={}},g}var 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","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","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"],a=["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","audio","canvas","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","video","view","vkern"],l=["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feMerge","feMergeNode","feMorphology","feOffset","feSpecularLighting","feTile","feTurbulence"],s=["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmuliscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mpspace","msqrt","mystyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover"],c=["#text"],d=["accept","action","align","alt","autocomplete","background","bgcolor","border","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","coords","datetime","default","dir","disabled","download","enctype","face","for","headers","height","hidden","high","href","hreflang","id","ismap","label","lang","list","loop","low","max","maxlength","media","method","min","multiple","name","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","span","srclang","start","src","step","style","summary","tabindex","title","type","usemap","valign","value","width","xmlns"],u=["accent-height","accumulate","additivive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","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","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","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","tabindex","targetx","targety","transform","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","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=["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","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"],f=["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"],p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};return r()}); | ||
//# sourceMappingURL=purify.min.js.map |
{ | ||
"scripts": { | ||
"build-demo": "node scripts/build-demo.js", | ||
"lint": "jshint src/purify.js", | ||
"minify": "scripts/minify.sh", | ||
"amend-minified": "scripts/amend-minified.sh", | ||
"test:jsdom": "node test/jsdom-node-runner --dot", | ||
"test:karma": "karma start test/karma.conf.js --log-level warn --single-run", | ||
"test:ci": "npm run lint && npm run test:jsdom && (([ \"${TRAVIS_PULL_REQUEST}\" != \"false\" ] || [ \"${TEST_BROWSERSTACK}\" != \"true\" ]) || karma start test/karma.conf.js --log-level error --reporters dots --single-run)", | ||
"test": "npm run lint && npm run test:jsdom && npm run test:karma -- --browsers Firefox,Chrome" | ||
"lint": "xo src/*.js", | ||
"format": "prettier --write --trailing-comma es5 --single-quote 'src/*.js'", | ||
"commit-amend-build": "scripts/commit-amend-build.sh", | ||
"prebuild": "rimraf dist/**", | ||
"dev": "cross-env NODE_ENV=development BABEL_ENV=rollup rollup -w -c -o dist/purify.js", | ||
"build": "run-p build:* build:umd:*", | ||
"build:umd": "cross-env NODE_ENV=development BABEL_ENV=rollup rollup -c -o dist/purify.js", | ||
"build:umd:min": "cross-env NODE_ENV=production BABEL_ENV=rollup rollup -c -o dist/purify.min.js", | ||
"test:jsdom": "cross-env NODE_ENV=test BABEL_ENV=rollup node test/jsdom-node-runner --dot", | ||
"test:karma": "cross-env NODE_ENV=test BABEL_ENV=rollup karma start test/karma.conf.js --log-level warn ", | ||
"test:ci": "cross-env NODE_ENV=test BABEL_ENV=rollup npm run lint && npm run test:jsdom && (([ \"${TRAVIS_PULL_REQUEST}\" != \"false\" ] || [ \"${TEST_BROWSERSTACK}\" != \"true\" ]) || karma start test/karma.conf.js --log-level error --reporters dots --single-run)", | ||
"test": "cross-env NODE_ENV=test BABEL_ENV=rollup npm run lint && npm run test:jsdom && npm run test:karma -- --browsers Chrome" | ||
}, | ||
@@ -18,13 +23,42 @@ "files": [ | ||
"lint", | ||
"minify", | ||
"amend-minified" | ||
"build", | ||
"commit-amend-build" | ||
], | ||
"xo": { | ||
"semicolon": true, | ||
"space": 2, | ||
"extends": [ | ||
"prettier" | ||
], | ||
"plugins": [ | ||
"prettier" | ||
], | ||
"rules": { | ||
"prettier/prettier": [ | ||
"error", | ||
{ | ||
"trailingComma": "es5", | ||
"singleQuote": true | ||
} | ||
] | ||
}, | ||
"globals": [ | ||
"window", | ||
"VERSION" | ||
] | ||
}, | ||
"devDependencies": { | ||
"babel": "^6.23.0", | ||
"babel-core": "^6.24.1", | ||
"babel-preset-env": "^1.4.0", | ||
"cross-env": "^5.0.0", | ||
"eslint-config-prettier": "^2.3.0", | ||
"eslint-plugin-prettier": "^2.1.2", | ||
"he": "^1.1.1", | ||
"jquery": "^2.2.3", | ||
"jsdom": "8.x.x", | ||
"jshint": "^2.9.2", | ||
"json-loader": "^0.5.4", | ||
"karma": "^0.13.22", | ||
"karma-browserstack-launcher": "1.0.0", | ||
"karma-chrome-launcher": "^1.0.1", | ||
"karma": "^1.7.0", | ||
"karma-browserstack-launcher": "1.2.0", | ||
"karma-chrome-launcher": "^2.1.1", | ||
"karma-firefox-launcher": "^1.0.0", | ||
@@ -35,13 +69,23 @@ "karma-fixture": "^0.2.6", | ||
"karma-qunit": "^1.0.0", | ||
"karma-webpack": "^1.7.0", | ||
"karma-rollup-plugin": "^0.2.4", | ||
"npm-run-all": "^4.0.2", | ||
"pre-commit": "^1.1.2", | ||
"prettier": "^1.5.2", | ||
"qunit-parameterize": "^0.4.0", | ||
"qunit-tap": "^1.5.0", | ||
"qunitjs": "^1.23.1", | ||
"uglify-js": "^2.6.2", | ||
"webpack": "^1.13.0" | ||
"rimraf": "^2.6.1", | ||
"rollup": "^0.41.6", | ||
"rollup-plugin-babel": "^2.7.1", | ||
"rollup-plugin-commonjs": "^8.0.2", | ||
"rollup-plugin-includepaths": "^0.2.2", | ||
"rollup-plugin-node-resolve": "^3.0.0", | ||
"rollup-plugin-replace": "^1.1.1", | ||
"rollup-plugin-uglify": "^2.0.0", | ||
"rollup-watch": "^4.0.0", | ||
"xo": "^0.18.1" | ||
}, | ||
"name": "dompurify", | ||
"description": "DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. It's written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Firefox and Chrome - as well as almost anything else using Blink or WebKit). DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not.", | ||
"version": "0.9.0", | ||
"version": "1.0.0", | ||
"main": "src/purify.js", | ||
@@ -48,0 +92,0 @@ "directories": { |
@@ -5,11 +5,11 @@ # DOMPurify [![Bower version](https://badge.fury.io/bo/dompurify.svg)](http://badge.fury.io/bo/dompurify) · [![npm version](https://badge.fury.io/js/dompurify.svg)](http://badge.fury.io/js/dompurify) · [![Build Status](https://travis-ci.org/cure53/DOMPurify.svg)](https://travis-ci.org/cure53/DOMPurify) · [![Downloads](https://img.shields.io/npm/dm/dompurify.svg)](https://www.npmjs.com/package/dompurify) | ||
DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. | ||
DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. | ||
It's also very simple to use and get started with. | ||
DOMPurify is written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Edge, Firefox and Chrome - as well as almost anything else using Blink or WebKit). It doesn't break on IE6 or other legacy browsers. It either uses [a fall-back](#what-about-older-browsers-like-msie8) or simply does nothing. | ||
DOMPurify is written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Edge, Firefox and Chrome - as well as almost anything else using Blink or WebKit). It doesn't break on MSIE6 or other legacy browsers. It either uses [a fall-back](#what-about-older-browsers-like-msie8) or simply does nothing. | ||
Our automated tests cover [16 different browsers](https://github.com/cure53/DOMPurify/blob/master/test/karma.conf.js#L185) right now. We also cover Node.js v4.0.0, v5.0.0 and v6.0.0, running DOMPurify on [jsdom](https://github.com/tmpvar/jsdom). | ||
Our automated tests cover [16 different browsers](https://github.com/cure53/DOMPurify/blob/master/test/karma.conf.js#L163) right now, more to come. We also cover Node.js v4.0.0, v5.0.0 and v6.0.0, running DOMPurify on [jsdom](https://github.com/tmpvar/jsdom). | ||
DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not. For more details please also read about our [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model) | ||
DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not. For more details please also read about our [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model). Please, read it. Like, really. | ||
@@ -62,9 +62,5 @@ ## What does it do? | ||
const createDOMPurify = require('dompurify'); | ||
const jsdom = require('jsdom'); | ||
const window = jsdom.jsdom('', { | ||
features: { | ||
FetchExternalResources: false, // disables resource loading over HTTP / filesystem | ||
ProcessExternalResources: false // do not execute JS within script blocks | ||
} | ||
}).defaultView; | ||
const { JSDOM } = require('jsdom'); | ||
const window = (new JSDOM('')).window; | ||
const DOMPurify = createDOMPurify(window); | ||
@@ -75,4 +71,2 @@ | ||
Strictly speaking, DOMPurify creates a document without a browsing context and you can replace it with `const window = jsdom.jsdom().defaultView;`, however, the longer case protects against accidental bugs in jsdom or DOMPurify. | ||
## Is there a demo? | ||
@@ -93,4 +87,4 @@ | ||
DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // becomes <svg><g></g></svg> | ||
DOMPurify.sanitize('<p>abc<iframe/\/src=jAva	script:alert(3)>def'); // becomes <p>abc</p> | ||
DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math></math> | ||
DOMPurify.sanitize('<p>abc<iframe/\/src=jAva	script:alert(3)>def'); // becomes <p>abcdef</p> | ||
DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math><mi></mi></math> | ||
DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // becomes <table><tbody><tr><td>HELLO</td></tr></tbody></table> | ||
@@ -127,2 +121,11 @@ DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><li><a href="//google.com">click</a></li></ul> | ||
// allow all safe HTML elements but neither SVG nor MathML | ||
var clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {html: true}}); | ||
// allow all safe SVG elements and SVG Filters | ||
var clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {svg: true, svgFilters: true}}); | ||
// allow all safe MathML elements and SVG | ||
var clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {mathMl: true, svg: true}}); | ||
// leave all as it is but forbid <style> | ||
@@ -211,2 +214,28 @@ var clean = DOMPurify.sanitize(dirty, {FORBID_TAGS: ['style']}); | ||
### Development and contributing | ||
#### Installation (`yarn i`) | ||
We support both `yarn` and `npm@5.2` officially while providing lock-files for either dependency manager to provide reproducible installs and builds on either or. TravisCI itself is configured to install dependencies using `yarn`. When using an older version of `npm` we can not fully ensure the versions of installed dependencies which might lead to unanticipated problems. | ||
#### Scripts | ||
We rely on npm run-scripts for integrating with out tooling infrastructure. We use ESLint as a pre-commit hook to ensure code consistency. Moreover, to ease formatting we use [prettier](https://github.com/prettier/prettier) while building the `/dist` assets happens through `rollup`. | ||
These are our npm scripts: | ||
- `npm run dev` to start building while watching sources for changes | ||
- `npm run test` to run our test suite via jsdom and karma | ||
- `test:jsdom` to only run tests through jsdom | ||
- `test:karma` to only run tests through karma | ||
- `npm run lint` to lint the sources using ESLint (via xo) | ||
- `npm run format` to format our sources using prettier to ease to pass ESLint | ||
- `npm run build` to build our distribution assets minified and unminified as a UMD module | ||
- `npm run build:umd` to only build an unminified UMD module | ||
- `npm run build:umd:min` to only build a minified UMD module | ||
Note: all run scripts triggered via `npm run <script>` can also be started using `yarn <script>`. | ||
There are more npm scripts but they are mainly to integrate with CI or are meant to be "private" for instance to amend build distribution files with every commit. | ||
## Security Mailing List | ||
@@ -222,12 +251,12 @@ | ||
Several people need to be listed here! | ||
Several people need to be listed here! | ||
[@garethheyes](https://twitter.com/garethheyes) and [@filedescriptor](https://twitter.com/filedescriptor) for invaluable help, [@shafigullin](https://twitter.com/shafigullin) for breaking the library multiple times and thereby strengthening it, [@mmrupp](https://twitter.com/mmrupp) and [@irsdl](https://twitter.com/irsdl) for doing the same. | ||
[@garethheyes](https://twitter.com/garethheyes) and [@filedescriptor](https://twitter.com/filedescriptor) for invaluable help, [@shafigullin](https://twitter.com/shafigullin) for breaking the library multiple times and thereby strengthening it, [@mmrupp](https://twitter.com/mmrupp) and [@irsdl](https://twitter.com/irsdl) for doing the same. And lastly, thanks to @ShikariSenpai and @ansjdnakjdnajkd for spotting the [massive Safari 10.1 bug](https://github.com/cure53/DOMPurify/releases/tag/0.8.6) in the first place. | ||
Big thanks also go to [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro) and [@fhemberger](https://twitter.com/fhemberger)! | ||
Big thanks also go to [@ydaniv](https://github.com/ydaniv), [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro) and [@fhemberger](https://twitter.com/fhemberger)! | ||
Further, thanks [@neilj](https://twitter.com/neilj) and [@0xsobky](https://twitter.com/0xsobky) for their code reviews and countless small optimizations, fixes and beautifications. | ||
Further, thanks [@neilj](https://twitter.com/neilj) and [@0xsobky](https://twitter.com/0xsobky) for their code reviews and countless small optimizations, fixes and beautifications. | ||
Big thanks also go to [@tdeekens](https://twitter.com/tdeekens) for doing all the hard work and getting us on track with Travis CI and BrowserStack. And thanks to [@Joris-van-der-Wel](https://github.com/Joris-van-der-Wel) for setting up DOMPurify for jsdom and creating the additional test suite. | ||
Big thanks also go to [@tdeekens](https://twitter.com/tdeekens) for doing all the hard work and getting us on track with Travis CI and BrowserStack. And thanks to [@Joris-van-der-Wel](https://github.com/Joris-van-der-Wel) for setting up DOMPurify for jsdom and creating the additional test suite. And again [@tdeekens](https://twitter.com/tdeekens) for his [incredible efforts](https://github.com/cure53/DOMPurify/pull/206) and contribution to refactor DOMPurify into using ES201x, proper build tools, better test coverage and much more! | ||
And last but not least, thanks to [BrowserStack](https://browserstack.com) for supporting this project with their services for free and delivering excellent, dedicated and very professional support on top of that. |
1747
src/purify.js
@@ -1,1004 +0,993 @@ | ||
;(function(factory) { | ||
'use strict'; | ||
/* global window: false, define: false, module: false */ | ||
var root = typeof window === 'undefined' ? null : window; | ||
import * as TAGS from './tags'; | ||
import * as ATTRS from './attrs'; | ||
import { addToSet, clone } from './utils'; | ||
if (typeof define === 'function' && define.amd) { | ||
define(function(){ return factory(root); }); | ||
} else if (typeof module !== 'undefined') { | ||
module.exports = factory(root); | ||
} else { | ||
root.DOMPurify = factory(root); | ||
} | ||
}(function factory(window) { | ||
'use strict'; | ||
function getGlobal() { | ||
// eslint-disable-next-line no-new-func | ||
return Function('return this')(); | ||
} | ||
var DOMPurify = function(window) { | ||
return factory(window); | ||
}; | ||
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 = '0.9.0'; | ||
/** | ||
* 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 = []; | ||
/** | ||
* 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; | ||
} | ||
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; | ||
var document = window.document; | ||
var originalDocument = document; | ||
var DocumentFragment = window.DocumentFragment; | ||
var HTMLTemplateElement = window.HTMLTemplateElement; | ||
var Node = window.Node; | ||
var NodeFilter = window.NodeFilter; | ||
var NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap; | ||
var Text = window.Text; | ||
var Comment = window.Comment; | ||
var DOMParser = window.DOMParser; | ||
var XMLHttpRequest = window.XMLHttpRequest; | ||
var encodeURI = window.encodeURI; | ||
var useXHR = false; | ||
var useDOMParser = false; // See comment below | ||
// 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') { | ||
var template = document.createElement('template'); | ||
if (template.content && template.content.ownerDocument) { | ||
document = template.content.ownerDocument; | ||
} | ||
} | ||
var implementation = document.implementation; | ||
var createNodeIterator = document.createNodeIterator; | ||
var getElementsByTagName = document.getElementsByTagName; | ||
var createDocumentFragment = document.createDocumentFragment; | ||
var importNode = originalDocument.importNode; | ||
return DOMPurify; | ||
} | ||
var hooks = {}; | ||
const originalDocument = window.document; | ||
let useDOMParser = false; // See comment below | ||
let useXHR = false; | ||
/** | ||
* Expose whether this browser supports running the full DOMPurify. | ||
*/ | ||
DOMPurify.isSupported = | ||
typeof implementation.createHTMLDocument !== 'undefined' && | ||
document.documentMode !== 9; | ||
let document = window.document; | ||
const { | ||
DocumentFragment, | ||
HTMLTemplateElement, | ||
Node, | ||
NodeFilter, | ||
NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap, | ||
Text, | ||
Comment, | ||
DOMParser, | ||
XMLHttpRequest = window.XMLHttpRequest, | ||
encodeURI = window.encodeURI, | ||
} = window; | ||
/* Add properties to a lookup table */ | ||
var _addToSet = function(set, array) { | ||
var l = array.length; | ||
while (l--) { | ||
if (typeof array[l] === 'string') { | ||
array[l] = array[l].toLowerCase(); | ||
} | ||
set[array[l]] = true; | ||
} | ||
return set; | ||
}; | ||
// 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; | ||
} | ||
} | ||
/* Shallow clone an object */ | ||
var _cloneObj = function(object) { | ||
var newObject = {}; | ||
var property; | ||
for (property in object) { | ||
if (object.hasOwnProperty(property)) { | ||
newObject[property] = object[property]; | ||
} | ||
} | ||
return newObject; | ||
}; | ||
const { | ||
implementation, | ||
createNodeIterator, | ||
getElementsByTagName, | ||
createDocumentFragment, | ||
} = document; | ||
const importNode = originalDocument.importNode; | ||
/** | ||
* We consider the elements and attributes below to be safe. Ideally | ||
* don't add any new ones but feel free to remove unwanted ones. | ||
*/ | ||
let hooks = {}; | ||
/* allowed element names */ | ||
var ALLOWED_TAGS = null; | ||
var DEFAULT_ALLOWED_TAGS = _addToSet({}, [ | ||
/** | ||
* Expose whether this browser supports running the full DOMPurify. | ||
*/ | ||
DOMPurify.isSupported = | ||
implementation && | ||
typeof implementation.createHTMLDocument !== 'undefined' && | ||
document.documentMode !== 9; | ||
// HTML | ||
'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','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','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', | ||
/** | ||
* We consider the elements and attributes below to be safe. Ideally | ||
* don't add any new ones but feel free to remove unwanted ones. | ||
*/ | ||
// SVG | ||
'svg','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','switch','symbol', | ||
'text','textpath','title','tref','tspan','view','vkern', | ||
/* allowed element names */ | ||
let ALLOWED_TAGS = null; | ||
const DEFAULT_ALLOWED_TAGS = addToSet({}, [ | ||
...TAGS.html, | ||
...TAGS.svg, | ||
...TAGS.svgFilters, | ||
...TAGS.mathMl, | ||
...TAGS.text, | ||
]); | ||
// SVG Filters | ||
'feBlend','feColorMatrix','feComponentTransfer','feComposite', | ||
'feConvolveMatrix','feDiffuseLighting','feDisplacementMap', | ||
'feFlood','feFuncA','feFuncB','feFuncG','feFuncR','feGaussianBlur', | ||
'feMerge','feMergeNode','feMorphology','feOffset', | ||
'feSpecularLighting','feTile','feTurbulence', | ||
/* Allowed attribute names */ | ||
let ALLOWED_ATTR = null; | ||
const DEFAULT_ALLOWED_ATTR = addToSet({}, [ | ||
...ATTRS.html, | ||
...ATTRS.svg, | ||
...ATTRS.mathMl, | ||
...ATTRS.xml, | ||
]); | ||
//MathML | ||
'math','menclose','merror','mfenced','mfrac','mglyph','mi','mlabeledtr', | ||
'mmuliscripts','mn','mo','mover','mpadded','mphantom','mroot','mrow', | ||
'ms','mpspace','msqrt','mystyle','msub','msup','msubsup','mtable','mtd', | ||
'mtext','mtr','munder','munderover', | ||
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ | ||
let FORBID_TAGS = null; | ||
//Text | ||
'#text' | ||
]); | ||
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ | ||
let FORBID_ATTR = null; | ||
/* Allowed attribute names */ | ||
var ALLOWED_ATTR = null; | ||
var DEFAULT_ALLOWED_ATTR = _addToSet({}, [ | ||
/* Decide if ARIA attributes are okay */ | ||
let ALLOW_ARIA_ATTR = true; | ||
// HTML | ||
'accept','action','align','alt','autocomplete','background','bgcolor', | ||
'border','cellpadding','cellspacing','checked','cite','class','clear','color', | ||
'cols','colspan','coords','datetime','default','dir','disabled', | ||
'download','enctype','face','for','headers','height','hidden','high','href', | ||
'hreflang','id','ismap','label','lang','list','loop', 'low','max', | ||
'maxlength','media','method','min','multiple','name','noshade','novalidate', | ||
'nowrap','open','optimum','pattern','placeholder','poster','preload','pubdate', | ||
'radiogroup','readonly','rel','required','rev','reversed','role','rows', | ||
'rowspan','spellcheck','scope','selected','shape','size','span', | ||
'srclang','start','src','step','style','summary','tabindex','title', | ||
'type','usemap','valign','value','width','xmlns', | ||
/* Decide if custom data attributes are okay */ | ||
let ALLOW_DATA_ATTR = true; | ||
// SVG | ||
'accent-height','accumulate','additivive','alignment-baseline', | ||
'ascent','attributename','attributetype','azimuth','basefrequency', | ||
'baseline-shift','begin','bias','by','clip','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','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', | ||
'image-rendering','in','in2','k','k1','k2','k3','k4','kerning','keypoints', | ||
'keysplines','keytimes','lengthadjust','letter-spacing','kernelmatrix', | ||
'kernelunitlength','lighting-color','local','marker-end','marker-mid', | ||
'marker-start','markerheight','markerunits','markerwidth','maskcontentunits', | ||
'maskunits','max','mask','mode','min','numoctaves','offset','operator', | ||
'opacity','order','orient','orientation','origin','overflow','paint-order', | ||
'path','pathlength','patterncontentunits','patterntransform','patternunits', | ||
'points','preservealpha','r','rx','ry','radius','refx','refy','repeatcount', | ||
'repeatdur','restart','result','rotate','scale','seed','shape-rendering', | ||
'specularconstant','specularexponent','spreadmethod','stddeviation','stitchtiles', | ||
'stop-color','stop-opacity','stroke-dasharray','stroke-dashoffset','stroke-linecap', | ||
'stroke-linejoin','stroke-miterlimit','stroke-opacity','stroke','stroke-width', | ||
'surfacescale','targetx','targety','transform','text-anchor','text-decoration', | ||
'text-rendering','textlength','u1','u2','unicode','values','viewbox', | ||
'visibility','vert-adv-y','vert-origin-x','vert-origin-y','word-spacing', | ||
'wrap','writing-mode','xchannelselector','ychannelselector','x','x1','x2', | ||
'y','y1','y2','z','zoomandpan', | ||
/* Decide if unknown protocols are okay */ | ||
let ALLOW_UNKNOWN_PROTOCOLS = false; | ||
// MathML | ||
'accent','accentunder','bevelled','close','columnsalign','columnlines', | ||
'columnspan','denomalign','depth','display','displaystyle','fence', | ||
'frame','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', | ||
/* Output should be safe for jQuery's $() factory? */ | ||
let SAFE_FOR_JQUERY = false; | ||
// XML | ||
'xlink:href','xml:id','xlink:title','xml:space','xmlns:xlink' | ||
]); | ||
/* Output should be safe for common template engines. | ||
* This means, DOMPurify removes data attributes, mustaches and ERB | ||
*/ | ||
let SAFE_FOR_TEMPLATES = false; | ||
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ | ||
var FORBID_TAGS = null; | ||
/* Specify template detection regex for SAFE_FOR_TEMPLATES mode */ | ||
const MUSTACHE_EXPR = /\{\{[\s\S]*|[\s\S]*\}\}/gm; | ||
const ERB_EXPR = /<%[\s\S]*|[\s\S]*%>/gm; | ||
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ | ||
var FORBID_ATTR = null; | ||
/* Decide if document with <html>... should be returned */ | ||
let WHOLE_DOCUMENT = false; | ||
/* Decide if ARIA attributes are okay */ | ||
var ALLOW_ARIA_ATTR = true; | ||
/* Track whether config is already set on this instance of DOMPurify. */ | ||
let SET_CONFIG = false; | ||
/* Decide if custom data attributes are okay */ | ||
var ALLOW_DATA_ATTR = true; | ||
/* 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 unknown protocols are okay */ | ||
var ALLOW_UNKNOWN_PROTOCOLS = false; | ||
/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string. | ||
* If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead | ||
*/ | ||
let RETURN_DOM = false; | ||
/* Output should be safe for jQuery's $() factory? */ | ||
var SAFE_FOR_JQUERY = false; | ||
/* Decide if a DOM `DocumentFragment` should be returned, instead of a html string */ | ||
let RETURN_DOM_FRAGMENT = false; | ||
/* Output should be safe for common template engines. | ||
* This means, DOMPurify removes data attributes, mustaches and ERB | ||
*/ | ||
var SAFE_FOR_TEMPLATES = false; | ||
/* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM | ||
* `Node` is imported into the current `Document`. If this flag is not enabled the | ||
* `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by | ||
* DOMPurify. */ | ||
let RETURN_DOM_IMPORT = false; | ||
/* Specify template detection regex for SAFE_FOR_TEMPLATES mode */ | ||
var MUSTACHE_EXPR = /\{\{[\s\S]*|[\s\S]*\}\}/gm; | ||
var ERB_EXPR = /<%[\s\S]*|[\s\S]*%>/gm; | ||
/* Output should be free from DOM clobbering attacks? */ | ||
let SANITIZE_DOM = true; | ||
/* Decide if document with <html>... should be returned */ | ||
var WHOLE_DOCUMENT = false; | ||
/* Keep element content when removing element? */ | ||
let KEEP_CONTENT = true; | ||
/* Track whether config is already set on this instance of DOMPurify. */ | ||
var SET_CONFIG = false; | ||
/* Allow usage of profiles like html, svg and mathMl */ | ||
let USE_PROFILES = {}; | ||
/* Decide if all elements (e.g. style, script) must be children of | ||
* document.body. By default, browsers might move them to document.head */ | ||
var FORCE_BODY = false; | ||
/* Tags to ignore content of when KEEP_CONTENT is true */ | ||
const FORBID_CONTENTS = addToSet({}, [ | ||
'audio', | ||
'head', | ||
'math', | ||
'script', | ||
'style', | ||
'template', | ||
'svg', | ||
'video', | ||
]); | ||
/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string. | ||
* If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead | ||
*/ | ||
var RETURN_DOM = false; | ||
/* Tags that are safe for data: URIs */ | ||
const DATA_URI_TAGS = addToSet({}, [ | ||
'audio', | ||
'video', | ||
'img', | ||
'source', | ||
'image', | ||
]); | ||
/* Decide if a DOM `DocumentFragment` should be returned, instead of a html string */ | ||
var RETURN_DOM_FRAGMENT = false; | ||
/* Attributes safe for values like "javascript:" */ | ||
const URI_SAFE_ATTRIBUTES = addToSet({}, [ | ||
'alt', | ||
'class', | ||
'for', | ||
'id', | ||
'label', | ||
'name', | ||
'pattern', | ||
'placeholder', | ||
'summary', | ||
'title', | ||
'value', | ||
'style', | ||
'xmlns', | ||
]); | ||
/* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM | ||
* `Node` is imported into the current `Document`. If this flag is not enabled the | ||
* `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by | ||
* DOMPurify. */ | ||
var RETURN_DOM_IMPORT = false; | ||
/* Keep a reference to config to pass to hooks */ | ||
let CONFIG = null; | ||
/* Output should be free from DOM clobbering attacks? */ | ||
var SANITIZE_DOM = true; | ||
/* Ideally, do not touch anything below this line */ | ||
/* ______________________________________________ */ | ||
/* Keep element content when removing element? */ | ||
var KEEP_CONTENT = true; | ||
const formElement = document.createElement('form'); | ||
/* Tags to ignore content of when KEEP_CONTENT is true */ | ||
var FORBID_CONTENTS = _addToSet({}, [ | ||
'audio', 'head', 'math', 'script', 'style', 'template', 'svg', 'video' | ||
]); | ||
/** | ||
* _parseConfig | ||
* | ||
* @param optional config literal | ||
*/ | ||
// eslint-disable-next-line complexity | ||
const _parseConfig = function(cfg) { | ||
/* Shield configuration object from tampering */ | ||
if (typeof cfg !== 'object') { | ||
cfg = {}; | ||
} | ||
/* Tags that are safe for data: URIs */ | ||
var DATA_URI_TAGS = _addToSet({}, [ | ||
'audio', 'video', 'img', 'source', 'image' | ||
]); | ||
/* Set configuration parameters */ | ||
ALLOWED_TAGS = | ||
'ALLOWED_TAGS' in cfg | ||
? addToSet({}, cfg.ALLOWED_TAGS) | ||
: DEFAULT_ALLOWED_TAGS; | ||
ALLOWED_ATTR = | ||
'ALLOWED_ATTR' in cfg | ||
? addToSet({}, cfg.ALLOWED_ATTR) | ||
: DEFAULT_ALLOWED_ATTR; | ||
FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {}; | ||
FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {}; | ||
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 | ||
SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false | ||
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_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false | ||
FORCE_BODY = cfg.FORCE_BODY || false; // Default false | ||
SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true | ||
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true | ||
/* Attributes safe for values like "javascript:" */ | ||
var URI_SAFE_ATTRIBUTES = _addToSet({}, [ | ||
'alt','class','for','id','label','name','pattern','placeholder', | ||
'summary','title','value','style','xmlns' | ||
]); | ||
if (SAFE_FOR_TEMPLATES) { | ||
ALLOW_DATA_ATTR = false; | ||
} | ||
/* Keep a reference to config to pass to hooks */ | ||
var CONFIG = null; | ||
if (RETURN_DOM_FRAGMENT) { | ||
RETURN_DOM = true; | ||
} | ||
/* Ideally, do not touch anything below this line */ | ||
/* ______________________________________________ */ | ||
/* 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); | ||
} | ||
} | ||
var formElement = document.createElement('form'); | ||
/* Merge configuration parameters */ | ||
if (cfg.ADD_TAGS) { | ||
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { | ||
ALLOWED_TAGS = clone(ALLOWED_TAGS); | ||
} | ||
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS); | ||
} | ||
if (cfg.ADD_ATTR) { | ||
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { | ||
ALLOWED_ATTR = clone(ALLOWED_ATTR); | ||
} | ||
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR); | ||
} | ||
if (cfg.ADD_URI_SAFE_ATTR) { | ||
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR); | ||
} | ||
/** | ||
* _parseConfig | ||
* | ||
* @param optional config literal | ||
*/ | ||
var _parseConfig = function(cfg) { | ||
/* Shield configuration object from tampering */ | ||
if (typeof cfg !== 'object') { | ||
cfg = {}; | ||
} | ||
/* Add #text in case KEEP_CONTENT is set to true */ | ||
if (KEEP_CONTENT) { | ||
ALLOWED_TAGS['#text'] = true; | ||
} | ||
/* Set configuration parameters */ | ||
ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? | ||
_addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS; | ||
ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? | ||
_addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR; | ||
FORBID_TAGS = 'FORBID_TAGS' in cfg ? | ||
_addToSet({}, cfg.FORBID_TAGS) : {}; | ||
FORBID_ATTR = 'FORBID_ATTR' in cfg ? | ||
_addToSet({}, cfg.FORBID_ATTR) : {}; | ||
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 | ||
SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false | ||
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_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false | ||
FORCE_BODY = cfg.FORCE_BODY || false; // Default false | ||
SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true | ||
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true | ||
// Prevent further manipulation of configuration. | ||
// Not available in IE8, Safari 5, etc. | ||
if (Object && 'freeze' in Object) { | ||
Object.freeze(cfg); | ||
} | ||
if (SAFE_FOR_TEMPLATES) { | ||
ALLOW_DATA_ATTR = false; | ||
} | ||
CONFIG = cfg; | ||
}; | ||
if (RETURN_DOM_FRAGMENT) { | ||
RETURN_DOM = true; | ||
} | ||
/** | ||
* _forceRemove | ||
* | ||
* @param a DOM node | ||
*/ | ||
const _forceRemove = function(node) { | ||
DOMPurify.removed.push({ element: node }); | ||
try { | ||
node.parentNode.removeChild(node); | ||
} catch (err) { | ||
node.outerHTML = ''; | ||
} | ||
}; | ||
/* Merge configuration parameters */ | ||
if (cfg.ADD_TAGS) { | ||
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { | ||
ALLOWED_TAGS = _cloneObj(ALLOWED_TAGS); | ||
} | ||
_addToSet(ALLOWED_TAGS, cfg.ADD_TAGS); | ||
} | ||
if (cfg.ADD_ATTR) { | ||
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { | ||
ALLOWED_ATTR = _cloneObj(ALLOWED_ATTR); | ||
} | ||
_addToSet(ALLOWED_ATTR, cfg.ADD_ATTR); | ||
} | ||
if (cfg.ADD_URI_SAFE_ATTR) { | ||
_addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR); | ||
} | ||
/** | ||
* _removeAttribute | ||
* | ||
* @param an Attribute name | ||
* @param a DOM node | ||
*/ | ||
const _removeAttribute = function(name, node) { | ||
DOMPurify.removed.push({ | ||
attribute: node.getAttributeNode(name), | ||
from: node, | ||
}); | ||
node.removeAttribute(name); | ||
}; | ||
/* Add #text in case KEEP_CONTENT is set to true */ | ||
if (KEEP_CONTENT) { ALLOWED_TAGS['#text'] = true; } | ||
/** | ||
* _initDocument | ||
* | ||
* @param a string of dirty markup | ||
* @return a DOM, filled with the dirty markup | ||
*/ | ||
const _initDocument = function(dirty) { | ||
/* Create a HTML document */ | ||
let doc; | ||
let body; | ||
// Prevent further manipulation of configuration. | ||
// Not available in IE8, Safari 5, etc. | ||
if (Object && 'freeze' in Object) { Object.freeze(cfg); } | ||
if (FORCE_BODY) { | ||
dirty = '<remove></remove>' + dirty; | ||
} | ||
CONFIG = cfg; | ||
}; | ||
/* Use XHR if necessary because Safari 10.1 and newer are buggy */ | ||
if (useXHR) { | ||
try { | ||
dirty = encodeURI(dirty); | ||
} catch (err) {} | ||
const xhr = new XMLHttpRequest(); | ||
xhr.responseType = 'document'; | ||
xhr.open('GET', 'data:text/html;charset=utf-8,' + dirty, false); | ||
xhr.send(null); | ||
doc = xhr.response; | ||
} | ||
/** | ||
* _forceRemove | ||
* | ||
* @param a DOM node | ||
*/ | ||
var _forceRemove = function(node) { | ||
DOMPurify.removed.push({element: node}); | ||
try { | ||
node.parentNode.removeChild(node); | ||
} catch (e) { | ||
node.outerHTML = ''; | ||
} | ||
}; | ||
/* Use DOMParser to workaround Firefox bug (see comment below) */ | ||
if (useDOMParser) { | ||
try { | ||
doc = new DOMParser().parseFromString(dirty, 'text/html'); | ||
} catch (err) {} | ||
} | ||
/** | ||
* _removeAttribute | ||
* | ||
* @param an Attribute name | ||
* @param a DOM node | ||
*/ | ||
var _removeAttribute = function(name, node) { | ||
DOMPurify.removed.push({ | ||
attribute: node.getAttributeNode(name), | ||
from: node | ||
}); | ||
node.removeAttribute(name); | ||
}; | ||
/* Otherwise use createHTMLDocument, because DOMParser is unsafe in | ||
Safari (see comment below) */ | ||
if (!doc || !doc.documentElement) { | ||
doc = implementation.createHTMLDocument(''); | ||
body = doc.body; | ||
body.parentNode.removeChild(body.parentNode.firstElementChild); | ||
body.outerHTML = dirty; | ||
} | ||
/** | ||
* _initDocument | ||
* | ||
* @param a string of dirty markup | ||
* @return a DOM, filled with the dirty markup | ||
*/ | ||
var _initDocument = function(dirty) { | ||
/* Create a HTML document */ | ||
var doc, body; | ||
/* Work on whole document or just its body */ | ||
return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; | ||
}; | ||
/* Fill body with bogus element */ | ||
if (FORCE_BODY) { | ||
dirty = '<remove></remove>' + dirty; | ||
// Safari 10.1+ (unfixed as of time of writing) has a catastrophic bug in | ||
// its implementation of DOMParser such that the following executes the | ||
// JavaScript: | ||
// | ||
// new DOMParser() | ||
// .parseFromString('<svg onload=alert(document.domain)>', 'text/html'); | ||
// | ||
// Later, it was also noticed that even more assumed benign and inert ways | ||
// of creating a document are now insecure thanks to Safari. So we work | ||
// around that with a feature test and use XHR to create the document in | ||
// case we really have to. That one seems safe for now. | ||
// | ||
// However, Firefox uses a different parser for innerHTML rather than | ||
// DOMParser (see https://bugzilla.mozilla.org/show_bug.cgi?id=1205631) | ||
// which means that you *must* use DOMParser, otherwise the output may | ||
// not be safe if used in a document.write context later. | ||
// | ||
// So we feature detect the Firefox bug and use the DOMParser if necessary. | ||
if (DOMPurify.isSupported) { | ||
(function() { | ||
let doc = _initDocument( | ||
'<svg><g onload="this.parentNode.remove()"></g></svg>' | ||
); | ||
if (!doc.querySelector('svg')) { | ||
useXHR = true; | ||
} | ||
try { | ||
doc = _initDocument( | ||
'<svg><p><style><img src="</style><img src=x onerror=alert(1)//">' | ||
); | ||
if (doc.querySelector('svg img')) { | ||
useDOMParser = true; | ||
} | ||
} catch (err) {} | ||
})(); | ||
} | ||
/* Use XHR if necessary because Safari 10.1 and newer are buggy */ | ||
if (useXHR) { | ||
try { | ||
dirty = encodeURI(dirty); | ||
} catch (e) {} | ||
var xhr = new XMLHttpRequest(); | ||
xhr.responseType = 'document'; | ||
xhr.open('GET', 'data:text/html;charset=utf-8,' + dirty, false); | ||
xhr.send(null); | ||
doc = xhr.response; | ||
} | ||
/** | ||
* _createIterator | ||
* | ||
* @param document/fragment to create iterator for | ||
* @return iterator instance | ||
*/ | ||
const _createIterator = function(root) { | ||
return createNodeIterator.call( | ||
root.ownerDocument || root, | ||
root, | ||
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, | ||
() => { | ||
return NodeFilter.FILTER_ACCEPT; | ||
}, | ||
false | ||
); | ||
}; | ||
/* Use DOMParser to workaround Firefox bug (see comment below) */ | ||
if (useDOMParser) { | ||
try { | ||
doc = new DOMParser().parseFromString(dirty, 'text/html'); | ||
} catch (e) {} | ||
} | ||
/** | ||
* _isClobbered | ||
* | ||
* @param element to check for clobbering attacks | ||
* @return true if clobbered, false if safe | ||
*/ | ||
const _isClobbered = function(elm) { | ||
if (elm instanceof Text || elm instanceof Comment) { | ||
return false; | ||
} | ||
if ( | ||
typeof elm.nodeName !== 'string' || | ||
typeof elm.textContent !== 'string' || | ||
typeof elm.removeChild !== 'function' || | ||
!(elm.attributes instanceof NamedNodeMap) || | ||
typeof elm.removeAttribute !== 'function' || | ||
typeof elm.setAttribute !== 'function' | ||
) { | ||
return true; | ||
} | ||
return false; | ||
}; | ||
/* Otherwise use createHTMLDocument, because DOMParser is unsafe in | ||
Safari (see comment below) */ | ||
if (!doc || !doc.documentElement) { | ||
doc = implementation.createHTMLDocument(''); | ||
body = doc.body; | ||
body.parentNode.removeChild(body.parentNode.firstElementChild); | ||
body.outerHTML = dirty; | ||
} | ||
/** | ||
* _isNode | ||
* | ||
* @param object to check whether it's a DOM node | ||
* @return true is object is a DOM node | ||
*/ | ||
const _isNode = function(obj) { | ||
return typeof Node === 'object' | ||
? obj instanceof Node | ||
: obj && | ||
typeof obj === 'object' && | ||
typeof obj.nodeType === 'number' && | ||
typeof obj.nodeName === 'string'; | ||
}; | ||
/* Work on whole document or just its body */ | ||
return getElementsByTagName.call(doc, | ||
WHOLE_DOCUMENT ? 'html' : 'body')[0]; | ||
}; | ||
// Safari 10.1+ (unfixed as of time of writing) has a catastrophic bug in | ||
// its implementation of DOMParser such that the following executes the | ||
// JavaScript: | ||
// | ||
// new DOMParser() | ||
// .parseFromString('<svg onload=alert(document.domain)>', 'text/html'); | ||
// | ||
// Later, it was also noticed that even more assumed benign and inert ways | ||
// of creating a document are now insecure thanks to Safari. So we work | ||
// around that with a feature test and use XHR to create the document in | ||
// case we really have to. That one seems safe for now. | ||
// | ||
// However, Firefox uses a different parser for innerHTML rather than | ||
// DOMParser (see https://bugzilla.mozilla.org/show_bug.cgi?id=1205631) | ||
// which means that you *must* use DOMParser, otherwise the output may | ||
// not be safe if used in a document.write context later. | ||
// | ||
// So we feature detect the Firefox bug and use the DOMParser if necessary. | ||
if (DOMPurify.isSupported) { | ||
(function () { | ||
var doc = _initDocument('<svg><g onload="this.parentNode.remove()"></g></svg>'); | ||
if (!doc.querySelector('svg')) { | ||
useXHR = true; | ||
} | ||
doc = _initDocument('<svg><p><style><img src="</style><img src=x onerror=alert(1)//">'); | ||
if (doc.querySelector('svg img')) { | ||
useDOMParser = true; | ||
} | ||
}()); | ||
/** | ||
* _executeHook | ||
* Execute user configurable hooks | ||
* | ||
* @param {String} entryPoint Name of the hook's entry point | ||
* @param {Node} currentNode | ||
*/ | ||
const _executeHook = function(entryPoint, currentNode, data) { | ||
if (!hooks[entryPoint]) { | ||
return; | ||
} | ||
/** | ||
* _createIterator | ||
* | ||
* @param document/fragment to create iterator for | ||
* @return iterator instance | ||
*/ | ||
var _createIterator = function(root) { | ||
return createNodeIterator.call(root.ownerDocument || root, | ||
root, | ||
NodeFilter.SHOW_ELEMENT | ||
| NodeFilter.SHOW_COMMENT | ||
| NodeFilter.SHOW_TEXT, | ||
function() { return NodeFilter.FILTER_ACCEPT; }, | ||
false | ||
); | ||
}; | ||
hooks[entryPoint].forEach(hook => { | ||
hook.call(DOMPurify, currentNode, data, CONFIG); | ||
}); | ||
}; | ||
/** | ||
* _isClobbered | ||
* | ||
* @param element to check for clobbering attacks | ||
* @return true if clobbered, false if safe | ||
*/ | ||
var _isClobbered = function(elm) { | ||
if (elm instanceof Text || elm instanceof Comment) { | ||
return false; | ||
} | ||
if ( typeof elm.nodeName !== 'string' | ||
|| typeof elm.textContent !== 'string' | ||
|| typeof elm.removeChild !== 'function' | ||
|| !(elm.attributes instanceof NamedNodeMap) | ||
|| typeof elm.removeAttribute !== 'function' | ||
|| typeof elm.setAttribute !== 'function' | ||
) { | ||
return true; | ||
} | ||
return false; | ||
}; | ||
/** | ||
* _sanitizeElements | ||
* | ||
* @protect nodeName | ||
* @protect textContent | ||
* @protect removeChild | ||
* | ||
* @param node to check for permission to exist | ||
* @return true if node was killed, false if left alive | ||
*/ | ||
const _sanitizeElements = function(currentNode) { | ||
let content; | ||
/** | ||
* _isNode | ||
* | ||
* @param object to check whether it's a DOM node | ||
* @return true is object is a DOM node | ||
*/ | ||
var _isNode = function(obj) { | ||
return ( | ||
typeof Node === "object" ? obj instanceof Node : obj | ||
&& typeof obj === "object" && typeof obj.nodeType === "number" | ||
&& typeof obj.nodeName==="string" | ||
); | ||
}; | ||
/* Execute a hook if present */ | ||
_executeHook('beforeSanitizeElements', currentNode, null); | ||
/** | ||
* _sanitizeElements | ||
* | ||
* @protect nodeName | ||
* @protect textContent | ||
* @protect removeChild | ||
* | ||
* @param node to check for permission to exist | ||
* @return true if node was killed, false if left alive | ||
*/ | ||
var _sanitizeElements = function(currentNode) { | ||
var tagName, content; | ||
/* Check if element is clobbered or can clobber */ | ||
if (_isClobbered(currentNode)) { | ||
_forceRemove(currentNode); | ||
return true; | ||
} | ||
/* Execute a hook if present */ | ||
_executeHook('beforeSanitizeElements', currentNode, null); | ||
/* Now let's check the element's type and name */ | ||
const tagName = currentNode.nodeName.toLowerCase(); | ||
/* Check if element is clobbered or can clobber */ | ||
if (_isClobbered(currentNode)) { | ||
_forceRemove(currentNode); | ||
return true; | ||
} | ||
/* Execute a hook if present */ | ||
_executeHook('uponSanitizeElement', currentNode, { | ||
tagName, | ||
allowedTags: ALLOWED_TAGS, | ||
}); | ||
/* Now let's check the element's type and name */ | ||
tagName = currentNode.nodeName.toLowerCase(); | ||
/* Remove element if anything forbids its presence */ | ||
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { | ||
/* Keep content except for black-listed elements */ | ||
if ( | ||
KEEP_CONTENT && | ||
!FORBID_CONTENTS[tagName] && | ||
typeof currentNode.insertAdjacentHTML === 'function' | ||
) { | ||
try { | ||
currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML); | ||
} catch (err) {} | ||
} | ||
_forceRemove(currentNode); | ||
return true; | ||
} | ||
/* Execute a hook if present */ | ||
_executeHook('uponSanitizeElement', currentNode, { | ||
tagName: tagName, | ||
allowedTags: ALLOWED_TAGS | ||
}); | ||
/* Convert markup to cover jQuery behavior */ | ||
if ( | ||
SAFE_FOR_JQUERY && | ||
!currentNode.firstElementChild && | ||
(!currentNode.content || !currentNode.content.firstElementChild) && | ||
/</g.test(currentNode.textContent) | ||
) { | ||
DOMPurify.removed.push({ element: currentNode.cloneNode() }); | ||
currentNode.innerHTML = currentNode.textContent.replace(/</g, '<'); | ||
} | ||
/* Remove element if anything forbids its presence */ | ||
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { | ||
/* Keep content except for black-listed elements */ | ||
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] | ||
&& typeof currentNode.insertAdjacentHTML === 'function') { | ||
try { | ||
currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML); | ||
} catch (e) {} | ||
} | ||
_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; | ||
content = content.replace(MUSTACHE_EXPR, ' '); | ||
content = content.replace(ERB_EXPR, ' '); | ||
if (currentNode.textContent !== content) { | ||
DOMPurify.removed.push({ element: currentNode.cloneNode() }); | ||
currentNode.textContent = content; | ||
} | ||
} | ||
/* Convert markup to cover jQuery behavior */ | ||
if (SAFE_FOR_JQUERY && !currentNode.firstElementChild && | ||
(!currentNode.content || !currentNode.content.firstElementChild) && | ||
/</g.test(currentNode.textContent)) { | ||
DOMPurify.removed.push({element: currentNode.cloneNode()}); | ||
currentNode.innerHTML = currentNode.textContent.replace(/</g, '<'); | ||
} | ||
/* Execute a hook if present */ | ||
_executeHook('afterSanitizeElements', currentNode, null); | ||
/* Sanitize element content to be template-safe */ | ||
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) { | ||
/* Get the element's text content */ | ||
content = currentNode.textContent; | ||
content = content.replace(MUSTACHE_EXPR, ' '); | ||
content = content.replace(ERB_EXPR, ' '); | ||
if (currentNode.textContent !== content) { | ||
DOMPurify.removed.push({element: currentNode.cloneNode()}); | ||
currentNode.textContent = content; | ||
} | ||
} | ||
return false; | ||
}; | ||
/* Execute a hook if present */ | ||
_executeHook('afterSanitizeElements', currentNode, null); | ||
const DATA_ATTR = /^data-[\-\w.\u00B7-\uFFFF]/; // eslint-disable-line no-useless-escape | ||
const ARIA_ATTR = /^aria-[\-\w]+$/; // eslint-disable-line no-useless-escape | ||
const IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; // eslint-disable-line no-useless-escape | ||
const IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i; | ||
/* This needs to be extensive thanks to Webkit/Blink's behavior */ | ||
const ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; | ||
return false; | ||
}; | ||
/** | ||
* _sanitizeAttributes | ||
* | ||
* @protect attributes | ||
* @protect nodeName | ||
* @protect removeAttribute | ||
* @protect setAttribute | ||
* | ||
* @param node to sanitize | ||
* @return void | ||
*/ | ||
// eslint-disable-next-line complexity | ||
const _sanitizeAttributes = function(currentNode) { | ||
let attr; | ||
let name; | ||
let value; | ||
let lcName; | ||
let idAttr; | ||
let attributes; | ||
let l; | ||
/* Execute a hook if present */ | ||
_executeHook('beforeSanitizeAttributes', currentNode, null); | ||
var DATA_ATTR = /^data-[\-\w.\u00B7-\uFFFF]/; | ||
var ARIA_ATTR = /^aria-[\-\w]+$/; | ||
var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; | ||
var IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i; | ||
/* This needs to be extensive thanks to Webkit/Blink's behavior */ | ||
var ATTR_WHITESPACE = /[\x00-\x20\xA0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; | ||
attributes = currentNode.attributes; | ||
/** | ||
* _sanitizeAttributes | ||
* | ||
* @protect attributes | ||
* @protect nodeName | ||
* @protect removeAttribute | ||
* @protect setAttribute | ||
* | ||
* @param node to sanitize | ||
* @return void | ||
*/ | ||
var _sanitizeAttributes = function(currentNode) { | ||
var attr, name, value, lcName, idAttr, attributes, hookEvent, l; | ||
/* Execute a hook if present */ | ||
_executeHook('beforeSanitizeAttributes', currentNode, null); | ||
/* Check if we have attributes; if not we might have a text node */ | ||
if (!attributes) { | ||
return; | ||
} | ||
attributes = currentNode.attributes; | ||
const hookEvent = { | ||
attrName: '', | ||
attrValue: '', | ||
keepAttr: true, | ||
allowedAttributes: ALLOWED_ATTR, | ||
}; | ||
l = attributes.length; | ||
/* Check if we have attributes; if not we might have a text node */ | ||
if (!attributes) { return; } | ||
/* Go backwards over all attributes; safely remove bad ones */ | ||
while (l--) { | ||
attr = attributes[l]; | ||
name = attr.name; | ||
value = attr.value.trim(); | ||
lcName = name.toLowerCase(); | ||
hookEvent = { | ||
attrName: '', | ||
attrValue: '', | ||
keepAttr: true, | ||
allowedAttributes: ALLOWED_ATTR | ||
}; | ||
l = attributes.length; | ||
/* Execute a hook if present */ | ||
hookEvent.attrName = lcName; | ||
hookEvent.attrValue = value; | ||
hookEvent.keepAttr = true; | ||
_executeHook('uponSanitizeAttribute', currentNode, hookEvent); | ||
value = hookEvent.attrValue; | ||
/* Go backwards over all attributes; safely remove bad ones */ | ||
while (l--) { | ||
attr = attributes[l]; | ||
name = attr.name; | ||
value = attr.value.trim(); | ||
lcName = name.toLowerCase(); | ||
/* Remove attribute */ | ||
// Safari (iOS + Mac), last tested v8.0.5, crashes if you try to | ||
// remove a "name" attribute from an <img> tag that has an "id" | ||
// attribute at the time. | ||
if ( | ||
lcName === 'name' && | ||
currentNode.nodeName === 'IMG' && | ||
attributes.id | ||
) { | ||
idAttr = attributes.id; | ||
attributes = Array.prototype.slice.apply(attributes); | ||
_removeAttribute('id', currentNode); | ||
_removeAttribute(name, currentNode); | ||
if (attributes.indexOf(idAttr) > l) { | ||
currentNode.setAttribute('id', idAttr.value); | ||
} | ||
} else if ( | ||
// This works around a bug in Safari, where input[type=file] | ||
// cannot be dynamically set after type has been removed | ||
currentNode.nodeName === 'INPUT' && | ||
lcName === 'type' && | ||
value === 'file' && | ||
(ALLOWED_ATTR[lcName] || !FORBID_ATTR[lcName]) | ||
) { | ||
continue; | ||
} else { | ||
// This avoids a crash in Safari v9.0 with double-ids. | ||
// The trick is to first set the id to be empty and then to | ||
// remove the attribute | ||
if (name === 'id') { | ||
currentNode.setAttribute(name, ''); | ||
} | ||
_removeAttribute(name, currentNode); | ||
} | ||
/* Execute a hook if present */ | ||
hookEvent.attrName = lcName; | ||
hookEvent.attrValue = value; | ||
hookEvent.keepAttr = true; | ||
_executeHook('uponSanitizeAttribute', currentNode, hookEvent ); | ||
value = hookEvent.attrValue; | ||
/* Did the hooks approve of the attribute? */ | ||
if (!hookEvent.keepAttr) { | ||
continue; | ||
} | ||
/* Remove attribute */ | ||
// Safari (iOS + Mac), last tested v8.0.5, crashes if you try to | ||
// remove a "name" attribute from an <img> tag that has an "id" | ||
// attribute at the time. | ||
if (lcName === 'name' && | ||
currentNode.nodeName === 'IMG' && attributes.id) { | ||
idAttr = attributes.id; | ||
attributes = Array.prototype.slice.apply(attributes); | ||
_removeAttribute('id', currentNode); | ||
_removeAttribute(name, currentNode); | ||
if (attributes.indexOf(idAttr) > l) { | ||
currentNode.setAttribute('id', idAttr.value); | ||
} | ||
} else if ( | ||
// This works around a bug in Safari, where input[type=file] | ||
// cannot be dynamically set after type has been removed | ||
currentNode.nodeName === 'INPUT' && lcName === 'type' && | ||
value === 'file' && (ALLOWED_ATTR[lcName] || !FORBID_ATTR[lcName])) { | ||
continue; | ||
} else { | ||
// This avoids a crash in Safari v9.0 with double-ids. | ||
// The trick is to first set the id to be empty and then to | ||
// remove the attribute | ||
if (name === 'id') { | ||
currentNode.setAttribute(name, ''); | ||
} | ||
_removeAttribute(name, currentNode); | ||
} | ||
/* Make sure attribute cannot clobber */ | ||
if ( | ||
SANITIZE_DOM && | ||
(lcName === 'id' || lcName === 'name') && | ||
(value in window || value in document || value in formElement) | ||
) { | ||
continue; | ||
} | ||
/* Did the hooks approve of the attribute? */ | ||
if (!hookEvent.keepAttr) { | ||
continue; | ||
} | ||
/* Sanitize attribute content to be template-safe */ | ||
if (SAFE_FOR_TEMPLATES) { | ||
value = value.replace(MUSTACHE_EXPR, ' '); | ||
value = value.replace(ERB_EXPR, ' '); | ||
} | ||
/* Make sure attribute cannot clobber */ | ||
if (SANITIZE_DOM && | ||
(lcName === 'id' || lcName === 'name') && | ||
(value in window || value in document || value in formElement)) { | ||
continue; | ||
} | ||
/* 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 && DATA_ATTR.test(lcName)) { | ||
// This attribute is safe | ||
} else if (ALLOW_ARIA_ATTR && ARIA_ATTR.test(lcName)) { | ||
// This attribute is safe | ||
/* Otherwise, check the name is permitted */ | ||
} else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) { | ||
continue; | ||
/* Sanitize attribute content to be template-safe */ | ||
if (SAFE_FOR_TEMPLATES) { | ||
value = value.replace(MUSTACHE_EXPR, ' '); | ||
value = value.replace(ERB_EXPR, ' '); | ||
} | ||
/* 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 (IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE, ''))) { | ||
// This attribute is safe | ||
/* Keep image data URIs alive if src/xlink:href is allowed */ | ||
} else if ( | ||
(lcName === 'src' || lcName === 'xlink:href') && | ||
value.indexOf('data:') === 0 && | ||
DATA_URI_TAGS[currentNode.nodeName.toLowerCase()] | ||
) { | ||
// 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 && | ||
!IS_SCRIPT_OR_DATA.test(value.replace(ATTR_WHITESPACE, '')) | ||
) { | ||
// This attribute is safe | ||
/* Check for binary attributes */ | ||
// eslint-disable-next-line no-negated-condition | ||
} else if (!value) { | ||
// Binary attributes are safe at this point | ||
/* Anything else, presume unsafe, do not add it back */ | ||
} else { | ||
continue; | ||
} | ||
/* 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 && DATA_ATTR.test(lcName)) { | ||
// This attribute is safe | ||
} | ||
else if (ALLOW_ARIA_ATTR && ARIA_ATTR.test(lcName)) { | ||
// This attribute is safe | ||
} | ||
/* Otherwise, check the name is permitted */ | ||
else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) { | ||
continue; | ||
} | ||
/* 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 (IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE,''))) { | ||
// This attribute is safe | ||
} | ||
/* Keep image data URIs alive if src/xlink:href is allowed */ | ||
else if ( | ||
(lcName === 'src' || lcName === 'xlink:href') && | ||
value.indexOf('data:') === 0 && | ||
DATA_URI_TAGS[currentNode.nodeName.toLowerCase()]) { | ||
// 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 && | ||
!IS_SCRIPT_OR_DATA.test(value.replace(ATTR_WHITESPACE,''))) { | ||
// This attribute is safe | ||
} | ||
/* Check for binary attributes */ | ||
else if (!value) { | ||
// binary attributes are safe at this point | ||
} | ||
/* Anything else, presume unsafe, do not add it back */ | ||
else { | ||
continue; | ||
} | ||
/* Handle invalid data-* attribute set by try-catching it */ | ||
try { | ||
currentNode.setAttribute(name, value); | ||
DOMPurify.removed.pop(); | ||
} catch (err) {} | ||
} | ||
/* Handle invalid data-* attribute set by try-catching it */ | ||
try { | ||
currentNode.setAttribute(name, value); | ||
DOMPurify.removed.pop(); | ||
} catch (e) {} | ||
} | ||
/* Execute a hook if present */ | ||
_executeHook('afterSanitizeAttributes', currentNode, null); | ||
}; | ||
/* Execute a hook if present */ | ||
_executeHook('afterSanitizeAttributes', currentNode, null); | ||
}; | ||
/** | ||
* _sanitizeShadowDOM | ||
* | ||
* @param fragment to iterate over recursively | ||
* @return void | ||
*/ | ||
const _sanitizeShadowDOM = function(fragment) { | ||
let shadowNode; | ||
const shadowIterator = _createIterator(fragment); | ||
/** | ||
* _sanitizeShadowDOM | ||
* | ||
* @param fragment to iterate over recursively | ||
* @return void | ||
*/ | ||
var _sanitizeShadowDOM = function(fragment) { | ||
var shadowNode; | ||
var shadowIterator = _createIterator(fragment); | ||
/* Execute a hook if present */ | ||
_executeHook('beforeSanitizeShadowDOM', fragment, null); | ||
/* Execute a hook if present */ | ||
_executeHook('beforeSanitizeShadowDOM', fragment, null); | ||
while ((shadowNode = shadowIterator.nextNode())) { | ||
/* Execute a hook if present */ | ||
_executeHook('uponSanitizeShadowNode', shadowNode, null); | ||
while ( (shadowNode = shadowIterator.nextNode()) ) { | ||
/* Execute a hook if present */ | ||
_executeHook('uponSanitizeShadowNode', shadowNode, null); | ||
/* Sanitize tags and elements */ | ||
if (_sanitizeElements(shadowNode)) { | ||
continue; | ||
} | ||
/* Sanitize tags and elements */ | ||
if (_sanitizeElements(shadowNode)) { | ||
continue; | ||
} | ||
/* Deep shadow DOM detected */ | ||
if (shadowNode.content instanceof DocumentFragment) { | ||
_sanitizeShadowDOM(shadowNode.content); | ||
} | ||
/* Deep shadow DOM detected */ | ||
if (shadowNode.content instanceof DocumentFragment) { | ||
_sanitizeShadowDOM(shadowNode.content); | ||
} | ||
/* Check attributes, sanitize if necessary */ | ||
_sanitizeAttributes(shadowNode); | ||
} | ||
/* Check attributes, sanitize if necessary */ | ||
_sanitizeAttributes(shadowNode); | ||
} | ||
/* Execute a hook if present */ | ||
_executeHook('afterSanitizeShadowDOM', fragment, null); | ||
}; | ||
/* 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} configuration object | ||
*/ | ||
// eslint-disable-next-line complexity | ||
DOMPurify.sanitize = function(dirty, cfg) { | ||
let body; | ||
let importedNode; | ||
let currentNode; | ||
let oldNode; | ||
let returnNode; | ||
/* 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 */ | ||
if (!dirty) { | ||
dirty = '<!-->'; | ||
} | ||
/** | ||
* _executeHook | ||
* Execute user configurable hooks | ||
* | ||
* @param {String} entryPoint Name of the hook's entry point | ||
* @param {Node} currentNode | ||
*/ | ||
var _executeHook = function(entryPoint, currentNode, data) { | ||
if (!hooks[entryPoint]) { return; } | ||
/* Stringify, in case dirty is an object */ | ||
if (typeof dirty !== 'string' && !_isNode(dirty)) { | ||
// eslint-disable-next-line no-negated-condition | ||
if (typeof dirty.toString !== 'function') { | ||
throw new TypeError('toString is not a function'); | ||
} else { | ||
dirty = dirty.toString(); | ||
} | ||
} | ||
hooks[entryPoint].forEach(function(hook) { | ||
hook.call(DOMPurify, currentNode, data, CONFIG); | ||
}); | ||
}; | ||
/** | ||
* sanitize | ||
* Public method providing core sanitation functionality | ||
* | ||
* @param {String|Node} dirty string or DOM node | ||
* @param {Object} configuration object | ||
*/ | ||
DOMPurify.sanitize = function(dirty, cfg) { | ||
var body, importedNode, currentNode, oldNode, nodeIterator, returnNode; | ||
/* 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 */ | ||
if (!dirty) { | ||
dirty = '<!-->'; | ||
/* Check we can run. Otherwise fall back or ignore */ | ||
if (!DOMPurify.isSupported) { | ||
if ( | ||
typeof window.toStaticHTML === 'object' || | ||
typeof window.toStaticHTML === 'function' | ||
) { | ||
if (typeof dirty === 'string') { | ||
return window.toStaticHTML(dirty); | ||
} else if (_isNode(dirty)) { | ||
return window.toStaticHTML(dirty.outerHTML); | ||
} | ||
} | ||
return dirty; | ||
} | ||
/* Stringify, in case dirty is an object */ | ||
if (typeof dirty !== 'string' && !_isNode(dirty)) { | ||
if (typeof dirty.toString !== 'function') { | ||
throw new TypeError('toString is not a function'); | ||
} else { | ||
dirty = dirty.toString(); | ||
} | ||
} | ||
/* Assign config vars */ | ||
if (!SET_CONFIG) { | ||
_parseConfig(cfg); | ||
} | ||
/* Check we can run. Otherwise fall back or ignore */ | ||
if (!DOMPurify.isSupported) { | ||
if (typeof window.toStaticHTML === 'object' | ||
|| typeof window.toStaticHTML === 'function') { | ||
if (typeof dirty === 'string') { | ||
return window.toStaticHTML(dirty); | ||
} else if (_isNode(dirty)) { | ||
return window.toStaticHTML(dirty.outerHTML); | ||
} | ||
} | ||
return dirty; | ||
} | ||
/* Clean up removed elements */ | ||
DOMPurify.removed = []; | ||
/* Assign config vars */ | ||
if (!SET_CONFIG) { | ||
_parseConfig(cfg); | ||
} | ||
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 { | ||
body.appendChild(importedNode); | ||
} | ||
} else { | ||
/* Exit directly if we have nothing to do */ | ||
if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) { | ||
return dirty; | ||
} | ||
/* Clean up removed elements */ | ||
DOMPurify.removed = []; | ||
/* Initialize the document to work on */ | ||
body = _initDocument(dirty); | ||
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 { | ||
body.appendChild(importedNode); | ||
} | ||
} else { | ||
/* Exit directly if we have nothing to do */ | ||
if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) { | ||
return dirty; | ||
} | ||
/* Check we have a DOM node from the data */ | ||
if (!body) { | ||
return RETURN_DOM ? null : ''; | ||
} | ||
} | ||
/* Initialize the document to work on */ | ||
body = _initDocument(dirty); | ||
/* Remove first element node (ours) if FORCE_BODY is set */ | ||
if (FORCE_BODY) { | ||
_forceRemove(body.firstChild); | ||
} | ||
/* Check we have a DOM node from the data */ | ||
if (!body) { | ||
return RETURN_DOM ? null : ''; | ||
} | ||
} | ||
/* Get node iterator */ | ||
const nodeIterator = _createIterator(body); | ||
/* Remove first element node (ours) if FORCE_BODY is set */ | ||
if (FORCE_BODY) { | ||
_forceRemove(body.firstChild); | ||
} | ||
/* Now start iterating over the created document */ | ||
while ((currentNode = nodeIterator.nextNode())) { | ||
/* Fix IE's strange behavior with manipulated textNodes #89 */ | ||
if (currentNode.nodeType === 3 && currentNode === oldNode) { | ||
continue; | ||
} | ||
/* Get node iterator */ | ||
nodeIterator = _createIterator(body); | ||
/* Sanitize tags and elements */ | ||
if (_sanitizeElements(currentNode)) { | ||
continue; | ||
} | ||
/* Now start iterating over the created document */ | ||
while ( (currentNode = nodeIterator.nextNode()) ) { | ||
/* Shadow DOM detected, sanitize it */ | ||
if (currentNode.content instanceof DocumentFragment) { | ||
_sanitizeShadowDOM(currentNode.content); | ||
} | ||
/* Fix IE's strange behavior with manipulated textNodes #89 */ | ||
if (currentNode.nodeType === 3 && currentNode === oldNode) { | ||
continue; | ||
} | ||
/* Check attributes, sanitize if necessary */ | ||
_sanitizeAttributes(currentNode); | ||
/* Sanitize tags and elements */ | ||
if (_sanitizeElements(currentNode)) { | ||
continue; | ||
} | ||
oldNode = currentNode; | ||
} | ||
/* Shadow DOM detected, sanitize it */ | ||
if (currentNode.content instanceof DocumentFragment) { | ||
_sanitizeShadowDOM(currentNode.content); | ||
} | ||
/* Return sanitized string or DOM */ | ||
if (RETURN_DOM) { | ||
if (RETURN_DOM_FRAGMENT) { | ||
returnNode = createDocumentFragment.call(body.ownerDocument); | ||
/* Check attributes, sanitize if necessary */ | ||
_sanitizeAttributes(currentNode); | ||
oldNode = currentNode; | ||
while (body.firstChild) { | ||
returnNode.appendChild(body.firstChild); | ||
} | ||
} else { | ||
returnNode = body; | ||
} | ||
/* Return sanitized string or DOM */ | ||
if (RETURN_DOM) { | ||
if (RETURN_DOM_IMPORT) { | ||
/* 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); | ||
} | ||
if (RETURN_DOM_FRAGMENT) { | ||
returnNode = createDocumentFragment.call(body.ownerDocument); | ||
return returnNode; | ||
} | ||
while (body.firstChild) { | ||
returnNode.appendChild(body.firstChild); | ||
} | ||
} else { | ||
returnNode = body; | ||
} | ||
return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; | ||
}; | ||
if (RETURN_DOM_IMPORT) { | ||
/* 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); | ||
} | ||
/** | ||
* Public method to set the configuration once | ||
* setConfig | ||
* | ||
* @param {Object} configuration object | ||
* @return void | ||
*/ | ||
DOMPurify.setConfig = function(cfg) { | ||
_parseConfig(cfg); | ||
SET_CONFIG = true; | ||
}; | ||
return returnNode; | ||
} | ||
/** | ||
* Public method to remove the configuration | ||
* clearConfig | ||
* | ||
* @return void | ||
*/ | ||
DOMPurify.clearConfig = function() { | ||
CONFIG = null; | ||
SET_CONFIG = false; | ||
}; | ||
return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; | ||
}; | ||
/** | ||
* AddHook | ||
* Public method to add DOMPurify hooks | ||
* | ||
* @param {String} entryPoint | ||
* @param {Function} hookFunction | ||
*/ | ||
DOMPurify.addHook = function(entryPoint, hookFunction) { | ||
if (typeof hookFunction !== 'function') { | ||
return; | ||
} | ||
hooks[entryPoint] = hooks[entryPoint] || []; | ||
hooks[entryPoint].push(hookFunction); | ||
}; | ||
/** | ||
* setConfig | ||
* Public method to set the configuration once | ||
* | ||
* @param {Object} configuration object | ||
* @return void | ||
*/ | ||
DOMPurify.setConfig = function(cfg) { | ||
_parseConfig(cfg); | ||
SET_CONFIG = true; | ||
}; | ||
/** | ||
* 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 | ||
* @return void | ||
*/ | ||
DOMPurify.removeHook = function(entryPoint) { | ||
if (hooks[entryPoint]) { | ||
hooks[entryPoint].pop(); | ||
} | ||
}; | ||
/** | ||
* clearConfig | ||
* Public method to remove the configuration | ||
* | ||
* @return void | ||
*/ | ||
DOMPurify.clearConfig = function() { | ||
CONFIG = null; | ||
SET_CONFIG = false; | ||
}; | ||
/** | ||
* RemoveHooks | ||
* Public method to remove all DOMPurify hooks at a given entryPoint | ||
* | ||
* @param {String} entryPoint | ||
* @return void | ||
*/ | ||
DOMPurify.removeHooks = function(entryPoint) { | ||
if (hooks[entryPoint]) { | ||
hooks[entryPoint] = []; | ||
} | ||
}; | ||
/** | ||
* addHook | ||
* Public method to add DOMPurify hooks | ||
* | ||
* @param {String} entryPoint | ||
* @param {Function} hookFunction | ||
*/ | ||
DOMPurify.addHook = function(entryPoint, hookFunction) { | ||
if (typeof hookFunction !== 'function') { return; } | ||
hooks[entryPoint] = hooks[entryPoint] || []; | ||
hooks[entryPoint].push(hookFunction); | ||
}; | ||
/** | ||
* RemoveAllHooks | ||
* Public method to remove all DOMPurify hooks | ||
* | ||
* @return void | ||
*/ | ||
DOMPurify.removeAllHooks = function() { | ||
hooks = {}; | ||
}; | ||
/** | ||
* 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 | ||
* @return void | ||
*/ | ||
DOMPurify.removeHook = function(entryPoint) { | ||
if (hooks[entryPoint]) { | ||
hooks[entryPoint].pop(); | ||
} | ||
}; | ||
return DOMPurify; | ||
} | ||
/** | ||
* removeHooks | ||
* Public method to remove all DOMPurify hooks at a given entryPoint | ||
* | ||
* @param {String} entryPoint | ||
* @return void | ||
*/ | ||
DOMPurify.removeHooks = function(entryPoint) { | ||
if (hooks[entryPoint]) { | ||
hooks[entryPoint] = []; | ||
} | ||
}; | ||
/** | ||
* removeAllHooks | ||
* Public method to remove all DOMPurify hooks | ||
* | ||
* @return void | ||
*/ | ||
DOMPurify.removeAllHooks = function() { | ||
hooks = {}; | ||
}; | ||
return DOMPurify; | ||
})); | ||
export default createDOMPurify(); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
234526
11
2321
0
256
35
2