diff --git a/README.md b/README.md index e64d86d4d..627e55b75 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. -It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version **v3.0.8**. +It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version **v3.0.9**. DOMPurify is written in JavaScript and works in all modern browsers (Safari (10+), Opera (15+), Edge, Firefox and Chrome - as well as almost anything else using Blink, Gecko or WebKit). It doesn't break on MSIE or other legacy browsers. It simply does nothing. @@ -413,6 +413,6 @@ Many people helped and help DOMPurify become what it is and need to be acknowled ## Testing powered by -
+
And last but not least, thanks to [BrowserStack Open-Source Program](https://www.browserstack.com/open-source) for supporting this project with their services for free and delivering excellent, dedicated and very professional support on top of that. diff --git a/bower.json b/bower.json index 8468dcadc..1a50444f9 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "DOMPurify", - "version": "3.0.8", + "version": "3.0.9", "homepage": "https://github.com/cure53/DOMPurify", "author": "Cure53 ", "description": "A DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG", diff --git a/demos/README.md b/demos/README.md index e3580bb3f..5528d43ba 100644 --- a/demos/README.md +++ b/demos/README.md @@ -12,7 +12,7 @@ This is the relevant code: ```javascript // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty); +const clean = DOMPurify.sanitize(dirty); ``` ### Config Demo [Link](config-demo.html) @@ -24,10 +24,10 @@ This is the relevant code: ```javascript // Specify a configuration directive, only

elements allowed // Note: We want to also keep

's text content, so we add #text too -var config = { ALLOWED_TAGS: ['p', '#text'], KEEP_CONTENT: false }; +const config = { ALLOWED_TAGS: ['p', '#text'], KEEP_CONTENT: false }; // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty, config); +const clean = DOMPurify.sanitize(dirty, config); ``` ### Advanced Config Demo [Link](advanced-config-demo.html) @@ -38,7 +38,7 @@ This is the relevant code: ```javascript // Specify a configuration directive -var config = { +const config = { ALLOWED_TAGS: ['p', '#text'], // only

and text nodes KEEP_CONTENT: false, // remove content from non-allow-listed nodes too ADD_ATTR: ['kitty-litter'], // permit kitty-litter attributes @@ -47,7 +47,7 @@ var config = { }; // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty, config); +const clean = DOMPurify.sanitize(dirty, config); ``` ### Hooks Demo [Link](hooks-demo.html) @@ -66,7 +66,7 @@ DOMPurify.addHook('beforeSanitizeAttributes', function (node) { }); // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty); +const clean = DOMPurify.sanitize(dirty); ``` ### Add hooks and remove hooks [Link](hooks-removal-demo.html) @@ -85,13 +85,13 @@ DOMPurify.addHook('beforeSanitizeAttributes', function (node) { }); // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty); +let clean = DOMPurify.sanitize(dirty); // now let's remove the hook again console.log(DOMPurify.removeHook('beforeSanitizeAttributes')); // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty); +let clean = DOMPurify.sanitize(dirty); ``` ### Hook to open all links in a new window [Link](hooks-target-blank-demo.html) @@ -117,7 +117,7 @@ DOMPurify.addHook('afterSanitizeAttributes', function (node) { }); // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty); +const clean = DOMPurify.sanitize(dirty); ``` ### Hook to white-list safe URI Schemes [Link](hooks-scheme-allowlist.html) @@ -130,15 +130,15 @@ This is the relevant code: ```javascript // allowed URI schemes -var allowlist = ['http', 'https', 'ftp']; +const allowlist = ['http', 'https', 'ftp']; // build fitting regex -var regex = RegExp('^(' + allowlist.join('|') + '):', 'gim'); +const regex = RegExp('^(' + allowlist.join('|') + '):', 'gim'); // Add a hook to enforce URI scheme allow-list DOMPurify.addHook('afterSanitizeAttributes', function (node) { // build an anchor to map URLs to - var anchor = document.createElement('a'); + const anchor = document.createElement('a'); // check all href attributes for validity if (node.hasAttribute('href')) { @@ -164,7 +164,7 @@ DOMPurify.addHook('afterSanitizeAttributes', function (node) { }); // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty); +const clean = DOMPurify.sanitize(dirty); ``` ### Hook to allow and sand-box all JavaScript [Link](hooks-mentaljs-demo.html) @@ -177,7 +177,7 @@ This is the relevant code: ```javascript // allow script elements -var config = { +const config = { ADD_TAGS: ['script'], ADD_ATTR: ['onclick', 'onmouseover', 'onload', 'onunload'], }; @@ -185,7 +185,7 @@ var config = { // Add a hook to sanitize all script content with MentalJS DOMPurify.addHook('uponSanitizeElement', function (node, data) { if (data.tagName === 'script') { - var script = node.textContent; + let script = node.textContent; if ( !script || 'src' in node.attributes || @@ -195,7 +195,7 @@ DOMPurify.addHook('uponSanitizeElement', function (node, data) { return node.parentNode.removeChild(node); } try { - var mental = MentalJS().parse({ + let mental = MentalJS().parse({ options: { eval: false, dom: true, @@ -212,7 +212,7 @@ DOMPurify.addHook('uponSanitizeElement', function (node, data) { // Add a hook to sanitize all white-listed events with MentalJS DOMPurify.addHook('uponSanitizeAttribute', function (node, data) { if (data.attrName.match(/^on\w+/)) { - var script = data.attrValue; + let script = data.attrValue; try { return (data.attrValue = MentalJS().parse({ options: { @@ -228,7 +228,7 @@ DOMPurify.addHook('uponSanitizeAttribute', function (node, data) { }); // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty, config); +const clean = DOMPurify.sanitize(dirty, config); ``` ### Hook to proxy all links [Link](hooks-link-proxy-demo.html) @@ -264,7 +264,7 @@ DOMPurify.addHook('afterSanitizeAttributes', function (node) { }); // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty); +const clean = DOMPurify.sanitize(dirty); ``` ### Hook to proxy all HTTP leaks including CSS [Link](hooks-proxy-demo.html) @@ -277,28 +277,28 @@ This is the relevant code: ```javascript // Specify proxy URL -var proxy = 'https://my.proxy/?url='; +const proxy = 'https://my.proxy/?url='; // What do we allow? Not much for now. But it's tight. -var config = { +const config = { FORBID_TAGS: ['svg'], WHOLE_DOCUMENT: true, }; // Specify attributes to proxy -var attributes = ['action', 'background', 'href', 'poster', 'src', 'srcset'] +const attributes = ['action', 'background', 'href', 'poster', 'src', 'srcset'] // specify the regex to detect external content -var regex = /(url\("?)(?!data:)/gim; +const regex = /(url\("?)(?!data:)/gim; /** * Take CSS property-value pairs and proxy URLs in values, * then add the styles to an array of property-value pairs */ function addStyles(output, styles) { - for (var prop = styles.length - 1; prop >= 0; prop--) { + for (let prop = styles.length - 1; prop >= 0; prop--) { if (styles[styles[prop]]) { - var url = styles[styles[prop]].replace(regex, '$1' + proxy); + let url = styles[styles[prop]].replace(regex, '$1' + proxy); styles[styles[prop]] = url; } if (styles[styles[prop]]) { @@ -312,8 +312,8 @@ function addStyles(output, styles) { * then create matching CSS text for later application to the DOM */ function addCSSRules(output, cssRules) { - for (var index = cssRules.length - 1; index >= 0; index--) { - var rule = cssRules[index]; + for (let index = cssRules.length - 1; index >= 0; index--) { + let rule = cssRules[index]; // check for rules with selector if (rule.type == 1 && rule.selectorText) { output.push(rule.selectorText + '{'); @@ -336,8 +336,8 @@ function addCSSRules(output, cssRules) { // check for @keyframes rules } else if (rule.type === rule.KEYFRAMES_RULE) { output.push('@keyframes ' + rule.name + '{'); - for (var i = rule.cssRules.length - 1; i >= 0; i--) { - var frame = rule.cssRules[i]; + for (let i = rule.cssRules.length - 1; i >= 0; i--) { + let frame = rule.cssRules[i]; if (frame.type === 8 && frame.keyText) { output.push(frame.keyText + '{'); if (frame.style) { @@ -365,7 +365,7 @@ function proxyAttribute(url) { // Add a hook to enforce proxy for leaky CSS rules DOMPurify.addHook('uponSanitizeElement', function (node, data) { if (data.tagName === 'style') { - var output = []; + let output = []; addCSSRules(output, node.sheet.cssRules); node.textContent = output.join('\n'); } @@ -374,7 +374,7 @@ DOMPurify.addHook('uponSanitizeElement', function (node, data) { // Add a hook to enforce proxy for all HTTP leaks incl. inline CSS DOMPurify.addHook('afterSanitizeAttributes', function (node) { // Check all src attributes and proxy them - for (var i = 0; i <= attributes.length - 1; i++) { + for (let i = 0; i <= attributes.length - 1; i++) { if (node.hasAttribute(attributes[i])) { node.setAttribute( attributes[i], @@ -385,12 +385,12 @@ DOMPurify.addHook('afterSanitizeAttributes', function (node) { // Check all style attribute values and proxy them if (node.hasAttribute('style')) { - var styles = node.style; - var output = []; - for (var prop = styles.length - 1; prop >= 0; prop--) { + let styles = node.style; + let output = []; + for (let prop = styles.length - 1; prop >= 0; prop--) { // we re-write each property-value pair to remove invalid CSS if (node.style[styles[prop]] && regex.test(node.style[styles[prop]])) { - var url = node.style[styles[prop]].replace(regex, '$1' + proxy); + let url = node.style[styles[prop]].replace(regex, '$1' + proxy); node.style[styles[prop]] = url; } output.push(styles[prop] + ':' + node.style[styles[prop]] + ';'); @@ -405,7 +405,7 @@ DOMPurify.addHook('afterSanitizeAttributes', function (node) { }); // Clean HTML string and write into our DIV -var clean = DOMPurify.sanitize(dirty, config); +const clean = DOMPurify.sanitize(dirty, config); ``` ### Hook to sanitize SVGs shown via an `` tag. [Link](hooks-svg-demo.html) @@ -423,14 +423,14 @@ DOMPurify.addHook('afterSanitizeAttributes', function (node) { }); // Clean SVG string and allow the "filter" tag -var clean = DOMPurify.sanitize(dirty, { ADD_TAGS: ['filter'] }); +const clean = DOMPurify.sanitize(dirty, { ADD_TAGS: ['filter'] }); // Remove partial XML comment left in the HTML -var badTag = clean.indexOf(']>'); -var pureSvg = clean.substring(badTag < 0 ? 0 : 5, clean.length); +let badTag = clean.indexOf(']>'); +let pureSvg = clean.substring(badTag < 0 ? 0 : 5, clean.length); // Show sanitized content in element -var img = new Image(); +let img = new Image(); img.src = 'data:image/svg+xml;base64,' + window.btoa(pureSvg); document.getElementById('sanitized').appendChild(img); ``` diff --git a/demos/advanced-config-demo.html b/demos/advanced-config-demo.html index 92b29d55b..9009f244e 100644 --- a/demos/advanced-config-demo.html +++ b/demos/advanced-config-demo.html @@ -9,28 +9,32 @@ diff --git a/demos/basic-demo.html b/demos/basic-demo.html index b7577b14f..e5b02cadf 100644 --- a/demos/basic-demo.html +++ b/demos/basic-demo.html @@ -9,15 +9,16 @@ diff --git a/demos/config-demo.html b/demos/config-demo.html index f84f901d8..c56e0caee 100644 --- a/demos/config-demo.html +++ b/demos/config-demo.html @@ -9,20 +9,23 @@ diff --git a/demos/hooks-demo.html b/demos/hooks-demo.html index bd6c099c3..15ab3cc20 100644 --- a/demos/hooks-demo.html +++ b/demos/hooks-demo.html @@ -9,23 +9,24 @@ diff --git a/demos/hooks-link-proxy-demo.html b/demos/hooks-link-proxy-demo.html index 02d0ed188..7a7019e5b 100644 --- a/demos/hooks-link-proxy-demo.html +++ b/demos/hooks-link-proxy-demo.html @@ -9,44 +9,42 @@ diff --git a/demos/hooks-mentaljs-demo.html b/demos/hooks-mentaljs-demo.html index e951316f4..5f4ddd730 100644 --- a/demos/hooks-mentaljs-demo.html +++ b/demos/hooks-mentaljs-demo.html @@ -11,84 +11,73 @@

diff --git a/demos/hooks-node-removal-demo.html b/demos/hooks-node-removal-demo.html index 012312b90..c3cef45b9 100644 --- a/demos/hooks-node-removal-demo.html +++ b/demos/hooks-node-removal-demo.html @@ -9,23 +9,21 @@ diff --git a/demos/hooks-node-removal2-demo.html b/demos/hooks-node-removal2-demo.html index f222c6ce7..1350f910e 100644 --- a/demos/hooks-node-removal2-demo.html +++ b/demos/hooks-node-removal2-demo.html @@ -9,53 +9,49 @@ diff --git a/demos/hooks-proxy-demo.html b/demos/hooks-proxy-demo.html index 17076ce1b..d6f976863 100644 --- a/demos/hooks-proxy-demo.html +++ b/demos/hooks-proxy-demo.html @@ -9,147 +9,100 @@ diff --git a/demos/hooks-removal-demo.html b/demos/hooks-removal-demo.html index 1134c7303..da9675a2b 100644 --- a/demos/hooks-removal-demo.html +++ b/demos/hooks-removal-demo.html @@ -9,90 +9,53 @@ diff --git a/demos/hooks-sanitize-css-demo.html b/demos/hooks-sanitize-css-demo.html index 5ebd354c5..f01cb6486 100644 --- a/demos/hooks-sanitize-css-demo.html +++ b/demos/hooks-sanitize-css-demo.html @@ -13,21 +13,21 @@ /* global DOMPurify */ 'use strict'; window.onload = function(){ - + // Specify dirty HTML - var dirty = document.getElementById('payload').value; + let dirty = document.getElementById('payload').value; // We can allow all (default elements) but SVG - var config = { + const config = { FORBID_TAGS: ['svg'] // SVG is not yet supported. Too messy. }; - // Specify CSS property allow-list - var allowed_properties = [ - 'color', + // Specify CSS property whitelist + const allowed_properties = [ + 'color', 'background', - 'border', - 'padding', + 'border', + 'padding', 'margin', 'font-family', 'content', @@ -35,48 +35,47 @@ ]; // Specify if CSS functions are permitted - var allow_css_functions = true; + const allow_css_functions = true; /** - * Take CSS property-value pairs and validate against allow-list, + * Take CSS property-value pairs and validate against white-list, * then add the styles to an array of property-value pairs */ function validateStyles(output, styles) { - // Validate regular CSS properties - for (var prop in styles) { - if (typeof styles[prop] === 'string') { - if (styles[prop] && allowed_properties.indexOf(prop) > -1) { - if (allow_css_functions || !/\w+\(/.test(styles[prop])) { - output.push(prop + ':' + styles[prop] +';'); - } + Object.keys(styles).forEach(prop => { + const value = styles[prop]; + if (value && typeof value === 'string') { + const normalizedProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase(); + if (allowed_properties.includes(normalizedProp) && + (allow_css_functions || !/\w+\(/.test(value))) { + output.push(`${normalizedProp}:${value};`); } } - } + }); } /** * Take CSS rules and analyze them, create string wrapper to - * apply them to the DOM later on. Note that only selector rules + * apply them to the DOM later on. Note that only selector rules * are supported right now */ function addCSSRules(output, cssRules) { - for (var index = cssRules.length-1; index >= 0; index--) { - var rule = cssRules[index]; + Array.from(cssRules).reverse().forEach(rule => { // check for rules with selector - if (rule.type == 1 && rule.selectorText) { - output.push(rule.selectorText + '{') + if (rule.type === 1 && rule.selectorText) { + output.push(`${rule.selectorText}{`); if (rule.style) { - validateStyles(output, rule.style) + validateStyles(output, rule.style); } output.push('}'); } - } + }); } // Add a hook to enforce CSS element sanitization DOMPurify.addHook('uponSanitizeElement', function(node, data) { if (data.tagName === 'style') { - var output = []; + let output = []; addCSSRules(output, node.sheet.cssRules); node.textContent = output.join("\n"); } @@ -86,13 +85,13 @@ DOMPurify.addHook('afterSanitizeAttributes', function(node) { // Nasty hack to fix baseURI + CSS problems in Chrome if (!node.ownerDocument.baseURI) { - var base = document.createElement('base'); + let base = document.createElement('base'); base.href = document.baseURI; node.ownerDocument.head.appendChild(base); } // Check all style attribute values and validate them if (node.hasAttribute('style')) { - var output = []; + let output = []; validateStyles(output, node.style); // re-add styles in case any are left if (output.length) { @@ -104,7 +103,7 @@ }); // Clean HTML string and write into our DIV - var clean = DOMPurify.sanitize(dirty, config); + let clean = DOMPurify.sanitize(dirty, config); document.getElementById('sanitized').innerHTML = clean; } diff --git a/demos/hooks-scheme-allowlist.html b/demos/hooks-scheme-allowlist.html index 9f78fa1f3..8eb7411fc 100644 --- a/demos/hooks-scheme-allowlist.html +++ b/demos/hooks-scheme-allowlist.html @@ -9,68 +9,69 @@ diff --git a/demos/hooks-svg-demo.html b/demos/hooks-svg-demo.html deleted file mode 100644 index efb7dfd13..000000000 --- a/demos/hooks-svg-demo.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - -
- - - - - diff --git a/demos/hooks-target-blank-demo.html b/demos/hooks-target-blank-demo.html index b87fd38a9..cfa64efcf 100644 --- a/demos/hooks-target-blank-demo.html +++ b/demos/hooks-target-blank-demo.html @@ -9,42 +9,35 @@ diff --git a/demos/trusted-types-demo.html b/demos/trusted-types-demo.html index 20b566d78..f90004957 100644 --- a/demos/trusted-types-demo.html +++ b/demos/trusted-types-demo.html @@ -2,7 +2,6 @@ -

'; + const config = { + CUSTOM_ELEMENT_HANDLING: { tagNameCheck: /.*/ }, + FORBID_CONTENTS: [""] + }; + const expected = ''; + let clean = DOMPurify.sanitize(dirty, config); + assert.contains(clean, expected); + }); }; }); diff --git a/website/index.html b/website/index.html index 61b34668b..4879c3bf5 100644 --- a/website/index.html +++ b/website/index.html @@ -2,7 +2,7 @@ - DOMPurify 3.0.8 "Higher Noon" + DOMPurify 3.0.9 "Waterfront" @@ -23,7 +23,7 @@ -

DOMPurify 3.0.8 "Higher Noon"

+

DOMPurify 3.0.9 "Waterfront"

npm version Build and Test