2025-06-03 11:49:59 +02:00
/*! @license DOMPurify 3.2.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.6/LICENSE */
2020-09-28 12:46:53 +02:00
2025-07-11 11:42:46 +02:00
const {
entries ,
setPrototypeOf ,
isFrozen ,
getPrototypeOf ,
getOwnPropertyDescriptor
} = Object ;
let {
freeze ,
seal ,
create
} = Object ; // eslint-disable-line import/no-mutable-exports
let {
apply ,
construct
} = typeof Reflect !== 'undefined' && Reflect ;
if ( ! freeze ) {
freeze = function freeze ( x ) {
return x ;
} ;
}
if ( ! seal ) {
seal = function seal ( x ) {
return x ;
} ;
}
if ( ! apply ) {
apply = function apply ( fun , thisValue , args ) {
return fun . apply ( thisValue , args ) ;
} ;
}
if ( ! construct ) {
construct = function construct ( Func , args ) {
return new Func ( ... args ) ;
} ;
}
const arrayForEach = unapply ( Array . prototype . forEach ) ;
const arrayLastIndexOf = unapply ( Array . prototype . lastIndexOf ) ;
const arrayPop = unapply ( Array . prototype . pop ) ;
const arrayPush = unapply ( Array . prototype . push ) ;
const arraySplice = unapply ( Array . prototype . splice ) ;
const stringToLowerCase = unapply ( String . prototype . toLowerCase ) ;
const stringToString = unapply ( String . prototype . toString ) ;
const stringMatch = unapply ( String . prototype . match ) ;
const stringReplace = unapply ( String . prototype . replace ) ;
const stringIndexOf = unapply ( String . prototype . indexOf ) ;
const stringTrim = unapply ( String . prototype . trim ) ;
const objectHasOwnProperty = unapply ( Object . prototype . hasOwnProperty ) ;
const regExpTest = unapply ( RegExp . prototype . test ) ;
const typeErrorCreate = unconstruct ( TypeError ) ;
/ * *
* Creates a new function that calls the given function with a specified thisArg and arguments .
*
* @ param func - The function to be wrapped and called .
* @ returns A new function that calls the given function with a specified thisArg and arguments .
* /
function unapply ( func ) {
return function ( thisArg ) {
if ( thisArg instanceof RegExp ) {
thisArg . lastIndex = 0 ;
}
for ( var _len = arguments . length , args = new Array ( _len > 1 ? _len - 1 : 0 ) , _key = 1 ; _key < _len ; _key ++ ) {
args [ _key - 1 ] = arguments [ _key ] ;
}
return apply ( func , thisArg , args ) ;
} ;
}
/ * *
* Creates a new function that constructs an instance of the given constructor function with the provided arguments .
*
* @ param func - The constructor function to be wrapped and called .
* @ returns A new function that constructs an instance of the given constructor function with the provided arguments .
* /
function unconstruct ( func ) {
return function ( ) {
for ( var _len2 = arguments . length , args = new Array ( _len2 ) , _key2 = 0 ; _key2 < _len2 ; _key2 ++ ) {
args [ _key2 ] = arguments [ _key2 ] ;
}
return construct ( func , args ) ;
} ;
}
/ * *
* Add properties to a lookup table
*
* @ param set - The set to which elements will be added .
* @ param array - The array containing elements to be added to the set .
* @ param transformCaseFunc - An optional function to transform the case of each element before adding to the set .
* @ returns The modified set with added elements .
* /
function addToSet ( set , array ) {
let transformCaseFunc = arguments . length > 2 && arguments [ 2 ] !== undefined ? arguments [ 2 ] : stringToLowerCase ;
if ( setPrototypeOf ) {
// Make 'in' and truthy checks like Boolean(set.constructor)
// independent of any properties defined on Object.prototype.
// Prevent prototype setters from intercepting set as a this value.
setPrototypeOf ( set , null ) ;
}
let l = array . length ;
while ( l -- ) {
let element = array [ l ] ;
if ( typeof element === 'string' ) {
const lcElement = transformCaseFunc ( element ) ;
if ( lcElement !== element ) {
// Config presets (e.g. tags.js, attrs.js) are immutable.
if ( ! isFrozen ( array ) ) {
array [ l ] = lcElement ;
}
element = lcElement ;
}
}
set [ element ] = true ;
}
return set ;
}
/ * *
* Clean up an array to harden against CSPP
*
* @ param array - The array to be cleaned .
* @ returns The cleaned version of the array
* /
function cleanArray ( array ) {
for ( let index = 0 ; index < array . length ; index ++ ) {
const isPropertyExist = objectHasOwnProperty ( array , index ) ;
if ( ! isPropertyExist ) {
array [ index ] = null ;
}
}
return array ;
}
/ * *
* Shallow clone an object
*
* @ param object - The object to be cloned .
* @ returns A new object that copies the original .
* /
function clone ( object ) {
const newObject = create ( null ) ;
for ( const [ property , value ] of entries ( object ) ) {
const isPropertyExist = objectHasOwnProperty ( object , property ) ;
if ( isPropertyExist ) {
if ( Array . isArray ( value ) ) {
newObject [ property ] = cleanArray ( value ) ;
} else if ( value && typeof value === 'object' && value . constructor === Object ) {
newObject [ property ] = clone ( value ) ;
} else {
newObject [ property ] = value ;
}
}
}
return newObject ;
}
/ * *
* This method automatically checks if the prop is function or getter and behaves accordingly .
*
* @ param object - The object to look up the getter function in its prototype chain .
* @ param prop - The property name for which to find the getter function .
* @ returns The getter function found in the prototype chain or a fallback function .
* /
function lookupGetter ( object , prop ) {
while ( object !== null ) {
const desc = getOwnPropertyDescriptor ( object , prop ) ;
if ( desc ) {
if ( desc . get ) {
return unapply ( desc . get ) ;
}
if ( typeof desc . value === 'function' ) {
return unapply ( desc . value ) ;
}
}
object = getPrototypeOf ( object ) ;
}
function fallbackValue ( ) {
return null ;
}
return fallbackValue ;
}
2020-09-28 12:46:53 +02:00
2025-07-11 11:42:46 +02:00
const html$1 = freeze ( [ 'a' , 'abbr' , 'acronym' , 'address' , 'area' , 'article' , 'aside' , 'audio' , 'b' , 'bdi' , 'bdo' , 'big' , 'blink' , 'blockquote' , 'body' , 'br' , 'button' , 'canvas' , 'caption' , 'center' , 'cite' , 'code' , 'col' , 'colgroup' , 'content' , 'data' , 'datalist' , 'dd' , 'decorator' , 'del' , 'details' , 'dfn' , 'dialog' , 'dir' , 'div' , 'dl' , 'dt' , 'element' , 'em' , 'fieldset' , 'figcaption' , 'figure' , 'font' , 'footer' , 'form' , 'h1' , 'h2' , 'h3' , 'h4' , 'h5' , 'h6' , 'head' , 'header' , 'hgroup' , 'hr' , 'html' , 'i' , 'img' , 'input' , 'ins' , 'kbd' , 'label' , 'legend' , 'li' , 'main' , 'map' , 'mark' , 'marquee' , 'menu' , 'menuitem' , 'meter' , 'nav' , 'nobr' , 'ol' , 'optgroup' , 'option' , 'output' , 'p' , 'picture' , 'pre' , 'progress' , 'q' , 'rp' , 'rt' , 'ruby' , 's' , 'samp' , 'section' , 'select' , 'shadow' , 'small' , 'source' , 'spacer' , 'span' , 'strike' , 'strong' , 'style' , 'sub' , 'summary' , 'sup' , 'table' , 'tbody' , 'td' , 'template' , 'textarea' , 'tfoot' , 'th' , 'thead' , 'time' , 'tr' , 'track' , 'tt' , 'u' , 'ul' , 'var' , 'video' , 'wbr' ] ) ;
const svg$1 = freeze ( [ 'svg' , 'a' , 'altglyph' , 'altglyphdef' , 'altglyphitem' , 'animatecolor' , 'animatemotion' , 'animatetransform' , 'circle' , 'clippath' , 'defs' , 'desc' , 'ellipse' , 'filter' , 'font' , 'g' , 'glyph' , 'glyphref' , 'hkern' , 'image' , 'line' , 'lineargradient' , 'marker' , 'mask' , 'metadata' , 'mpath' , 'path' , 'pattern' , 'polygon' , 'polyline' , 'radialgradient' , 'rect' , 'stop' , 'style' , 'switch' , 'symbol' , 'text' , 'textpath' , 'title' , 'tref' , 'tspan' , 'view' , 'vkern' ] ) ;
const svgFilters = freeze ( [ 'feBlend' , 'feColorMatrix' , 'feComponentTransfer' , 'feComposite' , 'feConvolveMatrix' , 'feDiffuseLighting' , 'feDisplacementMap' , 'feDistantLight' , 'feDropShadow' , 'feFlood' , 'feFuncA' , 'feFuncB' , 'feFuncG' , 'feFuncR' , 'feGaussianBlur' , 'feImage' , 'feMerge' , 'feMergeNode' , 'feMorphology' , 'feOffset' , 'fePointLight' , 'feSpecularLighting' , 'feSpotLight' , 'feTile' , 'feTurbulence' ] ) ;
// List of SVG elements that are disallowed by default.
// We still need to know them so that we can do namespace
// checks properly in case one wants to add them to
// allow-list.
const svgDisallowed = freeze ( [ 'animate' , 'color-profile' , 'cursor' , 'discard' , 'font-face' , 'font-face-format' , 'font-face-name' , 'font-face-src' , 'font-face-uri' , 'foreignobject' , 'hatch' , 'hatchpath' , 'mesh' , 'meshgradient' , 'meshpatch' , 'meshrow' , 'missing-glyph' , 'script' , 'set' , 'solidcolor' , 'unknown' , 'use' ] ) ;
const mathMl$1 = freeze ( [ 'math' , 'menclose' , 'merror' , 'mfenced' , 'mfrac' , 'mglyph' , 'mi' , 'mlabeledtr' , 'mmultiscripts' , 'mn' , 'mo' , 'mover' , 'mpadded' , 'mphantom' , 'mroot' , 'mrow' , 'ms' , 'mspace' , 'msqrt' , 'mstyle' , 'msub' , 'msup' , 'msubsup' , 'mtable' , 'mtd' , 'mtext' , 'mtr' , 'munder' , 'munderover' , 'mprescripts' ] ) ;
// Similarly to SVG, we want to know all MathML elements,
// even those that we disallow by default.
const mathMlDisallowed = freeze ( [ 'maction' , 'maligngroup' , 'malignmark' , 'mlongdiv' , 'mscarries' , 'mscarry' , 'msgroup' , 'mstack' , 'msline' , 'msrow' , 'semantics' , 'annotation' , 'annotation-xml' , 'mprescripts' , 'none' ] ) ;
const text = freeze ( [ '#text' ] ) ;
const html = freeze ( [ 'accept' , 'action' , 'align' , 'alt' , 'autocapitalize' , 'autocomplete' , 'autopictureinpicture' , 'autoplay' , 'background' , 'bgcolor' , 'border' , 'capture' , 'cellpadding' , 'cellspacing' , 'checked' , 'cite' , 'class' , 'clear' , 'color' , 'cols' , 'colspan' , 'controls' , 'controlslist' , 'coords' , 'crossorigin' , 'datetime' , 'decoding' , 'default' , 'dir' , 'disabled' , 'disablepictureinpicture' , 'disableremoteplayback' , 'download' , 'draggable' , 'enctype' , 'enterkeyhint' , 'face' , 'for' , 'headers' , 'height' , 'hidden' , 'high' , 'href' , 'hreflang' , 'id' , 'inputmode' , 'integrity' , 'ismap' , 'kind' , 'label' , 'lang' , 'list' , 'loading' , 'loop' , 'low' , 'max' , 'maxlength' , 'media' , 'method' , 'min' , 'minlength' , 'multiple' , 'muted' , 'name' , 'nonce' , 'noshade' , 'novalidate' , 'nowrap' , 'open' , 'optimum' , 'pattern' , 'placeholder' , 'playsinline' , 'popover' , 'popovertarget' , 'popovertargetaction' , 'poster' , 'preload' , 'pubdate' , 'radiogroup' , 'readonly' , 'rel' , 'required' , 'rev' , 'reversed' , 'role' , 'rows' , 'rowspan' , 'spellcheck' , 'scope' , 'selected' , 'shape' , 'size' , 'sizes' , 'span' , 'srclang' , 'start' , 'src' , 'srcset' , 'step' , 'style' , 'summary' , 'tabindex' , 'title' , 'translate' , 'type' , 'usemap' , 'valign' , 'value' , 'width' , 'wrap' , 'xmlns' , 'slot' ] ) ;
const svg = freeze ( [ 'accent-height' , 'accumulate' , 'additive' , 'alignment-baseline' , 'amplitude' , 'ascent' , 'attributename' , 'attributetype' , 'azimuth' , 'basefrequency' , 'baseline-shift' , 'begin' , 'bias' , 'by' , 'class' , 'clip' , 'clippathunits' , 'clip-path' , 'clip-rule' , 'color' , 'color-interpolation' , 'color-interpolation-filters' , 'color-profile' , 'color-rendering' , 'cx' , 'cy' , 'd' , 'dx' , 'dy' , 'diffuseconstant' , 'direction' , 'display' , 'divisor' , 'dur' , 'edgemode' , 'elevation' , 'end' , 'exponent' , 'fill' , 'fill-opacity' , 'fill-rule' , 'filter' , 'filterunits' , 'flood-color' , 'flood-opacity' , 'font-family' , 'font-size' , 'font-size-adjust' , 'font-stretch' , 'font-style' , 'font-variant' , 'font-weight' , 'fx' , 'fy' , 'g1' , 'g2' , 'glyph-name' , 'glyphref' , 'gradientunits' , 'gradienttransform' , 'height' , 'href' , 'id' , 'image-rendering' , 'in' , 'in2' , 'intercept' , 'k' , 'k1' , 'k2' , 'k3' , 'k4' , 'kerning' , 'keypoints' , 'keysplines' , 'keytimes' , 'lang' , 'lengthadjust' , 'letter-spacing' , 'kernelmatrix' , 'kernelunitlength' , 'lighting-color' , 'local' , 'marker-end' , 'marker-mid' , 'marker-start' , 'markerheight' , 'markerunits' , 'markerwidth' , 'maskcontentunits' , 'maskunits' , 'max' , 'mask' , 'media' , 'method' , 'mode' , 'min' , 'name' , 'numoctaves' , 'offset' , 'operator' , 'opacity' , 'order' , 'orient' , 'orientation' , 'origin' , 'overflow' , 'paint-order' , 'path' , 'pathlength' , 'patterncontentunits' , 'patterntransform' , 'patternunits' , 'points' , 'preservealpha' , 'preserveaspectratio' , 'primitiveunits' , 'r' , 'rx' , 'ry' , 'radius' , 'refx' , 'refy' , 'repeatcount' , 'repeatdur' , 'restart' , 'result' , 'rotate' , 'scale' , 'seed' , 'shape-rendering' , 'slope' , 'specularconstant' , 'specularexponent' , 'spreadmethod' , 'startoffset' , 'stddeviation' , 'stitchtiles' , 'stop-color' , 'stop-opacity' , 'stroke-dasharray' , 'stroke-dashoffset' , 'stroke-linecap' , 'stroke-linejoin' , 'stroke-miterlimit' , 'stroke-opacity' , 'stroke' , 'stroke-width' , 'style' , 'surfacescale' , 'systemlanguage' , 'tabindex' , 'tablevalues' , 'targetx' , 'targety' , 'transform' , 'transform-origin' , 'text-anchor' , 'text-decoration' , 'text-rendering' , 'textlength' , 'type' , 'u1' , 'u2' , 'unicode' , 'values' , 'viewbox' , 'visibility' , 'version' , 'vert-adv-y' , 'vert-origin-x' , 'vert-origin-y' , 'width' , 'word-spacing' , 'wrap' , 'writing-mode' , 'xchannelselector' , 'ychannelselector' , 'x' , 'x1' , 'x2' , 'xmlns' , 'y' , 'y1' , 'y2' , 'z' , 'zoomandpan' ] ) ;
const mathMl = freeze ( [ 'accent' , 'accentunder' , 'align' , 'bevelled' , 'close' , 'columnsalign' , 'columnlines' , 'columnspan' , 'denomalign' , 'depth' , 'dir' , 'display' , 'displaystyle' , 'encoding' , 'fence' , 'frame' , 'height' , 'href' , 'id' , 'largeop' , 'length' , 'linethickness' , 'lspace' , 'lquote' , 'mathbackground' , 'mathcolor' , 'mathsize' , 'mathvariant' , 'maxsize' , 'minsize' , 'movablelimits' , 'notation' , 'numalign' , 'open' , 'rowalign' , 'rowlines' , 'rowspacing' , 'rowspan' , 'rspace' , 'rquote' , 'scriptlevel' , 'scriptminsize' , 'scriptsizemultiplier' , 'selection' , 'separator' , 'separators' , 'stretchy' , 'subscriptshift' , 'supscriptshift' , 'symmetric' , 'voffset' , 'width' , 'xmlns' ] ) ;
const xml = freeze ( [ 'xlink:href' , 'xml:id' , 'xlink:title' , 'xml:space' , 'xmlns:xlink' ] ) ;
// eslint-disable-next-line unicorn/better-regex
const MUSTACHE _EXPR = seal ( /\{\{[\w\W]*|[\w\W]*\}\}/gm ) ; // Specify template detection regex for SAFE_FOR_TEMPLATES mode
const ERB _EXPR = seal ( /<%[\w\W]*|[\w\W]*%>/gm ) ;
const TMPLIT _EXPR = seal ( /\$\{[\w\W]*/gm ) ; // eslint-disable-line unicorn/better-regex
const DATA _ATTR = seal ( /^data-[\-\w.\u00B7-\uFFFF]+$/ ) ; // eslint-disable-line no-useless-escape
const ARIA _ATTR = seal ( /^aria-[\-\w]+$/ ) ; // eslint-disable-line no-useless-escape
const IS _ALLOWED _URI = seal ( /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
) ;
const IS _SCRIPT _OR _DATA = seal ( /^(?:\w+script|data):/i ) ;
const ATTR _WHITESPACE = seal ( /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
) ;
const DOCTYPE _NAME = seal ( /^html$/i ) ;
const CUSTOM _ELEMENT = seal ( /^[a-z][.\w]*(-[.\w]+)+$/i ) ;
var EXPRESSIONS = /*#__PURE__*/ Object . freeze ( {
_ _proto _ _ : null ,
ARIA _ATTR : ARIA _ATTR ,
ATTR _WHITESPACE : ATTR _WHITESPACE ,
CUSTOM _ELEMENT : CUSTOM _ELEMENT ,
DATA _ATTR : DATA _ATTR ,
DOCTYPE _NAME : DOCTYPE _NAME ,
ERB _EXPR : ERB _EXPR ,
IS _ALLOWED _URI : IS _ALLOWED _URI ,
IS _SCRIPT _OR _DATA : IS _SCRIPT _OR _DATA ,
MUSTACHE _EXPR : MUSTACHE _EXPR ,
TMPLIT _EXPR : TMPLIT _EXPR
} ) ;
/* eslint-disable @typescript-eslint/indent */
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
const NODE _TYPE = {
element : 1 ,
attribute : 2 ,
text : 3 ,
cdataSection : 4 ,
entityReference : 5 ,
// Deprecated
entityNode : 6 ,
// Deprecated
progressingInstruction : 7 ,
comment : 8 ,
document : 9 ,
documentType : 10 ,
documentFragment : 11 ,
notation : 12 // Deprecated
} ;
const getGlobal = function getGlobal ( ) {
return typeof window === 'undefined' ? null : window ;
} ;
/ * *
* Creates a no - op policy for internal use only .
* Don ' t export this function outside this module !
* @ param trustedTypes The policy factory .
* @ param purifyHostElement The Script element used to load DOMPurify ( to determine policy name suffix ) .
* @ return The policy created ( or null , if Trusted Types
* are not supported or creating the policy failed ) .
* /
const _createTrustedTypesPolicy = function _createTrustedTypesPolicy ( trustedTypes , purifyHostElement ) {
if ( typeof trustedTypes !== 'object' || typeof trustedTypes . createPolicy !== 'function' ) {
return null ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
// Allow the callers to control the unique policy name
// by adding a data-tt-policy-suffix to the script element with the DOMPurify.
// Policy creation with duplicate names throws in Trusted Types.
let suffix = null ;
const ATTR _NAME = 'data-tt-policy-suffix' ;
if ( purifyHostElement && purifyHostElement . hasAttribute ( ATTR _NAME ) ) {
suffix = purifyHostElement . getAttribute ( ATTR _NAME ) ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
const policyName = 'dompurify' + ( suffix ? '#' + suffix : '' ) ;
try {
return trustedTypes . createPolicy ( policyName , {
createHTML ( html ) {
return html ;
} ,
createScriptURL ( scriptUrl ) {
return scriptUrl ;
}
} ) ;
} catch ( _ ) {
// Policy creation failed (most likely another DOMPurify script has
// already run). Skip creating the policy, as this will only cause errors
// if TT are enforced.
console . warn ( 'TrustedTypes policy ' + policyName + ' could not be created.' ) ;
return null ;
2023-12-13 11:23:54 +01:00
}
2025-07-11 11:42:46 +02:00
} ;
const _createHooksMap = function _createHooksMap ( ) {
return {
afterSanitizeAttributes : [ ] ,
afterSanitizeElements : [ ] ,
afterSanitizeShadowDOM : [ ] ,
beforeSanitizeAttributes : [ ] ,
beforeSanitizeElements : [ ] ,
beforeSanitizeShadowDOM : [ ] ,
uponSanitizeAttribute : [ ] ,
uponSanitizeElement : [ ] ,
uponSanitizeShadowNode : [ ]
} ;
} ;
function createDOMPurify ( ) {
let window = arguments . length > 0 && arguments [ 0 ] !== undefined ? arguments [ 0 ] : getGlobal ( ) ;
const DOMPurify = root => createDOMPurify ( root ) ;
DOMPurify . version = '3.2.6' ;
DOMPurify . removed = [ ] ;
if ( ! window || ! window . document || window . document . nodeType !== NODE _TYPE . document || ! window . Element ) {
// Not running in a browser, provide a factory function
// so that you can pass your own Window
DOMPurify . isSupported = false ;
return DOMPurify ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
let {
document
} = window ;
const originalDocument = document ;
const currentScript = originalDocument . currentScript ;
const {
DocumentFragment ,
HTMLTemplateElement ,
Node ,
Element ,
NodeFilter ,
NamedNodeMap = window . NamedNodeMap || window . MozNamedAttrMap ,
HTMLFormElement ,
DOMParser ,
trustedTypes
} = window ;
const ElementPrototype = Element . prototype ;
const cloneNode = lookupGetter ( ElementPrototype , 'cloneNode' ) ;
const remove = lookupGetter ( ElementPrototype , 'remove' ) ;
const getNextSibling = lookupGetter ( ElementPrototype , 'nextSibling' ) ;
const getChildNodes = lookupGetter ( ElementPrototype , 'childNodes' ) ;
const getParentNode = lookupGetter ( ElementPrototype , 'parentNode' ) ;
// As per issue #47, the web-components registry is inherited by a
// new document created via createHTMLDocument. As per the spec
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
// a new empty registry is used when creating a template contents owner
// document, so we use that as our parent document to ensure nothing
// is inherited.
if ( typeof HTMLTemplateElement === 'function' ) {
const template = document . createElement ( 'template' ) ;
if ( template . content && template . content . ownerDocument ) {
document = template . content . ownerDocument ;
}
}
let trustedTypesPolicy ;
let emptyHTML = '' ;
const {
implementation ,
createNodeIterator ,
createDocumentFragment ,
getElementsByTagName
} = document ;
const {
importNode
} = originalDocument ;
let hooks = _createHooksMap ( ) ;
/ * *
* Expose whether this browser supports running the full DOMPurify .
* /
DOMPurify . isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation . createHTMLDocument !== undefined ;
const {
MUSTACHE _EXPR ,
ERB _EXPR ,
TMPLIT _EXPR ,
DATA _ATTR ,
ARIA _ATTR ,
IS _SCRIPT _OR _DATA ,
ATTR _WHITESPACE ,
CUSTOM _ELEMENT
} = EXPRESSIONS ;
let {
IS _ALLOWED _URI : IS _ALLOWED _URI$1
} = EXPRESSIONS ;
2023-12-13 11:23:54 +01:00
/ * *
2025-07-11 11:42:46 +02:00
* We consider the elements and attributes below to be safe . Ideally
* don ' t add any new ones but feel free to remove unwanted ones .
* /
/* allowed element names */
let ALLOWED _TAGS = null ;
const DEFAULT _ALLOWED _TAGS = addToSet ( { } , [ ... html$1 , ... svg$1 , ... svgFilters , ... mathMl$1 , ... text ] ) ;
/* Allowed attribute names */
let ALLOWED _ATTR = null ;
const DEFAULT _ALLOWED _ATTR = addToSet ( { } , [ ... html , ... svg , ... mathMl , ... xml ] ) ;
/ *
* Configure how DOMPurify should handle custom elements and their attributes as well as customized built - in elements .
* @ property { RegExp | Function | null } tagNameCheck one of [ null , regexPattern , predicate ] . Default : ` null ` ( disallow any custom elements )
* @ property { RegExp | Function | null } attributeNameCheck one of [ null , regexPattern , predicate ] . Default : ` null ` ( disallow any attributes not on the allow list )
* @ property { boolean } allowCustomizedBuiltInElements allow custom elements derived from built - ins if they pass CUSTOM _ELEMENT _HANDLING . tagNameCheck . Default : ` false ` .
* /
let CUSTOM _ELEMENT _HANDLING = Object . seal ( create ( null , {
tagNameCheck : {
writable : true ,
configurable : false ,
enumerable : true ,
value : null
} ,
attributeNameCheck : {
writable : true ,
configurable : false ,
enumerable : true ,
value : null
} ,
allowCustomizedBuiltInElements : {
writable : true ,
configurable : false ,
enumerable : true ,
value : false
}
} ) ) ;
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
let FORBID _TAGS = null ;
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
let FORBID _ATTR = null ;
/* Decide if ARIA attributes are okay */
let ALLOW _ARIA _ATTR = true ;
/* Decide if custom data attributes are okay */
let ALLOW _DATA _ATTR = true ;
/* Decide if unknown protocols are okay */
let ALLOW _UNKNOWN _PROTOCOLS = false ;
/ * D e c i d e i f s e l f - c l o s i n g t a g s i n a t t r i b u t e s a r e a l l o w e d .
* Usually removed due to a mXSS issue in jQuery 3.0 * /
let ALLOW _SELF _CLOSE _IN _ATTR = true ;
/ * O u t p u t s h o u l d b e s a f e f o r c o m m o n t e m p l a t e e n g i n e s .
* This means , DOMPurify removes data attributes , mustaches and ERB
* /
let SAFE _FOR _TEMPLATES = false ;
/ * O u t p u t s h o u l d b e s a f e e v e n f o r X M L u s e d w i t h i n H T M L a n d a l i k e .
* This means , DOMPurify removes comments when containing risky content .
* /
let SAFE _FOR _XML = true ;
/* Decide if document with <html>... should be returned */
let WHOLE _DOCUMENT = false ;
/* Track whether config is already set on this instance of DOMPurify. */
let SET _CONFIG = false ;
/ * D e c i d e i f a l l e l e m e n t s ( e . g . s t y l e , s c r i p t ) m u s t b e c h i l d r e n o f
* document . body . By default , browsers might move them to document . head * /
let FORCE _BODY = false ;
/ * D e c i d e i f a D O M ` H T M L B o d y E l e m e n t ` s h o u l d b e r e t u r n e d , i n s t e a d o f a h t m l
* string ( or a TrustedHTML object if Trusted Types are supported ) .
* If ` WHOLE_DOCUMENT ` is enabled a ` HTMLHtmlElement ` will be returned instead
* /
let RETURN _DOM = false ;
/ * D e c i d e i f a D O M ` D o c u m e n t F r a g m e n t ` s h o u l d b e r e t u r n e d , i n s t e a d o f a h t m l
* string ( or a TrustedHTML object if Trusted Types are supported ) * /
let RETURN _DOM _FRAGMENT = false ;
/ * T r y t o r e t u r n a T r u s t e d T y p e o b j e c t i n s t e a d o f a s t r i n g , r e t u r n a s t r i n g i n
* case Trusted Types are not supported * /
let RETURN _TRUSTED _TYPE = false ;
/ * O u t p u t s h o u l d b e f r e e f r o m D O M c l o b b e r i n g a t t a c k s ?
* This sanitizes markups named with colliding , clobberable built - in DOM APIs .
* /
let SANITIZE _DOM = true ;
/ * A c h i e v e f u l l D O M C l o b b e r i n g p r o t e c t i o n b y i s o l a t i n g t h e n a m e s p a c e o f n a m e d
* properties and JS variables , mitigating attacks that abuse the HTML / DOM spec rules .
*
* HTML / DOM spec rules that enable DOM Clobbering :
* - Named Access on Window ( § 7.3 . 3 )
* - DOM Tree Accessors ( § 3.1 . 5 )
* - Form Element Parent - Child Relations ( § 4.10 . 3 )
* - Iframe srcdoc / Nested WindowProxies ( § 4.8 . 5 )
* - HTMLCollection ( § 4.2 . 10.2 )
2023-12-13 11:23:54 +01:00
*
2025-07-11 11:42:46 +02:00
* Namespace isolation is implemented by prefixing ` id ` and ` name ` attributes
* with a constant string , i . e . , ` user-content- `
2023-12-13 11:23:54 +01:00
* /
2025-07-11 11:42:46 +02:00
let SANITIZE _NAMED _PROPS = false ;
const SANITIZE _NAMED _PROPS _PREFIX = 'user-content-' ;
/* Keep element content when removing element? */
let KEEP _CONTENT = true ;
/ * I f a ` N o d e ` i s p a s s e d t o s a n i t i z e ( ) , t h e n p e r f o r m s s a n i t i z a t i o n i n - p l a c e i n s t e a d
* of importing it into a new Document and returning a sanitized copy * /
let IN _PLACE = false ;
/* Allow usage of profiles like html, svg and mathMl */
let USE _PROFILES = { } ;
/* Tags to ignore content of when KEEP_CONTENT is true */
let FORBID _CONTENTS = null ;
const DEFAULT _FORBID _CONTENTS = addToSet ( { } , [ 'annotation-xml' , 'audio' , 'colgroup' , 'desc' , 'foreignobject' , 'head' , 'iframe' , 'math' , 'mi' , 'mn' , 'mo' , 'ms' , 'mtext' , 'noembed' , 'noframes' , 'noscript' , 'plaintext' , 'script' , 'style' , 'svg' , 'template' , 'thead' , 'title' , 'video' , 'xmp' ] ) ;
/* Tags that are safe for data: URIs */
let DATA _URI _TAGS = null ;
const DEFAULT _DATA _URI _TAGS = addToSet ( { } , [ 'audio' , 'video' , 'img' , 'source' , 'image' , 'track' ] ) ;
/* Attributes safe for values like "javascript:" */
let URI _SAFE _ATTRIBUTES = null ;
const DEFAULT _URI _SAFE _ATTRIBUTES = addToSet ( { } , [ 'alt' , 'class' , 'for' , 'id' , 'label' , 'name' , 'pattern' , 'placeholder' , 'role' , 'summary' , 'title' , 'value' , 'style' , 'xmlns' ] ) ;
const MATHML _NAMESPACE = 'http://www.w3.org/1998/Math/MathML' ;
const SVG _NAMESPACE = 'http://www.w3.org/2000/svg' ;
const HTML _NAMESPACE = 'http://www.w3.org/1999/xhtml' ;
/* Document namespace */
let NAMESPACE = HTML _NAMESPACE ;
let IS _EMPTY _INPUT = false ;
/* Allowed XHTML+XML namespaces */
let ALLOWED _NAMESPACES = null ;
const DEFAULT _ALLOWED _NAMESPACES = addToSet ( { } , [ MATHML _NAMESPACE , SVG _NAMESPACE , HTML _NAMESPACE ] , stringToString ) ;
let MATHML _TEXT _INTEGRATION _POINTS = addToSet ( { } , [ 'mi' , 'mo' , 'mn' , 'ms' , 'mtext' ] ) ;
let HTML _INTEGRATION _POINTS = addToSet ( { } , [ 'annotation-xml' ] ) ;
// Certain elements are allowed in both SVG and HTML
// namespace. We need to specify them explicitly
// so that they don't get erroneously deleted from
// HTML namespace.
const COMMON _SVG _AND _HTML _ELEMENTS = addToSet ( { } , [ 'title' , 'style' , 'font' , 'a' , 'script' ] ) ;
/* Parsing of strict XHTML documents */
let PARSER _MEDIA _TYPE = null ;
const SUPPORTED _PARSER _MEDIA _TYPES = [ 'application/xhtml+xml' , 'text/html' ] ;
const DEFAULT _PARSER _MEDIA _TYPE = 'text/html' ;
let transformCaseFunc = null ;
/* Keep a reference to config to pass to hooks */
let CONFIG = null ;
/* Ideally, do not touch anything below this line */
/* ______________________________________________ */
const formElement = document . createElement ( 'form' ) ;
const isRegexOrFunction = function isRegexOrFunction ( testValue ) {
return testValue instanceof RegExp || testValue instanceof Function ;
} ;
2023-12-13 11:23:54 +01:00
/ * *
2025-07-11 11:42:46 +02:00
* _parseConfig
2023-12-13 11:23:54 +01:00
*
2025-07-11 11:42:46 +02:00
* @ param cfg optional config literal
2023-12-13 11:23:54 +01:00
* /
2025-07-11 11:42:46 +02:00
// eslint-disable-next-line complexity
const _parseConfig = function _parseConfig ( ) {
let cfg = arguments . length > 0 && arguments [ 0 ] !== undefined ? arguments [ 0 ] : { } ;
if ( CONFIG && CONFIG === cfg ) {
return ;
}
/* Shield configuration object from tampering */
if ( ! cfg || typeof cfg !== 'object' ) {
cfg = { } ;
}
/* Shield configuration object from prototype pollution */
cfg = clone ( cfg ) ;
PARSER _MEDIA _TYPE =
// eslint-disable-next-line unicorn/prefer-includes
SUPPORTED _PARSER _MEDIA _TYPES . indexOf ( cfg . PARSER _MEDIA _TYPE ) === - 1 ? DEFAULT _PARSER _MEDIA _TYPE : cfg . PARSER _MEDIA _TYPE ;
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
transformCaseFunc = PARSER _MEDIA _TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase ;
/* Set configuration parameters */
ALLOWED _TAGS = objectHasOwnProperty ( cfg , 'ALLOWED_TAGS' ) ? addToSet ( { } , cfg . ALLOWED _TAGS , transformCaseFunc ) : DEFAULT _ALLOWED _TAGS ;
ALLOWED _ATTR = objectHasOwnProperty ( cfg , 'ALLOWED_ATTR' ) ? addToSet ( { } , cfg . ALLOWED _ATTR , transformCaseFunc ) : DEFAULT _ALLOWED _ATTR ;
ALLOWED _NAMESPACES = objectHasOwnProperty ( cfg , 'ALLOWED_NAMESPACES' ) ? addToSet ( { } , cfg . ALLOWED _NAMESPACES , stringToString ) : DEFAULT _ALLOWED _NAMESPACES ;
URI _SAFE _ATTRIBUTES = objectHasOwnProperty ( cfg , 'ADD_URI_SAFE_ATTR' ) ? addToSet ( clone ( DEFAULT _URI _SAFE _ATTRIBUTES ) , cfg . ADD _URI _SAFE _ATTR , transformCaseFunc ) : DEFAULT _URI _SAFE _ATTRIBUTES ;
DATA _URI _TAGS = objectHasOwnProperty ( cfg , 'ADD_DATA_URI_TAGS' ) ? addToSet ( clone ( DEFAULT _DATA _URI _TAGS ) , cfg . ADD _DATA _URI _TAGS , transformCaseFunc ) : DEFAULT _DATA _URI _TAGS ;
FORBID _CONTENTS = objectHasOwnProperty ( cfg , 'FORBID_CONTENTS' ) ? addToSet ( { } , cfg . FORBID _CONTENTS , transformCaseFunc ) : DEFAULT _FORBID _CONTENTS ;
FORBID _TAGS = objectHasOwnProperty ( cfg , 'FORBID_TAGS' ) ? addToSet ( { } , cfg . FORBID _TAGS , transformCaseFunc ) : clone ( { } ) ;
FORBID _ATTR = objectHasOwnProperty ( cfg , 'FORBID_ATTR' ) ? addToSet ( { } , cfg . FORBID _ATTR , transformCaseFunc ) : clone ( { } ) ;
USE _PROFILES = objectHasOwnProperty ( cfg , 'USE_PROFILES' ) ? cfg . USE _PROFILES : false ;
ALLOW _ARIA _ATTR = cfg . ALLOW _ARIA _ATTR !== false ; // Default true
ALLOW _DATA _ATTR = cfg . ALLOW _DATA _ATTR !== false ; // Default true
ALLOW _UNKNOWN _PROTOCOLS = cfg . ALLOW _UNKNOWN _PROTOCOLS || false ; // Default false
ALLOW _SELF _CLOSE _IN _ATTR = cfg . ALLOW _SELF _CLOSE _IN _ATTR !== false ; // Default true
SAFE _FOR _TEMPLATES = cfg . SAFE _FOR _TEMPLATES || false ; // Default false
SAFE _FOR _XML = cfg . SAFE _FOR _XML !== false ; // Default true
WHOLE _DOCUMENT = cfg . WHOLE _DOCUMENT || false ; // Default false
RETURN _DOM = cfg . RETURN _DOM || false ; // Default false
RETURN _DOM _FRAGMENT = cfg . RETURN _DOM _FRAGMENT || false ; // Default false
RETURN _TRUSTED _TYPE = cfg . RETURN _TRUSTED _TYPE || false ; // Default false
FORCE _BODY = cfg . FORCE _BODY || false ; // Default false
SANITIZE _DOM = cfg . SANITIZE _DOM !== false ; // Default true
SANITIZE _NAMED _PROPS = cfg . SANITIZE _NAMED _PROPS || false ; // Default false
KEEP _CONTENT = cfg . KEEP _CONTENT !== false ; // Default true
IN _PLACE = cfg . IN _PLACE || false ; // Default false
IS _ALLOWED _URI$1 = cfg . ALLOWED _URI _REGEXP || IS _ALLOWED _URI ;
NAMESPACE = cfg . NAMESPACE || HTML _NAMESPACE ;
MATHML _TEXT _INTEGRATION _POINTS = cfg . MATHML _TEXT _INTEGRATION _POINTS || MATHML _TEXT _INTEGRATION _POINTS ;
HTML _INTEGRATION _POINTS = cfg . HTML _INTEGRATION _POINTS || HTML _INTEGRATION _POINTS ;
CUSTOM _ELEMENT _HANDLING = cfg . CUSTOM _ELEMENT _HANDLING || { } ;
if ( cfg . CUSTOM _ELEMENT _HANDLING && isRegexOrFunction ( cfg . CUSTOM _ELEMENT _HANDLING . tagNameCheck ) ) {
CUSTOM _ELEMENT _HANDLING . tagNameCheck = cfg . CUSTOM _ELEMENT _HANDLING . tagNameCheck ;
}
if ( cfg . CUSTOM _ELEMENT _HANDLING && isRegexOrFunction ( cfg . CUSTOM _ELEMENT _HANDLING . attributeNameCheck ) ) {
CUSTOM _ELEMENT _HANDLING . attributeNameCheck = cfg . CUSTOM _ELEMENT _HANDLING . attributeNameCheck ;
}
if ( cfg . CUSTOM _ELEMENT _HANDLING && typeof cfg . CUSTOM _ELEMENT _HANDLING . allowCustomizedBuiltInElements === 'boolean' ) {
CUSTOM _ELEMENT _HANDLING . allowCustomizedBuiltInElements = cfg . CUSTOM _ELEMENT _HANDLING . allowCustomizedBuiltInElements ;
}
if ( SAFE _FOR _TEMPLATES ) {
ALLOW _DATA _ATTR = false ;
}
if ( RETURN _DOM _FRAGMENT ) {
RETURN _DOM = true ;
}
/* Parse profile info */
if ( USE _PROFILES ) {
ALLOWED _TAGS = addToSet ( { } , text ) ;
ALLOWED _ATTR = [ ] ;
if ( USE _PROFILES . html === true ) {
addToSet ( ALLOWED _TAGS , html$1 ) ;
addToSet ( ALLOWED _ATTR , html ) ;
}
if ( USE _PROFILES . svg === true ) {
addToSet ( ALLOWED _TAGS , svg$1 ) ;
addToSet ( ALLOWED _ATTR , svg ) ;
addToSet ( ALLOWED _ATTR , xml ) ;
}
if ( USE _PROFILES . svgFilters === true ) {
addToSet ( ALLOWED _TAGS , svgFilters ) ;
addToSet ( ALLOWED _ATTR , svg ) ;
addToSet ( ALLOWED _ATTR , xml ) ;
}
if ( USE _PROFILES . mathMl === true ) {
addToSet ( ALLOWED _TAGS , mathMl$1 ) ;
addToSet ( ALLOWED _ATTR , mathMl ) ;
addToSet ( ALLOWED _ATTR , xml ) ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
}
/* Merge configuration parameters */
if ( cfg . ADD _TAGS ) {
if ( ALLOWED _TAGS === DEFAULT _ALLOWED _TAGS ) {
ALLOWED _TAGS = clone ( ALLOWED _TAGS ) ;
}
addToSet ( ALLOWED _TAGS , cfg . ADD _TAGS , transformCaseFunc ) ;
}
if ( cfg . ADD _ATTR ) {
if ( ALLOWED _ATTR === DEFAULT _ALLOWED _ATTR ) {
ALLOWED _ATTR = clone ( ALLOWED _ATTR ) ;
}
addToSet ( ALLOWED _ATTR , cfg . ADD _ATTR , transformCaseFunc ) ;
}
if ( cfg . ADD _URI _SAFE _ATTR ) {
addToSet ( URI _SAFE _ATTRIBUTES , cfg . ADD _URI _SAFE _ATTR , transformCaseFunc ) ;
}
if ( cfg . FORBID _CONTENTS ) {
if ( FORBID _CONTENTS === DEFAULT _FORBID _CONTENTS ) {
FORBID _CONTENTS = clone ( FORBID _CONTENTS ) ;
}
addToSet ( FORBID _CONTENTS , cfg . FORBID _CONTENTS , transformCaseFunc ) ;
}
/* Add #text in case KEEP_CONTENT is set to true */
if ( KEEP _CONTENT ) {
ALLOWED _TAGS [ '#text' ] = true ;
}
/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
if ( WHOLE _DOCUMENT ) {
addToSet ( ALLOWED _TAGS , [ 'html' , 'head' , 'body' ] ) ;
}
/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
if ( ALLOWED _TAGS . table ) {
addToSet ( ALLOWED _TAGS , [ 'tbody' ] ) ;
delete FORBID _TAGS . tbody ;
}
if ( cfg . TRUSTED _TYPES _POLICY ) {
if ( typeof cfg . TRUSTED _TYPES _POLICY . createHTML !== 'function' ) {
throw typeErrorCreate ( 'TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.' ) ;
}
if ( typeof cfg . TRUSTED _TYPES _POLICY . createScriptURL !== 'function' ) {
throw typeErrorCreate ( 'TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.' ) ;
}
// Overwrite existing TrustedTypes policy.
trustedTypesPolicy = cfg . TRUSTED _TYPES _POLICY ;
// Sign local variables required by `sanitize`.
emptyHTML = trustedTypesPolicy . createHTML ( '' ) ;
} else {
// Uninitialized policy, attempt to initialize the internal dompurify policy.
if ( trustedTypesPolicy === undefined ) {
trustedTypesPolicy = _createTrustedTypesPolicy ( trustedTypes , currentScript ) ;
}
// If creating the internal policy succeeded sign internal variables.
if ( trustedTypesPolicy !== null && typeof emptyHTML === 'string' ) {
emptyHTML = trustedTypesPolicy . createHTML ( '' ) ;
}
}
// Prevent further manipulation of configuration.
// Not available in IE8, Safari 5, etc.
if ( freeze ) {
freeze ( cfg ) ;
}
CONFIG = cfg ;
} ;
/ * K e e p t r a c k o f a l l p o s s i b l e S V G a n d M a t h M L t a g s
* so that we can perform the namespace checks
* correctly . * /
const ALL _SVG _TAGS = addToSet ( { } , [ ... svg$1 , ... svgFilters , ... svgDisallowed ] ) ;
const ALL _MATHML _TAGS = addToSet ( { } , [ ... mathMl$1 , ... mathMlDisallowed ] ) ;
2023-12-13 11:23:54 +01:00
/ * *
2025-07-11 11:42:46 +02:00
* @ param element a DOM element whose namespace is being checked
* @ returns Return false if the element has a
* namespace that a spec - compliant parser would never
* return . Return true otherwise .
2023-12-13 11:23:54 +01:00
* /
2025-07-11 11:42:46 +02:00
const _checkValidNamespace = function _checkValidNamespace ( element ) {
let parent = getParentNode ( element ) ;
// In JSDOM, if we're inside shadow DOM, then parentNode
// can be null. We just simulate parent in this case.
if ( ! parent || ! parent . tagName ) {
parent = {
namespaceURI : NAMESPACE ,
tagName : 'template'
} ;
}
const tagName = stringToLowerCase ( element . tagName ) ;
const parentTagName = stringToLowerCase ( parent . tagName ) ;
if ( ! ALLOWED _NAMESPACES [ element . namespaceURI ] ) {
return false ;
}
if ( element . namespaceURI === SVG _NAMESPACE ) {
// The only way to switch from HTML namespace to SVG
// is via <svg>. If it happens via any other tag, then
// it should be killed.
if ( parent . namespaceURI === HTML _NAMESPACE ) {
return tagName === 'svg' ;
}
// The only way to switch from MathML to SVG is via`
// svg if parent is either <annotation-xml> or MathML
// text integration points.
if ( parent . namespaceURI === MATHML _NAMESPACE ) {
return tagName === 'svg' && ( parentTagName === 'annotation-xml' || MATHML _TEXT _INTEGRATION _POINTS [ parentTagName ] ) ;
}
// We only allow elements that are defined in SVG
// spec. All others are disallowed in SVG namespace.
return Boolean ( ALL _SVG _TAGS [ tagName ] ) ;
}
if ( element . namespaceURI === MATHML _NAMESPACE ) {
// The only way to switch from HTML namespace to MathML
// is via <math>. If it happens via any other tag, then
// it should be killed.
if ( parent . namespaceURI === HTML _NAMESPACE ) {
return tagName === 'math' ;
}
// The only way to switch from SVG to MathML is via
// <math> and HTML integration points
if ( parent . namespaceURI === SVG _NAMESPACE ) {
return tagName === 'math' && HTML _INTEGRATION _POINTS [ parentTagName ] ;
}
// We only allow elements that are defined in MathML
// spec. All others are disallowed in MathML namespace.
return Boolean ( ALL _MATHML _TAGS [ tagName ] ) ;
}
if ( element . namespaceURI === HTML _NAMESPACE ) {
// The only way to switch from SVG to HTML is via
// HTML integration points, and from MathML to HTML
// is via MathML text integration points
if ( parent . namespaceURI === SVG _NAMESPACE && ! HTML _INTEGRATION _POINTS [ parentTagName ] ) {
return false ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
if ( parent . namespaceURI === MATHML _NAMESPACE && ! MATHML _TEXT _INTEGRATION _POINTS [ parentTagName ] ) {
return false ;
}
// We disallow tags that are specific for MathML
// or SVG and should never appear in HTML namespace
return ! ALL _MATHML _TAGS [ tagName ] && ( COMMON _SVG _AND _HTML _ELEMENTS [ tagName ] || ! ALL _SVG _TAGS [ tagName ] ) ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
// For XHTML and XML documents that support custom namespaces
if ( PARSER _MEDIA _TYPE === 'application/xhtml+xml' && ALLOWED _NAMESPACES [ element . namespaceURI ] ) {
return true ;
}
// The code should never reach this place (this means
// that the element somehow got namespace that is not
// HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
// Return false just in case.
return false ;
} ;
2024-02-23 15:16:42 +01:00
/ * *
2025-07-11 11:42:46 +02:00
* _forceRemove
2024-02-23 15:16:42 +01:00
*
2025-07-11 11:42:46 +02:00
* @ param node a DOM node
2024-02-23 15:16:42 +01:00
* /
2025-07-11 11:42:46 +02:00
const _forceRemove = function _forceRemove ( node ) {
arrayPush ( DOMPurify . removed , {
element : node
} ) ;
try {
// eslint-disable-next-line unicorn/prefer-dom-node-remove
getParentNode ( node ) . removeChild ( node ) ;
} catch ( _ ) {
remove ( node ) ;
2024-02-23 15:16:42 +01:00
}
2025-07-11 11:42:46 +02:00
} ;
2023-12-13 11:23:54 +01:00
/ * *
2025-07-11 11:42:46 +02:00
* _removeAttribute
2023-12-13 11:23:54 +01:00
*
2025-07-11 11:42:46 +02:00
* @ param name an Attribute name
* @ param element a DOM node
2023-12-13 11:23:54 +01:00
* /
2025-07-11 11:42:46 +02:00
const _removeAttribute = function _removeAttribute ( name , element ) {
try {
arrayPush ( DOMPurify . removed , {
attribute : element . getAttributeNode ( name ) ,
from : element
} ) ;
} catch ( _ ) {
arrayPush ( DOMPurify . removed , {
attribute : null ,
from : element
} ) ;
}
element . removeAttribute ( name ) ;
// We void attribute values for unremovable "is" attributes
if ( name === 'is' ) {
if ( RETURN _DOM || RETURN _DOM _FRAGMENT ) {
try {
_forceRemove ( element ) ;
} catch ( _ ) { }
} else {
try {
element . setAttribute ( name , '' ) ;
} catch ( _ ) { }
2023-12-13 11:23:54 +01:00
}
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
} ;
2023-12-13 11:23:54 +01:00
/ * *
2025-07-11 11:42:46 +02:00
* _initDocument
2023-12-13 11:23:54 +01:00
*
2025-07-11 11:42:46 +02:00
* @ param dirty - a string of dirty markup
* @ return a DOM , filled with the dirty markup
2023-12-13 11:23:54 +01:00
* /
2025-07-11 11:42:46 +02:00
const _initDocument = function _initDocument ( dirty ) {
/* Create a HTML document */
let doc = null ;
let leadingWhitespace = null ;
if ( FORCE _BODY ) {
dirty = '<remove></remove>' + dirty ;
} else {
/* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
const matches = stringMatch ( dirty , /^[\r\n\t ]+/ ) ;
leadingWhitespace = matches && matches [ 0 ] ;
}
if ( PARSER _MEDIA _TYPE === 'application/xhtml+xml' && NAMESPACE === HTML _NAMESPACE ) {
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>' ;
}
const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy . createHTML ( dirty ) : dirty ;
/ *
* Use the DOMParser API by default , fallback later if needs be
* DOMParser not work for svg when has multiple root element .
* /
if ( NAMESPACE === HTML _NAMESPACE ) {
try {
doc = new DOMParser ( ) . parseFromString ( dirtyPayload , PARSER _MEDIA _TYPE ) ;
} catch ( _ ) { }
}
/* Use createHTMLDocument in case DOMParser is not available */
if ( ! doc || ! doc . documentElement ) {
doc = implementation . createDocument ( NAMESPACE , 'template' , null ) ;
try {
doc . documentElement . innerHTML = IS _EMPTY _INPUT ? emptyHTML : dirtyPayload ;
} catch ( _ ) {
// Syntax error if dirtyPayload is invalid xml
2021-03-30 11:50:45 +02:00
}
}
2025-07-11 11:42:46 +02:00
const body = doc . body || doc . documentElement ;
if ( dirty && leadingWhitespace ) {
body . insertBefore ( document . createTextNode ( leadingWhitespace ) , body . childNodes [ 0 ] || null ) ;
2021-03-30 11:50:45 +02:00
}
2025-07-11 11:42:46 +02:00
/* Work on whole document or just its body */
if ( NAMESPACE === HTML _NAMESPACE ) {
return getElementsByTagName . call ( doc , WHOLE _DOCUMENT ? 'html' : 'body' ) [ 0 ] ;
}
return WHOLE _DOCUMENT ? doc . documentElement : body ;
2024-05-13 11:23:39 +02:00
} ;
2025-07-11 11:42:46 +02:00
/ * *
* Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document .
*
* @ param root The root element or node to start traversing on .
* @ return The created NodeIterator
* /
const _createNodeIterator = function _createNodeIterator ( root ) {
return createNodeIterator . call ( root . ownerDocument || root , root ,
// eslint-disable-next-line no-bitwise
NodeFilter . SHOW _ELEMENT | NodeFilter . SHOW _COMMENT | NodeFilter . SHOW _TEXT | NodeFilter . SHOW _PROCESSING _INSTRUCTION | NodeFilter . SHOW _CDATA _SECTION , null ) ;
2023-12-13 11:23:54 +01:00
} ;
2018-10-23 14:23:02 +02:00
/ * *
2025-07-11 11:42:46 +02:00
* _isClobbered
*
* @ param element element to check for clobbering attacks
* @ return true if clobbered , false if safe
2018-10-23 14:23:02 +02:00
* /
2025-07-11 11:42:46 +02:00
const _isClobbered = function _isClobbered ( element ) {
return element instanceof HTMLFormElement && ( typeof element . nodeName !== 'string' || typeof element . textContent !== 'string' || typeof element . removeChild !== 'function' || ! ( element . attributes instanceof NamedNodeMap ) || typeof element . removeAttribute !== 'function' || typeof element . setAttribute !== 'function' || typeof element . namespaceURI !== 'string' || typeof element . insertBefore !== 'function' || typeof element . hasChildNodes !== 'function' ) ;
2020-09-28 12:46:53 +02:00
} ;
2025-07-11 11:42:46 +02:00
/ * *
* Checks whether the given object is a DOM node .
*
* @ param value object to check whether it ' s a DOM node
* @ return true is object is a DOM node
* /
const _isNode = function _isNode ( value ) {
return typeof Node === 'function' && value instanceof Node ;
2024-12-11 16:31:52 +01:00
} ;
2025-07-11 11:42:46 +02:00
function _executeHooks ( hooks , currentNode , data ) {
arrayForEach ( hooks , hook => {
hook . call ( DOMPurify , currentNode , data , CONFIG ) ;
} ) ;
}
/ * *
* _sanitizeElements
*
* @ protect nodeName
* @ protect textContent
* @ protect removeChild
* @ param currentNode to check for permission to exist
* @ return true if node was killed , false if left alive
* /
const _sanitizeElements = function _sanitizeElements ( currentNode ) {
let content = null ;
/* Execute a hook if present */
_executeHooks ( hooks . beforeSanitizeElements , currentNode , null ) ;
/* Check if element is clobbered or can clobber */
if ( _isClobbered ( currentNode ) ) {
_forceRemove ( currentNode ) ;
return true ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
/* Now let's check the element's type and name */
const tagName = transformCaseFunc ( currentNode . nodeName ) ;
/* Execute a hook if present */
_executeHooks ( hooks . uponSanitizeElement , currentNode , {
tagName ,
allowedTags : ALLOWED _TAGS
} ) ;
/* Detect mXSS attempts abusing namespace confusion */
if ( SAFE _FOR _XML && currentNode . hasChildNodes ( ) && ! _isNode ( currentNode . firstElementChild ) && regExpTest ( /<[/\w!]/g , currentNode . innerHTML ) && regExpTest ( /<[/\w!]/g , currentNode . textContent ) ) {
_forceRemove ( currentNode ) ;
return true ;
}
/* Remove any occurrence of processing instructions */
if ( currentNode . nodeType === NODE _TYPE . progressingInstruction ) {
_forceRemove ( currentNode ) ;
return true ;
}
/* Remove any kind of possibly harmful comments */
if ( SAFE _FOR _XML && currentNode . nodeType === NODE _TYPE . comment && regExpTest ( /<[/\w]/g , currentNode . data ) ) {
_forceRemove ( currentNode ) ;
return true ;
}
/* Remove element if anything forbids its presence */
if ( ! ALLOWED _TAGS [ tagName ] || FORBID _TAGS [ tagName ] ) {
/* Check if we have a custom element to handle */
if ( ! FORBID _TAGS [ tagName ] && _isBasicCustomElement ( tagName ) ) {
if ( CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof RegExp && regExpTest ( CUSTOM _ELEMENT _HANDLING . tagNameCheck , tagName ) ) {
2021-03-30 11:50:45 +02:00
return false ;
}
2025-07-11 11:42:46 +02:00
if ( CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof Function && CUSTOM _ELEMENT _HANDLING . tagNameCheck ( tagName ) ) {
2021-03-30 11:50:45 +02:00
return false ;
2024-02-23 15:16:42 +01:00
}
}
2025-07-11 11:42:46 +02:00
/* Keep content except for bad-listed elements */
if ( KEEP _CONTENT && ! FORBID _CONTENTS [ tagName ] ) {
const parentNode = getParentNode ( currentNode ) || currentNode . parentNode ;
const childNodes = getChildNodes ( currentNode ) || currentNode . childNodes ;
if ( childNodes && parentNode ) {
const childCount = childNodes . length ;
for ( let i = childCount - 1 ; i >= 0 ; -- i ) {
const childClone = cloneNode ( childNodes [ i ] , true ) ;
childClone . _ _removalCount = ( currentNode . _ _removalCount || 0 ) + 1 ;
parentNode . insertBefore ( childClone , getNextSibling ( currentNode ) ) ;
}
2021-11-09 16:54:36 +01:00
}
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
_forceRemove ( currentNode ) ;
return true ;
2024-12-11 16:31:52 +01:00
}
2025-07-11 11:42:46 +02:00
/* Check whether element has a valid namespace */
if ( currentNode instanceof Element && ! _checkValidNamespace ( currentNode ) ) {
_forceRemove ( currentNode ) ;
return true ;
}
/* Make sure that older browsers don't get fallback-tag mXSS */
if ( ( tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes' ) && regExpTest ( /<\/no(script|embed|frames)/i , currentNode . innerHTML ) ) {
_forceRemove ( currentNode ) ;
return true ;
}
/* Sanitize element content to be template-safe */
if ( SAFE _FOR _TEMPLATES && currentNode . nodeType === NODE _TYPE . text ) {
/* Get the element's text content */
content = currentNode . textContent ;
arrayForEach ( [ MUSTACHE _EXPR , ERB _EXPR , TMPLIT _EXPR ] , expr => {
content = stringReplace ( content , expr , ' ' ) ;
2020-09-28 12:46:53 +02:00
} ) ;
2025-07-11 11:42:46 +02:00
if ( currentNode . textContent !== content ) {
arrayPush ( DOMPurify . removed , {
element : currentNode . cloneNode ( )
2023-12-13 11:23:54 +01:00
} ) ;
2025-07-11 11:42:46 +02:00
currentNode . textContent = content ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
}
/* Execute a hook if present */
_executeHooks ( hooks . afterSanitizeElements , currentNode , null ) ;
return false ;
} ;
/ * *
* _isValidAttribute
*
* @ param lcTag Lowercase tag name of containing element .
* @ param lcName Lowercase attribute name .
* @ param value Attribute value .
* @ return Returns true if ` value ` is valid , otherwise false .
* /
// eslint-disable-next-line complexity
const _isValidAttribute = function _isValidAttribute ( lcTag , lcName , value ) {
/* Make sure attribute cannot clobber */
if ( SANITIZE _DOM && ( lcName === 'id' || lcName === 'name' ) && ( value in document || value in formElement ) ) {
2020-09-28 12:46:53 +02:00
return false ;
2025-07-11 11:42:46 +02:00
}
/ * A l l o w v a l i d d a t a - * a t t r i b u t e s : A t l e a s t o n e c h a r a c t e r a f t e r " - "
( https : //html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
XML - compatible ( https : //html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
We don 't need to check the value; it' s always URI safe . * /
if ( ALLOW _DATA _ATTR && ! FORBID _ATTR [ lcName ] && regExpTest ( DATA _ATTR , lcName ) ) ; else if ( ALLOW _ARIA _ATTR && regExpTest ( ARIA _ATTR , lcName ) ) ; else if ( ! ALLOWED _ATTR [ lcName ] || FORBID _ATTR [ lcName ] ) {
if (
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
// and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
_isBasicCustomElement ( lcTag ) && ( CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof RegExp && regExpTest ( CUSTOM _ELEMENT _HANDLING . tagNameCheck , lcTag ) || CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof Function && CUSTOM _ELEMENT _HANDLING . tagNameCheck ( lcTag ) ) && ( CUSTOM _ELEMENT _HANDLING . attributeNameCheck instanceof RegExp && regExpTest ( CUSTOM _ELEMENT _HANDLING . attributeNameCheck , lcName ) || CUSTOM _ELEMENT _HANDLING . attributeNameCheck instanceof Function && CUSTOM _ELEMENT _HANDLING . attributeNameCheck ( lcName ) ) ||
// Alternative, second condition checks if it's an `is`-attribute, AND
// the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
lcName === 'is' && CUSTOM _ELEMENT _HANDLING . allowCustomizedBuiltInElements && ( CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof RegExp && regExpTest ( CUSTOM _ELEMENT _HANDLING . tagNameCheck , value ) || CUSTOM _ELEMENT _HANDLING . tagNameCheck instanceof Function && CUSTOM _ELEMENT _HANDLING . tagNameCheck ( value ) ) ) ; else {
2020-09-28 12:46:53 +02:00
return false ;
2020-02-13 11:08:24 +01:00
}
2025-07-11 11:42:46 +02:00
/* Check value is safe. First, is attr inert? If so, is safe */
} else if ( URI _SAFE _ATTRIBUTES [ lcName ] ) ; else if ( regExpTest ( IS _ALLOWED _URI$1 , stringReplace ( value , ATTR _WHITESPACE , '' ) ) ) ; else if ( ( lcName === 'src' || lcName === 'xlink:href' || lcName === 'href' ) && lcTag !== 'script' && stringIndexOf ( value , 'data:' ) === 0 && DATA _URI _TAGS [ lcTag ] ) ; else if ( ALLOW _UNKNOWN _PROTOCOLS && ! regExpTest ( IS _SCRIPT _OR _DATA , stringReplace ( value , ATTR _WHITESPACE , '' ) ) ) ; else if ( value ) {
return false ;
} else ;
return true ;
} ;
/ * *
* _isBasicCustomElement
* checks if at least one dash is included in tagName , and it ' s not the first char
* for more sophisticated checking see https : //github.com/sindresorhus/validate-element-name
*
* @ param tagName name of the tag of the node to sanitize
* @ returns Returns true if the tag name meets the basic criteria for a custom element , otherwise false .
* /
const _isBasicCustomElement = function _isBasicCustomElement ( tagName ) {
return tagName !== 'annotation-xml' && stringMatch ( tagName , CUSTOM _ELEMENT ) ;
} ;
/ * *
* _sanitizeAttributes
*
* @ protect attributes
* @ protect nodeName
* @ protect removeAttribute
* @ protect setAttribute
*
* @ param currentNode to sanitize
* /
const _sanitizeAttributes = function _sanitizeAttributes ( currentNode ) {
/* Execute a hook if present */
_executeHooks ( hooks . beforeSanitizeAttributes , currentNode , null ) ;
const {
attributes
} = currentNode ;
/* Check if we have attributes; if not we might have a text node */
if ( ! attributes || _isClobbered ( currentNode ) ) {
return ;
}
const hookEvent = {
attrName : '' ,
attrValue : '' ,
keepAttr : true ,
allowedAttributes : ALLOWED _ATTR ,
forceKeepAttr : undefined
2022-06-28 10:02:52 +02:00
} ;
2025-07-11 11:42:46 +02:00
let l = attributes . length ;
/* Go backwards over all attributes; safely remove bad ones */
while ( l -- ) {
const attr = attributes [ l ] ;
2023-07-04 10:39:10 +02:00
const {
2025-07-11 11:42:46 +02:00
name ,
namespaceURI ,
value : attrValue
} = attr ;
const lcName = transformCaseFunc ( name ) ;
const initValue = attrValue ;
let value = name === 'value' ? initValue : stringTrim ( initValue ) ;
2024-02-23 15:16:42 +01:00
/* Execute a hook if present */
2025-07-11 11:42:46 +02:00
hookEvent . attrName = lcName ;
hookEvent . attrValue = value ;
hookEvent . keepAttr = true ;
hookEvent . forceKeepAttr = undefined ; // Allows developers to see this is a property they can set
_executeHooks ( hooks . uponSanitizeAttribute , currentNode , hookEvent ) ;
value = hookEvent . attrValue ;
/ * F u l l D O M C l o b b e r i n g p r o t e c t i o n v i a n a m e s p a c e i s o l a t i o n ,
* Prefix id and name attributes with ` user-content- `
* /
if ( SANITIZE _NAMED _PROPS && ( lcName === 'id' || lcName === 'name' ) ) {
// Remove the attribute with this value
_removeAttribute ( name , currentNode ) ;
// Prefix the value and later re-create the attribute with the sanitized value
value = SANITIZE _NAMED _PROPS _PREFIX + value ;
}
/* Work around a security issue with comments inside attributes */
if ( SAFE _FOR _XML && regExpTest ( /((--!?|])>)|<\/(style|title)/i , value ) ) {
_removeAttribute ( name , currentNode ) ;
continue ;
}
/* Did the hooks approve of the attribute? */
if ( hookEvent . forceKeepAttr ) {
continue ;
}
/* Did the hooks approve of the attribute? */
if ( ! hookEvent . keepAttr ) {
_removeAttribute ( name , currentNode ) ;
continue ;
}
/* Work around a security issue in jQuery 3.0 */
if ( ! ALLOW _SELF _CLOSE _IN _ATTR && regExpTest ( /\/>/i , value ) ) {
_removeAttribute ( name , currentNode ) ;
continue ;
}
/* Sanitize attribute content to be template-safe */
if ( SAFE _FOR _TEMPLATES ) {
arrayForEach ( [ MUSTACHE _EXPR , ERB _EXPR , TMPLIT _EXPR ] , expr => {
value = stringReplace ( value , expr , ' ' ) ;
} ) ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
/* Is `value` valid for this attribute? */
const lcTag = transformCaseFunc ( currentNode . nodeName ) ;
if ( ! _isValidAttribute ( lcTag , lcName , value ) ) {
_removeAttribute ( name , currentNode ) ;
continue ;
}
/* Handle attributes that require Trusted Types */
if ( trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes . getAttributeType === 'function' ) {
if ( namespaceURI ) ; else {
switch ( trustedTypes . getAttributeType ( lcTag , lcName ) ) {
case 'TrustedHTML' :
{
value = trustedTypesPolicy . createHTML ( value ) ;
break ;
}
case 'TrustedScriptURL' :
{
value = trustedTypesPolicy . createScriptURL ( value ) ;
break ;
}
2020-09-28 12:46:53 +02:00
}
}
}
2025-07-11 11:42:46 +02:00
/* Handle invalid data-* attribute set by try-catching it */
if ( value !== initValue ) {
try {
if ( namespaceURI ) {
currentNode . setAttributeNS ( namespaceURI , name , value ) ;
} else {
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
currentNode . setAttribute ( name , value ) ;
2022-06-28 10:02:52 +02:00
}
2025-07-11 11:42:46 +02:00
if ( _isClobbered ( currentNode ) ) {
_forceRemove ( currentNode ) ;
} else {
arrayPop ( DOMPurify . removed ) ;
}
} catch ( _ ) {
_removeAttribute ( name , currentNode ) ;
2020-09-28 12:46:53 +02:00
}
2018-10-23 14:23:02 +02:00
}
2025-07-11 11:42:46 +02:00
}
/* Execute a hook if present */
_executeHooks ( hooks . afterSanitizeAttributes , currentNode , null ) ;
} ;
/ * *
* _sanitizeShadowDOM
*
* @ param fragment to iterate over recursively
* /
const _sanitizeShadowDOM = function _sanitizeShadowDOM ( fragment ) {
let shadowNode = null ;
const shadowIterator = _createNodeIterator ( fragment ) ;
/* Execute a hook if present */
_executeHooks ( hooks . beforeSanitizeShadowDOM , fragment , null ) ;
while ( shadowNode = shadowIterator . nextNode ( ) ) {
/* Execute a hook if present */
_executeHooks ( hooks . uponSanitizeShadowNode , shadowNode , null ) ;
/* Sanitize tags and elements */
_sanitizeElements ( shadowNode ) ;
/* Check attributes next */
_sanitizeAttributes ( shadowNode ) ;
/* Deep shadow DOM detected */
if ( shadowNode . content instanceof DocumentFragment ) {
_sanitizeShadowDOM ( shadowNode . content ) ;
2018-10-23 14:23:02 +02:00
}
2025-07-11 11:42:46 +02:00
}
/* Execute a hook if present */
_executeHooks ( hooks . afterSanitizeShadowDOM , fragment , null ) ;
} ;
// eslint-disable-next-line complexity
DOMPurify . sanitize = function ( dirty ) {
let cfg = arguments . length > 1 && arguments [ 1 ] !== undefined ? arguments [ 1 ] : { } ;
let body = null ;
let importedNode = null ;
let currentNode = null ;
let returnNode = null ;
/ * M a k e s u r e w e h a v e a s t r i n g t o s a n i t i z e .
DO NOT return early , as this will return the wrong type if
the user has requested a DOM object rather than a string * /
IS _EMPTY _INPUT = ! dirty ;
if ( IS _EMPTY _INPUT ) {
dirty = '<!-->' ;
}
/* Stringify, in case dirty is an object */
if ( typeof dirty !== 'string' && ! _isNode ( dirty ) ) {
if ( typeof dirty . toString === 'function' ) {
dirty = dirty . toString ( ) ;
if ( typeof dirty !== 'string' ) {
throw typeErrorCreate ( 'dirty is not a string, aborting' ) ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
} else {
throw typeErrorCreate ( 'toString is not a function' ) ;
2018-10-23 14:23:02 +02:00
}
2025-07-11 11:42:46 +02:00
}
/* Return dirty HTML if DOMPurify cannot run */
if ( ! DOMPurify . isSupported ) {
return dirty ;
}
/* Assign config vars */
if ( ! SET _CONFIG ) {
_parseConfig ( cfg ) ;
}
/* Clean up removed elements */
DOMPurify . removed = [ ] ;
/* Check if dirty is correctly typed for IN_PLACE */
if ( typeof dirty === 'string' ) {
IN _PLACE = false ;
}
if ( IN _PLACE ) {
/* Do some early pre-sanitization to avoid unsafe root nodes */
if ( dirty . nodeName ) {
const tagName = transformCaseFunc ( dirty . nodeName ) ;
if ( ! ALLOWED _TAGS [ tagName ] || FORBID _TAGS [ tagName ] ) {
throw typeErrorCreate ( 'root node is forbidden and cannot be sanitized in-place' ) ;
2020-09-28 12:46:53 +02:00
}
2022-06-28 10:02:52 +02:00
}
2025-07-11 11:42:46 +02:00
} else if ( dirty instanceof Node ) {
/ * I f d i r t y i s a D O M e l e m e n t , a p p e n d t o a n e m p t y d o c u m e n t t o a v o i d
elements being stripped by the parser * /
body = _initDocument ( '<!---->' ) ;
importedNode = body . ownerDocument . importNode ( dirty , true ) ;
if ( importedNode . nodeType === NODE _TYPE . element && importedNode . nodeName === 'BODY' ) {
/* Node is already a body, use as is */
body = importedNode ;
} else if ( importedNode . nodeName === 'HTML' ) {
body = importedNode ;
} else {
// eslint-disable-next-line unicorn/prefer-dom-node-append
body . appendChild ( importedNode ) ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
} else {
/* Exit directly if we have nothing to do */
if ( ! RETURN _DOM && ! SAFE _FOR _TEMPLATES && ! WHOLE _DOCUMENT &&
// eslint-disable-next-line unicorn/prefer-includes
dirty . indexOf ( '<' ) === - 1 ) {
return trustedTypesPolicy && RETURN _TRUSTED _TYPE ? trustedTypesPolicy . createHTML ( dirty ) : dirty ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
/* Initialize the document to work on */
body = _initDocument ( dirty ) ;
/* Check we have a DOM node from the data */
if ( ! body ) {
return RETURN _DOM ? null : RETURN _TRUSTED _TYPE ? emptyHTML : '' ;
2020-09-28 12:46:53 +02:00
}
2025-07-11 11:42:46 +02:00
}
/* Remove first element node (ours) if FORCE_BODY is set */
if ( body && FORCE _BODY ) {
_forceRemove ( body . firstChild ) ;
}
/* Get node iterator */
const nodeIterator = _createNodeIterator ( IN _PLACE ? dirty : body ) ;
/* Now start iterating over the created document */
while ( currentNode = nodeIterator . nextNode ( ) ) {
/* Sanitize tags and elements */
_sanitizeElements ( currentNode ) ;
/* Check attributes next */
_sanitizeAttributes ( currentNode ) ;
/* Shadow DOM detected, sanitize it */
if ( currentNode . content instanceof DocumentFragment ) {
_sanitizeShadowDOM ( currentNode . content ) ;
2025-01-30 10:57:13 +01:00
}
2025-07-11 11:42:46 +02:00
}
/* If we sanitized `dirty` in-place, return it. */
if ( IN _PLACE ) {
return dirty ;
}
/* Return sanitized string or DOM */
if ( RETURN _DOM ) {
if ( RETURN _DOM _FRAGMENT ) {
returnNode = createDocumentFragment . call ( body . ownerDocument ) ;
while ( body . firstChild ) {
// eslint-disable-next-line unicorn/prefer-dom-node-append
returnNode . appendChild ( body . firstChild ) ;
}
} else {
returnNode = body ;
}
if ( ALLOWED _ATTR . shadowroot || ALLOWED _ATTR . shadowrootmode ) {
/ *
AdoptNode ( ) is not used because internal state is not reset
( e . g . the past names map of a HTMLFormElement ) , this is safe
in theory but we would rather not risk another attack vector .
The state that is cloned by importNode ( ) is explicitly defined
by the specs .
* /
returnNode = importNode . call ( originalDocument , returnNode , true ) ;
}
return returnNode ;
}
let serializedHTML = WHOLE _DOCUMENT ? body . outerHTML : body . innerHTML ;
/* Serialize doctype if allowed */
if ( WHOLE _DOCUMENT && ALLOWED _TAGS [ '!doctype' ] && body . ownerDocument && body . ownerDocument . doctype && body . ownerDocument . doctype . name && regExpTest ( DOCTYPE _NAME , body . ownerDocument . doctype . name ) ) {
serializedHTML = '<!DOCTYPE ' + body . ownerDocument . doctype . name + '>\n' + serializedHTML ;
}
/* Sanitize final string template-safe */
if ( SAFE _FOR _TEMPLATES ) {
arrayForEach ( [ MUSTACHE _EXPR , ERB _EXPR , TMPLIT _EXPR ] , expr => {
serializedHTML = stringReplace ( serializedHTML , expr , ' ' ) ;
} ) ;
}
return trustedTypesPolicy && RETURN _TRUSTED _TYPE ? trustedTypesPolicy . createHTML ( serializedHTML ) : serializedHTML ;
} ;
DOMPurify . setConfig = function ( ) {
let cfg = arguments . length > 0 && arguments [ 0 ] !== undefined ? arguments [ 0 ] : { } ;
_parseConfig ( cfg ) ;
SET _CONFIG = true ;
} ;
DOMPurify . clearConfig = function ( ) {
CONFIG = null ;
SET _CONFIG = false ;
} ;
DOMPurify . isValidAttribute = function ( tag , attr , value ) {
/* Initialize shared config vars if necessary. */
if ( ! CONFIG ) {
_parseConfig ( { } ) ;
}
const lcTag = transformCaseFunc ( tag ) ;
const lcName = transformCaseFunc ( attr ) ;
return _isValidAttribute ( lcTag , lcName , value ) ;
} ;
DOMPurify . addHook = function ( entryPoint , hookFunction ) {
if ( typeof hookFunction !== 'function' ) {
return ;
}
arrayPush ( hooks [ entryPoint ] , hookFunction ) ;
} ;
DOMPurify . removeHook = function ( entryPoint , hookFunction ) {
if ( hookFunction !== undefined ) {
const index = arrayLastIndexOf ( hooks [ entryPoint ] , hookFunction ) ;
return index === - 1 ? undefined : arraySplice ( hooks [ entryPoint ] , index , 1 ) [ 0 ] ;
}
return arrayPop ( hooks [ entryPoint ] ) ;
} ;
DOMPurify . removeHooks = function ( entryPoint ) {
hooks [ entryPoint ] = [ ] ;
} ;
DOMPurify . removeAllHooks = function ( ) {
hooks = _createHooksMap ( ) ;
} ;
return DOMPurify ;
}
var purify = createDOMPurify ( ) ;
2018-10-23 14:23:02 +02:00
2025-07-11 11:42:46 +02:00
export { purify as default } ;
//# sourceMappingURL=purify.es.mjs.map