@locker/html-sanitizer
Advanced tools
Comparing version 0.14.20 to 0.14.21
@@ -82,3 +82,3 @@ /*! | ||
const ATTRIBUTES = ['href', 'xlink:href']; | ||
const SANITIZER_HOOKS = new shared.MapCtor([['uponSanitizeAttribute', sanitizeHrefAttributeHook]]); | ||
const SANITIZER_HOOKS = new shared.MapCtor([['uponSanitizeAttribute', sanitizeHrefAttributeHook], ['uponSanitizeElement', allowCustomTagHook]]); | ||
const URL_SCHEMES = ['http:', 'https:']; | ||
@@ -93,4 +93,8 @@ const { | ||
const urlReplacer = /[^a-z0-9]+/gi; | ||
const urlReplacer = /[^a-z0-9]+/gi; // The Regex is based on the WHATWG spec: | ||
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name | ||
// However, DOMPurify sanitizes unicode characters (\u0000-\uFFFF) in tag name. | ||
const customTagRegex = /^[a-z]([-_.\w])*-([-.0-9_a-z\xB7\xC0-\xD6\xD8-\xF6\xF8-\u37D0\u37F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u10000-\uEFFFF])*/; | ||
function checkExistingAndDequeue(container, normalizedHref) { | ||
@@ -201,10 +205,22 @@ if (shared.SetHas(queue, normalizedHref.normalizedUrl)) { | ||
return sharedDom.ElementInnerHTMLGetter(htmlTemplate); | ||
} | ||
function sanitizeDocument(doc) { | ||
const content = sharedDom.ElementOuterHTMLGetter(sharedDom.DocumentDocumentElementGetter(doc)); | ||
const contentSanitized = sanitize(content); | ||
const newDoc = sharedDom.DOMImplementationCreateDocument(sharedDom.DocumentImplementation(doc), sharedDom.NAMESPACE_XHTML, 'html'); | ||
sharedDom.ElementInnerHTMLSetter(sharedDom.DocumentDocumentElementGetter(newDoc), contentSanitized); | ||
return newDoc; | ||
} // Sanitize a URL representing a SVG href attribute value. | ||
function sanitizeHrefAttributeHook(node, data) { | ||
function sanitizeHrefAttributeHook(node, data, _config) { | ||
const { | ||
attrValue, | ||
attrName | ||
} = data; | ||
const nodeName = sharedDom.NodeNameGetter(node); | ||
if (data.attrValue && nodeName === 'USE' && shared.ArrayIncludes(ATTRIBUTES, data.attrName)) { | ||
data.attrValue = sanitizeSvgHrefValue(data.attrValue); | ||
if (attrValue && nodeName === 'USE' && shared.ArrayIncludes(ATTRIBUTES, attrName)) { | ||
data.attrValue = sanitizeSvgHrefValue(attrValue); | ||
} | ||
@@ -239,7 +255,19 @@ | ||
function sanitizeSvgInnerHtml(el, dirty) { | ||
const ownerDoc = sharedDom.NodeOwnerDocumentGetter(el); | ||
function createSvgContainer(ownerDoc) { | ||
return sharedDom.DocumentCreateElementNS(ownerDoc, sharedDom.NAMESPACE_SVG, 'svg'); | ||
} | ||
function sanitizeSvgInnerHtml(stringOrSvg, dirty = '') { | ||
let container; | ||
const ownerDoc = typeof stringOrSvg === 'string' ? document : sharedDom.NodeOwnerDocumentGetter(stringOrSvg); | ||
const comment = sharedDom.DocumentCreateComment(ownerDoc, ''); | ||
const closestSvg = sharedDom.ElementClosest(el, 'svg'); | ||
const container = closestSvg ? sharedDom.NodeClone(closestSvg, false) : sharedDom.DocumentCreateElementNS(ownerDoc, 'http://www.w3.org/2000/svg', 'svg'); | ||
if (typeof stringOrSvg === 'string') { | ||
dirty = stringOrSvg; | ||
container = createSvgContainer(ownerDoc); | ||
} else { | ||
const closestSvg = sharedDom.ElementClosest(stringOrSvg, 'svg'); | ||
container = closestSvg ? sharedDom.NodeClone(closestSvg, false) : createSvgContainer(ownerDoc); | ||
} | ||
sharedDom.NodeAppendChild(container, comment); | ||
@@ -258,5 +286,18 @@ const outerHTML = sharedDom.ElementOuterHTMLGetter(container); | ||
function allowCustomTagHook(node, data, _config) { | ||
const { | ||
allowedTags, | ||
tagName | ||
} = data; | ||
if (!allowedTags[tagName] && customTagRegex.test(tagName)) { | ||
allowedTags[tagName] = true; | ||
} | ||
} | ||
exports.CONFIG = config; | ||
exports.allowCustomTagHook = allowCustomTagHook; | ||
exports.blobSanitizer = blobSanitizer; | ||
exports.sanitize = sanitize; | ||
exports.sanitizeDocument = sanitizeDocument; | ||
exports.sanitizeHrefAttributeHook = sanitizeHrefAttributeHook; | ||
@@ -268,2 +309,2 @@ exports.sanitizeSvgHrefValue = sanitizeSvgHrefValue; | ||
exports.svgSanitizer = svgSanitizer; | ||
/*! version: 0.14.20 */ | ||
/*! version: 0.14.21 */ |
@@ -5,3 +5,3 @@ /*! | ||
import { ArrayConcat, ArrayFilter, ArrayIncludes, WeakMapCtor, WeakMapGet, MapForEach, WeakMapSet, MapCtor, SetCtor, StringStartsWith, StringReplace, SetHas, SetAdd, SetDelete, StringSplit, StringToLowerCase } from '@locker/shared'; | ||
import { DocumentCreateElement, ElementInnerHTMLSetter, HTMLTemplateElementContentGetter, ElementInnerHTMLGetter, NodeNameGetter, DocumentGetElementById, NodeOwnerDocumentGetter, DocumentCreateComment, ElementClosest, NodeClone, DocumentCreateElementNS, NodeAppendChild, ElementOuterHTMLGetter, NodeFirstChildGetter, WindowSetInterval, XhrCtor, EventTargetAddEventListener, XhrStatusGetter, XhrResponseTextGetter, DocumentFragmentGetElementById, ElementSetAttribute, XhrOpen, XhrSend, HTMLAnchorElementHrefSetter, HTMLAnchorElementHrefGetter, HTMLAnchorElementProtocolGetter, ElementQuerySelector, WindowClearInterval, DocumentBodyGetter } from '@locker/shared-dom'; | ||
import { DocumentCreateElement, ElementInnerHTMLSetter, HTMLTemplateElementContentGetter, ElementInnerHTMLGetter, ElementOuterHTMLGetter, DocumentDocumentElementGetter, DOMImplementationCreateDocument, DocumentImplementation, NAMESPACE_XHTML, NodeNameGetter, DocumentGetElementById, NodeOwnerDocumentGetter, DocumentCreateComment, DocumentCreateElementNS, NAMESPACE_SVG, ElementClosest, NodeClone, NodeAppendChild, NodeFirstChildGetter, WindowSetInterval, XhrCtor, EventTargetAddEventListener, XhrStatusGetter, XhrResponseTextGetter, DocumentFragmentGetElementById, ElementSetAttribute, XhrOpen, XhrSend, HTMLAnchorElementHrefSetter, HTMLAnchorElementHrefGetter, HTMLAnchorElementProtocolGetter, ElementQuerySelector, WindowClearInterval, DocumentBodyGetter } from '@locker/shared-dom'; | ||
import DOMPurify from 'dompurify'; | ||
@@ -66,3 +66,3 @@ const ariaAttributes = ['aria-activedescendant', 'aria-atomic', 'aria-autocomplete', 'aria-busy', 'aria-checked', 'aria-controls', 'aria-describedby', 'aria-disabled', 'aria-readonly', 'aria-dropeffect', 'aria-expanded', 'aria-flowto', 'aria-grabbed', 'aria-haspopup', 'aria-hidden', 'aria-disabled', 'aria-invalid', 'aria-label', 'aria-labelledby', 'aria-level', 'aria-live', 'aria-multiline', 'aria-multiselectable', 'aria-orientation', 'aria-owns', 'aria-posinset', 'aria-pressed', 'aria-readonly', 'aria-relevant', 'aria-required', 'aria-selected', 'aria-setsize', 'aria-sort', 'aria-valuemax', 'aria-valuemin', 'aria-valuenow', 'aria-valuetext', 'role', 'target']; | ||
const ATTRIBUTES = ['href', 'xlink:href']; | ||
const SANITIZER_HOOKS = new MapCtor([['uponSanitizeAttribute', sanitizeHrefAttributeHook]]); | ||
const SANITIZER_HOOKS = new MapCtor([['uponSanitizeAttribute', sanitizeHrefAttributeHook], ['uponSanitizeElement', allowCustomTagHook]]); | ||
const URL_SCHEMES = ['http:', 'https:']; | ||
@@ -77,4 +77,8 @@ const { | ||
const urlReplacer = /[^a-z0-9]+/gi; | ||
const urlReplacer = /[^a-z0-9]+/gi; // The Regex is based on the WHATWG spec: | ||
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name | ||
// However, DOMPurify sanitizes unicode characters (\u0000-\uFFFF) in tag name. | ||
const customTagRegex = /^[a-z]([-_.\w])*-([-.0-9_a-z\xB7\xC0-\xD6\xD8-\xF6\xF8-\u37D0\u37F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u10000-\uEFFFF])*/; | ||
function checkExistingAndDequeue(container, normalizedHref) { | ||
@@ -185,10 +189,22 @@ if (SetHas(queue, normalizedHref.normalizedUrl)) { | ||
return ElementInnerHTMLGetter(htmlTemplate); | ||
} | ||
function sanitizeDocument(doc) { | ||
const content = ElementOuterHTMLGetter(DocumentDocumentElementGetter(doc)); | ||
const contentSanitized = sanitize(content); | ||
const newDoc = DOMImplementationCreateDocument(DocumentImplementation(doc), NAMESPACE_XHTML, 'html'); | ||
ElementInnerHTMLSetter(DocumentDocumentElementGetter(newDoc), contentSanitized); | ||
return newDoc; | ||
} // Sanitize a URL representing a SVG href attribute value. | ||
function sanitizeHrefAttributeHook(node, data) { | ||
function sanitizeHrefAttributeHook(node, data, _config) { | ||
const { | ||
attrValue, | ||
attrName | ||
} = data; | ||
const nodeName = NodeNameGetter(node); | ||
if (data.attrValue && nodeName === 'USE' && ArrayIncludes(ATTRIBUTES, data.attrName)) { | ||
data.attrValue = sanitizeSvgHrefValue(data.attrValue); | ||
if (attrValue && nodeName === 'USE' && ArrayIncludes(ATTRIBUTES, attrName)) { | ||
data.attrValue = sanitizeSvgHrefValue(attrValue); | ||
} | ||
@@ -223,7 +239,19 @@ | ||
function sanitizeSvgInnerHtml(el, dirty) { | ||
const ownerDoc = NodeOwnerDocumentGetter(el); | ||
function createSvgContainer(ownerDoc) { | ||
return DocumentCreateElementNS(ownerDoc, NAMESPACE_SVG, 'svg'); | ||
} | ||
function sanitizeSvgInnerHtml(stringOrSvg, dirty = '') { | ||
let container; | ||
const ownerDoc = typeof stringOrSvg === 'string' ? document : NodeOwnerDocumentGetter(stringOrSvg); | ||
const comment = DocumentCreateComment(ownerDoc, ''); | ||
const closestSvg = ElementClosest(el, 'svg'); | ||
const container = closestSvg ? NodeClone(closestSvg, false) : DocumentCreateElementNS(ownerDoc, 'http://www.w3.org/2000/svg', 'svg'); | ||
if (typeof stringOrSvg === 'string') { | ||
dirty = stringOrSvg; | ||
container = createSvgContainer(ownerDoc); | ||
} else { | ||
const closestSvg = ElementClosest(stringOrSvg, 'svg'); | ||
container = closestSvg ? NodeClone(closestSvg, false) : createSvgContainer(ownerDoc); | ||
} | ||
NodeAppendChild(container, comment); | ||
@@ -242,3 +270,14 @@ const outerHTML = ElementOuterHTMLGetter(container); | ||
export { config as CONFIG, blobSanitizer, sanitize, sanitizeHrefAttributeHook, sanitizeSvgHrefValue, sanitizeSvgInnerHtml, sanitizeSvgTextReturnDOM, sanitizer, svgSanitizer }; | ||
/*! version: 0.14.20 */ | ||
function allowCustomTagHook(node, data, _config) { | ||
const { | ||
allowedTags, | ||
tagName | ||
} = data; | ||
if (!allowedTags[tagName] && customTagRegex.test(tagName)) { | ||
allowedTags[tagName] = true; | ||
} | ||
} | ||
export { config as CONFIG, allowCustomTagHook, blobSanitizer, sanitize, sanitizeDocument, sanitizeHrefAttributeHook, sanitizeSvgHrefValue, sanitizeSvgInnerHtml, sanitizeSvgTextReturnDOM, sanitizer, svgSanitizer }; | ||
/*! version: 0.14.21 */ |
{ | ||
"name": "@locker/html-sanitizer", | ||
"version": "0.14.20", | ||
"version": "0.14.21", | ||
"license": "Salesforce Developer Agreement", | ||
@@ -20,4 +20,4 @@ "author": "Salesforce UI Security Team", | ||
"dependencies": { | ||
"@locker/shared": "0.14.20", | ||
"@locker/shared-dom": "0.14.20", | ||
"@locker/shared": "0.14.21", | ||
"@locker/shared-dom": "0.14.21", | ||
"@types/dompurify": "2.2.2", | ||
@@ -30,3 +30,3 @@ "dompurify": "2.2.9" | ||
], | ||
"gitHead": "293789c383e41efa0b3f2a96e8330e61321e2956" | ||
"gitHead": "fc65225812f2c777feec425d009f8a46032939da" | ||
} |
@@ -1,2 +0,2 @@ | ||
import { SanitizeAttributeHookEvent } from 'dompurify'; | ||
import { Config, HookEvent } from 'dompurify'; | ||
import * as CONFIG from './config'; | ||
@@ -8,7 +8,9 @@ import { sanitizer as getSanitizerForConfig } from './dompurify-wrapper'; | ||
export declare function sanitize(dirty: string): string; | ||
export declare function sanitizeHrefAttributeHook(node: Node, data: SanitizeAttributeHookEvent): SanitizeAttributeHookEvent; | ||
export declare function sanitizeDocument(doc: Document): Document; | ||
export declare function sanitizeHrefAttributeHook(node: Node, data: HookEvent, _config: Config): HookEvent; | ||
export declare function sanitizeSvgHrefValue(url: string): string; | ||
export declare function sanitizeSvgInnerHtml(el: SVGElement, dirty: string): string; | ||
export declare function sanitizeSvgInnerHtml(stringOrSvg: string | SVGElement, dirty?: string): string; | ||
export declare function sanitizeSvgTextReturnDOM(dirty: string): DocumentFragment; | ||
export declare function allowCustomTagHook(node: Element, data: HookEvent, _config: Config): void; | ||
export * from './types'; | ||
//# sourceMappingURL=index.d.ts.map |
Sorry, the diff of this file is not supported yet
32724
519
+ Added@locker/shared@0.14.21(transitive)
+ Added@locker/shared-dom@0.14.21(transitive)
- Removed@locker/shared@0.14.20(transitive)
- Removed@locker/shared-dom@0.14.20(transitive)
Updated@locker/shared@0.14.21
Updated@locker/shared-dom@0.14.21