[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