dompurify
Advanced tools
Comparing version 0.6.7 to 0.7.0
{ | ||
"name": "DOMPurify", | ||
"version": "0.6.7", | ||
"version": "0.7.0", | ||
"homepage": "https://github.com/cure53/DOMPurify", | ||
@@ -5,0 +5,0 @@ "author": "Cure53 <info@cure53.de>", |
@@ -57,6 +57,6 @@ ## What is this? | ||
// Add a hook to convert all text to capitals | ||
DOMPurify.addHook('beforeSanitizeAttributes', function(node){ | ||
DOMPurify.addHook('beforeSanitizeAttributes', function(node) { | ||
// Set text node content to uppercase | ||
if(node.nodeName && node.nodeName === '#text') { | ||
node.textContent=node.textContent.toUpperCase(); | ||
if (node.nodeName && node.nodeName === '#text') { | ||
node.textContent = node.textContent.toUpperCase(); | ||
} | ||
@@ -76,6 +76,6 @@ }); | ||
// Add a hook to convert all text to capitals | ||
DOMPurify.addHook('beforeSanitizeAttributes', function(node){ | ||
DOMPurify.addHook('beforeSanitizeAttributes', function(node) { | ||
// Set text node content to uppercase | ||
if(node.nodeName && node.nodeName === '#text') { | ||
node.textContent=node.textContent.toUpperCase(); | ||
if (node.nodeName && node.nodeName === '#text') { | ||
node.textContent = node.textContent.toUpperCase(); | ||
} | ||
@@ -101,11 +101,11 @@ }); | ||
// Add a hook to make all links open a new window | ||
DOMPurify.addHook('afterSanitizeAttributes', function(node){ | ||
DOMPurify.addHook('afterSanitizeAttributes', function(node) { | ||
// set all elements owning target to target=_blank | ||
if('target' in node){ | ||
if ('target' in node) { | ||
node.setAttribute('target','_blank'); | ||
} | ||
// set non-HTML/MathML links to xlink:show=new | ||
if(!node.hasAttribute('target') | ||
if (!node.hasAttribute('target') | ||
&& (node.hasAttribute('xlink:href') | ||
|| node.hasAttribute('href'))){ | ||
|| node.hasAttribute('href'))) { | ||
node.setAttribute('xlink:show', 'new'); | ||
@@ -131,6 +131,6 @@ } | ||
// build fitting regex | ||
var regex = RegExp('^('+whitelist.join('|')+'):', 'gim'); | ||
var regex = RegExp('^(' + whitelist.join('|') + '):', 'gim'); | ||
// Add a hook to enforce URI scheme whitelist | ||
DOMPurify.addHook('afterSanitizeAttributes', function(node){ | ||
DOMPurify.addHook('afterSanitizeAttributes', function(node) { | ||
@@ -141,5 +141,5 @@ // build an anchor to map URLs to | ||
// check all href attributes for validity | ||
if(node.hasAttribute('href')){ | ||
if (node.hasAttribute('href')) { | ||
anchor.href = node.getAttribute('href'); | ||
if(anchor.protocol && !anchor.protocol.match(regex)){ | ||
if (anchor.protocol && !anchor.protocol.match(regex)) { | ||
node.removeAttribute('href'); | ||
@@ -149,5 +149,5 @@ } | ||
// check all action attributes for validity | ||
if(node.hasAttribute('action')){ | ||
if (node.hasAttribute('action')) { | ||
anchor.href = node.getAttribute('action'); | ||
if(anchor.protocol && !anchor.protocol.match(regex)){ | ||
if (anchor.protocol && !anchor.protocol.match(regex)) { | ||
node.removeAttribute('action'); | ||
@@ -157,5 +157,5 @@ } | ||
// check all xlink:href attributes for validity | ||
if(node.hasAttribute('xlink:href')){ | ||
if (node.hasAttribute('xlink:href')) { | ||
anchor.href = node.getAttribute('xlink:href'); | ||
if(anchor.protocol && !anchor.protocol.match(regex)){ | ||
if (anchor.protocol && !anchor.protocol.match(regex)) { | ||
node.removeAttribute('xlink:href'); | ||
@@ -185,8 +185,8 @@ } | ||
// Add a hook to sanitize all script content with MentalJS | ||
DOMPurify.addHook('uponSanitizeElement', function(node, data){ | ||
if(data.tagName === 'script'){ | ||
DOMPurify.addHook('uponSanitizeElement', function(node, data) { | ||
if (data.tagName === 'script') { | ||
var script = node.textContent; | ||
if(!script || 'src' in node.attributes | ||
if (!script || 'src' in node.attributes | ||
|| 'href' in node.attributes | ||
|| 'xlink:href' in node.attributes){ | ||
|| 'xlink:href' in node.attributes) { | ||
return node.parentNode.removeChild(node) | ||
@@ -196,3 +196,9 @@ } | ||
var mental = MentalJS().parse( | ||
{options:{eval:false, dom:true}, code:script} | ||
{ | ||
options: { | ||
eval: false, | ||
dom:true | ||
}, | ||
code:script | ||
} | ||
); | ||
@@ -207,8 +213,14 @@ return node.textContent = mental; | ||
// Add a hook to sanitize all white-listed events with MentalJS | ||
DOMPurify.addHook('uponSanitizeAttribute', function(node, data){ | ||
if(data.attrName.match(/^on\w+/)) { | ||
DOMPurify.addHook('uponSanitizeAttribute', function(node, data) { | ||
if (data.attrName.match(/^on\w+/)) { | ||
var script = data.attrValue; | ||
try { | ||
return data.attrValue = MentalJS().parse( | ||
{options:{eval:false, dom:true}, code:script} | ||
{ | ||
options: { | ||
eval: false, | ||
dom:true | ||
}, | ||
code:script | ||
} | ||
); | ||
@@ -232,5 +244,5 @@ } catch(e) { | ||
// Add a hook to make all links point to a proxy | ||
DOMPurify.addHook('afterSanitizeAttributes', function(node){ | ||
DOMPurify.addHook('afterSanitizeAttributes', function(node) { | ||
// proxy form actions | ||
if('action' in node){ | ||
if ('action' in node) { | ||
node.setAttribute('action', proxy | ||
@@ -240,3 +252,3 @@ + encodeURIComponent(node.getAttribute('action'))); | ||
// proxy regular HTML links | ||
if(node.hasAttribute('href')){ | ||
if (node.hasAttribute('href')) { | ||
node.setAttribute('href', proxy | ||
@@ -246,3 +258,3 @@ + encodeURIComponent(node.getAttribute('href'))); | ||
// proxy SVG/MathML links | ||
if(node.hasAttribute('xlink:href')){ | ||
if (node.hasAttribute('xlink:href')) { | ||
node.setAttribute('xlink:href', proxy | ||
@@ -285,9 +297,9 @@ + encodeURIComponent(node.getAttribute('xlink:href'))); | ||
function addStyles(output, styles) { | ||
for (var prop=styles.length-1; prop>=0; prop--) { | ||
if(styles[styles[prop]]){ | ||
var url = styles[styles[prop]].replace(regex, '$1'+proxy); | ||
styles[styles[prop]]=url; | ||
for (var prop = styles.length-1; prop >= 0; prop--) { | ||
if (styles[styles[prop]]) { | ||
var url = styles[styles[prop]].replace(regex, '$1' + proxy); | ||
styles[styles[prop]] = url; | ||
} | ||
if(styles[styles[prop]]) { | ||
output.push(styles[prop]+':'+styles[styles[prop]]+';'); | ||
if (styles[styles[prop]]) { | ||
output.push(styles[prop] + ':' + styles[styles[prop]] + ';'); | ||
} | ||
@@ -302,6 +314,6 @@ } | ||
function addCSSRules(output, cssRules) { | ||
for (var index=cssRules.length-1; index>=0; index--) { | ||
for (var index = cssRules.length-1; index >= 0; index--) { | ||
var rule = cssRules[index]; | ||
// check for rules with selector | ||
if (rule.type == 1 && rule.selectorText){ | ||
if (rule.type == 1 && rule.selectorText) { | ||
output.push(rule.selectorText + '{') | ||
@@ -327,3 +339,3 @@ if (rule.style) { | ||
output.push('@keyframes ' + rule.name + '{'); | ||
for (var i=rule.cssRules.length-1;i>=0;i--) { | ||
for (var i=rule.cssRules.length-1; i>=0; i--) { | ||
var frame = rule.cssRules[i]; | ||
@@ -355,3 +367,3 @@ if (frame.type === 8 && frame.keyText) { | ||
// Add a hook to enforce proxy for leaky CSS rules | ||
DOMPurify.addHook('uponSanitizeElement', function (node, data) { | ||
DOMPurify.addHook('uponSanitizeElement', function(node, data) { | ||
if (data.tagName === 'style') { | ||
@@ -365,9 +377,9 @@ var output = []; | ||
// Add a hook to enforce proxy for all HTTP leaks incl. inline CSS | ||
DOMPurify.addHook('afterSanitizeAttributes', function(node){ | ||
DOMPurify.addHook('afterSanitizeAttributes', function(node) { | ||
// Check all src attributes and proxy them | ||
for(var i = 0; i<=attributes.length-1; i++){ | ||
if(node.hasAttribute(attributes[i])){ | ||
for(var i = 0; i <= attributes.length-1; i++) { | ||
if (node.hasAttribute(attributes[i])) { | ||
node.setAttribute(attributes[i], proxyAttribute( | ||
node.getAttribute(attributes[i])) | ||
node.getAttribute(attributes[i])) | ||
); | ||
@@ -378,15 +390,15 @@ } | ||
// Check all style attribute values and proxy them | ||
if(node.hasAttribute('style')){ | ||
if (node.hasAttribute('style')) { | ||
var styles = node.style; | ||
var output = []; | ||
for(var prop=styles.length-1; prop>=0; prop--) { | ||
for(var 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) | ||
node.style[styles[prop]]=url; | ||
if (node.style[styles[prop]] && regex.test(node.style[styles[prop]])) { | ||
var url = node.style[styles[prop]].replace(regex, '$1' + proxy) | ||
node.style[styles[prop]] = url; | ||
} | ||
output.push(styles[prop]+':'+node.style[styles[prop]]+';'); | ||
output.push(styles[prop] + ':' + node.style[styles[prop]] + ';'); | ||
} | ||
// re-add styles in case any are left | ||
if(output.length) { | ||
if (output.length) { | ||
node.setAttribute('style', output.join("")); | ||
@@ -393,0 +405,0 @@ } else { |
@@ -6,4 +6,13 @@ { | ||
"jshint": "node node_modules/jshint/bin/jshint src/purify.js || true", | ||
"test": "npm run jshint;./node_modules/.bin/karma start test/karma.conf.js --single-run" | ||
"minify": "scripts/minify.sh", | ||
"amend-minified": "scripts/amend-minified.sh", | ||
"test": "npm run jshint;./node_modules/.bin/karma start test/karma.conf.js --single-run", | ||
"local-test": "npm run jshint;./node_modules/.bin/karma start test/karma.conf.js --browsers Chrome --single-run" | ||
}, | ||
"pre-commit": [ | ||
"jshint", | ||
"minify", | ||
"local-test", | ||
"amend-minified" | ||
], | ||
"devDependencies": { | ||
@@ -19,6 +28,9 @@ "jshint": "^2.4.4", | ||
"karma-json-fixtures-preprocessor": "0.0.5", | ||
"karma-mocha-reporter": "^1.1.1", | ||
"karma-qunit": "^0.1.5", | ||
"karma-webpack": "^1.7.0", | ||
"pre-commit": "^1.1.1", | ||
"qunit-parameterize": "^0.4.0", | ||
"qunitjs": "^1.14.0", | ||
"uglify-js": "^2.4.24", | ||
"webpack": "^1.12.1" | ||
@@ -28,4 +40,4 @@ }, | ||
"description": "DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. It's written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Firefox and Chrome - as well as almost anything else using Blink or WebKit). DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not.", | ||
"version": "0.6.7", | ||
"main": "purify.js", | ||
"version": "0.7.0", | ||
"main": "src/purify.js", | ||
"directories": { | ||
@@ -32,0 +44,0 @@ "test": "test" |
@@ -7,3 +7,3 @@ # DOMPurify [![Bower version](https://badge.fury.io/bo/dompurify.svg)](http://badge.fury.io/bo/dompurify) · [![npm version](https://badge.fury.io/js/dompurify.svg)](http://badge.fury.io/js/dompurify) · [![Build Status](https://travis-ci.org/cure53/DOMPurify.svg?branch=master)](https://travis-ci.org/cure53/DOMPurify) | ||
It's written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Spartan, Firefox and Chrome - as well as almost anything else using Blink or WebKit). It doesn't break on IE6 or other legacy browsers. It simply does nothing there. | ||
It's written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Spartan, Firefox and Chrome - as well as almost anything else using Blink or WebKit). It doesn't break on IE6 or other legacy browsers. It simply does nothing there. Our automated tests cover 8 different browsers right now. | ||
@@ -16,3 +16,2 @@ DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not. For more details please also read about our [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model) | ||
## How do I use it? | ||
@@ -22,6 +21,14 @@ | ||
### Using the unminified development version | ||
```html | ||
<script type="text/javascript" src="purify.js"></script> | ||
<script type="text/javascript" src="src/purify.js"></script> | ||
``` | ||
### Using the minified and tested production version (source-map available) | ||
```html | ||
<script type="text/javascript" src="dist/purify.min.js"></script> | ||
``` | ||
Afterwards you can sanitize strings by executing the following code: | ||
@@ -33,2 +40,4 @@ | ||
The resulting HTML can be written into a DOM element using `innerHTML` or the DOM using `document.write()`. That is fully up to you. But keep in mind, if you use the sanitized HTML with jQuery's very insecure `elm.html()` method, then the `SAFE_FOR_JQUERY` flag has to be set to make sure it's safe! Other than that, all is fine. | ||
If you're using an [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD) module loader like [Require.js](http://requirejs.org/), you can load this script asynchronously as well: | ||
@@ -53,3 +62,2 @@ | ||
## Is there a demo? | ||
@@ -72,3 +80,2 @@ | ||
DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math></math> | ||
DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // becomes <table><tbody><tr><td>HELLO</td></tr></tbody></table> | ||
@@ -161,8 +168,7 @@ DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><li><a href="//google.com">click</a></li></ul> | ||
## Continuous Integration | ||
## Unit tests | ||
We are currently using Travis CI in combination with BrowserStack. This gives us the possibility to confirm for each and every commit that all is going according to plan in all supported browsers. Check out the build logs here: https://travis-ci.org/cure53/DOMPurify | ||
To run the test suite, you need [Node.js](http://nodejs.org/download/) first. Install the dependencies with `npm install`, then start the test server with `npm test`. You can run the tests in your browser from **http://localhost:8000/test/**. | ||
## Security Mailing List | ||
@@ -177,3 +183,3 @@ | ||
We recently implemented a Hook-API allowing developers to create their own DOMPurify plugins and customize its functionality without changing the core. Thus, we are looking forward for plugins and extensions - pull requests are welcome! | ||
We recently implemented a Hook-API allowing developers to create their own DOMPurify plugins and customize its functionality without changing the core. Thus, we are looking forward for plugins and extensions - pull requests are welcome! Oh, and we will increase the amount of browsers and HTML-mappings in our automates tests to make sure nothing slips through. | ||
@@ -180,0 +186,0 @@ ## Who contributed? |
@@ -24,3 +24,3 @@ ;(function(factory) { | ||
*/ | ||
DOMPurify.version = '0.6.7'; | ||
DOMPurify.version = '0.7.0'; | ||
@@ -53,2 +53,3 @@ if (!window || !window.document || window.document.nodeType !== 9) { | ||
} | ||
var implementation = document.implementation; | ||
var createNodeIterator = document.createNodeIterator; | ||
@@ -65,3 +66,4 @@ var getElementsByTagName = document.getElementsByTagName; | ||
DOMPurify.isSupported = | ||
typeof DOMParser !== 'undefined' && document.documentMode !== 9; | ||
typeof implementation.createHTMLDocument !== 'undefined' && | ||
document.documentMode !== 9; | ||
@@ -307,9 +309,25 @@ /* Add properties to a lookup table */ | ||
var _initDocument = function(dirty) { | ||
/* Create a HTML document using DOMParser */ | ||
var doc = new DOMParser().parseFromString(dirty, "text/html"); | ||
var doc, body; | ||
try { | ||
doc = new DOMParser().parseFromString(dirty, "text/html"); | ||
} catch (e) {} | ||
/* Some browsers throw, some browsers return null for the code above | ||
DOMParser with text/html support is only in very recent browsers. */ | ||
if (!doc){ | ||
doc = implementation.createHTMLDocument(''); | ||
body = doc.body; | ||
body.parentNode.removeChild(body.parentNode.firstElementChild); | ||
body.outerHTML = dirty; | ||
} | ||
/* Work on whole document or just its body */ | ||
return getElementsByTagName.call(doc, | ||
WHOLE_DOCUMENT ? 'html' : 'body')[0]; | ||
if (typeof doc.getElementsByTagName === 'function'){ | ||
return doc.getElementsByTagName( | ||
WHOLE_DOCUMENT ? 'html' : 'body')[0]; | ||
} else { | ||
return getElementsByTagName.call(doc, | ||
WHOLE_DOCUMENT ? 'html' : 'body')[0]; | ||
} | ||
}; | ||
@@ -429,8 +447,8 @@ | ||
var hookEvent = { | ||
attrName: '', | ||
attrValue: '', | ||
keepAttr: true | ||
}, | ||
l = attributes.length, | ||
attr, name, value, lcName, idAttr; | ||
attrName: '', | ||
attrValue: '', | ||
keepAttr: true | ||
}; | ||
var l = attributes.length; | ||
var attr, name, value, lcName, idAttr; | ||
@@ -570,2 +588,6 @@ /* Go backwards over all attributes; safely remove bad ones */ | ||
DOMPurify.sanitize = function(dirty, cfg) { | ||
if (!dirty) { | ||
dirty = ''; | ||
} | ||
/* Check we can run. Otherwise fall back or ignore */ | ||
@@ -572,0 +594,0 @@ if (!DOMPurify.isSupported) { |
@@ -9,2 +9,3 @@ module.exports = function(config) { | ||
'node_modules/qunit-parameterize/qunit-parameterize.js', | ||
'test/config/setup.js', | ||
'test/**/*.spec.js' | ||
@@ -18,3 +19,3 @@ ], | ||
reporters: ['progress'], | ||
reporters: ['mocha'], | ||
@@ -37,3 +38,4 @@ exclude: [], | ||
'test', | ||
'src' | ||
'src', | ||
'dist' | ||
], | ||
@@ -84,3 +86,3 @@ extensions: ['', '.js', '.json'] | ||
}, | ||
bs_yosemite_safari8: { | ||
bs_yosemite_safari_8: { | ||
base: 'BrowserStack', | ||
@@ -93,3 +95,3 @@ device: null, | ||
}, | ||
bs_win81_opera31: { | ||
bs_win81_opera_31: { | ||
base: 'BrowserStack', | ||
@@ -101,2 +103,18 @@ device: null, | ||
os_version: '8.1' | ||
}, | ||
bs_win7_firefox_12: { | ||
base: 'BrowserStack', | ||
device: null, | ||
os: 'Windows', | ||
browser_version: '12.0', | ||
browser: 'firefox', | ||
os_version: '7' | ||
}, | ||
bs_win81_chrome_22: { | ||
base: 'BrowserStack', | ||
device: null, | ||
os: 'Windows', | ||
browser_version: '22.0', | ||
browser: 'chrome', | ||
os_version: '8.1' | ||
} | ||
@@ -110,3 +128,6 @@ }, | ||
'bs_yosemite_firefox_40', | ||
'bs_win81_opera31' | ||
'bs_yosemite_safari_8', | ||
'bs_win81_opera_31', | ||
'bs_win7_firefox_12', | ||
'bs_win81_chrome_22' | ||
], | ||
@@ -116,2 +137,3 @@ | ||
'karma-webpack', | ||
'karma-mocha-reporter', | ||
'karma-chrome-launcher', | ||
@@ -118,0 +140,0 @@ 'karma-browserstack-launcher', |
var | ||
DOMPurify = require('purify'), | ||
testSuite = require('test-suite'), | ||
tests = require('fixtures/expect'), | ||
@@ -8,218 +9,3 @@ xssTests = tests.filter( function( element ) { | ||
window.alert = function() { | ||
window.xssed = true; | ||
}; | ||
QUnit.assert.contains = function( needle, haystack, message ) { | ||
var result = haystack.indexOf(needle) > -1; | ||
QUnit.push(result, needle, haystack, message); | ||
}; | ||
QUnit.module( "DOMPurifyr", { | ||
beforeEach: function() { | ||
} | ||
}); | ||
QUnit | ||
.cases(tests) | ||
.test( 'Sanitization test', function(params, assert) { | ||
assert.contains( DOMPurify.sanitize( params.payload ), params.expected, 'Payload: ' + params.payload); | ||
}); | ||
// Config-Flag Tests | ||
QUnit.test( 'Config-Flag tests: KEEP_CONTENT + ALLOWED_TAGS / ALLOWED_ATTR', function(assert) { | ||
// KEEP_CONTENT + ALLOWED_TAGS / ALLOWED_ATTR | ||
assert.equal( DOMPurify.sanitize( '<iframe>Hello</iframe>', {KEEP_CONTENT: false} ), ""); | ||
assert.contains( DOMPurify.sanitize( '<a href="#">abc<b style="color:red">123</b><q class="cite">123</b></a>', {ALLOWED_TAGS: ['b', 'q'], ALLOWED_ATTR: ['style'], KEEP_CONTENT: true}), | ||
["abc<b style=\"color:red\">123</b><q>123</q>", "abc<b style=\"color: red;\">123</b><q>123</q>"] | ||
); | ||
assert.equal( DOMPurify.sanitize( '<a href="#">abc<b style="color:red">123</b><q class="cite">123</b></a>', {ALLOWED_TAGS: ['b', 'q'], ALLOWED_ATTR: ['style'], KEEP_CONTENT: false}), ""); | ||
assert.equal( DOMPurify.sanitize( '<a href="#">abc</a>', {ALLOWED_TAGS: ['b', 'q'], KEEP_CONTENT: false}), ""); | ||
assert.equal( DOMPurify.sanitize( '<form><input name="parentNode"></form>', {ALLOWED_TAGS: ['input'], KEEP_CONTENT: true}), "<input>" ); | ||
}); | ||
QUnit.test( 'Config-Flag tests: ALLOW_DATA_ATTR', function(assert) { | ||
// ALLOW_DATA_ATTR | ||
assert.equal( DOMPurify.sanitize( '<a href="#" data-abc\"="foo">abc</a>', {ALLOW_DATA_ATTR: true}), "<a href=\"#\">abc</a>" ); | ||
assert.equal( DOMPurify.sanitize( '<a href="#" data-abc="foo">abc</a>', {ALLOW_DATA_ATTR: false}), "<a href=\"#\">abc</a>" ); | ||
assert.contains( DOMPurify.sanitize( '<a href="#" data-abc="foo">abc</a>', {ALLOW_DATA_ATTR: true}), | ||
["<a data-abc=\"foo\" href=\"#\">abc</a>", "<a href=\"#\" data-abc=\"foo\">abc</a>"] | ||
); | ||
assert.contains( DOMPurify.sanitize( '<a href="#" data-abc-1-2-3="foo">abc</a>', {ALLOW_DATA_ATTR: true}), | ||
["<a data-abc-1-2-3=\"foo\" href=\"#\">abc</a>", "<a href=\"#\" data-abc-1-2-3=\"foo\">abc</a>"] | ||
); | ||
assert.equal( DOMPurify.sanitize( '<a href="#" data-""="foo">abc</a>', {ALLOW_DATA_ATTR: true}), "<a href=\"#\">abc</a>" ); | ||
assert.contains( DOMPurify.sanitize( '<a href="#" data-äöü="foo">abc</a>', {ALLOW_DATA_ATTR: true}), | ||
["<a href=\"#\" data-äöü=\"foo\">abc</a>", "<a data-äöü=\"foo\" href=\"#\">abc</a>"] | ||
); | ||
assert.contains( DOMPurify.sanitize( '<a href="#" data-\u00B7._="foo">abc</a>', {ALLOW_DATA_ATTR: true}), | ||
["<a data-\u00B7._=\"foo\" href=\"#\">abc</a>", "<a href=\"#\">abc</a>"] // IE11 and Edge throw an InvalidCharacterError | ||
); | ||
assert.equal( DOMPurify.sanitize( '<a href="#" data-\u00B5="foo">abc</a>', {ALLOW_DATA_ATTR: true}), "<a href=\"#\">abc</a>" ); | ||
}); | ||
QUnit.test( 'Config-Flag tests: ADD_TAGS', function(assert) { | ||
// ADD_TAGS | ||
assert.equal( DOMPurify.sanitize( '<my-component>abc</my-component>', {ADD_TAGS: ['my-component']}), "<my-component>abc</my-component>" ); | ||
assert.equal( DOMPurify.sanitize( '<my-ĸompønent>abc</my-ĸompønent>', {ADD_TAGS: ['my-ĸompønent']}), "<my-ĸompønent>abc</my-ĸompønent>" ); | ||
}); | ||
QUnit.test( 'Config-Flag tests: ADD_TAGS + ADD_ATTR', function(assert) { | ||
// ADD_TAGS + ADD_ATTR | ||
assert.equal( DOMPurify.sanitize( '<my-component my-attr="foo">abc</my-component>', {ADD_TAGS: ['my-component']}), "<my-component>abc</my-component>" ); | ||
assert.equal( DOMPurify.sanitize( '<my-component my-attr="foo">abc</my-component>', {ADD_TAGS: ['my-component'], ADD_ATTR: ['my-attr']}), "<my-component my-attr=\"foo\">abc</my-component>" ); | ||
assert.equal( DOMPurify.sanitize( '<my-ĸompønent my-æŧŧr="foo">abc</my-ĸompønent>', {ADD_TAGS: ['my-ĸompønent']}), "<my-ĸompønent>abc</my-ĸompønent>" ); | ||
assert.equal( DOMPurify.sanitize( '<my-ĸompønent my-æŧŧr="foo">abc</my-ĸompønent>', {ADD_TAGS: ['my-ĸompønent'], ADD_ATTR: ['my-æŧŧr']}), "<my-ĸompønent my-æŧŧr=\"foo\">abc</my-ĸompønent>" ); | ||
}); | ||
QUnit.test( 'Config-Flag tests: SAFE_FOR_JQUERY', function(assert) { | ||
//SAFE_FOR_JQUERY | ||
assert.equal( DOMPurify.sanitize( '<a>123</a><option><style><img src=x onerror=alert(1)>', {SAFE_FOR_JQUERY: false}), "<a>123</a><option><style><img src=x onerror=alert(1)></style></option>" ); | ||
assert.equal( DOMPurify.sanitize( '<a>123</a><option><style><img src=x onerror=alert(1)>', {SAFE_FOR_JQUERY: true}), "<a>123</a><option><style><img src=x onerror=alert(1)></style></option>" ); | ||
assert.equal( DOMPurify.sanitize( '<option><style></option></select><b><img src=xx: onerror=alert(1)></style></option>', {SAFE_FOR_JQUERY: false}), "<option><style></option></select><b><img src=xx: onerror=alert(1)></style></option>" ); | ||
assert.equal( DOMPurify.sanitize( '<option><style></option></select><b><img src=xx: onerror=alert(1)></style></option>', {SAFE_FOR_JQUERY: true}), "<option><style></option></select><b><img src=xx: onerror=alert(1)></style></option>" ); | ||
assert.equal( DOMPurify.sanitize( '<option><iframe></select><b><script>alert(1)<\/script>', {SAFE_FOR_JQUERY: false, KEEP_CONTENT: false}), "<option></option>" ); | ||
assert.equal( DOMPurify.sanitize( '<option><iframe></select><b><script>alert(1)<\/script>', {SAFE_FOR_JQUERY: true, KEEP_CONTENT: false}), "<option></option>" ); | ||
assert.equal( DOMPurify.sanitize( '<b><style><style/><img src=xx: onerror=alert(1)>', {SAFE_FOR_JQUERY: false}), "<b><style><style/><img src=xx: onerror=alert(1)></style></b>" ); | ||
assert.equal( DOMPurify.sanitize( '<b><style><style/><img src=xx: onerror=alert(1)>', {SAFE_FOR_JQUERY: true}), "<b><style><style/><img src=xx: onerror=alert(1)></style></b>" ); | ||
}); | ||
QUnit.test( 'Config-Flag tests: SANITIZE_DOM', function(assert) { | ||
// SANITIZE_DOM | ||
assert.equal( DOMPurify.sanitize( '<form name="window">', {SANITIZE_DOM: true}), "<form></form>" ); | ||
assert.equal( DOMPurify.sanitize( '<img src="x" name="implementation">', {SANITIZE_DOM: true}), '<img src="x">' ); | ||
assert.equal( DOMPurify.sanitize( '<img src="x" name="createNodeIterator">', {SANITIZE_DOM: true}), '<img src="x">' ); | ||
assert.equal( DOMPurify.sanitize( '<img src="x" name="getElementById">', {SANITIZE_DOM: false}), "<img name=\"getElementById\" src=\"x\">" ); | ||
assert.equal( DOMPurify.sanitize( '<img src="x" name="getElementById">', {SANITIZE_DOM: true}), "<img src=\"x\">" ); | ||
assert.equal( DOMPurify.sanitize( '<a href="x" id="location">click</a>', {SANITIZE_DOM: true}), "<a href=\"x\">click</a>" ); | ||
assert.contains( DOMPurify.sanitize( '<form><input name="attributes"></form>', {ADD_TAGS: ['form'], SANITIZE_DOM: false}), | ||
["", "<form><input name=\"attributes\"></form>"] | ||
); | ||
assert.contains( DOMPurify.sanitize( '<form><input name="attributes"></form>', {ADD_TAGS: ['form'], SANITIZE_DOM: true}), | ||
["", "<form><input name=\"attributes\"></form>", "<form><input></form>"] | ||
); | ||
}); | ||
QUnit.test( 'Config-Flag tests: WHOLE_DOCUMENT', function(assert) { | ||
//WHOLE_DOCUMENT | ||
assert.equal( DOMPurify.sanitize( '123', {WHOLE_DOCUMENT: false}), "123" ); | ||
assert.equal( DOMPurify.sanitize( '123', {WHOLE_DOCUMENT: true}), "<html><head></head><body>123</body></html>" ); | ||
assert.equal( DOMPurify.sanitize( '<style>*{color:red}</style>', {WHOLE_DOCUMENT: false}), "" ); | ||
assert.equal( DOMPurify.sanitize( '<style>*{color:red}</style>', {WHOLE_DOCUMENT: true}), "<html><head><style>*{color:red}</style></head><body></body></html>" ); | ||
assert.equal( DOMPurify.sanitize( '123<style>*{color:red}</style>', {WHOLE_DOCUMENT: false}), "123<style>*{color:red}</style>" ); | ||
assert.equal( DOMPurify.sanitize( '123<style>*{color:red}</style>', {WHOLE_DOCUMENT: true}), "<html><head></head><body>123<style>*{color:red}</style></body></html>" ); | ||
}); | ||
QUnit.test( 'Config-Flag tests: RETURN_DOM', function(assert) { | ||
//RETURN_DOM | ||
assert.equal( DOMPurify.sanitize( '<a>123<b>456</b></a>', {RETURN_DOM: true}).outerHTML, "<body><a>123<b>456</b></a></body>" ); | ||
assert.equal( DOMPurify.sanitize( '<a>123<b>456<script>alert(1)<\/script></b></a>', {RETURN_DOM: true}).outerHTML, "<body><a>123<b>456</b></a></body>" ); | ||
assert.equal( DOMPurify.sanitize( '<a>123<b>456</b></a>', {RETURN_DOM: true, WHOLE_DOCUMENT: true}).outerHTML, "<html><head></head><body><a>123<b>456</b></a></body></html>" ); | ||
assert.equal( DOMPurify.sanitize( '<a>123<b>456<script>alert(1)<\/script></b></a>', {RETURN_DOM: true, WHOLE_DOCUMENT: true}).outerHTML, "<html><head></head><body><a>123<b>456</b></a></body></html>" ); | ||
assert.equal( DOMPurify.sanitize( '123', {RETURN_DOM: true}).outerHTML, "<body>123</body>" ); | ||
}); | ||
QUnit.test( 'Config-Flag tests: RETURN_DOM_IMPORT', function(assert) { | ||
//RETURN_DOM_IMPORT | ||
assert.notEqual( DOMPurify.sanitize( '123', {RETURN_DOM : true }).ownerDocument, document ); | ||
assert.notEqual( DOMPurify.sanitize( '123', {RETURN_DOM : true, RETURN_DOM_IMPORT: false}).ownerDocument, document ); | ||
assert.equal ( DOMPurify.sanitize( '123', {RETURN_DOM : true, RETURN_DOM_IMPORT: true }).ownerDocument, document ); | ||
assert.notEqual( DOMPurify.sanitize( '123', {RETURN_DOM_FRAGMENT: true }).ownerDocument, document ); | ||
assert.notEqual( DOMPurify.sanitize( '123', {RETURN_DOM_FRAGMENT: true, RETURN_DOM_IMPORT: false}).ownerDocument, document ); | ||
assert.equal ( DOMPurify.sanitize( '123', {RETURN_DOM_FRAGMENT: true, RETURN_DOM_IMPORT: true }).ownerDocument, document ); | ||
}); | ||
QUnit.test( 'Config-Flag tests: RETURN_DOM_FRAGMENT', function(assert) { | ||
//RETURN_DOM_FRAGMENT | ||
// attempt clobbering | ||
var fragment = DOMPurify.sanitize( 'foo<img id="createDocumentFragment">', {RETURN_DOM_FRAGMENT: true}); | ||
assert.equal(fragment.nodeType, 11); | ||
assert.notEqual(fragment.ownerDocument, document); | ||
assert.equal(fragment.firstChild && fragment.firstChild.nodeValue, 'foo'); | ||
// again, but without SANITIZE_DOM | ||
fragment = DOMPurify.sanitize( 'foo<img id="createDocumentFragment">', {RETURN_DOM_FRAGMENT: true, SANITIZE_DOM: false}); | ||
assert.equal(fragment.nodeType, 11); | ||
assert.notEqual(fragment.ownerDocument, document); | ||
assert.equal(fragment.firstChild && fragment.firstChild.nodeValue, 'foo'); | ||
}); | ||
QUnit.test( 'Config-Flag tests: FORBID_TAGS', function(assert) { | ||
//FORBID_TAGS | ||
assert.equal( DOMPurify.sanitize( '<a>123<b>456</b></a>', {FORBID_TAGS: ['b']}), "<a>123456</a>" ); | ||
assert.equal( DOMPurify.sanitize( '<a>123<b>456<script>alert(1)<\/script></b></a>789', {FORBID_TAGS: ['a', 'b']}), "123456789" ); | ||
assert.equal( DOMPurify.sanitize( '<a>123<b>456</b></a>', {FORBID_TAGS: ['c']}), "<a>123<b>456</b></a>" ); | ||
assert.equal( DOMPurify.sanitize( '<a>123<b>456<script>alert(1)<\/script></b></a>789', {FORBID_TAGS: ['script', 'b']}), "<a>123456</a>789" ); | ||
assert.equal( DOMPurify.sanitize( '<a>123<b>456</b></a>', {ADD_TAGS: ['b'], FORBID_TAGS: ['b']}), "<a>123456</a>" ); | ||
}); | ||
QUnit.test( 'Config-Flag tests: FORBID_ATTR', function(assert) { | ||
//FORBID_ATTR | ||
assert.equal( DOMPurify.sanitize( '<a x="1">123<b>456</b></a>', {FORBID_ATTR: ['x']}), "<a>123<b>456</b></a>" ); | ||
assert.equal( DOMPurify.sanitize( '<a class="0" x="1">123<b y="1">456<script>alert(1)<\/script></b></a>789', {FORBID_ATTR: ['x', 'y']}), "<a class=\"0\">123<b>456</b></a>789" ); | ||
assert.equal( DOMPurify.sanitize( '<a y="1">123<b y="1" y="2">456</b></a>', {FORBID_ATTR: ['y']}), "<a>123<b>456</b></a>" ); | ||
assert.equal( DOMPurify.sanitize( '<a>123<b x="1">456<script y="1">alert(1)<\/script></b></a>789', {FORBID_ATTR: ['x', 'y']}), "<a>123<b>456</b></a>789" ); | ||
}); | ||
// XSS tests: Native DOM methods (alert() should not be called) | ||
QUnit | ||
.cases(xssTests) | ||
.asyncTest('XSS test: native', function(params, assert) { | ||
document.getElementById( 'qunit-fixture' ).innerHTML = DOMPurify.sanitize( params.payload ); | ||
setTimeout(function() { | ||
QUnit.start(); | ||
assert.notEqual( window.xssed, true, 'alert() was called' ); | ||
// Teardown | ||
document.getElementById( 'qunit-fixture' ).innerHTML = ''; | ||
window.xssed = false; | ||
}, 100); | ||
}); | ||
// XSS tests: jQuery (alert() should not be called) | ||
QUnit | ||
.cases(xssTests) | ||
.asyncTest('XSS test: jQuery', function(params, assert) { | ||
jQuery( '#qunit-fixture' ).html( DOMPurify.sanitize( params.payload, {SAFE_FOR_JQUERY: true} ) ); | ||
setTimeout(function() { | ||
QUnit.start(); | ||
assert.notEqual( window.xssed, true, 'alert() was called' ); | ||
// Teardown | ||
jQuery( '#qunit-fixture' ).empty(); | ||
window.xssed = false; | ||
}, 100); | ||
}); | ||
// Check for isSupported property | ||
QUnit.test( 'DOMPurify property tests', function(assert) { | ||
assert.equal( typeof DOMPurify.isSupported, 'boolean' ); | ||
}); | ||
// Test with a custom window object | ||
QUnit.test( 'DOMPurify custom window tests', function(assert) { | ||
assert.strictEqual(typeof DOMPurify(null).version, 'string'); | ||
assert.strictEqual(DOMPurify(null).isSupported, false); | ||
assert.strictEqual(DOMPurify(null).sanitize, undefined); | ||
assert.strictEqual(typeof DOMPurify({}).version, 'string'); | ||
assert.strictEqual(DOMPurify({}).isSupported, false); | ||
assert.strictEqual(DOMPurify({}).sanitize, undefined); | ||
assert.strictEqual(typeof DOMPurify({document: 'not really a document'}).version, 'string'); | ||
assert.strictEqual(DOMPurify({document: 'not really a document'}).isSupported, false); | ||
assert.strictEqual(DOMPurify({document: 'not really a document'}).sanitize, undefined); | ||
assert.strictEqual(typeof DOMPurify(window).version, 'string'); | ||
assert.strictEqual(typeof DOMPurify(window).sanitize, 'function'); | ||
}); | ||
// Test to prevent security issues with pre-clobbered DOM | ||
QUnit.test( 'sanitize() should not throw if the original document is clobbered _after_ DOMPurify has been instantiated', function(assert) { | ||
var evilNode = document.createElement('div'); | ||
evilNode.innerHTML = '<img id="implementation"><img id="createNodeIterator"><img id="importNode"><img id="createElement">'; | ||
document.body.appendChild(evilNode); | ||
try { | ||
// tests implementation and createNodeIterator | ||
var resultPlain = DOMPurify.sanitize('123'); | ||
// tests importNode | ||
var resultImport = DOMPurify.sanitize( '123', {RETURN_DOM : true, RETURN_DOM_IMPORT: true }); | ||
// tests createElement | ||
var resultBody = DOMPurify.sanitize( '123<img id="body">'); | ||
} finally { | ||
// clean up before doing the actual assertions, otherwise qunit/jquery/etc might blow up | ||
document.body.removeChild(evilNode); | ||
} | ||
assert.equal( resultPlain, '123' ); | ||
assert.equal( resultImport.ownerDocument, document ); | ||
assert.equal( resultBody, '123<img>' ); | ||
} ); | ||
// Test to check against a hang in MSIE (#89) | ||
QUnit.test( 'sanitize() should not hang on MSIE when hook changes textContent', function(assert) { | ||
DOMPurify.addHook('afterSanitizeElements', function(node) { | ||
if (node.nodeType && node.nodeType === document.TEXT_NODE) { | ||
node.textContent = 'foo'; | ||
} | ||
return node; | ||
}); | ||
var dirty = '<div><p>This is a beatufiul text</p><p>This is too</p></div>'; | ||
var modified = '<div><p>foo</p><p>foo</p></div>'; | ||
assert.equal(modified, DOMPurify.sanitize(dirty)); | ||
DOMPurify.removeHooks('afterSanitizeElements') | ||
} ); | ||
QUnit.module('DOMPurify src'); | ||
testSuite(DOMPurify, tests, xssTests); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
603065
34
10372
185
17