[Pkg-javascript-commits] [dompurify.js] 01/04: New upstream version 0.9.0~dfsg1
Alexandre Viau
aviau at moszumanska.debian.org
Tue Jul 4 19:13:52 UTC 2017
This is an automated email from the git hooks/post-receive script.
aviau pushed a commit to branch master
in repository dompurify.js.
commit 11b6746ebfd7bd06bf9b7767cd2e30dc3679238a
Author: aviau <aviau at debian.org>
Date: Tue Jul 4 15:10:32 2017 -0400
New upstream version 0.9.0~dfsg1
---
.github/ISSUE_TEMPLATE.md | 23 ++++
.github/PULL_REQUEST_TEMPLATE.md | 16 +++
README.md | 20 +++-
bower.json | 6 +-
demos/hooks-mentaljs-demo.html | 122 ++++++++++-----------
demos/hooks-scheme-whitelist.html | 2 +-
demos/lib/Mental.js | 190 +++++++++------------------------
package.json | 6 +-
src/purify.js | 215 +++++++++++++++++++++++++++++++-------
test/fixtures/expect.js | 34 ++++--
test/karma.conf.js | 42 +++++++-
test/test-suite.js | 106 ++++++++++++++++---
website/index.html | 4 +-
13 files changed, 510 insertions(+), 276 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..0b12f69
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,23 @@
+> This issue proposes a [bug, feature] which...
+
+### Background & Context
+
+Please provide some more detailed information about the general background and context of this issue and delete non applicable sections below.
+
+### Bug
+
+#### Input
+
+Some HTML which is thrown at DOMPurify.
+
+#### Given output
+
+The output given by DOMPurify.
+
+#### Expected output
+
+The expected output.
+
+### Feature
+
+Briefly outline the proposed feature, its value and a potentially proposed implementation from a high level.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..e8de5ae
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,16 @@
+> This pull request [implements, fixes, changes]...
+
+### Background & Context
+
+Briefly outline why this PR was needed, a minimal context, culminating in your implementation.
+
+### Tasks
+
+- Add tasks to give a quick overview for QA and reviewers
+
+### Dependencies
+
+If there are any dependencies on PRs or API work then list them here.
+
+- [x] Resolved dependency
+- [ ] Open dependency
diff --git a/README.md b/README.md
index f88c6f7..11c64c0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# 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)
+# 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)](https://travis-ci.org/cure53/DOMPurify) · [![Downloads](https://img.shields.io/npm/dm/dompurify.svg)](https://www.npmjs.com/package/dompurify)
[![NPM](https://nodei.co/npm/dompurify.png)](https://nodei.co/npm/dompurify/)
@@ -6,7 +6,9 @@ DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathM
It's also very simple to use and get started with.
-DOMPurify is written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Edge, 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 [12 different browsers](https://github.com/cure53/DOMPurify/blob/master/test/karma.conf.js#L153) right now. We also cover Node.js v4.0.0, v5.0.0 and v6.0.0, running DOMPurify on [jsdom](ht [...]
+DOMPurify is written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Edge, Firefox and Chrome - as well as almost anything else using Blink or WebKit). It doesn't break on IE6 or other legacy browsers. It either uses [a fall-back](#what-about-older-browsers-like-msie8) or simply does nothing.
+
+Our automated tests cover [16 different browsers](https://github.com/cure53/DOMPurify/blob/master/test/karma.conf.js#L185) right now. We also cover Node.js v4.0.0, v5.0.0 and v6.0.0, running DOMPurify on [jsdom](https://github.com/tmpvar/jsdom).
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)
@@ -65,7 +67,7 @@ const window = jsdom.jsdom('', {
}).defaultView;
const DOMPurify = createDOMPurify(window);
-const clean = DOMPurify.sanitize(dirty));
+const clean = DOMPurify.sanitize(dirty);
```
Strictly speaking, DOMPurify creates a document without a browsing context and you can replace it with `const window = jsdom.jsdom().defaultView;`, however, the longer case protects against accidental bugs in jsdom or DOMPurify.
@@ -95,6 +97,11 @@ DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><l
DOMPurify currently supports HTML5, SVG and MathML. DOMPurify per default allows CSS, HTML custom data attributes. DOMPurify also supports the Shadow DOM - and sanitizes DOM templates recursively. DOMPurify also allows you to sanitize HTML for being used with the jQuery `$()` and `elm.html()` methods but requires the `SAFE_FOR_JQUERY` flag for that - see below.
+## What about older browsers like MSIE8?
+
+DOMPurify offers a fall-back behavior for older MSIE browsers. It uses the MSIE-only `toStaticHTML` feature to sanitize. Note however that in this fall-back mode, pretty much none of the configuration flags shown below have any effect. You need to handle that yourself.
+
+If not even `toStaticHTML` is supported, DOMPurify does nothing at all. It simply returns exactly the string that you fed it.
## Can I configure it?
@@ -153,9 +160,16 @@ var clean = DOMPurify.sanitize(dirty, {SANITIZE_DOM: false});
// discard an element's content when the element is removed (default is true)
var clean = DOMPurify.sanitize(dirty, {KEEP_CONTENT: false});
+
+// glue elements like style, script or others to document.body and prevent unintuitive browser behavior in several edge-cases (default is false)
+var clean = DOMPurify.sanitize(dirty, {FORCE_BODY: true});
```
There is even [more examples here](https://github.com/cure53/DOMPurify/tree/master/demos#what-is-this), showing how you can run, customize and configure DOMPurify to fit your needs.
+## Persistent Configuration
+
+Instead of repeatedly passing the same configuration to `DOMPurify.sanitize`, you can use the `DOMPurify.setConfig` method. Your configuration will persist until your next call to `DOMPurify.setConfig`, or until you invoke `DOMPurify.clearConfig` to reset it. Remember that there is only one active configuration, which means once it is set, all extra configuration parameters passed to `DOMPurify.sanitize` are ignored.
+
## Hooks
DOMPurify allows you to augment its functionality by attaching one or more functions with the `DOMPurify.addHook` method to one of the following hooks:
diff --git a/bower.json b/bower.json
index c878922..4df6349 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "DOMPurify",
- "version": "0.8.2",
+ "version": "0.8.9",
"homepage": "https://github.com/cure53/DOMPurify",
"author": "Cure53 <info at cure53.de>",
"description": "A DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG",
@@ -24,7 +24,9 @@
],
"ignore": [
"**/.*",
+ "demos",
+ "scripts",
"test",
- "demo"
+ "website"
]
}
diff --git a/demos/hooks-mentaljs-demo.html b/demos/hooks-mentaljs-demo.html
index ea79091..aacc5b4 100644
--- a/demos/hooks-mentaljs-demo.html
+++ b/demos/hooks-mentaljs-demo.html
@@ -8,19 +8,19 @@
<body>
<!-- Our DIV to receive content -->
<div id="sanitized"></div>
-
+ <div id="test"></div>
<!-- Now let's sanitize that content -->
<script>
/* jshint globalstrict:true */
/* global DOMPurify */
'use strict';
-
// Initialize MentalJS
- MentalJS().init({dom: true});
+ MentalJS().init({dom: true, parseInnerHTML: filter});
// Specify dirty HTML
- var dirty = 'abc<script>alert(1)<\/script>\
- <img src="xyz" onload="alert(2)">\
+ var dirty = 'abc<script>alert(\'from script:\'+location)<\/script>\
+ <img src="xyz" onerror="document.getElementById(\'test\').innerHTML=\'<img src=123 onerror=alert(/from_innerhtml/)>\';" />\
+ <img src="xyz" onerror="alert(\'from img:\'+location)">\
<img src="xyz" onload="alert`3`">\
<img src="xyz" onload="">\
<script src=//evil.com><\/script>\
@@ -28,64 +28,68 @@
<script src=//evil.com>alert(5)<\/script>\
<svg><script xlink:href=//evil.com>alert(6)<\/script></svg>\
<svg><script href="//evil.com">123<\/script><p>';
-
- // allow script elements
- var config = {
- ADD_TAGS: ['script'],
- ADD_ATTR: ['onclick', 'onmouseover', 'onload', 'onunload']
- }
-
- // Add a hook to sanitize all script content with MentalJS
- DOMPurify.addHook('uponSanitizeElement', function(node, data) {
- if (data.tagName === 'script') {
- var script = node.textContent;
- if (!script || 'src' in node.attributes
- || 'href' in node.attributes
- || 'xlink:href' in node.attributes) {
- return node.parentNode.removeChild(node)
- }
- try {
- // Pass scripts to MentalJS
- var mental = MentalJS().parse(
- {
- options: {
- eval: false,
- dom:true
- },
- code:script
- }
- );
- return node.textContent = mental;
- } catch (e) {
- return node.parentNode.removeChild(node);
+ // Add a hook to sanitize all script content with MentalJS
+ DOMPurify.addHook('uponSanitizeElement', function(node, data) {
+ if (data.tagName === 'script') {
+ var script = node.textContent;
+ if (!script || 'src' in node.attributes
+ || 'href' in node.attributes
+ || 'xlink:href' in node.attributes) {
+ return node.parentNode.removeChild(node)
+ }
+ try {
+ // Pass scripts to MentalJS
+ var mental = MentalJS().parse(
+ {
+ options: {
+ eval: false,
+ dom:false
+ },
+ code:script
+ }
+ );
+ var scriptNode = document.createElement('script');
+ scriptNode.appendChild(document.createTextNode(mental));
+ document.head.appendChild(scriptNode);
+ return node.parentNode.removeChild(node);
+ } catch (e) {
+ return node.parentNode.removeChild(node);
+ }
}
- }
- });
+ });
- // 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;
- try {
- // Pass scripts to MentalJS
- return data.attrValue = MentalJS().parse(
- {
- options: {
- eval: false,
- dom: true
- },
- code: script
- }
- );
- } catch (e) {
- return data.attrValue = '';
+ // 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;
+ try {
+ // Pass scripts to MentalJS
+ return data.attrValue = MentalJS().parse(
+ {
+ options: {
+ eval: false,
+ dom: false
+ },
+ code: script
+ }
+ );
+ } catch (e) {
+ return data.attrValue = '';
+ }
}
- }
- });
+ });
- // Clean HTML string and write into our DIV
- var clean = DOMPurify.sanitize(dirty, config);
- document.getElementById('sanitized').innerHTML = clean;
+ function filter(dirty) {
+ // allow script elements
+ var config = {
+ ADD_TAGS: ['script'],
+ ADD_ATTR: ['onclick', 'onmouseover', 'onload', 'onunload','onerror']
+ };
+ // Clean HTML string and write into our DIV
+ var clean = DOMPurify.sanitize(dirty, config);
+ return clean;
+ }
+ document.getElementById('sanitized').innerHTML = filter(dirty);
</script>
</body>
</html>
diff --git a/demos/hooks-scheme-whitelist.html b/demos/hooks-scheme-whitelist.html
index c6b0738..b3cb9a5 100644
--- a/demos/hooks-scheme-whitelist.html
+++ b/demos/hooks-scheme-whitelist.html
@@ -38,7 +38,7 @@
var whitelist = ['http', 'https', 'ftp'];
// build fitting regex
- var regex = RegExp('^('+whitelist.join('|')+'):', 'gim');
+ var regex = RegExp('^('+whitelist.join('|')+'):', 'im');
// Add a hook to enforce URI scheme whitelist
DOMPurify.addHook('afterSanitizeAttributes', function(node){
diff --git a/demos/lib/Mental.js b/demos/lib/Mental.js
index 5363296..157b883 100644
--- a/demos/lib/Mental.js
+++ b/demos/lib/Mental.js
@@ -5775,21 +5775,21 @@
}
};
- exports.version = "0.3.10";
+ exports.version = "0.4.0";
exports.parse = function() {
var js = MentalJS();
};
MentalJS = function() {
function Mental() {
- var that = this,scoping = '$', replaceScoping = new RegExp('[' + scoping + ']'),
- attributeWhitelist = /^(?:style|accesskey|align|alink|alt|bgcolor|border|cellpadding|cellspacing|class|color|cols|colspan|coords|dir|face|height|hspace|id|ismap|lang|marginheight|marginwidth|multiple|name|nohref|noresize|noshade|nowrap|ref|rel|rev|rows|rowspan|scrolling|size|shape|span|summary|tabindex|target|title|type|usemap|valign|value|vlink|vspace|width)$/i,
- attributeWhitelistList = 'accesskey|align|alink|alt|bgcolor|border|cellpadding|cellspacing|class|color|cols|colspan|coords|dir|face|height|hspace|id|ismap|lang|marginheight|marginwidth|multiple|name|nohref|noresize|noshade|nowrap|ref|rel|rev|rows|rowspan|scrolling|size|shape|span|summary|tabindex|target|title|type|usemap|valign|value|vlink|vspace|width'.split('|'),
- urlBasedAttributes = /^(?:href|src|action)$/i, urlBasedAttributesList = ['href', 'src', 'action'], allowedEvents = /^(?:onabort|onactivate|onafterprint|onafterupdate|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditfocus|onbeforepaste|onbeforeprint|onbeforeunload|onbegin|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend [...]
- allowedEventsList = 'onabort|onactivate|onafterprint|onafterupdate|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditfocus|onbeforepaste|onbeforeprint|onbeforeunload|onbegin|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragleave|ondragenter|ondragover|ondragdrop|ondrop|onend|onerror|onerrorupdate|onexit|onfilterch [...]
- allowedCSSProperties = ["azimuth", "background", "backgroundAttachment", "backgroundColor", "backgroundImage", "backgroundPosition", "backgroundRepeat", "border", "borderCollapse", "borderColor", "borderSpacing", "borderStyle", "borderTop", "borderRight", "borderBottom", "borderLeft", "borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle", "borderTopWidth", "borderRightWidt [...]
+ var that = this,scoping = '$', replaceScoping = new RegExp('[' + scoping + ']'),
+ attributeWhitelist = /^(?:style|accesskey|align|alink|alt|bgcolor|border|cellpadding|cellspacing|class|color|cols|colspan|coords|dir|face|height|hspace|id|ismap|lang|marginheight|marginwidth|multiple|name|nohref|noresize|noshade|nowrap|ref|rel|rev|rows|rowspan|scrolling|size|shape|span|summary|tabindex|target|title|type|usemap|valign|value|vlink|vspace|width)$/i,
+ attributeWhitelistList = 'accesskey|align|alink|alt|bgcolor|border|cellpadding|cellspacing|class|color|cols|colspan|coords|dir|face|height|hspace|id|ismap|lang|marginheight|marginwidth|multiple|name|nohref|noresize|noshade|nowrap|ref|rel|rev|rows|rowspan|scrolling|size|shape|span|summary|tabindex|target|title|type|usemap|valign|value|vlink|vspace|width'.split('|'),
+ urlBasedAttributes = /^(?:href|src|action)$/i, urlBasedAttributesList = ['href', 'src', 'action'], allowedEvents = /^(?:onabort|onactivate|onafterprint|onafterupdate|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditfocus|onbeforepaste|onbeforeprint|onbeforeunload|onbegin|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend [...]
+ allowedEventsList = 'onabort|onactivate|onafterprint|onafterupdate|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditfocus|onbeforepaste|onbeforeprint|onbeforeunload|onbegin|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragleave|ondragenter|ondragover|ondragdrop|ondrop|onend|onerror|onerrorupdate|onexit|onfilterch [...]
+ allowedCSSProperties = ["azimuth", "background", "backgroundAttachment", "backgroundColor", "backgroundImage", "backgroundPosition", "backgroundRepeat", "border", "borderCollapse", "borderColor", "borderSpacing", "borderStyle", "borderTop", "borderRight", "borderBottom", "borderLeft", "borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle", "borderTopWidth", "borderRightWidt [...]
setTimeoutIDS = {}, setIntervalIDS = {};
this.init = init;
- function init(config) {
+ function init(config) {
M = {
O : function(obj) {
var keys = Object.keys(obj), key;
@@ -5814,7 +5814,8 @@
Object.defineProperty(obj, key.replace(new RegExp(replaceScoping.source + '$', 'i'), ''), {
configurable : true,
enumerable : true,
- writable : true
+ writable : true,
+ value : obj[key]
});
Object.defineProperty(obj, key, {
set : function(len) {
@@ -5942,105 +5943,10 @@
get : function() {
return this.innerHTML;
},
- set : function(innerHTML) {
- if (config.parseInnerHTML) {
- this.innerHTML = config.parseInnerHTML(innerHTML);
- return innerHTML;
- }
- var node = document.implementation.createHTMLDocument('');
- node.body.innerHTML = innerHTML;
- var ni = document.createNodeIterator(node.body, NodeFilter.SHOW_ELEMENT, null, false), elementNode, anchor = document.createElement('a'), scripts = [], i, script, code, elementsToRemove = [];
- while ( elementNode = ni.nextNode()) {
- if (!allowedTagsRegEx.test(elementNode.nodeName)) {
- elementsToRemove.push(elementNode);
- }
- if (elementNode.nodeName.toLowerCase() === 'script') {
- if (elementNode.text.length) {
- scripts.push({
- type : 'inline',
- code : elementNode.text
- });
- } else {
- anchor.href = elementNode.getAttribute('src');
- if ((anchor.protocol === 'http:' || anchor.protocol === 'https:') && anchor.host.replace(/:\d+$/, '') === location.host.replace(/:\d+$/, '')) {
- scripts.push({
- type : 'external',
- src : elementNode.getAttribute('src')
- });
- }
- }
- elementsToRemove.push(elementNode);
- continue;
- }
-
- if (elementNode.attributes instanceof HTMLElement || typeof elementNode.setAttribute !== 'function' || typeof elementNode.getAttribute !== 'function' || typeof elementNode.removeAttribute !== 'function') {
- elementsToRemove.push(elementNode);
- continue;
- }
-
- for ( i = elementNode.attributes.length - 1; i > -1; i--) {
-
- if (urlBasedAttributes.test(elementNode.attributes[i].name)) {
- anchor.href = elementNode.attributes[i].value;
- if ((anchor.protocol === 'http:' || anchor.protocol === 'https:') && anchor.host.replace(/:\d+$/, '') === location.host.replace(/:\d+$/, '')) {
- elementNode.setAttribute(elementNode.attributes[i].name, elementNode.attributes[i].value + '');
- } else {
- elementNode.setAttribute(elementNode.attributes[i].name, '#');
- }
- continue;
- }
- if (allowedEvents.test(elementNode.attributes[i].name)) {
- var js = MentalJS();
- try {
- elementNode.setAttribute(elementNode.attributes[i].name, js.parse({
- options : {
- eval : false
- },
- code : elementNode.attributes[i].value
- }));
- } catch(e) {
- elementNode.setAttribute(elementNode.attributes[i].name, '');
- }
- continue;
- }
- if (!attributeWhitelist.test(elementNode.attributes[i].name)) {
- elementNode.removeAttribute(elementNode.attributes[i].name);
- }
- }
- }
-
- for ( i = 0; i < elementsToRemove.length; i++) {
- try {
- elementsToRemove[i].parentNode.removeChild(elementsToRemove[i]);
- } catch(e) {
- };
- try {
- node.body.removeChild(elementsToRemove[i]);
- } catch(e) {
- };
- }
-
- anchor = null;
- this.innerHTML = node.body.innerHTML;
- for ( i = 0; i < scripts.length; i++) {
- script = document.createElement('script');
- if (scripts[i].type === 'inline') {
- var js = MentalJS();
- try {
- code = document.createTextNode(js.parse({
- options : {
- eval : false
- },
- code : scripts[i].code
- }));
- script.appendChild(code);
- } catch(e) {
- }
- } else {
- script.src = scripts[i].src;
- }
- document.getElementsByTagName('head')[0].appendChild(script);
- }
+ set : function(innerHTML) {
+ var clean = config.parseInnerHTML(innerHTML);
+ this.innerHTML = clean;
+ return this.innerHTML;
}
},
'textContent$' : {
@@ -6314,7 +6220,7 @@
FUNCTION.constructor$ = FUNCTION;
- Function$ = FUNCTION;
+ Function$ = FUNCTION;
Boolean.constructor$ = Function$;
Boolean.prototype.constructor$ = Boolean;
Boolean$ = Boolean;
@@ -6400,13 +6306,13 @@
alert$ = ALERT;
var EVAL = function(str) {
var js = MentalJS(), converted;
- if ( typeof str !== 'function') {
+ if ( typeof str !== 'function') {
return eval(js.parse({
options : {
eval : false
},
code : str,
- converted : function(converted) {
+ converted : function(converted) {
if (config.evalCode) {
config.evalCode(converted);
}
@@ -6415,7 +6321,7 @@
} else {
if (config.evalCode) {
config.evalCode(str);
- }
+ }
return eval(str);
}
};
@@ -6449,10 +6355,10 @@
this.length = len;
}
});
-
+
Object.preventExtensions(Object.prototype);
Object.preventExtensions(Array.prototype);
-
+
Object.defineProperties(window, {
'undefined$' : {
configurable : true,
@@ -6803,7 +6709,7 @@
return document.documentElement.compareDocumentPosition.apply(document.documentElement, arguments)
}
}
- });
+ });
createSandboxedNode(Element.prototype);
createSandboxedNode(DocumentFragment.prototype);
Object.defineProperties(HTMLScriptElement.prototype, {
@@ -7010,11 +6916,11 @@
}
}
});
-
+
Object.freeze(Element.prototype);
Object.freeze(DocumentFragment.prototype);
Object.freeze(HTMLScriptElement.prototype);
- Object.freeze(HTMLStyleElement.prototype);
+ Object.freeze(HTMLStyleElement.prototype);
}
};
@@ -7022,7 +6928,7 @@
if (!Object.defineProperty) {
error("MentalJS requires ES5. Please upgrade your browser.");
}
- var parseTreeOutput = '', converted, pos = 0, chr, index, result;
+ var parseTreeOutput = '', converted, pos = 0, chr, index, result;
function error(str) {
var e = new Error();
@@ -7087,8 +6993,8 @@
var chr1 = code.charCodeAt(pos), chr2 = code.charAt(pos + 1), chr3 = code.charAt(pos + 2), chr4 = code.charAt(pos + 3), chr5 = code.charAt(pos + 4), hex;
if (chr1 !== 0x75) {
error("Invalid unicode escape. Expected u.");
- }
- hex = +('0x' + chr2 + chr3 + chr4 + chr5);
+ }
+ hex = +('0x' + chr2 + chr3 + chr4 + chr5);
if ((hex === hex && hex !== hex) || /[^a-f0-9]/i.test(''+chr2+chr3+chr4+chr5)) {
error("Invalid unicode escape. Expected valid hex sequence.");
}
@@ -7268,8 +7174,8 @@
}
}
- function identifierAsi() {
- if (!rules[state][lastState] && newLineFlag) {
+ function identifierAsi() {
+ if (!rules[state][lastState] && newLineFlag) {
if (left) {
asi(true);
left = 1;
@@ -7299,7 +7205,7 @@
break;
}
outputLine += code.charAt(pos++);
- }
+ }
iLen = outputLine.length;
if (iLen === 1 || iLen > 10) {
outputLine = outputLine + scoping;
@@ -7310,36 +7216,36 @@
outputLine = outputLine + scoping;
identifierStates();
return false;
- }
+ }
}
identifierAsi();
}
- function identifierStates() {
+ function identifierStates() {
if (rules[50][lastState]) {
state = 50;
- outputLine = ' ' + outputLine;
+ outputLine = ' ' + outputLine;
} else if (rules[25][lastState]) {
- state = 25;
+ state = 25;
} else if (rules[98][lastState]) {
- state = 98;
+ state = 98;
} else if (rules[53][lastState]) {
state = 53;
- outputLine = ' ' + outputLine;
+ outputLine = ' ' + outputLine;
} else if (rules[48][lastState]) {
state = 48;
} else if (rules[55][lastState]) {
- state = 55;
+ state = 55;
} else if (rules[137][lastState]) {
state = 137;
- left = 1;
+ left = 1;
} else if (rules[67][lastState]) {
state = 67;
- left = 1;
- } else {
+ left = 1;
+ } else {
if (!rules[67][lastState] && newLineFlag) {
asi(true);
- }
+ }
state = 67;
left = 1;
}
@@ -7543,7 +7449,7 @@
function number() {
while (pos < len) {
chr = code.charCodeAt(pos);
- if (chr >= 0x31 && chr <= 0x39) {
+ if (chr >= 0x31 && chr <= 0x39) {
if (states.e) {
states.e = 2;
}
@@ -7579,7 +7485,7 @@
if (states.dot || states.e || (states.zeroFirst && states.output.length != 1)) {
break;
}
- states.dot = 1;
+ states.dot = 1;
} else {
cached = chr;
break;
@@ -7649,8 +7555,8 @@
states.dot = 1;
states.dotFirst = 1;
} else if (chr === 0x30) {
- states.zeroFirst = 1;
- states.output += '' + code.charAt(pos);
+ states.zeroFirst = 1;
+ states.output += '' + code.charAt(pos);
} else {
states.output = code.charAt(pos);
}
@@ -8500,7 +8406,7 @@
if (parseTreeFlag) {
that.parseTree(parseTreeOutput);
}
- if (convertedFlag) {
+ if (convertedFlag) {
that.converted(output);
}
return output;
@@ -8523,10 +8429,10 @@
}
if (obj.complete) {
this.complete = obj.complete;
- }
+ }
if (obj.parseTree) {
this.parseTree = obj.parseTree;
- }
+ }
converted = rewrite(obj.code);
if (this.options.eval) {
return execute(converted);
@@ -8537,4 +8443,4 @@
};
return new Mental;
};
-})( typeof exports === "undefined" ? (window.mentaljs = {}) : exports);
\ No newline at end of file
+})( typeof exports === "undefined" ? (window.mentaljs = {}) : exports);
diff --git a/package.json b/package.json
index 4e46575..0a40660 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,10 @@
"test:ci": "npm run lint && npm run test:jsdom && (([ \"${TRAVIS_PULL_REQUEST}\" != \"false\" ] || [ \"${TEST_BROWSERSTACK}\" != \"true\" ]) || karma start test/karma.conf.js --log-level error --reporters dots --single-run)",
"test": "npm run lint && npm run test:jsdom && npm run test:karma -- --browsers Firefox,Chrome"
},
+ "files": [
+ "src",
+ "dist"
+ ],
"pre-commit": [
"lint",
"minify",
@@ -37,7 +41,7 @@
},
"name": "dompurify",
"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.8.2",
+ "version": "0.8.9",
"main": "src/purify.js",
"directories": {
"test": "test"
diff --git a/src/purify.js b/src/purify.js
index 4270208..8a5e2ab 100644
--- a/src/purify.js
+++ b/src/purify.js
@@ -21,7 +21,7 @@
* Version label, exposed for easier checks
* if DOMPurify is up to date or not
*/
- DOMPurify.version = '0.8.2';
+ DOMPurify.version = '0.9.0';
/**
* Array of elements that DOMPurify removed during sanitation.
@@ -40,12 +40,17 @@
var originalDocument = document;
var DocumentFragment = window.DocumentFragment;
var HTMLTemplateElement = window.HTMLTemplateElement;
+ var Node = window.Node;
var NodeFilter = window.NodeFilter;
var NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap;
var Text = window.Text;
var Comment = window.Comment;
var DOMParser = window.DOMParser;
-
+ var XMLHttpRequest = window.XMLHttpRequest;
+ var encodeURI = window.encodeURI;
+ var useXHR = false;
+ var useDOMParser = false; // See comment below
+
// As per issue #47, the web-components registry is inherited by a
// new document created via createHTMLDocument. As per the spec
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
@@ -158,7 +163,7 @@
'hreflang','id','ismap','label','lang','list','loop', 'low','max',
'maxlength','media','method','min','multiple','name','noshade','novalidate',
'nowrap','open','optimum','pattern','placeholder','poster','preload','pubdate',
- 'radiogroup','readonly','rel','required','rev','reversed','rows',
+ 'radiogroup','readonly','rel','required','rev','reversed','role','rows',
'rowspan','spellcheck','scope','selected','shape','size','span',
'srclang','start','src','step','style','summary','tabindex','title',
'type','usemap','valign','value','width','xmlns',
@@ -212,6 +217,9 @@
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
var FORBID_ATTR = null;
+ /* Decide if ARIA attributes are okay */
+ var ALLOW_ARIA_ATTR = true;
+
/* Decide if custom data attributes are okay */
var ALLOW_DATA_ATTR = true;
@@ -233,6 +241,13 @@
/* Decide if document with <html>... should be returned */
var WHOLE_DOCUMENT = false;
+ /* Track whether config is already set on this instance of DOMPurify. */
+ var SET_CONFIG = false;
+
+ /* Decide if all elements (e.g. style, script) must be children of
+ * document.body. By default, browsers might move them to document.head */
+ var FORCE_BODY = false;
+
/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string.
* If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
*/
@@ -255,12 +270,12 @@
/* Tags to ignore content of when KEEP_CONTENT is true */
var FORBID_CONTENTS = _addToSet({}, [
- 'audio', 'head', 'math', 'script', 'style', 'svg', 'video'
+ 'audio', 'head', 'math', 'script', 'style', 'template', 'svg', 'video'
]);
/* Tags that are safe for data: URIs */
var DATA_URI_TAGS = _addToSet({}, [
- 'audio', 'video', 'img', 'source'
+ 'audio', 'video', 'img', 'source', 'image'
]);
/* Attributes safe for values like "javascript:" */
@@ -297,6 +312,7 @@
_addToSet({}, cfg.FORBID_TAGS) : {};
FORBID_ATTR = 'FORBID_ATTR' in cfg ?
_addToSet({}, cfg.FORBID_ATTR) : {};
+ ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false
@@ -305,6 +321,7 @@
RETURN_DOM = cfg.RETURN_DOM || false; // Default false
RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false
+ FORCE_BODY = cfg.FORCE_BODY || false; // Default false
SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
@@ -329,6 +346,9 @@
}
_addToSet(ALLOWED_ATTR, cfg.ADD_ATTR);
}
+ if (cfg.ADD_URI_SAFE_ATTR) {
+ _addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
+ }
/* Add #text in case KEEP_CONTENT is set to true */
if (KEEP_CONTENT) { ALLOWED_TAGS['#text'] = true; }
@@ -375,15 +395,35 @@
* @return a DOM, filled with the dirty markup
*/
var _initDocument = function(dirty) {
- /* Create a HTML document using DOMParser */
+ /* Create a HTML document */
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.
- See #159 why the check here is extra-thorough */
+ /* Fill body with bogus element */
+ if (FORCE_BODY) {
+ dirty = '<remove></remove>' + dirty;
+ }
+
+ /* Use XHR if necessary because Safari 10.1 and newer are buggy */
+ if (useXHR) {
+ try {
+ dirty = encodeURI(dirty);
+ } catch (e) {}
+ var xhr = new XMLHttpRequest();
+ xhr.responseType = 'document';
+ xhr.open('GET', 'data:text/html;charset=utf-8,' + dirty, false);
+ xhr.send(null);
+ doc = xhr.response;
+ }
+
+ /* Use DOMParser to workaround Firefox bug (see comment below) */
+ if (useDOMParser) {
+ try {
+ doc = new DOMParser().parseFromString(dirty, 'text/html');
+ } catch (e) {}
+ }
+
+ /* Otherwise use createHTMLDocument, because DOMParser is unsafe in
+ Safari (see comment below) */
if (!doc || !doc.documentElement) {
doc = implementation.createHTMLDocument('');
body = doc.body;
@@ -392,14 +432,41 @@
}
/* Work on whole document or just its body */
- if (typeof doc.getElementsByTagName === 'function') {
- return doc.getElementsByTagName(
- WHOLE_DOCUMENT ? 'html' : 'body')[0];
- }
return getElementsByTagName.call(doc,
WHOLE_DOCUMENT ? 'html' : 'body')[0];
};
+ // Safari 10.1+ (unfixed as of time of writing) has a catastrophic bug in
+ // its implementation of DOMParser such that the following executes the
+ // JavaScript:
+ //
+ // new DOMParser()
+ // .parseFromString('<svg onload=alert(document.domain)>', 'text/html');
+ //
+ // Later, it was also noticed that even more assumed benign and inert ways
+ // of creating a document are now insecure thanks to Safari. So we work
+ // around that with a feature test and use XHR to create the document in
+ // case we really have to. That one seems safe for now.
+ //
+ // However, Firefox uses a different parser for innerHTML rather than
+ // DOMParser (see https://bugzilla.mozilla.org/show_bug.cgi?id=1205631)
+ // which means that you *must* use DOMParser, otherwise the output may
+ // not be safe if used in a document.write context later.
+ //
+ // So we feature detect the Firefox bug and use the DOMParser if necessary.
+ if (DOMPurify.isSupported) {
+ (function () {
+ var doc = _initDocument('<svg><g onload="this.parentNode.remove()"></g></svg>');
+ if (!doc.querySelector('svg')) {
+ useXHR = true;
+ }
+ doc = _initDocument('<svg><p><style><img src="</style><img src=x onerror=alert(1)//">');
+ if (doc.querySelector('svg img')) {
+ useDOMParser = true;
+ }
+ }());
+ }
+
/**
* _createIterator
*
@@ -440,6 +507,20 @@
};
/**
+ * _isNode
+ *
+ * @param object to check whether it's a DOM node
+ * @return true is object is a DOM node
+ */
+ var _isNode = function(obj) {
+ return (
+ typeof Node === "object" ? obj instanceof Node : obj
+ && typeof obj === "object" && typeof obj.nodeType === "number"
+ && typeof obj.nodeName==="string"
+ );
+ };
+
+ /**
* _sanitizeElements
*
* @protect nodeName
@@ -451,6 +532,7 @@
*/
var _sanitizeElements = function(currentNode) {
var tagName, content;
+
/* Execute a hook if present */
_executeHook('beforeSanitizeElements', currentNode, null);
@@ -465,7 +547,8 @@
/* Execute a hook if present */
_executeHook('uponSanitizeElement', currentNode, {
- tagName: tagName
+ tagName: tagName,
+ allowedTags: ALLOWED_TAGS
});
/* Remove element if anything forbids its presence */
@@ -508,6 +591,7 @@
};
var DATA_ATTR = /^data-[\-\w.\u00B7-\uFFFF]/;
+ var ARIA_ATTR = /^aria-[\-\w]+$/;
var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
var IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i;
/* This needs to be extensive thanks to Webkit/Blink's behavior */
@@ -537,7 +621,8 @@
hookEvent = {
attrName: '',
attrValue: '',
- keepAttr: true
+ keepAttr: true,
+ allowedAttributes: ALLOWED_ATTR
};
l = attributes.length;
@@ -545,7 +630,7 @@
while (l--) {
attr = attributes[l];
name = attr.name;
- value = attr.value;
+ value = attr.value.trim();
lcName = name.toLowerCase();
/* Execute a hook if present */
@@ -568,10 +653,16 @@
if (attributes.indexOf(idAttr) > l) {
currentNode.setAttribute('id', idAttr.value);
}
+ } else if (
+ // This works around a bug in Safari, where input[type=file]
+ // cannot be dynamically set after type has been removed
+ currentNode.nodeName === 'INPUT' && lcName === 'type' &&
+ value === 'file' && (ALLOWED_ATTR[lcName] || !FORBID_ATTR[lcName])) {
+ continue;
} else {
// This avoids a crash in Safari v9.0 with double-ids.
// The trick is to first set the id to be empty and then to
- // remove the attriubute
+ // remove the attribute
if (name === 'id') {
currentNode.setAttribute(name, '');
}
@@ -603,6 +694,9 @@
if (ALLOW_DATA_ATTR && DATA_ATTR.test(lcName)) {
// This attribute is safe
}
+ else if (ALLOW_ARIA_ATTR && ARIA_ATTR.test(lcName)) {
+ // This attribute is safe
+ }
/* Otherwise, check the name is permitted */
else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
continue;
@@ -616,9 +710,9 @@
else if (IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE,''))) {
// This attribute is safe
}
- /* Keep image data URIs alive if src is allowed */
+ /* Keep image data URIs alive if src/xlink:href is allowed */
else if (
- lcName === 'src' &&
+ (lcName === 'src' || lcName === 'xlink:href') &&
value.indexOf('data:') === 0 &&
DATA_URI_TAGS[currentNode.nodeName.toLowerCase()]) {
// This attribute is safe
@@ -705,20 +799,20 @@
* sanitize
* Public method providing core sanitation functionality
*
- * @param {String} dirty string
+ * @param {String|Node} dirty string or DOM node
* @param {Object} configuration object
*/
DOMPurify.sanitize = function(dirty, cfg) {
- var body, currentNode, oldNode, nodeIterator, returnNode;
+ var body, importedNode, currentNode, oldNode, nodeIterator, returnNode;
/* Make sure we have a string to sanitize.
DO NOT return early, as this will return the wrong type if
the user has requested a DOM object rather than a string */
if (!dirty) {
- dirty = '';
+ dirty = '<!-->';
}
/* Stringify, in case dirty is an object */
- if (typeof dirty !== 'string') {
+ if (typeof dirty !== 'string' && !_isNode(dirty)) {
if (typeof dirty.toString !== 'function') {
throw new TypeError('toString is not a function');
} else {
@@ -730,28 +824,52 @@
if (!DOMPurify.isSupported) {
if (typeof window.toStaticHTML === 'object'
|| typeof window.toStaticHTML === 'function') {
- return window.toStaticHTML(dirty);
+ if (typeof dirty === 'string') {
+ return window.toStaticHTML(dirty);
+ } else if (_isNode(dirty)) {
+ return window.toStaticHTML(dirty.outerHTML);
+ }
}
return dirty;
}
/* Assign config vars */
- _parseConfig(cfg);
+ if (!SET_CONFIG) {
+ _parseConfig(cfg);
+ }
/* Clean up removed elements */
DOMPurify.removed = [];
- /* Exit directly if we have nothing to do */
- if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) {
- return dirty;
- }
+ if (dirty instanceof Node) {
+ /* If dirty is a DOM element, append to an empty document to avoid
+ elements being stripped by the parser */
+ body = _initDocument('<!-->');
+ importedNode = body.ownerDocument.importNode(dirty, true);
+ if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
+ /* Node is already a body, use as is */
+ body = importedNode;
+ } else {
+ body.appendChild(importedNode);
+ }
+ } else {
+ /* Exit directly if we have nothing to do */
+ if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) {
+ return dirty;
+ }
- /* Initialize the document to work on */
- body = _initDocument(dirty);
+ /* Initialize the document to work on */
+ body = _initDocument(dirty);
- /* Check we have a DOM node from the data */
- if (!body) {
- return RETURN_DOM ? null : '';
+ /* Check we have a DOM node from the data */
+ if (!body) {
+ return RETURN_DOM ? null : '';
+ }
+ }
+
+ /* Remove first element node (ours) if FORCE_BODY is set */
+ if (FORCE_BODY) {
+ _forceRemove(body.firstChild);
}
/* Get node iterator */
@@ -810,6 +928,29 @@
};
/**
+ * setConfig
+ * Public method to set the configuration once
+ *
+ * @param {Object} configuration object
+ * @return void
+ */
+ DOMPurify.setConfig = function(cfg) {
+ _parseConfig(cfg);
+ SET_CONFIG = true;
+ };
+
+ /**
+ * clearConfig
+ * Public method to remove the configuration
+ *
+ * @return void
+ */
+ DOMPurify.clearConfig = function() {
+ CONFIG = null;
+ SET_CONFIG = false;
+ };
+
+ /**
* addHook
* Public method to add DOMPurify hooks
*
diff --git a/test/fixtures/expect.js b/test/fixtures/expect.js
index 19db849..ac4cfd6 100644
--- a/test/fixtures/expect.js
+++ b/test/fixtures/expect.js
@@ -1,5 +1,23 @@
module.exports = [
{
+ "title": "Don't remove data URIs from SVG imaged (see #205)",
+ "payload": "<svg><image id=\"v-146\" width=\"500\" height=\"500\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xlink:href=\"data:image/svg+xml;utf8,%3Csvg%20viewBox%3D%220%200%20100%20100%22%20height%3D%22100%22%20width%3D%22100%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20data-name%3D%22Layer%201%22%20id%3D%22Layer_1%22%3E%0A%20%20%3Ctitle%3ECompute%3C%2Ftitle%3E%0A%20%20%3Cg%3E%0A%20%20%20%20%3Crect%20fill%3D%22%239d5025%22%20ry%3D%229.12%22%20rx%3D%229.12%22%20heigh [...]
+ "expected": [
+ "<svg><image style=\"border-color: rgb(51, 51, 51); box-sizing: border-box; color: rgb(51, 51, 51); cursor: move; font-family: sans-serif; font-size: 14px; line-height: 20px; outline-color: rgb(51, 51, 51); text-size-adjust: 100%; column-rule-color: rgb(51, 51, 51); -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-text-emphasis-color: rgb(51, 51, 51); -webkit-text-fill-color: rgb(51, 51, 51); -webkit-text-stroke-color: rgb(51, 51, 51); [...]
+ "<svg xmlns=\"http://www.w3.org/2000/svg\"><image id=\"v-146\" style=\"border-color: rgb(51, 51, 51); box-sizing: border-box; color: rgb(51, 51, 51); cursor: move; font-family: sans-serif; font-size: 14px; line-height: 20px; outline-color: rgb(51, 51, 51); text-size-adjust: 100%; column-rule-color: rgb(51, 51, 51); -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-text-emphasis-color: rgb(51, 51, 51); -webkit-text-fill-color: rgb(51, 51 [...]
+ "<svg xmlns=\"http://www.w3.org/2000/svg\"><image id=\"v-146\" style=\"border-color: rgb(51, 51, 51); box-sizing: border-box; color: rgb(51, 51, 51); cursor: move; font-family: sans-serif; font-size: 14px; line-height: 20px; outline-color: rgb(51, 51, 51); text-size-adjust: 100%; column-rule-color: rgb(51, 51, 51); -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-text-emphasis-color: rgb(51, 51, 51); -webkit-text-fill-color: rgb(51, 51 [...]
+ "<svg xmlns=\"http://www.w3.org/2000/svg\"><image id=\"v-146\" style=\"border-color: rgb(51, 51, 51); color: rgb(51, 51, 51); line-height: 20px; font-family: sans-serif; font-size: 14px; cursor: move; outline-color: rgb(51, 51, 51); box-sizing: border-box; column-rule-color: rgb(51, 51, 51); text-size-adjust: 100%; -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-text-emphasis-color: rgb(51, 51, 51); -webkit-text-fill-color: rgb(51, 51 [...]
+ ]
+ },
+ {
+ "title": "Don't remove ARIA attributes if not prohibited (see #203)",
+ "payload": "<div aria-labelledby=\"msg--title\" role=\"dialog\" class=\"msg\"><button class=\"modal-close\" aria-label=\"close\" type=\"button\"><i class=\"icon-close\"></i>some button</button></div>",
+ "expected": [
+ "<div class=\"msg\" role=\"dialog\" aria-labelledby=\"msg--title\"><button type=\"button\" aria-label=\"close\" class=\"modal-close\"><i class=\"icon-close\"></i>some button</button></div>",
+ "<div class=\"msg\" role=\"dialog\" aria-labelledby=\"msg--title\"><button class=\"modal-close\" aria-label=\"close\" type=\"button\"><i class=\"icon-close\"></i>some button</button></div>"
+ ]
+ },
+ {
"title": "Don't remove binary attributes if considered safe (see #168)",
"payload": "<input type=checkbox checked><input type=checkbox onclick>",
"expected": [
@@ -30,7 +48,7 @@ module.exports = [
"title": "src Attributes for IMG, AUDIO, VIDEO and SOURCE (see #131)",
"payload": "<img src=\"data:,123\"><audio src=\"data:,456\"></audio><video src=\"data:,789\"></video><source src=\"data:,012\"><div src=\"data:,345\">",
"expected": "<img src=\"data:,123\"><audio src=\"data:,456\"></audio><video src=\"data:,789\"></video><source src=\"data:,012\"><div></div>"
- },
+ },
{
"title": "DOM Clobbering against document.createElement() (see #47)",
"payload": "<img src=x name=createElement><img src=y id=createElement>",
@@ -56,6 +74,10 @@ module.exports = [
"payload": "<img src=data:image/jpeg,ab798ewqxbaudbuoibeqbla>",
"expected": "<img src=\"data:image/jpeg,ab798ewqxbaudbuoibeqbla\">"
}, {
+ "title": "Image with data URI src with whitespace",
+ "payload": "<img src=\"\r\ndata:image/jpeg,ab798ewqxbaudbuoibeqbla\">",
+ "expected": "<img src=\"data:image/jpeg,ab798ewqxbaudbuoibeqbla\">"
+ }, {
"title": "Image with JavaScript URI src (DoS on Firefox)",
"payload": "<img src='javascript:while(1){}'>",
"expected": "<img>"
@@ -603,15 +625,6 @@ module.exports = [
"<div id=\"86\"><input>//[\"'`-->]]>]</div><div id=\"87\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<a xmlns:xlink=\"http://www.w3.org/1999/xlink\"><rect fill=\"white\" width=\"1000\" height=\"1000\" FILL=\"white\" /></a>\n</svg>//[\"'`-->]]>]</div>"
]
}, {
- "payload": "<div id=\"88\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<animation xlink:href=\"javascript:alert(88)\"/>\n<animation xlink:href=\"data:text/xml,%3Csvg xmlns='http://www.w3.org/2000/svg' onload='alert(88)'%3E%3C/svg%3E\"/>\n\n<image xlink:href=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' onload='alert(88)'%3E%3C/svg%3E\"/>\n\n<foreignObject xlink:href=\"javascript:alert(88)\"/>\n<foreignObject xlink:href=\ [...]
- "expected": [
- "<div id=\"88\"><svg xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\">\n\n\n\n<image></image>\n\n\n\n\n</svg>//[\"'`-->]]>]</div>",
- "<div id=\"88\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n\n\n\n<image></image>\n\n\n\n\n</svg>//[\"'`-->]]>]</div>",
- "<div id=\"88\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:NS1=\"\" NS1:xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n\n\n\n<image />\n\n\n\n\n</svg>//[\"'`-->]]>]</div>",
- "<div id=\"88\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\">\n\n\n\n<image />\n\n\n\n\n</svg>//[\"'`-->]]>]</div>",
- "<div id=\"88\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n\n\n\n<image />\n\n\n\n\n</svg>//[\"'`-->]]>]</div>"
- ]
- }, {
"payload": "<div id=\"89\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<set attributeName=\"onmouseover\" to=\"alert(89)\"/>\n<animate attributeName=\"onunload\" to=\"alert(89)\"/>\n</svg>//[\"'`-->]]>]</div>",
"expected": [
"<div id=\"89\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n\n\n</svg>//[\"'`-->]]>]</div>",
@@ -656,6 +669,7 @@ module.exports = [
"<div id=\"95\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n\n\n\n</svg>//[\"'`-->]]>]</div>",
"<div id=\"95\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n\n</svg>//[\"'`-->]]>]</div>",
"<div id=\"95\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:NS1=\"\" NS1:xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n\n</svg>//[\"'`-->]]>]</div>",
+ "<div id=\"95\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:NS1=\"\" NS1:xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\">\n\n</svg>//[\"'`-->]]>]</div>",
"<div id=\"95\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\">\n\n</svg>//[\"'`-->]]>]</div>",
"<div id=\"95\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n\n</svg>//[\"'`-->]]>]</div>"
]
diff --git a/test/karma.conf.js b/test/karma.conf.js
index 11296f7..a742525 100644
--- a/test/karma.conf.js
+++ b/test/karma.conf.js
@@ -84,14 +84,22 @@ module.exports = function(config) {
browser: 'firefox',
os_version: 'Yosemite'
},
- bs_yosemite_safari_8: {
+ bs_sierra_safari_10: {
+ base: 'BrowserStack',
+ device: null,
+ os: 'OS X',
+ browser_version: '10.0',
+ browser: 'safari',
+ os_version: 'Sierra'
+ },
+ bs_yosemite_safari_9: {
base: 'BrowserStack',
device: null,
os: 'OS X',
browser_version: '8.0',
browser: 'safari',
os_version: 'Yosemite'
- },
+ },
bs_win81_opera_31: {
base: 'BrowserStack',
device: null,
@@ -132,6 +140,14 @@ module.exports = function(config) {
browser: 'edge',
os_version: '10'
},
+ bs_win10_edge_14: {
+ base: 'BrowserStack',
+ device: null,
+ os: 'Windows',
+ browser_version: '14.0',
+ browser: 'edge',
+ os_version: '10'
+ },
bs_win10_firefox_46: {
base: 'BrowserStack',
device: null,
@@ -140,6 +156,14 @@ module.exports = function(config) {
browser: 'firefox',
os_version: '10'
},
+ bs_win10_firefox_52: {
+ base: 'BrowserStack',
+ device: null,
+ os: 'Windows',
+ browser_version: '52.0',
+ browser: 'firefox',
+ os_version: '10'
+ },
bs_win10_chrome_50: {
base: 'BrowserStack',
device: null,
@@ -147,6 +171,14 @@ module.exports = function(config) {
browser_version: '50.0',
browser: 'chrome',
os_version: '10'
+ },
+ bs_win10_chrome_57: {
+ base: 'BrowserStack',
+ device: null,
+ os: 'Windows',
+ browser_version: '57.0',
+ browser: 'chrome',
+ os_version: '10'
}
},
@@ -156,13 +188,17 @@ module.exports = function(config) {
'bs_mavericks_chrome_44',
'bs_yosemite_firefox_40',
'bs_yosemite_safari_8',
+ 'bs_sierra_safari_10',
'bs_win81_opera_31',
'bs_win7_firefox_20',
'bs_win7_firefox_15',
'bs_win81_chrome_22',
'bs_win10_edge_13',
+ 'bs_win10_edge_14',
'bs_win10_firefox_46',
- 'bs_win10_chrome_50'
+ 'bs_win10_firefox_52',
+ 'bs_win10_chrome_50',
+ 'bs_win10_chrome_57'
],
browserDisconnectTimeout: 10000,
diff --git a/test/test-suite.js b/test/test-suite.js
index c723fb8..090f266 100644
--- a/test/test-suite.js
+++ b/test/test-suite.js
@@ -7,7 +7,7 @@ module.exports = function(DOMPurify, window, tests, xssTests) {
.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
@@ -74,15 +74,15 @@ module.exports = function(DOMPurify, window, tests, xssTests) {
assert.equal( DOMPurify.sanitize( '<a>123{{45{{6}}<b><style><% alert(1)%> %></style>456</b></a>', {SAFE_FOR_TEMPLATES: true}), "<a> <b><style> </style>456</b></a>" );
assert.equal( DOMPurify.sanitize( '<a>123{{45}}6}}<b><style><% <%alert(1) %></style>456</b></a>', {SAFE_FOR_TEMPLATES: true}), "<a> <b><style> </style>456</b></a>" );
assert.equal( DOMPurify.sanitize( '<a>123{{<b>456}}</b><style><% alert(1) %></style>456</a>', {SAFE_FOR_TEMPLATES: true}), "<a>123 <b> </b><style> </style>456</a>" );
- assert.contains( DOMPurify.sanitize( '<b>{{evil<script>alert(1)</script><form><img src=x name=textContent></form>}}</b>', {SAFE_FOR_TEMPLATES: true}),
- ["<b> </b>", "<b> </b>", "<b> <form><img src=\"x\"></form> </b>"]
+ assert.contains( DOMPurify.sanitize( '<b>{{evil<script>alert(1)</script><form><img src=x name=textContent></form>}}</b>', {SAFE_FOR_TEMPLATES: true}),
+ ["<b> </b>", "<b> </b>", "<b> <form><img src=\"x\"></form> </b>"]
);
- assert.contains( DOMPurify.sanitize( '<b>he{{evil<script>alert(1)</script><form><img src=x name=textContent></form>}}ya</b>', {SAFE_FOR_TEMPLATES: true}),
+ assert.contains( DOMPurify.sanitize( '<b>he{{evil<script>alert(1)</script><form><img src=x name=textContent></form>}}ya</b>', {SAFE_FOR_TEMPLATES: true}),
["<b>he ya</b>", "<b>he </b>", "<b>he <form><img src=\"x\"></form> ya</b>"]
);
assert.equal( DOMPurify.sanitize( '<a>123<% <b>456}}</b><style>{{ alert(1) }}</style>456 %></a>', {SAFE_FOR_TEMPLATES: true}), "<a>123 <b> </b><style> </style> </a>" );
assert.equal( DOMPurify.sanitize( '<a href="}}javascript:alert(1)"></a>', {SAFE_FOR_TEMPLATES: true}), "<a></a>" );
- });
+ });
QUnit.test( 'Config-Flag tests: SANITIZE_DOM', function(assert) {
// SANITIZE_DOM
assert.equal( DOMPurify.sanitize( '<form name="window">', {SANITIZE_DOM: true}), "<form></form>" );
@@ -155,7 +155,7 @@ module.exports = function(DOMPurify, window, tests, xssTests) {
QUnit.test( 'Test dirty being an array', function(assert) {
assert.equal( DOMPurify.sanitize( ['<a>123<b>456</b></a>']), "<a>123<b>456</b></a>" );
assert.equal( DOMPurify.sanitize( ['<img src=', 'x onerror=alert(1)>']), "<img src=\",x\">" );
- });
+ });
// XSS tests: Native DOM methods (alert() should not be called)
QUnit
.cases(xssTests)
@@ -195,7 +195,7 @@ module.exports = function(DOMPurify, window, tests, xssTests) {
window.xssed = false;
iframe.parentNode.removeChild(iframe);
}
- document.body.appendChild(iframe);
+ document.body.appendChild(iframe);
});
// cross-check that document.write into iframe works properly
QUnit
@@ -211,7 +211,7 @@ module.exports = function(DOMPurify, window, tests, xssTests) {
iframe.parentNode.removeChild(iframe);
}
document.body.appendChild(iframe);
- });
+ });
// Check for isSupported property
QUnit.test( 'DOMPurify property tests', function(assert) {
assert.equal( typeof DOMPurify.isSupported, 'boolean' );
@@ -260,9 +260,38 @@ module.exports = function(DOMPurify, window, tests, xssTests) {
});
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));
+ assert.equal(DOMPurify.sanitize(dirty), modified);
DOMPurify.removeHooks('afterSanitizeElements')
} );
+ // Tests to ensure that a configuration can be set and cleared
+ QUnit.test( 'ensure that a persistent configuration can be set and cleared', function(assert) {
+ var dirty = '<my-component>abc</my-component>';
+ assert.equal( DOMPurify.sanitize(dirty), "abc");
+ DOMPurify.setConfig({ADD_TAGS: ['my-component']});
+ assert.equal( DOMPurify.sanitize(dirty), '<my-component>abc</my-component>');
+ DOMPurify.clearConfig();
+ assert.equal( DOMPurify.sanitize(dirty), "abc");
+ });
+ // Test to ensure that a hook can add allowed tags / attributes on the fly
+ QUnit.test( 'ensure that a hook can add allowed tags / attributes on the fly', function(assert) {
+ DOMPurify.addHook('uponSanitizeElement', function(node, data){
+ if(node.nodeName && node.nodeName.match(/^\w+-\w+$/)
+ && !data.allowedTags[data.tagName]) {
+ data.allowedTags[data.tagName] = true;
+ }
+ });
+ DOMPurify.addHook('uponSanitizeAttribute', function(node, data){
+ if(data.attrName && data.attrName.match(/^\w+-\w+$/)
+ && !data.allowedAttributes[data.attrName]) {
+ data.allowedAttributes[data.attrName] = true;
+ }
+ });
+ var dirty = '<p>HE<iframe></iframe><is-custom onload="alert(1)" super-custom="test" />LLO</p>';
+ var modified = '<p>HE<is-custom super-custom="test">LLO</is-custom></p>';
+ assert.equal(DOMPurify.sanitize(dirty), modified);
+ DOMPurify.removeHooks('uponSanitizeElement');
+ DOMPurify.removeHooks('uponSanitizeAttribute');
+ } );
QUnit.test( 'sanitize() should allow unknown protocols when ALLOW_UNKNOWN_PROTOCOLS is true', function (assert) {
var dirty = '<div><a href="spotify:track:12345"><img src="cid:1234567"></a></div>';
assert.equal(dirty, DOMPurify.sanitize(dirty, {ALLOW_UNKNOWN_PROTOCOLS: true}));
@@ -278,7 +307,7 @@ module.exports = function(DOMPurify, window, tests, xssTests) {
var dirty = '<p onFoo="123">HELLO</p>';
var modified = '<p>HELLO</p>';
assert.equal(modified, DOMPurify.sanitize(dirty, {ALLOW_UNKNOWN_PROTOCOLS: true}));
- } );
+ } );
// Test 1 to check if the element count in DOMPurify.removed is correct
QUnit.test( 'DOMPurify.removed should contain one element', function (assert) {
@@ -301,35 +330,35 @@ module.exports = function(DOMPurify, window, tests, xssTests) {
assert.equal(DOMPurify.removed.length, 1);
} );
- // Test 4 to check that DOMPurify.removed is correct in SAFE_FOR_TEMLATES mode
+ // Test 4 to check that DOMPurify.removed is correct in SAFE_FOR_TEMLATES mode
QUnit.test( 'DOMPurify.removed should be correct in SAFE_FOR_TEMPLATES mode', function (assert) {
var dirty = '<a>123{{456}}</a>';
DOMPurify.sanitize(dirty, {WHOLE_DOCUMENT: true, SAFE_FOR_TEMPLATES: true});
assert.equal(DOMPurify.removed.length, 1);
} );
- // Test 5 to check that DOMPurify.removed is correct in SAFE_FOR_TEMLATES mode
+ // Test 5 to check that DOMPurify.removed is correct in SAFE_FOR_TEMLATES mode
QUnit.test( 'DOMPurify.removed should be correct in SAFE_FOR_TEMPLATES mode', function (assert) {
var dirty = '<a>123{{456}}<b>456{{789}}</b></a>';
DOMPurify.sanitize(dirty, {WHOLE_DOCUMENT: true, SAFE_FOR_TEMPLATES: true});
assert.equal(DOMPurify.removed.length, 2);
} );
- // Test 6 to check that DOMPurify.removed is correct in SAFE_FOR_TEMLATES mode
+ // Test 6 to check that DOMPurify.removed is correct in SAFE_FOR_TEMLATES mode
QUnit.test( 'DOMPurify.removed should be correct in SAFE_FOR_TEMPLATES mode', function (assert) {
var dirty = '<img src=1 width="{{123}}">';
DOMPurify.sanitize(dirty, {WHOLE_DOCUMENT: true, SAFE_FOR_TEMPLATES: true});
assert.equal(DOMPurify.removed.length, 1);
} );
- // Test 7 to check that DOMPurify.removed is correct in SAFE_FOR_JQUERY mode
+ // Test 7 to check that DOMPurify.removed is correct in SAFE_FOR_JQUERY mode
QUnit.test( 'DOMPurify.removed should be correct in SAFE_FOR_JQUERY mode', function (assert) {
var dirty = '<option><iframe></select><b><script>alert(1)<\/script>';
DOMPurify.sanitize(dirty, {SAFE_FOR_JQUERY: true});
assert.equal(DOMPurify.removed.length, 2);
} );
- // Test 8 to check that DOMPurify.removed is correct if tags are clean
+ // Test 8 to check that DOMPurify.removed is correct if tags are clean
QUnit.test( 'DOMPurify.removed should not contain elements if tags are permitted', function (assert) {
var dirty = '<a>123</a>';
DOMPurify.sanitize(dirty);
@@ -350,10 +379,55 @@ module.exports = function(DOMPurify, window, tests, xssTests) {
assert.equal(DOMPurify.removed.length, 0);
} );
- // Test 11 to check that DOMPurify.removed does not have false positive elements in SAFE_FOR_JQUERY mode
+ // Test 11 to check that DOMPurify.removed does not have false positive elements in SAFE_FOR_JQUERY mode
QUnit.test( 'DOMPurify.removed should not contain elements for valid data in SAFE_FOR_JQUERY mode', function (assert) {
var dirty = '1';
DOMPurify.sanitize(dirty, {WHOLE_DOCUMENT: true, SAFE_FOR_JQUERY: true});
assert.equal(DOMPurify.removed.length, 0);
} );
+ // Tests to make sure that the node scanning feature delivers acurate results on all browsers
+ QUnit.test( 'DOMPurify should deliver acurate results when sanitizing nodes 1', function (assert) {
+ var clean = DOMPurify.sanitize(document.createElement('td'));
+ assert.equal(clean, "<td></td>");
+ } );
+ QUnit.test( 'DOMPurify should deliver acurate results when sanitizing nodes 2', function (assert) {
+ var clean = DOMPurify.sanitize(document.createElement('td'), {RETURN_DOM: true});
+ assert.equal(clean.outerHTML, "<body><td></td></body>");
+ } );
+ // Test to make sure that URI_safe attributes can be configured too
+ QUnit.test( 'DOMPurify should deliver acurate results when sanitizing nodes 2', function (assert) {
+ var clean = DOMPurify.sanitize('<b typeof="bla:h">123</b>', {ALLOWED_ATTR: ['typeof'], ADD_URI_SAFE_ATTR: ['typeof']});
+ assert.equal(clean, "<b typeof=\"bla:h\">123</b>");
+ } );
+ // Test to make sure that empty HTML doesn't return null on MSIE11 (#198)
+ QUnit.test( 'Empty HTML shouldn\'t return null on MSIE11 in RETURN_DOM_FRAGMENT mode', function (assert) {
+ var clean = DOMPurify.sanitize('', {RETURN_DOM: true, RETURN_DOM_FRAGMENT: true});
+ assert.equal(typeof clean, "object");
+ } );
+ // Tests to make sure that FORCE_BODY pushes elements to document.body (#199)
+ QUnit.test( 'FORCE_BODY needs to push some elements to document.body', function (assert) {
+ var clean = DOMPurify.sanitize('<style>123</style>', {FORCE_BODY: true});
+ assert.equal(clean, "<style>123</style>");
+ } );
+ QUnit.test( 'FORCE_BODY needs to push some elements to document.body', function (assert) {
+ var clean = DOMPurify.sanitize('<script>123</script>', {FORCE_BODY: true, ADD_TAGS: ['script']});
+ assert.equal(clean, "<script>123</script>");
+ } );
+ QUnit.test( 'FORCE_BODY needs to push some elements to document.body', function (assert) {
+ var clean = DOMPurify.sanitize(' AAAAA', {FORCE_BODY: true});
+ assert.equal(clean, " AAAAA");
+ } );
+ QUnit.test( 'Lack of FORCE_BODY needs to push some elements to document.head', function (assert) {
+ var clean = DOMPurify.sanitize('<style>123</style>', {FORCE_BODY: false});
+ assert.equal(clean, "");
+ } );
+ // Test to make sure that ALLOW_ARIA_ATTR is working as expected (#198)
+ QUnit.test( 'Config-Flag tests: ALLOW_ARIA_ATTR', function(assert) {
+ assert.contains( DOMPurify.sanitize( "<a aria-abc=\"foo\" href=\"#\">abc</a>", {ALLOW_ARIA_ATTR: true}),
+ ["<a aria-abc=\"foo\" href=\"#\">abc</a>", "<a href=\"#\" aria-abc=\"foo\">abc</a>"]
+ );
+ assert.equal( DOMPurify.sanitize( '<a href="#" aria-aöü="foo">abc</a>', {ALLOW_ARIA_ATTR: true}), '<a href="#">abc</a>' );
+ assert.equal( DOMPurify.sanitize( '<a href="#" aria-abc="foo">abc</a>', {ALLOW_ARIA_ATTR: false}), "<a href=\"#\">abc</a>" );
+ assert.equal( DOMPurify.sanitize( '<a href="#" aria-äöü="foo">abc</a>', {ALLOW_ARIA_ATTR: false}), "<a href=\"#\">abc</a>" );
+ });
}
diff --git a/website/index.html b/website/index.html
index 43995ad..9b0ca61 100644
--- a/website/index.html
+++ b/website/index.html
@@ -2,7 +2,7 @@
<html>
<head>
<meta charset="UTF-8">
- <title>DOMPurify 0.8.2 "Wapiti Elk"</title>
+ <title>DOMPurify 0.9.0 "Pears not Apples"</title>
<script src="../dist/purify.min.js"></script>
<!-- we don't actually need it - just to demo and test the $(html) sanitation -->
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
@@ -23,7 +23,7 @@
</script>
</head>
<body>
- <h4>DOMPurify 0.8.2 "Wapiti Elk"</h4>
+ <h4>DOMPurify 0.9.0 "Pears not Apples"</h4>
<p>
<a href="http://badge.fury.io/bo/dompurify"><img style="max-width:100%;" alt="Bower version" src="https://badge.fury.io/bo/dompurify.svg"></a> · <a href="http://badge.fury.io/js/dompurify"><img style="max-width:100%;" alt="npm version" src="https://badge.fury.io/js/dompurify.svg"></a> · <a href="https://travis-ci.org/cure53/DOMPurify"><img style="max-width:100%;" alt="Build Status" src="https://travis-ci.org/cure53/DOMPurify.svg?branch=master"></a>
</p>
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/dompurify.js.git
More information about the Pkg-javascript-commits
mailing list