| |
|
|
| import type { |
| TrustedHTML, |
| TrustedTypesWindow, |
| } from 'trusted-types/lib/index.js'; |
| import type { Config, UseProfilesConfig } from './config'; |
| import * as TAGS from './tags.js'; |
| import * as ATTRS from './attrs.js'; |
| import * as EXPRESSIONS from './regexp.js'; |
| import { |
| addToSet, |
| clone, |
| entries, |
| freeze, |
| arrayForEach, |
| arrayIsArray, |
| arrayLastIndexOf, |
| arrayPop, |
| arrayPush, |
| arraySplice, |
| stringMatch, |
| stringReplace, |
| stringToLowerCase, |
| stringToString, |
| stringIndexOf, |
| stringTrim, |
| regExpTest, |
| isRegex, |
| typeErrorCreate, |
| lookupGetter, |
| create, |
| objectHasOwnProperty, |
| stringifyValue, |
| } from './utils.js'; |
|
|
| export type { Config } from './config'; |
|
|
| declare const VERSION: string; |
|
|
| |
| const NODE_TYPE = { |
| element: 1, |
| attribute: 2, |
| text: 3, |
| cdataSection: 4, |
| entityReference: 5, |
| entityNode: 6, |
| progressingInstruction: 7, |
| comment: 8, |
| document: 9, |
| documentType: 10, |
| documentFragment: 11, |
| notation: 12, |
| }; |
|
|
| const getGlobal = function (): WindowLike { |
| return typeof window === 'undefined' ? null : window; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| const _createTrustedTypesPolicy = function ( |
| trustedTypes: TrustedTypePolicyFactory, |
| purifyHostElement: HTMLScriptElement |
| ) { |
| if ( |
| typeof trustedTypes !== 'object' || |
| typeof trustedTypes.createPolicy !== 'function' |
| ) { |
| return null; |
| } |
|
|
| |
| |
| |
| let suffix = null; |
| const ATTR_NAME = 'data-tt-policy-suffix'; |
| if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) { |
| suffix = purifyHostElement.getAttribute(ATTR_NAME); |
| } |
|
|
| const policyName = 'dompurify' + (suffix ? '#' + suffix : ''); |
|
|
| try { |
| return trustedTypes.createPolicy(policyName, { |
| createHTML(html) { |
| return html; |
| }, |
| createScriptURL(scriptUrl) { |
| return scriptUrl; |
| }, |
| }); |
| } catch (_) { |
| |
| |
| |
| console.warn( |
| 'TrustedTypes policy ' + policyName + ' could not be created.' |
| ); |
| return null; |
| } |
| }; |
|
|
| const _createHooksMap = function (): HooksMap { |
| return { |
| afterSanitizeAttributes: [], |
| afterSanitizeElements: [], |
| afterSanitizeShadowDOM: [], |
| beforeSanitizeAttributes: [], |
| beforeSanitizeElements: [], |
| beforeSanitizeShadowDOM: [], |
| uponSanitizeAttribute: [], |
| uponSanitizeElement: [], |
| uponSanitizeShadowNode: [], |
| }; |
| }; |
|
|
| function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify { |
| const DOMPurify: DOMPurify = (root: WindowLike) => createDOMPurify(root); |
|
|
| DOMPurify.version = VERSION; |
|
|
| DOMPurify.removed = []; |
|
|
| if ( |
| !window || |
| !window.document || |
| window.document.nodeType !== NODE_TYPE.document || |
| !window.Element |
| ) { |
| |
| |
| DOMPurify.isSupported = false; |
|
|
| return DOMPurify; |
| } |
|
|
| let { document } = window; |
|
|
| const originalDocument = document; |
| const currentScript: HTMLScriptElement = |
| originalDocument.currentScript as HTMLScriptElement; |
| const { |
| DocumentFragment, |
| HTMLTemplateElement, |
| Node, |
| Element, |
| NodeFilter, |
| NamedNodeMap = window.NamedNodeMap || (window as any).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'); |
|
|
| |
| |
| |
| |
| |
| |
| 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(); |
|
|
| |
| |
| |
| 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 } = EXPRESSIONS; |
|
|
| |
| |
| |
| |
|
|
| |
| let ALLOWED_TAGS = null; |
| const DEFAULT_ALLOWED_TAGS = addToSet({}, [ |
| ...TAGS.html, |
| ...TAGS.svg, |
| ...TAGS.svgFilters, |
| ...TAGS.mathMl, |
| ...TAGS.text, |
| ]); |
|
|
| |
| let ALLOWED_ATTR = null; |
| const DEFAULT_ALLOWED_ATTR = addToSet({}, [ |
| ...ATTRS.html, |
| ...ATTRS.svg, |
| ...ATTRS.mathMl, |
| ...ATTRS.xml, |
| ]); |
|
|
| |
| |
| |
| |
| |
| |
| let CUSTOM_ELEMENT_HANDLING = Object.seal( |
| create(null, { |
| tagNameCheck: { |
| writable: true, |
| configurable: false, |
| enumerable: true, |
| value: null, |
| }, |
| attributeNameCheck: { |
| writable: true, |
| configurable: false, |
| enumerable: true, |
| value: null, |
| }, |
| allowCustomizedBuiltInElements: { |
| writable: true, |
| configurable: false, |
| enumerable: true, |
| value: false, |
| }, |
| }) |
| ); |
|
|
| |
| let FORBID_TAGS = null; |
|
|
| |
| let FORBID_ATTR = null; |
|
|
| |
| const EXTRA_ELEMENT_HANDLING = Object.seal( |
| create(null, { |
| tagCheck: { |
| writable: true, |
| configurable: false, |
| enumerable: true, |
| value: null, |
| }, |
| attributeCheck: { |
| writable: true, |
| configurable: false, |
| enumerable: true, |
| value: null, |
| }, |
| }) |
| ); |
|
|
| |
| let ALLOW_ARIA_ATTR = true; |
|
|
| |
| let ALLOW_DATA_ATTR = true; |
|
|
| |
| let ALLOW_UNKNOWN_PROTOCOLS = false; |
|
|
| |
| |
| let ALLOW_SELF_CLOSE_IN_ATTR = true; |
|
|
| |
| |
| |
| let SAFE_FOR_TEMPLATES = false; |
|
|
| |
| |
| |
| let SAFE_FOR_XML = true; |
|
|
| |
| let WHOLE_DOCUMENT = false; |
|
|
| |
| let SET_CONFIG = false; |
|
|
| |
| |
| let FORCE_BODY = false; |
|
|
| |
| |
| |
| |
| let RETURN_DOM = false; |
|
|
| |
| |
| let RETURN_DOM_FRAGMENT = false; |
|
|
| |
| |
| let RETURN_TRUSTED_TYPE = false; |
|
|
| |
| |
| |
| let SANITIZE_DOM = true; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| let SANITIZE_NAMED_PROPS = false; |
| const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-'; |
|
|
| |
| let KEEP_CONTENT = true; |
|
|
| |
| |
| let IN_PLACE = false; |
|
|
| |
| let USE_PROFILES: UseProfilesConfig | false = {}; |
|
|
| |
| 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', |
| ]); |
|
|
| |
| let DATA_URI_TAGS = null; |
| const DEFAULT_DATA_URI_TAGS = addToSet({}, [ |
| 'audio', |
| 'video', |
| 'img', |
| 'source', |
| 'image', |
| 'track', |
| ]); |
|
|
| |
| let URI_SAFE_ATTRIBUTES = null; |
| const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [ |
| 'alt', |
| 'class', |
| 'for', |
| 'id', |
| 'label', |
| 'name', |
| 'pattern', |
| 'placeholder', |
| 'role', |
| 'summary', |
| 'title', |
| 'value', |
| 'style', |
| 'xmlns', |
| ]); |
|
|
| const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML'; |
| const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; |
| const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; |
| |
| let NAMESPACE = HTML_NAMESPACE; |
| let IS_EMPTY_INPUT = false; |
|
|
| |
| let ALLOWED_NAMESPACES = null; |
| const DEFAULT_ALLOWED_NAMESPACES = addToSet( |
| {}, |
| [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], |
| stringToString |
| ); |
|
|
| let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, [ |
| 'mi', |
| 'mo', |
| 'mn', |
| 'ms', |
| 'mtext', |
| ]); |
|
|
| let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']); |
|
|
| |
| |
| |
| |
| const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [ |
| 'title', |
| 'style', |
| 'font', |
| 'a', |
| 'script', |
| ]); |
|
|
| |
| let PARSER_MEDIA_TYPE: null | DOMParserSupportedType = null; |
| const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html']; |
| const DEFAULT_PARSER_MEDIA_TYPE = 'text/html'; |
| let transformCaseFunc: null | Parameters<typeof addToSet>[2] = null; |
|
|
| |
| let CONFIG: Config | null = null; |
|
|
| |
| |
|
|
| const formElement = document.createElement('form'); |
|
|
| const isRegexOrFunction = function ( |
| testValue: unknown |
| ): testValue is Function | RegExp { |
| return testValue instanceof RegExp || testValue instanceof Function; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| const _parseConfig = function (cfg: Config = {}): void { |
| if (CONFIG && CONFIG === cfg) { |
| return; |
| } |
|
|
| |
| if (!cfg || typeof cfg !== 'object') { |
| cfg = {}; |
| } |
|
|
| |
| cfg = clone(cfg); |
|
|
| PARSER_MEDIA_TYPE = |
| |
| SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 |
| ? DEFAULT_PARSER_MEDIA_TYPE |
| : cfg.PARSER_MEDIA_TYPE; |
|
|
| |
| transformCaseFunc = |
| PARSER_MEDIA_TYPE === 'application/xhtml+xml' |
| ? stringToString |
| : stringToLowerCase; |
|
|
| |
| ALLOWED_TAGS = |
| objectHasOwnProperty(cfg, 'ALLOWED_TAGS') && |
| arrayIsArray(cfg.ALLOWED_TAGS) |
| ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) |
| : DEFAULT_ALLOWED_TAGS; |
| ALLOWED_ATTR = |
| objectHasOwnProperty(cfg, 'ALLOWED_ATTR') && |
| arrayIsArray(cfg.ALLOWED_ATTR) |
| ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) |
| : DEFAULT_ALLOWED_ATTR; |
| ALLOWED_NAMESPACES = |
| objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') && |
| arrayIsArray(cfg.ALLOWED_NAMESPACES) |
| ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) |
| : DEFAULT_ALLOWED_NAMESPACES; |
| URI_SAFE_ATTRIBUTES = |
| objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && |
| arrayIsArray(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') && |
| arrayIsArray(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') && |
| arrayIsArray(cfg.FORBID_CONTENTS) |
| ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) |
| : DEFAULT_FORBID_CONTENTS; |
| FORBID_TAGS = |
| objectHasOwnProperty(cfg, 'FORBID_TAGS') && arrayIsArray(cfg.FORBID_TAGS) |
| ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) |
| : clone({}); |
| FORBID_ATTR = |
| objectHasOwnProperty(cfg, 'FORBID_ATTR') && arrayIsArray(cfg.FORBID_ATTR) |
| ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) |
| : clone({}); |
| USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') |
| ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' |
| ? clone(cfg.USE_PROFILES) |
| : cfg.USE_PROFILES |
| : false; |
|
|
| ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; |
| ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; |
| ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; |
| ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; |
| SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; |
| SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; |
| WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; |
| RETURN_DOM = cfg.RETURN_DOM || false; |
| RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; |
| RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; |
| FORCE_BODY = cfg.FORCE_BODY || false; |
| SANITIZE_DOM = cfg.SANITIZE_DOM !== false; |
| SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; |
| KEEP_CONTENT = cfg.KEEP_CONTENT !== false; |
| IN_PLACE = cfg.IN_PLACE || false; |
| IS_ALLOWED_URI = isRegex(cfg.ALLOWED_URI_REGEXP) |
| ? cfg.ALLOWED_URI_REGEXP |
| : EXPRESSIONS.IS_ALLOWED_URI; |
|
|
| NAMESPACE = |
| typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; |
|
|
| MATHML_TEXT_INTEGRATION_POINTS = |
| objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && |
| cfg.MATHML_TEXT_INTEGRATION_POINTS && |
| typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' |
| ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) |
| : addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); |
|
|
| HTML_INTEGRATION_POINTS = |
| objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && |
| cfg.HTML_INTEGRATION_POINTS && |
| typeof cfg.HTML_INTEGRATION_POINTS === 'object' |
| ? clone(cfg.HTML_INTEGRATION_POINTS) |
| : addToSet({}, ['annotation-xml']); |
|
|
| const customElementHandling = |
| objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && |
| cfg.CUSTOM_ELEMENT_HANDLING && |
| typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' |
| ? clone(cfg.CUSTOM_ELEMENT_HANDLING) |
| : create(null); |
|
|
| CUSTOM_ELEMENT_HANDLING = create(null); |
|
|
| if ( |
| objectHasOwnProperty(customElementHandling, 'tagNameCheck') && |
| isRegexOrFunction(customElementHandling.tagNameCheck) |
| ) { |
| CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; |
| } |
|
|
| if ( |
| objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && |
| isRegexOrFunction(customElementHandling.attributeNameCheck) |
| ) { |
| CUSTOM_ELEMENT_HANDLING.attributeNameCheck = |
| customElementHandling.attributeNameCheck; |
| } |
|
|
| if ( |
| objectHasOwnProperty( |
| customElementHandling, |
| 'allowCustomizedBuiltInElements' |
| ) && |
| typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean' |
| ) { |
| CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = |
| customElementHandling.allowCustomizedBuiltInElements; |
| } |
|
|
| if (SAFE_FOR_TEMPLATES) { |
| ALLOW_DATA_ATTR = false; |
| } |
|
|
| if (RETURN_DOM_FRAGMENT) { |
| RETURN_DOM = true; |
| } |
|
|
| |
| if (USE_PROFILES) { |
| ALLOWED_TAGS = addToSet({}, TAGS.text); |
| ALLOWED_ATTR = create(null); |
| 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); |
| } |
| } |
|
|
| |
| |
| EXTRA_ELEMENT_HANDLING.tagCheck = null; |
| EXTRA_ELEMENT_HANDLING.attributeCheck = null; |
|
|
| |
| if (objectHasOwnProperty(cfg, 'ADD_TAGS')) { |
| if (typeof cfg.ADD_TAGS === 'function') { |
| EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS; |
| } else if (arrayIsArray(cfg.ADD_TAGS)) { |
| if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { |
| ALLOWED_TAGS = clone(ALLOWED_TAGS); |
| } |
|
|
| addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc); |
| } |
| } |
|
|
| if (objectHasOwnProperty(cfg, 'ADD_ATTR')) { |
| if (typeof cfg.ADD_ATTR === 'function') { |
| EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR; |
| } else if (arrayIsArray(cfg.ADD_ATTR)) { |
| if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { |
| ALLOWED_ATTR = clone(ALLOWED_ATTR); |
| } |
|
|
| addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc); |
| } |
| } |
|
|
| if ( |
| objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && |
| arrayIsArray(cfg.ADD_URI_SAFE_ATTR) |
| ) { |
| addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc); |
| } |
|
|
| if ( |
| objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && |
| arrayIsArray(cfg.FORBID_CONTENTS) |
| ) { |
| if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) { |
| FORBID_CONTENTS = clone(FORBID_CONTENTS); |
| } |
|
|
| addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc); |
| } |
|
|
| if ( |
| objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && |
| arrayIsArray(cfg.ADD_FORBID_CONTENTS) |
| ) { |
| if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) { |
| FORBID_CONTENTS = clone(FORBID_CONTENTS); |
| } |
|
|
| addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc); |
| } |
|
|
| |
| if (KEEP_CONTENT) { |
| ALLOWED_TAGS['#text'] = true; |
| } |
|
|
| |
| if (WHOLE_DOCUMENT) { |
| addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); |
| } |
|
|
| |
| if (ALLOWED_TAGS.table) { |
| addToSet(ALLOWED_TAGS, ['tbody']); |
| delete FORBID_TAGS.tbody; |
| } |
|
|
| if (cfg.TRUSTED_TYPES_POLICY) { |
| if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') { |
| throw typeErrorCreate( |
| 'TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.' |
| ); |
| } |
|
|
| if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') { |
| throw typeErrorCreate( |
| 'TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.' |
| ); |
| } |
|
|
| |
| trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY; |
|
|
| |
| emptyHTML = trustedTypesPolicy.createHTML(''); |
| } else { |
| |
| if (trustedTypesPolicy === undefined) { |
| trustedTypesPolicy = _createTrustedTypesPolicy( |
| trustedTypes, |
| currentScript |
| ); |
| } |
|
|
| |
| if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') { |
| emptyHTML = trustedTypesPolicy.createHTML(''); |
| } |
| } |
|
|
| |
| |
| if (freeze) { |
| freeze(cfg); |
| } |
|
|
| CONFIG = cfg; |
| }; |
|
|
| |
| |
| |
| const ALL_SVG_TAGS = addToSet({}, [ |
| ...TAGS.svg, |
| ...TAGS.svgFilters, |
| ...TAGS.svgDisallowed, |
| ]); |
| const ALL_MATHML_TAGS = addToSet({}, [ |
| ...TAGS.mathMl, |
| ...TAGS.mathMlDisallowed, |
| ]); |
|
|
| |
| |
| |
| |
| |
| |
| const _checkValidNamespace = function (element: Element): boolean { |
| let parent = getParentNode(element); |
|
|
| |
| |
| if (!parent || !parent.tagName) { |
| parent = { |
| namespaceURI: NAMESPACE, |
| tagName: 'template', |
| }; |
| } |
|
|
| const tagName = stringToLowerCase(element.tagName); |
| const parentTagName = stringToLowerCase(parent.tagName); |
|
|
| if (!ALLOWED_NAMESPACES[element.namespaceURI]) { |
| return false; |
| } |
|
|
| if (element.namespaceURI === SVG_NAMESPACE) { |
| |
| |
| |
| if (parent.namespaceURI === HTML_NAMESPACE) { |
| return tagName === 'svg'; |
| } |
|
|
| |
| |
| |
| if (parent.namespaceURI === MATHML_NAMESPACE) { |
| return ( |
| tagName === 'svg' && |
| (parentTagName === 'annotation-xml' || |
| MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) |
| ); |
| } |
|
|
| |
| |
| return Boolean(ALL_SVG_TAGS[tagName]); |
| } |
|
|
| if (element.namespaceURI === MATHML_NAMESPACE) { |
| |
| |
| |
| if (parent.namespaceURI === HTML_NAMESPACE) { |
| return tagName === 'math'; |
| } |
|
|
| |
| |
| if (parent.namespaceURI === SVG_NAMESPACE) { |
| return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName]; |
| } |
|
|
| |
| |
| return Boolean(ALL_MATHML_TAGS[tagName]); |
| } |
|
|
| if (element.namespaceURI === HTML_NAMESPACE) { |
| |
| |
| |
| if ( |
| parent.namespaceURI === SVG_NAMESPACE && |
| !HTML_INTEGRATION_POINTS[parentTagName] |
| ) { |
| return false; |
| } |
|
|
| if ( |
| parent.namespaceURI === MATHML_NAMESPACE && |
| !MATHML_TEXT_INTEGRATION_POINTS[parentTagName] |
| ) { |
| return false; |
| } |
|
|
| |
| |
| return ( |
| !ALL_MATHML_TAGS[tagName] && |
| (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]) |
| ); |
| } |
|
|
| |
| if ( |
| PARSER_MEDIA_TYPE === 'application/xhtml+xml' && |
| ALLOWED_NAMESPACES[element.namespaceURI] |
| ) { |
| return true; |
| } |
|
|
| |
| |
| |
| |
| return false; |
| }; |
|
|
| |
| |
| |
| |
| |
| const _forceRemove = function (node: Node): void { |
| arrayPush(DOMPurify.removed, { element: node }); |
|
|
| try { |
| |
| getParentNode(node).removeChild(node); |
| } catch (_) { |
| remove(node); |
| } |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| const _removeAttribute = function (name: string, element: Element): void { |
| try { |
| arrayPush(DOMPurify.removed, { |
| attribute: element.getAttributeNode(name), |
| from: element, |
| }); |
| } catch (_) { |
| arrayPush(DOMPurify.removed, { |
| attribute: null, |
| from: element, |
| }); |
| } |
|
|
| element.removeAttribute(name); |
|
|
| |
| if (name === 'is') { |
| if (RETURN_DOM || RETURN_DOM_FRAGMENT) { |
| try { |
| _forceRemove(element); |
| } catch (_) {} |
| } else { |
| try { |
| element.setAttribute(name, ''); |
| } catch (_) {} |
| } |
| } |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| const _initDocument = function (dirty: string): Document { |
| |
| let doc = null; |
| let leadingWhitespace = null; |
|
|
| if (FORCE_BODY) { |
| dirty = '<remove></remove>' + dirty; |
| } else { |
| |
| const matches = stringMatch(dirty, /^[\r\n\t ]+/); |
| leadingWhitespace = matches && matches[0]; |
| } |
|
|
| if ( |
| PARSER_MEDIA_TYPE === 'application/xhtml+xml' && |
| NAMESPACE === HTML_NAMESPACE |
| ) { |
| |
| dirty = |
| '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + |
| dirty + |
| '</body></html>'; |
| } |
|
|
| const dirtyPayload = trustedTypesPolicy |
| ? trustedTypesPolicy.createHTML(dirty) |
| : dirty; |
| |
| |
| |
| |
| if (NAMESPACE === HTML_NAMESPACE) { |
| try { |
| doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE); |
| } catch (_) {} |
| } |
|
|
| |
| if (!doc || !doc.documentElement) { |
| doc = implementation.createDocument(NAMESPACE, 'template', null); |
| try { |
| doc.documentElement.innerHTML = IS_EMPTY_INPUT |
| ? emptyHTML |
| : dirtyPayload; |
| } catch (_) { |
| |
| } |
| } |
|
|
| const body = doc.body || doc.documentElement; |
|
|
| if (dirty && leadingWhitespace) { |
| body.insertBefore( |
| document.createTextNode(leadingWhitespace), |
| body.childNodes[0] || null |
| ); |
| } |
|
|
| |
| if (NAMESPACE === HTML_NAMESPACE) { |
| return getElementsByTagName.call( |
| doc, |
| WHOLE_DOCUMENT ? 'html' : 'body' |
| )[0]; |
| } |
|
|
| return WHOLE_DOCUMENT ? doc.documentElement : body; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| const _createNodeIterator = function (root: Node): NodeIterator { |
| return createNodeIterator.call( |
| root.ownerDocument || root, |
| root, |
| |
| NodeFilter.SHOW_ELEMENT | |
| NodeFilter.SHOW_COMMENT | |
| NodeFilter.SHOW_TEXT | |
| NodeFilter.SHOW_PROCESSING_INSTRUCTION | |
| NodeFilter.SHOW_CDATA_SECTION, |
| null |
| ); |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| const _isClobbered = function (element: Element): boolean { |
| 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') |
| ); |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| const _isNode = function (value: unknown): value is Node { |
| return typeof Node === 'function' && value instanceof Node; |
| }; |
|
|
| function _executeHooks<T extends HookFunction>( |
| hooks: HookFunction[], |
| currentNode: Parameters<T>[0], |
| data: Parameters<T>[1] |
| ): void { |
| arrayForEach(hooks, (hook: T) => { |
| hook.call(DOMPurify, currentNode, data, CONFIG); |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const _sanitizeElements = function (currentNode: any): boolean { |
| let content = null; |
|
|
| |
| _executeHooks(hooks.beforeSanitizeElements, currentNode, null); |
|
|
| |
| if (_isClobbered(currentNode)) { |
| _forceRemove(currentNode); |
| return true; |
| } |
|
|
| |
| const tagName = transformCaseFunc(currentNode.nodeName); |
|
|
| |
| _executeHooks(hooks.uponSanitizeElement, currentNode, { |
| tagName, |
| allowedTags: ALLOWED_TAGS, |
| }); |
|
|
| |
| if ( |
| SAFE_FOR_XML && |
| currentNode.hasChildNodes() && |
| !_isNode(currentNode.firstElementChild) && |
| regExpTest(/<[/\w!]/g, currentNode.innerHTML) && |
| regExpTest(/<[/\w!]/g, currentNode.textContent) |
| ) { |
| _forceRemove(currentNode); |
| return true; |
| } |
|
|
| |
| if ( |
| SAFE_FOR_XML && |
| currentNode.namespaceURI === HTML_NAMESPACE && |
| tagName === 'style' && |
| _isNode(currentNode.firstElementChild) |
| ) { |
| _forceRemove(currentNode); |
| return true; |
| } |
|
|
| |
| if (currentNode.nodeType === NODE_TYPE.progressingInstruction) { |
| _forceRemove(currentNode); |
| return true; |
| } |
|
|
| |
| if ( |
| SAFE_FOR_XML && |
| currentNode.nodeType === NODE_TYPE.comment && |
| regExpTest(/<[/\w]/g, currentNode.data) |
| ) { |
| _forceRemove(currentNode); |
| return true; |
| } |
|
|
| |
| if ( |
| FORBID_TAGS[tagName] || |
| (!( |
| EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && |
| EXTRA_ELEMENT_HANDLING.tagCheck(tagName) |
| ) && |
| !ALLOWED_TAGS[tagName]) |
| ) { |
| |
| if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) { |
| if ( |
| CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && |
| regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName) |
| ) { |
| return false; |
| } |
|
|
| if ( |
| CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && |
| CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName) |
| ) { |
| return false; |
| } |
| } |
|
|
| |
| if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) { |
| const parentNode = getParentNode(currentNode) || 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); |
| parentNode.insertBefore(childClone, getNextSibling(currentNode)); |
| } |
| } |
| } |
|
|
| _forceRemove(currentNode); |
| return true; |
| } |
|
|
| |
| if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) { |
| _forceRemove(currentNode); |
| return true; |
| } |
|
|
| |
| if ( |
| (tagName === 'noscript' || |
| tagName === 'noembed' || |
| tagName === 'noframes') && |
| regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML) |
| ) { |
| _forceRemove(currentNode); |
| return true; |
| } |
|
|
| |
| if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) { |
| |
| content = currentNode.textContent; |
|
|
| arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr: RegExp) => { |
| content = stringReplace(content, expr, ' '); |
| }); |
|
|
| if (currentNode.textContent !== content) { |
| arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() }); |
| currentNode.textContent = content; |
| } |
| } |
|
|
| |
| _executeHooks(hooks.afterSanitizeElements, currentNode, null); |
|
|
| return false; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const _isValidAttribute = function ( |
| lcTag: string, |
| lcName: string, |
| value: string |
| ): boolean { |
| |
| if (FORBID_ATTR[lcName]) { |
| return false; |
| } |
|
|
| |
| if ( |
| SANITIZE_DOM && |
| (lcName === 'id' || lcName === 'name') && |
| (value in document || value in formElement) |
| ) { |
| return false; |
| } |
|
|
| const nameIsPermitted = |
| ALLOWED_ATTR[lcName] || |
| (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && |
| EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)); |
|
|
| |
| |
| |
| |
| if ( |
| ALLOW_DATA_ATTR && |
| !FORBID_ATTR[lcName] && |
| regExpTest(DATA_ATTR, lcName) |
| ) { |
| |
| } else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) { |
| |
| |
| } else if (!nameIsPermitted || FORBID_ATTR[lcName]) { |
| if ( |
| |
| |
| |
| (_isBasicCustomElement(lcTag) && |
| ((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && |
| regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag)) || |
| (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && |
| CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag))) && |
| ((CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && |
| regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName)) || |
| (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && |
| CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName, lcTag)))) || |
| |
| |
| (lcName === 'is' && |
| CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && |
| ((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && |
| regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value)) || |
| (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && |
| CUSTOM_ELEMENT_HANDLING.tagNameCheck(value)))) |
| ) { |
| |
| |
| } else { |
| return false; |
| } |
| |
| } else if (URI_SAFE_ATTRIBUTES[lcName]) { |
| |
| |
| |
| } else if ( |
| regExpTest(IS_ALLOWED_URI, 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; |
| }; |
|
|
| |
| |
| |
| const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, [ |
| 'annotation-xml', |
| 'color-profile', |
| 'font-face', |
| 'font-face-format', |
| 'font-face-name', |
| 'font-face-src', |
| 'font-face-uri', |
| 'missing-glyph', |
| ]); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| const _isBasicCustomElement = function (tagName: string): boolean { |
| return ( |
| !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && |
| regExpTest(CUSTOM_ELEMENT, tagName) |
| ); |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const _sanitizeAttributes = function (currentNode: Element): void { |
| |
| _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null); |
|
|
| const { attributes } = currentNode; |
|
|
| |
| if (!attributes || _isClobbered(currentNode)) { |
| return; |
| } |
|
|
| const hookEvent = { |
| attrName: '', |
| attrValue: '', |
| keepAttr: true, |
| allowedAttributes: ALLOWED_ATTR, |
| forceKeepAttr: undefined, |
| }; |
| let l = attributes.length; |
|
|
| |
| while (l--) { |
| const attr = attributes[l]; |
| const { name, namespaceURI, value: attrValue } = attr; |
| const lcName = transformCaseFunc(name); |
|
|
| const initValue = attrValue; |
| let value = name === 'value' ? initValue : stringTrim(initValue); |
|
|
| |
| hookEvent.attrName = lcName; |
| hookEvent.attrValue = value; |
| hookEvent.keepAttr = true; |
| hookEvent.forceKeepAttr = undefined; |
| _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent); |
| value = hookEvent.attrValue; |
|
|
| |
| |
| |
| if ( |
| SANITIZE_NAMED_PROPS && |
| (lcName === 'id' || lcName === 'name') && |
| stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0 |
| ) { |
| |
| _removeAttribute(name, currentNode); |
| |
| value = SANITIZE_NAMED_PROPS_PREFIX + value; |
| } |
| |
| |
|
|
| |
| if ( |
| SAFE_FOR_XML && |
| regExpTest( |
| /((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, |
| value |
| ) |
| ) { |
| _removeAttribute(name, currentNode); |
| continue; |
| } |
|
|
| |
| if (lcName === 'attributename' && stringMatch(value, 'href')) { |
| _removeAttribute(name, currentNode); |
| continue; |
| } |
|
|
| |
| if (hookEvent.forceKeepAttr) { |
| continue; |
| } |
|
|
| |
| if (!hookEvent.keepAttr) { |
| _removeAttribute(name, currentNode); |
| continue; |
| } |
|
|
| |
| if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) { |
| _removeAttribute(name, currentNode); |
| continue; |
| } |
|
|
| |
| if (SAFE_FOR_TEMPLATES) { |
| arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr: RegExp) => { |
| value = stringReplace(value, expr, ' '); |
| }); |
| } |
|
|
| |
| const lcTag = transformCaseFunc(currentNode.nodeName); |
| if (!_isValidAttribute(lcTag, lcName, value)) { |
| _removeAttribute(name, currentNode); |
| continue; |
| } |
|
|
| |
| 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; |
| } |
|
|
| default: { |
| break; |
| } |
| } |
| } |
| } |
|
|
| |
| if (value !== initValue) { |
| try { |
| if (namespaceURI) { |
| currentNode.setAttributeNS(namespaceURI, name, value); |
| } else { |
| |
| currentNode.setAttribute(name, value); |
| } |
|
|
| if (_isClobbered(currentNode)) { |
| _forceRemove(currentNode); |
| } else { |
| arrayPop(DOMPurify.removed); |
| } |
| } catch (_) { |
| _removeAttribute(name, currentNode); |
| } |
| } |
| } |
|
|
| |
| _executeHooks(hooks.afterSanitizeAttributes, currentNode, null); |
| }; |
|
|
| |
| |
| |
| |
| |
| const _sanitizeShadowDOM = function (fragment: DocumentFragment): void { |
| let shadowNode = null; |
| const shadowIterator = _createNodeIterator(fragment); |
|
|
| |
| _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null); |
|
|
| while ((shadowNode = shadowIterator.nextNode())) { |
| |
| _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null); |
|
|
| |
| _sanitizeElements(shadowNode); |
|
|
| |
| _sanitizeAttributes(shadowNode); |
|
|
| |
| if (shadowNode.content instanceof DocumentFragment) { |
| _sanitizeShadowDOM(shadowNode.content); |
| } |
| } |
|
|
| |
| _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null); |
| }; |
|
|
| |
| DOMPurify.sanitize = function (dirty, cfg = {}) { |
| let body = null; |
| let importedNode = null; |
| let currentNode = null; |
| let returnNode = null; |
| |
| |
| |
| IS_EMPTY_INPUT = !dirty; |
| if (IS_EMPTY_INPUT) { |
| dirty = '<!-->'; |
| } |
|
|
| |
| if (typeof dirty !== 'string' && !_isNode(dirty)) { |
| dirty = stringifyValue(dirty); |
|
|
| if (typeof dirty !== 'string') { |
| throw typeErrorCreate('dirty is not a string, aborting'); |
| } |
| } |
|
|
| |
| if (!DOMPurify.isSupported) { |
| return dirty; |
| } |
|
|
| |
| if (!SET_CONFIG) { |
| _parseConfig(cfg); |
| } |
|
|
| |
| DOMPurify.removed = []; |
|
|
| |
| if (typeof dirty === 'string') { |
| IN_PLACE = false; |
| } |
|
|
| if (IN_PLACE) { |
| |
| const nn = (dirty as Node).nodeName; |
| if (typeof nn === 'string') { |
| const tagName = transformCaseFunc(nn); |
| if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { |
| throw typeErrorCreate( |
| 'root node is forbidden and cannot be sanitized in-place' |
| ); |
| } |
| } |
| } else if (dirty instanceof Node) { |
| |
| |
| body = _initDocument('<!---->'); |
| importedNode = body.ownerDocument.importNode(dirty, true); |
| if ( |
| importedNode.nodeType === NODE_TYPE.element && |
| importedNode.nodeName === 'BODY' |
| ) { |
| |
| body = importedNode; |
| } else if (importedNode.nodeName === 'HTML') { |
| body = importedNode; |
| } else { |
| |
| body.appendChild(importedNode); |
| } |
| } else { |
| |
| if ( |
| !RETURN_DOM && |
| !SAFE_FOR_TEMPLATES && |
| !WHOLE_DOCUMENT && |
| |
| dirty.indexOf('<') === -1 |
| ) { |
| return trustedTypesPolicy && RETURN_TRUSTED_TYPE |
| ? trustedTypesPolicy.createHTML(dirty) |
| : dirty; |
| } |
|
|
| |
| body = _initDocument(dirty); |
|
|
| |
| if (!body) { |
| return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : ''; |
| } |
| } |
|
|
| |
| if (body && FORCE_BODY) { |
| _forceRemove(body.firstChild); |
| } |
|
|
| |
| const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body); |
|
|
| |
| while ((currentNode = nodeIterator.nextNode())) { |
| |
| _sanitizeElements(currentNode); |
|
|
| |
| _sanitizeAttributes(currentNode); |
|
|
| |
| if (currentNode.content instanceof DocumentFragment) { |
| _sanitizeShadowDOM(currentNode.content); |
| } |
| } |
|
|
| |
| if (IN_PLACE) { |
| return dirty; |
| } |
|
|
| |
| if (RETURN_DOM) { |
| if (SAFE_FOR_TEMPLATES) { |
| body.normalize(); |
| let html = body.innerHTML; |
| arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr: RegExp) => { |
| html = stringReplace(html, expr, ' '); |
| }); |
| body.innerHTML = html; |
| } |
|
|
| if (RETURN_DOM_FRAGMENT) { |
| returnNode = createDocumentFragment.call(body.ownerDocument); |
|
|
| while (body.firstChild) { |
| |
| returnNode.appendChild(body.firstChild); |
| } |
| } else { |
| returnNode = body; |
| } |
|
|
| if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) { |
| |
| |
| |
| |
| |
| |
| |
| returnNode = importNode.call(originalDocument, returnNode, true); |
| } |
|
|
| return returnNode; |
| } |
|
|
| let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; |
|
|
| |
| if ( |
| WHOLE_DOCUMENT && |
| ALLOWED_TAGS['!doctype'] && |
| body.ownerDocument && |
| body.ownerDocument.doctype && |
| body.ownerDocument.doctype.name && |
| regExpTest(EXPRESSIONS.DOCTYPE_NAME, body.ownerDocument.doctype.name) |
| ) { |
| serializedHTML = |
| '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML; |
| } |
|
|
| |
| if (SAFE_FOR_TEMPLATES) { |
| arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr: RegExp) => { |
| serializedHTML = stringReplace(serializedHTML, expr, ' '); |
| }); |
| } |
|
|
| return trustedTypesPolicy && RETURN_TRUSTED_TYPE |
| ? trustedTypesPolicy.createHTML(serializedHTML) |
| : serializedHTML; |
| }; |
|
|
| DOMPurify.setConfig = function (cfg = {}) { |
| _parseConfig(cfg); |
| SET_CONFIG = true; |
| }; |
|
|
| DOMPurify.clearConfig = function () { |
| CONFIG = null; |
| SET_CONFIG = false; |
| }; |
|
|
| DOMPurify.isValidAttribute = function (tag, attr, value) { |
| |
| if (!CONFIG) { |
| _parseConfig({}); |
| } |
|
|
| const lcTag = transformCaseFunc(tag); |
| const lcName = transformCaseFunc(attr); |
| return _isValidAttribute(lcTag, lcName, value); |
| }; |
|
|
| DOMPurify.addHook = function ( |
| entryPoint: keyof HooksMap, |
| hookFunction: HookFunction |
| ) { |
| if (typeof hookFunction !== 'function') { |
| return; |
| } |
|
|
| arrayPush(hooks[entryPoint], hookFunction); |
| }; |
|
|
| DOMPurify.removeHook = function ( |
| entryPoint: keyof HooksMap, |
| hookFunction: 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: keyof HooksMap) { |
| hooks[entryPoint] = []; |
| }; |
|
|
| DOMPurify.removeAllHooks = function () { |
| hooks = _createHooksMap(); |
| }; |
|
|
| return DOMPurify; |
| } |
|
|
| export default createDOMPurify(); |
|
|
| export interface DOMPurify { |
| |
| |
| |
| (root?: WindowLike): DOMPurify; |
|
|
| |
| |
| |
| |
| version: string; |
|
|
| |
| |
| |
| |
| removed: Array<RemovedElement | RemovedAttribute>; |
|
|
| |
| |
| |
| isSupported: boolean; |
|
|
| |
| |
| |
| |
| |
| setConfig(cfg?: Config): void; |
|
|
| |
| |
| |
| clearConfig(): void; |
|
|
| |
| |
| |
| |
| |
| |
| |
| sanitize( |
| dirty: string | Node, |
| cfg: Config & { RETURN_TRUSTED_TYPE: true } |
| ): TrustedHTML; |
|
|
| |
| |
| |
| |
| |
| |
| |
| sanitize(dirty: Node, cfg: Config & { IN_PLACE: true }): Node; |
|
|
| |
| |
| |
| |
| |
| |
| |
| sanitize(dirty: string | Node, cfg: Config & { RETURN_DOM: true }): Node; |
|
|
| |
| |
| |
| |
| |
| |
| |
| sanitize( |
| dirty: string | Node, |
| cfg: Config & { RETURN_DOM_FRAGMENT: true } |
| ): DocumentFragment; |
|
|
| |
| |
| |
| |
| |
| |
| |
| sanitize(dirty: string | Node, cfg?: Config): string; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| isValidAttribute(tag: string, attr: string, value: string): boolean; |
|
|
| |
| |
| |
| |
| |
| |
| addHook(entryPoint: BasicHookName, hookFunction: NodeHook): void; |
|
|
| |
| |
| |
| |
| |
| |
| addHook(entryPoint: ElementHookName, hookFunction: ElementHook): void; |
|
|
| |
| |
| |
| |
| |
| |
| addHook( |
| entryPoint: DocumentFragmentHookName, |
| hookFunction: DocumentFragmentHook |
| ): void; |
|
|
| |
| |
| |
| |
| |
| |
| addHook( |
| entryPoint: 'uponSanitizeElement', |
| hookFunction: UponSanitizeElementHook |
| ): void; |
|
|
| |
| |
| |
| |
| |
| |
| addHook( |
| entryPoint: 'uponSanitizeAttribute', |
| hookFunction: UponSanitizeAttributeHook |
| ): void; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| removeHook( |
| entryPoint: BasicHookName, |
| hookFunction?: NodeHook |
| ): NodeHook | undefined; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| removeHook( |
| entryPoint: ElementHookName, |
| hookFunction?: ElementHook |
| ): ElementHook | undefined; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| removeHook( |
| entryPoint: DocumentFragmentHookName, |
| hookFunction?: DocumentFragmentHook |
| ): DocumentFragmentHook | undefined; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| removeHook( |
| entryPoint: 'uponSanitizeElement', |
| hookFunction?: UponSanitizeElementHook |
| ): UponSanitizeElementHook | undefined; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| removeHook( |
| entryPoint: 'uponSanitizeAttribute', |
| hookFunction?: UponSanitizeAttributeHook |
| ): UponSanitizeAttributeHook | undefined; |
|
|
| |
| |
| |
| |
| |
| removeHooks(entryPoint: HookName): void; |
|
|
| |
| |
| |
| removeAllHooks(): void; |
| } |
|
|
| |
| |
| |
| export interface RemovedElement { |
| |
| |
| |
| element: Node; |
| } |
|
|
| |
| |
| |
| export interface RemovedAttribute { |
| |
| |
| |
| attribute: Attr | null; |
|
|
| |
| |
| |
| from: Node; |
| } |
|
|
| type BasicHookName = |
| | 'beforeSanitizeElements' |
| | 'afterSanitizeElements' |
| | 'uponSanitizeShadowNode'; |
| type ElementHookName = 'beforeSanitizeAttributes' | 'afterSanitizeAttributes'; |
| type DocumentFragmentHookName = |
| | 'beforeSanitizeShadowDOM' |
| | 'afterSanitizeShadowDOM'; |
| type UponSanitizeElementHookName = 'uponSanitizeElement'; |
| type UponSanitizeAttributeHookName = 'uponSanitizeAttribute'; |
|
|
| interface HooksMap { |
| beforeSanitizeElements: NodeHook[]; |
| afterSanitizeElements: NodeHook[]; |
| beforeSanitizeShadowDOM: DocumentFragmentHook[]; |
| uponSanitizeShadowNode: NodeHook[]; |
| afterSanitizeShadowDOM: DocumentFragmentHook[]; |
| beforeSanitizeAttributes: ElementHook[]; |
| afterSanitizeAttributes: ElementHook[]; |
| uponSanitizeElement: UponSanitizeElementHook[]; |
| uponSanitizeAttribute: UponSanitizeAttributeHook[]; |
| } |
|
|
| type ArrayElement<T> = T extends Array<infer U> ? U : never; |
|
|
| type HookFunction = ArrayElement<HooksMap[keyof HooksMap]>; |
|
|
| export type HookName = |
| | BasicHookName |
| | ElementHookName |
| | DocumentFragmentHookName |
| | UponSanitizeElementHookName |
| | UponSanitizeAttributeHookName; |
|
|
| export type NodeHook = ( |
| this: DOMPurify, |
| currentNode: Node, |
| hookEvent: null, |
| config: Config |
| ) => void; |
|
|
| export type ElementHook = ( |
| this: DOMPurify, |
| currentNode: Element, |
| hookEvent: null, |
| config: Config |
| ) => void; |
|
|
| export type DocumentFragmentHook = ( |
| this: DOMPurify, |
| currentNode: DocumentFragment, |
| hookEvent: null, |
| config: Config |
| ) => void; |
|
|
| export type UponSanitizeElementHook = ( |
| this: DOMPurify, |
| currentNode: Node, |
| hookEvent: UponSanitizeElementHookEvent, |
| config: Config |
| ) => void; |
|
|
| export type UponSanitizeAttributeHook = ( |
| this: DOMPurify, |
| currentNode: Element, |
| hookEvent: UponSanitizeAttributeHookEvent, |
| config: Config |
| ) => void; |
|
|
| export interface UponSanitizeElementHookEvent { |
| tagName: string; |
| allowedTags: Record<string, boolean>; |
| } |
|
|
| export interface UponSanitizeAttributeHookEvent { |
| attrName: string; |
| attrValue: string; |
| keepAttr: boolean; |
| allowedAttributes: Record<string, boolean>; |
| forceKeepAttr: boolean | undefined; |
| } |
|
|
| |
| |
| |
| export type WindowLike = Pick< |
| typeof globalThis, |
| | 'DocumentFragment' |
| | 'HTMLTemplateElement' |
| | 'Node' |
| | 'Element' |
| | 'NodeFilter' |
| | 'NamedNodeMap' |
| | 'HTMLFormElement' |
| | 'DOMParser' |
| > & { |
| document?: Document; |
| MozNamedAttrMap?: typeof window.NamedNodeMap; |
| } & Pick<TrustedTypesWindow, 'trustedTypes'>; |
|
|