[Pkg-javascript-commits] [dompurify.js] 01/03: Imported Upstream version 0.8.2~dfsg1

Alexandre Viau aviau at moszumanska.debian.org
Sat Jun 25 16:58:02 UTC 2016


This is an automated email from the git hooks/post-receive script.

aviau pushed a commit to branch master
in repository dompurify.js.

commit cd6c66edd7966f425512d1f721f0f73121c2b075
Author: aviau <alexandre at alexandreviau.net>
Date:   Sat Jun 25 18:51:36 2016 +0200

    Imported Upstream version 0.8.2~dfsg1
---
 .travis.yml               |  27 ++++++---
 README.md                 |  44 ++++++++++----
 bower.json                |   7 +--
 package.json              |  44 +++++++-------
 src/purify.js             | 143 ++++++++++++++++++++++++++++++++++------------
 test/fixtures/expect.js   |  70 ++++++++++++++++-------
 test/jsdom-node-runner.js |  24 ++++++++
 test/jsdom-node.js        |  52 +++++++++++++++++
 test/karma.conf.js        |  45 ++++++++++++---
 test/purify.min.spec.js   |   2 +-
 test/purify.spec.js       |   2 +-
 test/test-suite.js        | 100 +++++++++++++++++++++++++++++++-
 website/index.html        |   4 +-
 13 files changed, 445 insertions(+), 119 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 2076b69..cefdb1c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,22 @@
 sudo: false
 language: node_js
-node_js:
-- '4.2'
-before_install:
-- npm install -g bower
-- bower install
+script: npm run test:ci
 notifications:
-  email: false
+   email: false
 env:
-  global:
-  - secure: GQ/MxXGdlpjXdnASyAeuTUvErhx61l+IS2rCo7vIrxKFxIqpa7AQnGp/eoBDitscvDgYIiVhXOPnTdjYYT6UgMwX169Fm9lg3jbFDqAHCsWQmtEIDKHyNwT2gi97rLqBDyXpwTmQebF2s7P914OayyoaY1NnTU/JGRdDi79lm80=
-  - secure: ZWko24CzEV7geTReAQhMBElwB7WBj+V+tZztjpOrogMS7BLT43qwTNF9FPuvdk9JjBV/sEdz3NWZhutXDkcNyLdCtLNIgxRkqCbXXn+S4cW4lYxPHWXyvtNu7mzuow1chq74L45CBpOFzpnkUsP13X4F8rU7o8XDvDsoPHZZYtc=
+   global:
+      - secure: GQ/MxXGdlpjXdnASyAeuTUvErhx61l+IS2rCo7vIrxKFxIqpa7AQnGp/eoBDitscvDgYIiVhXOPnTdjYYT6UgMwX169Fm9lg3jbFDqAHCsWQmtEIDKHyNwT2gi97rLqBDyXpwTmQebF2s7P914OayyoaY1NnTU/JGRdDi79lm80=
+      - secure: ZWko24CzEV7geTReAQhMBElwB7WBj+V+tZztjpOrogMS7BLT43qwTNF9FPuvdk9JjBV/sEdz3NWZhutXDkcNyLdCtLNIgxRkqCbXXn+S4cW4lYxPHWXyvtNu7mzuow1chq74L45CBpOFzpnkUsP13X4F8rU7o8XDvDsoPHZZYtc=
+      - CXX=g++-4.8
+matrix:
+   include:
+      - node_js: "5"
+      - node_js: "6"
+      - node_js: "4"
+        env: TEST_BROWSERSTACK=true
+addons:
+  apt:
+    sources:
+      - ubuntu-toolchain-r-test
+    packages:
+      - g++-4.8
diff --git a/README.md b/README.md
index 0e21e5c..f88c6f7 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ 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 [9 different browsers](https://github.com/cure53/DOMPurify/blob/master/test/karma.conf.js#L125) right now.
+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 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)
 
@@ -38,6 +38,8 @@ var clean = DOMPurify.sanitize(dirty);
 
 The resulting HTML can be written into a DOM element using `innerHTML` or the DOM using `document.write()`. That is fully up to you. But keep in mind, if you use the sanitized HTML with jQuery's very insecure `elm.html()` method, then the `SAFE_FOR_JQUERY` flag has to be set to make sure it's safe! Other than that, all is fine.
 
+After sanitizing your markup, you can also have a look at the property `DOMPurify.removed` and find out, what elements and attributes were thrown out.
+
 If you're using an [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD) module loader like [Require.js](http://requirejs.org/), you can load this script asynchronously as well:
 
 ```javascript
@@ -46,17 +48,28 @@ require(['dompurify'], function(DOMPurify) {
 });
 ```
 
-You can also grab the files straight from npm (requires either [io.js](https://iojs.org) or [Browserify](http://browserify.org/), **Node.js 0.x is not supported**):  
+DOMPurify also works server-side with node.js as well as client-side via [Browserify](http://browserify.org/) or similar translators.  Node.js 0.x is not supported; either [io.js](https://iojs.org) or Node.js 4.x or newer is required.
 
 ```bash
 npm install dompurify
 ```
 
 ```javascript
-var DOMPurify = require('dompurify');
-var clean = DOMPurify.sanitize(dirty);
+const createDOMPurify = require('dompurify');
+const jsdom = require('jsdom');
+const window = jsdom.jsdom('', {
+  features: {
+    FetchExternalResources: false, // disables resource loading over HTTP / filesystem
+    ProcessExternalResources: false // do not execute JS within script blocks
+  }
+}).defaultView;
+const DOMPurify = createDOMPurify(window);
+
+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.
+
 ## Is there a demo?
 
 Of course there is a demo! [Play with DOMPurify](https://cure53.de/purify)
@@ -115,6 +128,10 @@ var clean = DOMPurify.sanitize(dirty, {ADD_ATTR: ['my-attr']});
 // prohibit HTML5 data attributes (default is true)
 var clean = DOMPurify.sanitize(dirty, {ALLOW_DATA_ATTR: false});
 
+// allow external protocol handlers in URL attributes (default is false)
+// by default only http, https, ftp, ftps, tel and mailto are allowed.
+var clean = DOMPurify.sanitize(dirty, {ALLOW_UNKNOWN_PROTOCOLS: true});
+
 // return a DOM HTMLBodyElement instead of an HTML string (default is false)
 var clean = DOMPurify.sanitize(dirty, {RETURN_DOM: true});
 
@@ -137,7 +154,7 @@ 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});
 ```
-There is even [more examples here](https://github.com/cure53/DOMPurify/tree/master/demos#what-it-this), showing how you can run, customize and configure DOMPurify to fit your needs.
+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.
 
 ## Hooks
 
@@ -168,7 +185,9 @@ DOMPurify.addHook('beforeSanitizeElements', function(currentNode, data, config)
 
 We are currently using Travis CI in combination with BrowserStack. This gives us the possibility to confirm for each and every commit that all is going according to plan in all supported browsers. Check out the build logs here: https://travis-ci.org/cure53/DOMPurify
 
-You can further run local tests by executing `npm run-script local-test` or, in case you have a BrowserStack account with automation available, run the tests using `npm run-script ci-test`.
+You can further run local tests by executing `npm test`. The tests work fine with Node.js v0.6.2 and jsdom at 8.5.0.
+
+All relevant commits will be signed with the key `0x24BB6BF4` for additional security (since 8th of April 2016).
 
 ## Security Mailing List
 
@@ -176,15 +195,18 @@ We maintain a mailing list that notifies whenever a security-critical release of
 
 [https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security](https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security)
 
+Feature releases will not be announced to this list.
+
+## Who contributed?
 
-## What's on the road-map?
+Several people need to be listed here! 
 
-We recently implemented a Hook-API allowing developers to create their own DOMPurify plugins and customize its functionality without changing the core. Thus, we are looking forward for plugins and extensions - pull requests are welcome! Oh, and we will increase the amount of browsers and HTML-mappings in our automates tests to make sure nothing slips through.
+[@garethheyes](https://twitter.com/garethheyes) and [@filedescriptor](https://twitter.com/filedescriptor) for invaluable help, [@shafigullin](https://twitter.com/shafigullin) for breaking the library multiple times and thereby strengthening it, [@mmrupp](https://twitter.com/mmrupp) and [@irsdl](https://twitter.com/irsdl) for doing the same.
 
-## Who contributed?
+Big thanks also go to [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro) and [@fhemberger](https://twitter.com/fhemberger)! 
 
-Several people need to be listed here! [@garethheyes](https://twitter.com/garethheyes) and [@filedescriptor](https://twitter.com/filedescriptor) for invaluable help, [@shafigullin](https://twitter.com/shafigullin) for breaking the library multiple times and thereby strengthening it, [@mmrupp](https://twitter.com/mmrupp) and [@irsdl](https://twitter.com/irsdl) for doing the same.
+Further, thanks [@neilj](https://twitter.com/neilj) and [@0xsobky](https://twitter.com/0xsobky) for their code reviews and countless small optimizations, fixes and beautifications. 
 
-Big thanks also go to [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro) and [@fhemberger](https://twitter.com/fhemberger)! Further, thanks [@neilj](https://twitter.com/neilj) and [@0xsobky](https://twitter.com/0xsobky) for their code reviews and countless small optimizations, fixes and beautifications. Big thanks also go [...]
+Big thanks also go to [@tdeekens](https://twitter.com/tdeekens) for doing all the hard work and getting us on track with Travis CI and BrowserStack. And thanks to [@Joris-van-der-Wel](https://github.com/Joris-van-der-Wel) for setting up DOMPurify for jsdom and creating the additional test suite.
 
 And last but not least, thanks to [BrowserStack](https://browserstack.com) for supporting this project with their services for free and delivering excellent, dedicated and very professional support on top of that.
diff --git a/bower.json b/bower.json
index a08c55a..c878922 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
 {
   "name": "DOMPurify",
-  "version": "0.7.4",
+  "version": "0.8.2",
   "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",
@@ -26,8 +26,5 @@
     "**/.*",
     "test",
     "demo"
-  ],
-  "devDependencies": {
-    "jQuery": "1.11.0"
-  }
+  ]
 }
diff --git a/package.json b/package.json
index 2af2207..4e46575 100644
--- a/package.json
+++ b/package.json
@@ -1,41 +1,43 @@
 {
   "scripts": {
     "build-demo": "node scripts/build-demo.js",
-    "qunit": "node scripts/server.js",
-    "jshint": "node node_modules/jshint/bin/jshint src/purify.js || true",
+    "lint": "jshint src/purify.js",
     "minify": "scripts/minify.sh",
     "amend-minified": "scripts/amend-minified.sh",
-    "test": "npm run jshint && npm run-script travis-ci",
-    "travis-ci": "[ \"${TRAVIS_PULL_REQUEST}\" = \"false\" ] && ./node_modules/.bin/karma start test/karma.conf.js --log-level warn --reporters dots --single-run || false",
-    "ci-test": "./node_modules/.bin/karma start test/karma.conf.js --single-run",
-    "local-test": "npm run jshint;./node_modules/.bin/karma start test/karma.conf.js --browsers Firefox,Chrome --single-run"
+    "test:jsdom": "node test/jsdom-node-runner --dot",
+    "test:karma": "karma start test/karma.conf.js --log-level warn --single-run",
+    "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"
   },
   "pre-commit": [
-    "jshint",
+    "lint",
     "minify",
     "amend-minified"
   ],
   "devDependencies": {
-    "jshint": "^2.4.4",
-    "json-loader": "^0.5.2",
-    "karma": "^0.13.15",
-    "karma-browserstack-launcher": "git://github.com/shirish87/karma-browserstack-launcher.git#global_poll_0.1.6",
-    "karma-chrome-launcher": "^0.2.1",
-    "karma-firefox-launcher": "^0.1.6",
-    "karma-fixture": "^0.2.5",
-    "karma-html2js-preprocessor": "^0.1.0",
-    "karma-json-fixtures-preprocessor": "0.0.5",
-    "karma-qunit": "^0.1.8",
+    "jquery": "^2.2.3",
+    "jsdom": "8.x.x",
+    "jshint": "^2.9.2",
+    "json-loader": "^0.5.4",
+    "karma": "^0.13.22",
+    "karma-browserstack-launcher": "1.0.0",
+    "karma-chrome-launcher": "^1.0.1",
+    "karma-firefox-launcher": "^1.0.0",
+    "karma-fixture": "^0.2.6",
+    "karma-html2js-preprocessor": "^1.0.0",
+    "karma-json-fixtures-preprocessor": "0.0.6",
+    "karma-qunit": "^1.0.0",
     "karma-webpack": "^1.7.0",
     "pre-commit": "^1.1.2",
     "qunit-parameterize": "^0.4.0",
-    "qunitjs": "^1.20.0",
-    "uglify-js": "^2.5.0",
-    "webpack": "^1.12.1"
+    "qunit-tap": "^1.5.0",
+    "qunitjs": "^1.23.1",
+    "uglify-js": "^2.6.2",
+    "webpack": "^1.13.0"
   },
   "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.7.4",
+  "version": "0.8.2",
   "main": "src/purify.js",
   "directories": {
     "test": "test"
diff --git a/src/purify.js b/src/purify.js
index 71f62b7..4270208 100644
--- a/src/purify.js
+++ b/src/purify.js
@@ -21,7 +21,13 @@
      * Version label, exposed for easier checks
      * if DOMPurify is up to date or not
      */
-    DOMPurify.version = '0.7.4';
+    DOMPurify.version = '0.8.2';
+
+    /**
+     * Array of elements that DOMPurify removed during sanitation.
+     * Empty if nothing was removed.
+     */
+    DOMPurify.removed = [];
 
     if (!window || !window.document || window.document.nodeType !== 9) {
         // not running in a browser, provide a factory function
@@ -47,7 +53,10 @@
     // document, so we use that as our parent document to ensure nothing
     // is inherited.
     if (typeof HTMLTemplateElement === 'function') {
-        document = document.createElement('template').content.ownerDocument;
+        var template = document.createElement('template');
+        if (template.content && template.content.ownerDocument) {
+            document = template.content.ownerDocument;
+        }
     }
     var implementation = document.implementation;
     var createNodeIterator = document.createNodeIterator;
@@ -68,6 +77,9 @@
     var _addToSet = function(set, array) {
         var l = array.length;
         while (l--) {
+            if (typeof array[l] === 'string') {
+                array[l] = array[l].toLowerCase();
+            }
             set[array[l]] = true;
         }
         return set;
@@ -112,7 +124,7 @@
         // SVG
         'svg','altglyph','altglyphdef','altglyphitem','animatecolor',
         'animatemotion','animatetransform','circle','clippath','defs','desc',
-        'ellipse','font','g','glyph','glyphref','hkern','image','line',
+        'ellipse','filter','font','g','glyph','glyphref','hkern','image','line',
         'lineargradient','marker','mask','metadata','mpath','path','pattern',
         'polygon','polyline','radialgradient','rect','stop','switch','symbol',
         'text','textpath','title','tref','tspan','view','vkern',
@@ -121,7 +133,7 @@
         'feBlend','feColorMatrix','feComponentTransfer','feComposite',
         'feConvolveMatrix','feDiffuseLighting','feDisplacementMap',
         'feFlood','feFuncA','feFuncB','feFuncG','feFuncR','feGaussianBlur',
-        'feImage','feMerge','feMergeNode','feMorphology','feOffset',
+        'feMerge','feMergeNode','feMorphology','feOffset',
         'feSpecularLighting','feTile','feTurbulence',
 
         //MathML
@@ -169,9 +181,9 @@
         'opacity','order','orient','orientation','origin','overflow','paint-order',
         'path','pathlength','patterncontentunits','patterntransform','patternunits',
         'points','preservealpha','r','rx','ry','radius','refx','refy','repeatcount',
-        'repeatdur','restart','rotate','scale','seed','shape-rendering','specularconstant',
-        'specularexponent','spreadmethod','stddeviation','stitchtiles','stop-color',
-        'stop-opacity','stroke-dasharray','stroke-dashoffset','stroke-linecap',
+        'repeatdur','restart','result','rotate','scale','seed','shape-rendering',
+        'specularconstant','specularexponent','spreadmethod','stddeviation','stitchtiles',
+        'stop-color','stop-opacity','stroke-dasharray','stroke-dashoffset','stroke-linecap',
         'stroke-linejoin','stroke-miterlimit','stroke-opacity','stroke','stroke-width',
         'surfacescale','targetx','targety','transform','text-anchor','text-decoration',
         'text-rendering','textlength','u1','u2','unicode','values','viewbox',
@@ -203,6 +215,9 @@
     /* Decide if custom data attributes are okay */
     var ALLOW_DATA_ATTR = true;
 
+    /* Decide if unknown protocols are okay */
+    var ALLOW_UNKNOWN_PROTOCOLS = false;
+
     /* Output should be safe for jQuery's $() factory? */
     var SAFE_FOR_JQUERY = false;
 
@@ -283,6 +298,7 @@
         FORBID_ATTR = 'FORBID_ATTR' in cfg ?
             _addToSet({}, cfg.FORBID_ATTR) : {};
         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
         SAFE_FOR_TEMPLATES  = cfg.SAFE_FOR_TEMPLATES  ||  false; // Default false
         WHOLE_DOCUMENT      = cfg.WHOLE_DOCUMENT      ||  false; // Default false
@@ -330,6 +346,7 @@
      * @param  a DOM node
      */
     var _forceRemove = function(node) {
+        DOMPurify.removed.push({element: node});
         try {
             node.parentNode.removeChild(node);
         } catch (e) {
@@ -338,6 +355,20 @@
     };
 
    /**
+     * _removeAttribute
+     *
+     * @param  an Attribute name
+     * @param  a DOM node
+     */
+    var _removeAttribute = function(name, node) {
+        DOMPurify.removed.push({
+            attribute: node.getAttributeNode(name),
+            from: node
+        });
+        node.removeAttribute(name);
+    };
+
+   /**
      * _initDocument
      *
      * @param  a string of dirty markup
@@ -351,8 +382,9 @@
         } catch (e) {}
 
         /* Some browsers throw, some browsers return null for the code above
-           DOMParser with text/html support is only in very recent browsers. */
-        if (!doc) {
+           DOMParser with text/html support is only in very recent browsers.
+           See #159 why the check here is extra-thorough */
+        if (!doc || !doc.documentElement) {
             doc = implementation.createHTMLDocument('');
             body = doc.body;
             body.parentNode.removeChild(body.parentNode.firstElementChild);
@@ -451,7 +483,9 @@
 
         /* Convert markup to cover jQuery behavior */
         if (SAFE_FOR_JQUERY && !currentNode.firstElementChild &&
-                (!currentNode.content || !currentNode.content.firstElementChild)) {
+                (!currentNode.content || !currentNode.content.firstElementChild) &&
+                /</g.test(currentNode.textContent)) {
+            DOMPurify.removed.push({element: currentNode.cloneNode()});
             currentNode.innerHTML = currentNode.textContent.replace(/</g, '<');
         }
 
@@ -461,7 +495,10 @@
             content = currentNode.textContent;
             content = content.replace(MUSTACHE_EXPR, ' ');
             content = content.replace(ERB_EXPR, ' ');
-            currentNode.textContent = content;
+            if (currentNode.textContent !== content) {
+                DOMPurify.removed.push({element: currentNode.cloneNode()});
+                currentNode.textContent = content;
+            }
         }
 
         /* Execute a hook if present */
@@ -470,8 +507,9 @@
         return false;
     };
 
-    var DATA_ATTR = /^data-[\w.\u00B7-\uFFFF-]/;
+    var DATA_ATTR = /^data-[\-\w.\u00B7-\uFFFF]/;
     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 */
     var ATTR_WHITESPACE = /[\x00-\x20\xA0\u1680\u180E\u2000-\u2029\u205f\u3000]/g;
 
@@ -525,8 +563,8 @@
                     currentNode.nodeName === 'IMG' && attributes.id) {
                 idAttr = attributes.id;
                 attributes = Array.prototype.slice.apply(attributes);
-                currentNode.removeAttribute('id');
-                currentNode.removeAttribute(name);
+                _removeAttribute('id', currentNode);
+                _removeAttribute(name, currentNode);
                 if (attributes.indexOf(idAttr) > l) {
                     currentNode.setAttribute('id', idAttr.value);
                 }
@@ -537,7 +575,7 @@
                 if (name === 'id') {
                     currentNode.setAttribute(name, '');
                 }
-                currentNode.removeAttribute(name);
+                _removeAttribute(name, currentNode);
             }
 
             /* Did the hooks approve of the attribute? */
@@ -558,29 +596,55 @@
                 value = value.replace(ERB_EXPR, ' ');
             }
 
-            if (
-                /* Check the name is permitted */
-                (ALLOWED_ATTR[lcName] && !FORBID_ATTR[lcName] && (
-                  /* Check no script, data or unknown possibly unsafe URI
-                     unless we know URI values are safe for that attribute */
-                  URI_SAFE_ATTRIBUTES[lcName] ||
-                  IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE,'')) ||
-                  /* Keep image data URIs alive if src is allowed */
-                  (lcName === 'src' && value.indexOf('data:') === 0 &&
-                   DATA_URI_TAGS[currentNode.nodeName.toLowerCase()])
-                )) ||
-                /* Allow potentially valid data-* attributes:
-                 * At least one character after "-" (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
-                 * XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
-                 * We don't need to check the value; it's always URI safe.
-                 */
-                 (ALLOW_DATA_ATTR && DATA_ATTR.test(lcName))
-            ) {
-                /* Handle invalid data-* attribute set by try-catching it */
-                try {
-                    currentNode.setAttribute(name, value);
-                } catch (e) {}
+            /* Allow valid data-* attributes: At least one character after "-"
+               (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
+               XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
+               We don't need to check the value; it's always URI safe. */
+            if (ALLOW_DATA_ATTR && DATA_ATTR.test(lcName)) {
+                // This attribute is safe
+            }
+            /* Otherwise, check the name is permitted */
+            else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
+                continue;
+            }
+            /* Check value is safe. First, is attr inert? If so, is safe */
+            else if (URI_SAFE_ATTRIBUTES[lcName]) {
+                // This attribute is safe
+            }
+            /* Check no script, data or unknown possibly unsafe URI
+               unless we know URI values are safe for that attribute */
+            else if (IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE,''))) {
+                // This attribute is safe
+            }
+            /* Keep image data URIs alive if src is allowed */
+            else if (
+                lcName === 'src' &&
+                value.indexOf('data:') === 0 &&
+                DATA_URI_TAGS[currentNode.nodeName.toLowerCase()]) {
+                // This attribute is safe
             }
+            /* Allow unknown protocols: This provides support for links that
+               are handled by protocol handlers which may be unknown ahead of
+               time, e.g. fb:, spotify: */
+            else if (
+                ALLOW_UNKNOWN_PROTOCOLS &&
+                !IS_SCRIPT_OR_DATA.test(value.replace(ATTR_WHITESPACE,''))) {
+                // This attribute is safe
+            }
+            /* Check for binary attributes */
+            else if (!value) {
+                // binary attributes are safe at this point
+            }
+            /* Anything else, presume unsafe, do not add it back */
+            else {
+                continue;
+            }
+
+            /* Handle invalid data-* attribute set by try-catching it */
+            try {
+                currentNode.setAttribute(name, value);
+                DOMPurify.removed.pop();
+            } catch (e) {}
         }
 
         /* Execute a hook if present */
@@ -674,6 +738,9 @@
         /* Assign config vars */
         _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;
@@ -789,7 +856,7 @@
      * @return void
      */
     DOMPurify.removeAllHooks = function() {
-        hooks = [];
+        hooks = {};
     };
 
     return DOMPurify;
diff --git a/test/fixtures/expect.js b/test/fixtures/expect.js
index 8faa7bd..19db849 100644
--- a/test/fixtures/expect.js
+++ b/test/fixtures/expect.js
@@ -1,5 +1,27 @@
 module.exports = [
   {
+      "title": "Don't remove binary attributes if considered safe (see #168)",
+      "payload": "<input type=checkbox checked><input type=checkbox onclick>",
+      "expected": [
+             "<input checked=\"\" type=\"checkbox\"><input type=\"checkbox\">",
+             "<input type=\"checkbox\" checked=\"\"><input type=\"checkbox\">",
+             "<input type=\"checkbox\" checked=\"\" value=\"\"><input type=\"checkbox\" value=\"\">"
+       ]
+  },
+  {
+      "title": "Avoid over-zealous stripping of SVG filter elements (see #144)",
+      "payload": "<svg><defs><filter id=\"f1\"><feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"15\" /></filter></defs><rect width=\"90\" height=\"90\" stroke=\"green\" stroke-width=\"3\" fill=\"yellow\" filter=\"url(#f1)\" /></svg>",
+      "expected": [
+            "<svg><defs><filter id=\"f1\"><feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"15\" /></filter></defs><rect width=\"90\" height=\"90\" stroke=\"green\" stroke-width=\"3\" fill=\"yellow\" filter=\"url(#f1)\" /></svg>",
+            "<svg><defs><filter id=\"f1\"><feGaussianBlur stdDeviation=\"15\" in=\"SourceGraphic\"></feGaussianBlur></filter></defs><rect filter=\"url(#f1)\" fill=\"yellow\" stroke-width=\"3\" stroke=\"green\" height=\"90\" width=\"90\"></rect></svg>",
+            "<svg xmlns=\"http://www.w3.org/2000/svg\"><defs><filter id=\"f1\"><feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"15\" /></filter></defs><rect filter=\"url("#f1")\" fill=\"yellow\" stroke=\"green\" stroke-width=\"3\" width=\"90\" height=\"90\" /></svg>",
+            "<svg xmlns=\"http://www.w3.org/2000/svg\"><defs><filter id=\"f1\"><feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"15\" /></filter></defs><rect filter=\"url(#f1)\" fill=\"yellow\" stroke=\"green\" stroke-width=\"3\" width=\"90\" height=\"90\" FILTER=\"url(#f1)\" /></svg>",
+            "<svg><defs><filter id=\"f1\"><fegaussianblur in=\"SourceGraphic\" stdDeviation=\"15\" /></filter></defs><rect width=\"90\" height=\"90\" stroke=\"green\" stroke-width=\"3\" fill=\"yellow\" filter=\"url(#f1)\" /></svg>",
+            "<svg><defs><filter id=\"f1\"><fegaussianblur stdDeviation=\"15\" in=\"SourceGraphic\"></fegaussianblur></filter></defs><rect filter=\"url(#f1)\" fill=\"yellow\" stroke-width=\"3\" stroke=\"green\" height=\"90\" width=\"90\"></rect></svg>",
+            "<svg xmlns=\"http://www.w3.org/2000/svg\"><defs><filter id=\"f1\"><feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"15\" /></filter></defs><rect filter=\"url(#f1)\" fill=\"yellow\" stroke=\"green\" stroke-width=\"3\" width=\"90\" height=\"90\" STROKE-WIDTH=\"3\" STROKE=\"green\" FILL=\"yellow\" FILTER=\"url(#f1)\" /></svg>"
+       ]
+     },
+  {
       "title": "safe usage of URI-like attribute values (see #135)",
       "payload": "<b href=\"javascript:alert(1)\" title=\"javascript:alert(2)\"></b>",
       "expected": "<b title=\"javascript:alert(2)\"></b>"
@@ -194,7 +216,8 @@ module.exports = [
       "payload": "<image name=body><img src=x><svg onload=alert(1); autofocus>, <keygen onfocus=alert(1); autofocus>",
       "expected": [
           "<img><img src=\"x\"><svg>, </svg>",
-          "<img><img src=\"x\"><svg xmlns=\"http://www.w3.org/2000/svg\">, </svg></svg>"
+          "<img><img src=\"x\"><svg xmlns=\"http://www.w3.org/2000/svg\">, </svg></svg>",
+          "<img><img src=\"x\"><svg xmlns=\"http://www.w3.org/2000/svg\">, </svg>"
       ]
   }, {
       "title": "Bypass using multiple unknown attributes",
@@ -243,10 +266,10 @@ module.exports = [
       "payload": "<form action=\"javasc\nript:alert(1)\"><button>XXX</button></form>",
       "expected": "<form><button>XXX</button></form>"
   }, {
-      "payload": "<div id=\"1\"><form id=\"test\"></form><button form=\"test\" formaction=\"javascript:alert(1)\">X</button>//[\"'`-->]]>]</div>",
+      "payload": "<div id=\"1\"><form id=\"foobar\"></form><button form=\"foobar\" formaction=\"javascript:alert(1)\">X</button>//[\"'`-->]]>]</div>",
       "expected": [
-          "<div id=\"1\"><form></form><button>X</button>//[\"'`-->]]>]</div>",
-          "<div id=\"1\"><form><button>X</button>//[\"'`-->]]>]</form></div>"
+          "<div id=\"1\"><form id=\"foobar\"></form><button>X</button>//[\"'`-->]]>]</div>",
+          "<div id=\"1\"><form id=\"foobar\"><button>X</button>//[\"'`-->]]>]</form></div>"
       ]
   }, {
       "payload": "<div id=\"2\"><meta charset=\"x-imap4-modified-utf7\">&ADz&AGn&AG0&AEf&ACA&AHM&AHI&AGO&AD0&AGn&ACA&AG8Abg&AGUAcgByAG8AcgA9AGEAbABlAHIAdAAoADEAKQ&ACAAPABi//[\"'`-->]]>]</div>",
@@ -319,10 +342,10 @@ module.exports = [
       "payload": "<div id=\"22\"><input onblur=focus() autofocus><input>//[\"'`-->]]>]</div>",
       "expected": "<div id=\"22\"><input><input>//[\"'`-->]]>]</div>"
   }, {
-      "payload": "<div id=\"23\"><form id=test onforminput=alert(23)><input></form><button form=test onformchange=alert(2)>X</button>//[\"'`-->]]>]</div>",
+      "payload": "<div id=\"23\"><form id=foobar onforminput=alert(23)><input></form><button form=test onformchange=alert(2)>X</button>//[\"'`-->]]>]</div>",
       "expected": [
-          "<div id=\"23\"><form><input></form><button>X</button>//[\"'`-->]]>]</div>",
-          "<div id=\"23\"><form><input><button>X</button>//[\"'`-->]]>]</form></div>"
+          "<div id=\"23\"><form id=\"foobar\"><input></form><button>X</button>//[\"'`-->]]>]</div>",
+          "<div id=\"23\"><form id=\"foobar\"><input><button>X</button>//[\"'`-->]]>]</form></div>"
       ]
   }, {
       "payload": "<div id=\"24\">1<set/xmlns=`urn:schemas-microsoft-com:time` style=`behAvior:url(#default#time2)` attributename=`innerhtml` to=`<img/src=\"x\"onerror=alert(24)>`>//[\"'`-->]]>]</div>",
@@ -393,8 +416,10 @@ module.exports = [
       "payload": "<!-- IE9+, FF4+, Opera 11.60+, Safari 4.0.4+, GC7+  -->\n<svg><![CDATA[><image xlink:href=\"]]><img src=x onerror=alert(2)//\"></svg>//[\"'`-->]]>]</div>",
       "expected": [
           "<svg>><image xlink:href=\"</svg><img src=\"x\">//[\"'`-->]]>]",
+          "<svg>><image xlink:href=\"<img src=\"x\"></svg>//[\"'`-->]]>]",
           "<svg>><image xlink:href=\"<img src=\"x\"></img></svg>//[\"'`-->]]>]",
-          "<svg xmlns=\"http://www.w3.org/2000/svg\">><image xlink:href=\"</svg></svg><img src=\"x\">//[\"'`-->]]>]"
+          "<svg xmlns=\"http://www.w3.org/2000/svg\">><image xlink:href=\"</svg></svg><img src=\"x\">//[\"'`-->]]>]",
+          "<svg xmlns=\"http://www.w3.org/2000/svg\">><image xlink:href=\"</svg><img src=\"x\">//[\"'`-->]]>]"
       ]
   }, {
       "payload": "<div id=\"40\"><style><img src=\"</style><img src=x onerror=alert(40)//\">//[\"'`-->]]>]</div>",
@@ -489,13 +514,6 @@ module.exports = [
           "<div id=\"60\"><div>XXX</div>//[\"'`-->]]>]</div>"
       ]
   }, {
-      "payload": "<div id=\"61\"><div style=\"3\t\u0006f\n\u00006c\f\u000006F\nR:\u0000072 Ed;color\u0000\bla:yellow\u0000\bla;col\u0000\u0000 \u00A0or:blue;\">XXX</div>//[\"'`-->]]>]</div>",
-      "expected": [
-          "<div id=\"61\"><div style=\"3\t\u0006f\n\uFFFD6c\f\uFFFD06F\nR:\uFFFD072 Ed;color\uFFFD\bla:yellow\uFFFD\bla;col\uFFFD\uFFFD  or:blue;\">XXX</div>//[\"'`-->]]>]</div>",
-          "<div id=\"61\"><div>XXX</div>//[\"'`-->]]>]</div>",
-          "<div id=\"61\"></div>"
-      ]
-  }, {
       "payload": "<div id=\"62\"><!-- IE 6-8 -->\n<x '=\"foo\"><x foo='><img src=x onerror=alert(62)//'>\n<!-- IE 6-9 -->\n<! '=\"foo\"><x foo='><img src=x onerror=alert(2)//'>\n<? '=\"foo\"><x foo='><img src=x onerror=alert(3)//'>//[\"'`-->]]>]</div>",
       "expected": "<div id=\"62\">\n\n\n\n//[\"'`-->]]>]</div>"
   }, {
@@ -581,7 +599,8 @@ 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\" height=\"1000\" width=\"1000\"></rect></a>\n</svg>//[\"'`-->]]>]</div>",
           "<div id=\"86\"><input>//[\"'`-->]]>]</div><div id=\"87\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\">\n<a xmlns:NS1=\"\" NS1:xmlns:xlink=\"http://www.w3.org/1999/xlink\"><rect fill=\"white\" width=\"1000\" height=\"1000\" /></a>\n</svg>//[\"'`-->]]>]</div>",
           "<div id=\"86\"><input>//[\"'`-->]]>]</div><div id=\"87\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\">\n<a><rect fill=\"white\" width=\"1000\" height=\"1000\" /></a>\n</svg>//[\"'`-->]]>]</div>",
-          "<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\" /></a>\n</svg>//[\"'`-->]]>]</div>"
+          "<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\" /></a>\n</svg>//[\"'`-->]]>]</div>",
+          "<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=\ [...]
@@ -617,7 +636,8 @@ module.exports = [
       "payload": "<div id=\"93\"><div style=\"list-style:url(http://foo.f)\u0010url(javascript:alert(93));\">X</div>//[\"'`-->]]>]</div>",
       "expected": [
           "<div id=\"93\"><div style=\"list-style:url(http://foo.f)\u0010url(javascript:alert(93));\">X</div>//[\"'`-->]]>]</div>",
-          "<div id=\"93\"><div>X</div>//[\"'`-->]]>]</div>"
+          "<div id=\"93\"><div>X</div>//[\"'`-->]]>]</div>",
+          "<div id=\"93\"><div style=\"list-style:url(http://foo.f)url(javascript:alert(93));\">X</div>//[\"'`-->]]>]</div>"
       ]
   }, {
       "payload": "<div id=\"94\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<handler xmlns:ev=\"http://www.w3.org/2001/xml-events\" ev:event=\"load\">alert(94)</handler>\n</svg>//[\"'`-->]]>]</div>",
@@ -704,7 +724,8 @@ module.exports = [
           "<div id=\"109\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\">\n<a id=\"x\"><rect fill=\"white\" width=\"1000\" height=\"1000\" /></a>\n<rect style=\"filter: url("#c"); clip-path: url("test3.svg#a"); fill: url(#b); marker-end: url("#d"); marker-mid: url("#d"); marker-start: url("#d"); mask: url("#e"); stroke: url(#f);\" fill=\"white\" />\n</svg>//[\"'`-->]]>]</div>",
           "<div id=\"109\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\">\n<a id=\"x\"><rect fill=\"white\" width=\"1000\" height=\"1000\" /></a>\n<rect style=\"filter: url(#c); clip-path: url("test3.svg#a"); fill: url(#b); marker-end: url("#d"); marker-mid: url("#d"); marker-start: url("#d"); mask: url("#e"); stroke: url(#f);\" fill=\"white\" />\n</svg>//[\"'`-->]]>]</div>",
           "<div id=\"109\"><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\">\n<a id=\"x\"><rect fill=\"white\" width=\"1000\" height=\"1000\" /></a>\n<rect style=\"clip-path: url("test3.svg#a"); fill: url(#b); marker-end: url("#d"); marker-mid: url("#d"); marker-start: url("#d"); mask: url("#e"); stroke: url(#f);\" fill=\"white\" />\n</svg>//[\"'`-->]]>]</div>",
-          "<div id=\"109\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<a id=\"x\"><rect fill=\"white\" width=\"1000\" height=\"1000\" /></a>\n<rect style=\"clip-path:url(test3.svg#a);fill:url(#b);filter:url(#c);marker:url(#d);mask:url(#e);stroke:url(#f);\" fill=\"white\" />\n</svg>//[\"'`-->]]>]</div>"
+          "<div id=\"109\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<a id=\"x\"><rect fill=\"white\" width=\"1000\" height=\"1000\" /></a>\n<rect style=\"clip-path:url(test3.svg#a);fill:url(#b);filter:url(#c);marker:url(#d);mask:url(#e);stroke:url(#f);\" fill=\"white\" />\n</svg>//[\"'`-->]]>]</div>",
+          "<div id=\"109\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<a id=\"x\"><rect fill=\"white\" width=\"1000\" height=\"1000\" FILL=\"white\" /></a>\n<rect style=\"clip-path:url(test3.svg#a);fill:url(#b);filter:url(#c);marker:url(#d);mask:url(#e);stroke:url(#f);\" fill=\"white\" FILL=\"white\" />\n</svg>//[\"'`-->]]>]</div>"
       ]
   }, {
       "payload": "<div id=\"110\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M0,0\" style=\"marker-start:url(test4.svg#a)\"/>\n</svg>//[\"'`-->]]>]</div>",
@@ -777,7 +798,8 @@ module.exports = [
           "<div id=\"125\">]><svg xmlns=\"http://www.w3.org/2000/svg\">        <circle r=\"40\" fill=\"red\"></circle></svg>//[\"'`-->]]>]</div>",
           "<div id=\"125\">]><svg xmlns=\"http://www.w3.org/2000/svg\">                                        <circle r=\"40\" fill=\"red\"></circle></svg>//[\"'`-->]]>]</div>",
           "<div id=\"125\">]><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\">        <circle fill=\"red\" r=\"40\" /></svg>//[\"'`-->]]>]</div>",
-          "<div id=\"125\">]><svg xmlns=\"http://www.w3.org/2000/svg\">        <circle fill=\"red\" r=\"40\" /></svg>//[\"'`-->]]>]</div>"
+          "<div id=\"125\">]><svg xmlns=\"http://www.w3.org/2000/svg\">        <circle fill=\"red\" r=\"40\" /></svg>//[\"'`-->]]>]</div>",
+          "<div id=\"125\">]><svg xmlns=\"http://www.w3.org/2000/svg\">        <circle fill=\"red\" r=\"40\" FILL=\"red\" /></svg>//[\"'`-->]]>]</div>"
       ]
   }, {
       "payload": "<div id=\"126\"><object id=\"x\" classid=\"clsid:CB927D12-4FF7-4a9e-A169-56E4B8A75598\"></object>\n<object classid=\"clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B\" onqt_error=\"alert(126)\" style=\"behavior:url(#x);\"><param name=postdomevents /></object>//[\"'`-->]]>]</div>",
@@ -795,7 +817,9 @@ module.exports = [
       "expected": [
           "<div id=\"128\"><svg><style></style></svg><img src=\"x\">//[\"'`-->]]>]</div>",
           "<div id=\"128\"><svg><style><img src=\"x\">//[\"'`-->]]>]</img></style></svg></div>",
-          "<div id=\"128\"><svg xmlns=\"http://www.w3.org/2000/svg\"><style /></svg></svg><img src=\"x\">//[\"'`-->]]>]</div>"
+          "<div id=\"128\"><svg xmlns=\"http://www.w3.org/2000/svg\"><style /></svg></svg><img src=\"x\">//[\"'`-->]]>]</div>",
+          "<div id=\"128\"><svg><style><img src=\"x\"></style></svg></div>",
+          "<div id=\"128\"><svg xmlns=\"http://www.w3.org/2000/svg\"><style /></svg><img src=\"x\">//[\"'`-->]]>]</div>"
       ]
   }, {
       "title": "Inline SVG (data-uri)",
@@ -805,7 +829,8 @@ module.exports = [
           "<div id=\"129\"><svg><image style=\"filter:url("data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22><script>parent.alert(129)</script></svg>")\">\n\n</image></svg>//[\"'`-->]]>]</div>",
           "<div id=\"129\"><svg xmlns=\"http://www.w3.org/2000/svg\"><image style=\"filter: url("data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22><script>parent.alert(129)</script></svg>");\">\n\n</image></image></svg>//[\"'`-->]]>]</div>",
           "<div id=\"129\"><svg xmlns=\"http://www.w3.org/2000/svg\"><image>\n\n</image></image></svg>//[\"'`-->]]>]</div>",
-          "<div id=\"129\"><svg xmlns=\"http://www.w3.org/2000/svg\"><image style=\"filter:url("data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22><script>parent.alert(129)</script></svg>")\">\n\n</image></image></svg>//[\"'`-->]]>]</div>"
+          "<div id=\"129\"><svg xmlns=\"http://www.w3.org/2000/svg\"><image style=\"filter:url("data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22><script>parent.alert(129)</script></svg>")\">\n\n</image></image></svg>//[\"'`-->]]>]</div>",
+          "<div id=\"129\"><svg xmlns=\"http://www.w3.org/2000/svg\"><image style=\"filter:url("data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22><script>parent.alert(129)</script></svg>")\">\n\n</image></svg>//[\"'`-->]]>]</div>"
       ]
   }, {
       "title": "MathML",
@@ -861,7 +886,8 @@ module.exports = [
           "<div id=\"137\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<a xlink:href=\"?\">\n<circle r=\"400\" />\n\n</a>//[\"'`-->]]>]</svg></svg></div>",
           "<div id=\"137\"><svg>\n<a xlink:href=\"?\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<circle r=\"400\"></circle>\n\n</a>//[\"'`-->]]>]</svg></div>",
           "<div id=\"137\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<a xmlns:NS1=\"\" NS1:xlink:href=\"?\" xmlns:NS2=\"\" NS2:xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<circle r=\"400\" />\n\n</a>//[\"'`-->]]>]</svg></svg></div>",
-          "<div id=\"137\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<a xlink:href=\"?\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<circle r=\"400\" />\n\n</a>//[\"'`-->]]>]</svg></svg></div>"
+          "<div id=\"137\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<a xlink:href=\"?\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<circle r=\"400\" />\n\n</a>//[\"'`-->]]>]</svg></svg></div>",
+          "<div id=\"137\"><svg xmlns=\"http://www.w3.org/2000/svg\">\n<a xlink:href=\"?\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<circle r=\"400\" />\n\n</a>//[\"'`-->]]>]</svg></div>"
       ]
   }, {
       "title": "Removing name attr from img with id can crash Safari",
diff --git a/test/jsdom-node-runner.js b/test/jsdom-node-runner.js
new file mode 100644
index 0000000..11b0e63
--- /dev/null
+++ b/test/jsdom-node-runner.js
@@ -0,0 +1,24 @@
+/* jshint node: true, esnext: true */
+/* global QUnit */
+'use strict';
+
+global.QUnit = require('qunitjs');
+const qunitTap = require('qunit-tap');
+const argument = process.argv[2];
+
+qunitTap(QUnit, line => {
+    if (/^not ok/.test(line)) {
+        process.exitCode = 1;
+        return console.log('\n', line);
+    }
+
+    if (argument === '--dot') {
+        return process.stdout.write('.');
+    }
+
+    console.log(line);
+});
+
+require('./jsdom-node');
+
+QUnit.load();
diff --git a/test/jsdom-node.js b/test/jsdom-node.js
new file mode 100644
index 0000000..2d0a3ca
--- /dev/null
+++ b/test/jsdom-node.js
@@ -0,0 +1,52 @@
+/* jshint node: true, esnext: true */
+/* global QUnit */
+'use strict';
+
+// Test DOMPurify + jsdom using Node.js (version 4 and up)
+const
+    dompurify = require('../'),
+    jsdom = require('jsdom'),
+    testSuite = require('./test-suite'),
+    tests = require('./fixtures/expect'),
+    xssTests = tests.filter( element => /alert/.test( element.payload ) );
+
+require('qunit-parameterize/qunit-parameterize');
+
+QUnit.assert.contains = function( needle, haystack, message ) {
+    const result = haystack.indexOf(needle) > -1;
+    this.push(result, needle, haystack, message);
+};
+
+QUnit.config.autostart = false;
+
+jsdom.env({
+    html: `<html><head></head><body><div id="qunit-fixture"></div></body></html>`,
+    scripts: ['node_modules/jquery/dist/jquery.js'],
+    features: {
+        ProcessExternalResources: ["script"] // needed for firing the onload event for about:blank iframes
+    },
+    done(err, window) {
+        QUnit.module('DOMPurify in jsdom');
+        if (err) {
+            console.error('Unexpected error returned by jsdom.env():', err, err.stack);
+            process.exit(1);
+        }
+
+        if (!window.jQuery) {
+            console.warn('Unable to load jQuery');
+        }
+
+        const DOMPurify = dompurify(window);
+        if (!DOMPurify.isSupported) {
+            console.error('Unexpected error returned by jsdom.env():', err, err.stack);
+            process.exit(1);
+        }
+
+        window.alert = () => {
+            window.xssed = true;
+        };
+
+        testSuite(DOMPurify, window, tests, xssTests);
+        QUnit.start();
+    }
+});
diff --git a/test/karma.conf.js b/test/karma.conf.js
index 03d4394..11296f7 100644
--- a/test/karma.conf.js
+++ b/test/karma.conf.js
@@ -4,7 +4,7 @@ module.exports = function(config) {
     basePath: '../',
     frameworks: ['qunit'],
     files: [
-      'bower_components/jQuery/dist/jquery.js',
+      'node_modules/jquery/dist/jquery.js',
       'node_modules/qunit-parameterize/qunit-parameterize.js',
       'test/config/setup.js',
       'test/**/*.spec.js'
@@ -49,6 +49,10 @@ module.exports = function(config) {
       }
     },
 
+    webpackMiddleware: {
+      noInfo: true
+    },
+
     customLaunchers: {
       bs_win81_ie_11: {
         base: 'BrowserStack',
@@ -96,11 +100,19 @@ module.exports = function(config) {
         browser: 'opera',
         os_version: '8.1'
       },
-      bs_win7_firefox_12: {
+      bs_win7_firefox_20: {
+        base: 'BrowserStack',
+        device: null,
+        os: 'Windows',
+        browser_version: '20.0',
+        browser: 'firefox',
+        os_version: '7'
+      },
+      bs_win7_firefox_15: {
         base: 'BrowserStack',
         device: null,
         os: 'Windows',
-        browser_version: '12.0',
+        browser_version: '15.0',
         browser: 'firefox',
         os_version: '7'
       },
@@ -112,13 +124,29 @@ module.exports = function(config) {
         browser: 'chrome',
         os_version: '8.1'
       },
-      bs_win10_edge_12: {
+      bs_win10_edge_13: {
         base: 'BrowserStack',
         device: null,
         os: 'Windows',
-        browser_version: '12.0',
+        browser_version: '13.0',
         browser: 'edge',
         os_version: '10'
+      },
+      bs_win10_firefox_46: {
+        base: 'BrowserStack',
+        device: null,
+        os: 'Windows',
+        browser_version: '46.0',
+        browser: 'firefox',
+        os_version: '10'
+      },
+      bs_win10_chrome_50: {
+        base: 'BrowserStack',
+        device: null,
+        os: 'Windows',
+        browser_version: '50.0',
+        browser: 'chrome',
+        os_version: '10'
       }
     },
 
@@ -129,9 +157,12 @@ module.exports = function(config) {
       'bs_yosemite_firefox_40',
       'bs_yosemite_safari_8',
       'bs_win81_opera_31',
-      'bs_win7_firefox_12',
+      'bs_win7_firefox_20',
+      'bs_win7_firefox_15',
       'bs_win81_chrome_22',
-      'bs_win10_edge_12'
+      'bs_win10_edge_13',
+      'bs_win10_firefox_46',
+      'bs_win10_chrome_50'
     ],
 
     browserDisconnectTimeout: 10000,
diff --git a/test/purify.min.spec.js b/test/purify.min.spec.js
index ee797fa..e9abe63 100644
--- a/test/purify.min.spec.js
+++ b/test/purify.min.spec.js
@@ -7,4 +7,4 @@ var
   });
 
 QUnit.module('DOMPurify dist');
-testSuite(DOMPurify, tests, xssTests);
+testSuite(DOMPurify, window, tests, xssTests);
diff --git a/test/purify.spec.js b/test/purify.spec.js
index f03a6a0..8ad757a 100644
--- a/test/purify.spec.js
+++ b/test/purify.spec.js
@@ -7,4 +7,4 @@ var
   });
 
 QUnit.module('DOMPurify src');
-testSuite(DOMPurify, tests, xssTests);
+testSuite(DOMPurify, window, tests, xssTests);
diff --git a/test/test-suite.js b/test/test-suite.js
index e97c290..c723fb8 100644
--- a/test/test-suite.js
+++ b/test/test-suite.js
@@ -1,4 +1,7 @@
-module.exports = function(DOMPurify, tests, xssTests) {
+module.exports = function(DOMPurify, window, tests, xssTests) {
+  var document = window.document;
+  var jQuery = window.jQuery;
+
   QUnit
     .cases(tests)
     .test( 'Sanitization test', function(params, assert) {
@@ -75,7 +78,7 @@ module.exports = function(DOMPurify, tests, xssTests) {
           ["<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}), 
-          ["<b>he  ya</b>", "<b>he </b>", "<b>he <form><img src=\"x\"></form> ya</b>"] // Investigate on Safari 8! 
+          ["<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>" );
@@ -260,4 +263,97 @@ module.exports = function(DOMPurify, tests, xssTests) {
       assert.equal(modified, DOMPurify.sanitize(dirty));
       DOMPurify.removeHooks('afterSanitizeElements')
   } );
+  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}));
+  } );
+
+  QUnit.test( 'sanitize() should not allow javascript when ALLOW_UNKNOWN_PROTOCOLS is true', function (assert) {
+      var dirty = '<div><a href="javascript:alert(document.title)"><img src="cid:1234567"/></a></div>';
+      var modified = '<div><a><img src="cid:1234567"></a></div>';
+      assert.equal(modified, DOMPurify.sanitize(dirty, {ALLOW_UNKNOWN_PROTOCOLS: true}));
+  } );
+
+  QUnit.test( 'Regression-Test to make sure #166 stays fixed', function (assert) {
+      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) {
+      var dirty = '<svg onload=alert(1)><filter><feGaussianBlur /></filter></svg>';
+      DOMPurify.sanitize(dirty);
+      assert.equal(DOMPurify.removed.length, 1);
+  } );
+
+  // Test 2 to check if the element count in DOMPurify.removed is correct
+  QUnit.test( 'DOMPurify.removed should contain two elements', function (assert) {
+      var dirty = '1<script>alert(1)<\/script><svg onload=alert(1)><filter><feGaussianBlur /></filter></svg>';
+      DOMPurify.sanitize(dirty);
+      assert.equal(DOMPurify.removed.length, 2);
+  } );
+
+  // Test 3 to check if the element count in DOMPurify.removed is correct
+  QUnit.test( 'DOMPurify.removed should be correct', function (assert) {
+      var dirty = '<img src=x onerror="alert(1)">';
+      DOMPurify.sanitize(dirty);
+      assert.equal(DOMPurify.removed.length, 1);
+  } );
+
+  // 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 
+  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 
+  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 
+  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 
+  QUnit.test( 'DOMPurify.removed should not contain elements if tags are permitted', function (assert) {
+      var dirty = '<a>123</a>';
+      DOMPurify.sanitize(dirty);
+      assert.equal(DOMPurify.removed.length, 0);
+  } );
+
+  // Test 9 to check that DOMPurify.removed is correct if the tags and attributes are clean
+  QUnit.test( 'DOMPurify.removed should not contain elements if all tags and attrs are permitted', function (assert) {
+      var dirty = '<img src=x>';
+      DOMPurify.sanitize(dirty);
+      assert.equal(DOMPurify.removed.length, 0);
+  } );
+
+  // Test 10 to check that DOMPurify.removed does not have false positive elements in SAFE_FOR_TEMLATES mode
+  QUnit.test( 'DOMPurify.removed should not contain elements for valid data in SAFE_FOR_TEMLATES mode', function (assert) {
+      var dirty = '1';
+      DOMPurify.sanitize(dirty, {WHOLE_DOCUMENT: true, SAFE_FOR_TEMPLATES: true});
+      assert.equal(DOMPurify.removed.length, 0);
+  } );
+
+  // 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);
+  } );
 }
diff --git a/website/index.html b/website/index.html
index a09fa20..43995ad 100644
--- a/website/index.html
+++ b/website/index.html
@@ -2,7 +2,7 @@
 <html>
     <head>
         <meta charset="UTF-8">
-        <title>DOMPurify 0.7.4 "Common Snapping Turtle"</title>
+        <title>DOMPurify 0.8.2 "Wapiti Elk"</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.7.4 "Common Snapping Turtle"</h4>
+        <h4>DOMPurify 0.8.2 "Wapiti Elk"</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